user-selectable light and dark themes
cookies, template rendering with different CSS files via default or request param or cookie, etc.
This commit is contained in:
		
							parent
							
								
									5ca483a904
								
							
						
					
					
						commit
						7cf11986c5
					
				| @ -34,5 +34,11 @@ class Config(object): | |||||||
| 
 | 
 | ||||||
|     MARKDOWN_EXTENSIONS = ['meta', 'tables'] |     MARKDOWN_EXTENSIONS = ['meta', 'tables'] | ||||||
| 
 | 
 | ||||||
|  |     PAGE_STYLES = { | ||||||
|  |         'DEFAULT': 'css/light.css', | ||||||
|  |         'dark': 'css/dark.css', | ||||||
|  |         'light': 'css/light.css', | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     TITLE_SUFFIX = 'incorporeal.org' |     TITLE_SUFFIX = 'incorporeal.org' | ||||||
|     MEDIA_DIR = 'media' |     MEDIA_DIR = 'media' | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import os | |||||||
| 
 | 
 | ||||||
| from flask import Blueprint, Markup, abort | from flask import Blueprint, Markup, abort | ||||||
| from flask import current_app as app | from flask import current_app as app | ||||||
| from flask import redirect, render_template | from flask import make_response, redirect, render_template, request | ||||||
| from tzlocal import get_localzone | from tzlocal import get_localzone | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| @ -35,10 +35,29 @@ def display_page(path): | |||||||
|         content = Markup(app.config['md'].convert(entry)) |         content = Markup(app.config['md'].convert(entry)) | ||||||
|         logger.debug("file metadata: %s", app.config['md'].Meta) |         logger.debug("file metadata: %s", app.config['md'].Meta) | ||||||
|         title = " ".join(app.config['md'].Meta.get('title')) if app.config['md'].Meta.get('title') else "" |         title = " ".join(app.config['md'].Meta.get('title')) if app.config['md'].Meta.get('title') else "" | ||||||
|         return render_template('base.html', title=title, content=content, navs=parent_navs, |         return render('base.html', title=title, content=content, navs=parent_navs, | ||||||
|                       mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z')) |                       mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z')) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def render(template_name_or_list, **context): | ||||||
|  |     """Wrap Flask's render_template. | ||||||
|  | 
 | ||||||
|  |     * Determine the proper site theme to use in the template and provide it. | ||||||
|  |     """ | ||||||
|  |     selected_style = request.args.get('style', None) | ||||||
|  |     if selected_style: | ||||||
|  |         user_style = selected_style | ||||||
|  |     else: | ||||||
|  |         user_style = request.cookies.get('user-style') | ||||||
|  |         logger.debug("user style cookie: %s", user_style) | ||||||
|  |     context['user_style'] = app.config['PAGE_STYLES'].get(user_style, app.config['PAGE_STYLES']['DEFAULT']) | ||||||
|  | 
 | ||||||
|  |     resp = make_response(render_template(template_name_or_list, **context)) | ||||||
|  |     if selected_style: | ||||||
|  |         resp.set_cookie('user-style', selected_style) | ||||||
|  |     return resp | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def resolve_page_file(path): | def resolve_page_file(path): | ||||||
|     """Manipulate the request path to find appropriate page file. |     """Manipulate the request path to find appropriate page file. | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										52
									
								
								incorporealcms/static/css/dark.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								incorporealcms/static/css/dark.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | html { | ||||||
|  |     color: #CCC; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body { | ||||||
|  |     background: #444; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .site-wrap { | ||||||
|  |     background: black; | ||||||
|  | 
 | ||||||
|  |     border: 1px solid #222; | ||||||
|  |     border-top: none; | ||||||
|  |     border-bottom: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1, h2, h3, h4, h5, h6 { | ||||||
|  |     color: #B31D15; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a:link, a:visited { | ||||||
|  |     color: #BBB; | ||||||
|  |     border-bottom: 1px dotted #CCC; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a:hover, a:active { | ||||||
|  |     color: #B14640; | ||||||
|  |     border-bottom: 1px dotted #CCC; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section.nav, section.styles { | ||||||
|  |     color: #BBB; | ||||||
|  |     background: #222; | ||||||
|  |     border-bottom: 1px solid #222; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section.nav a, section.styles a { | ||||||
|  |     color: #BBB; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table, th, td { | ||||||
|  |     border: 1px solid #333; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | th { | ||||||
|  |     background: #333; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | blockquote { | ||||||
|  |     background-color: rgba(120, 120, 120, 0.3); | ||||||
|  |     border: 1px solid #222; | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								incorporealcms/static/css/light.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								incorporealcms/static/css/light.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | html { | ||||||
|  |     color: #222; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body { | ||||||
|  |     background: #888; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .site-wrap { | ||||||
|  |     background: white; | ||||||
|  | 
 | ||||||
|  |     border: 1px solid #ddd; | ||||||
|  |     border-top: none; | ||||||
|  |     border-bottom: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1, h2, h3, h4, h5, h6 { | ||||||
|  |     color: #811610; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a:link, a:visited { | ||||||
|  |     color: #222; | ||||||
|  |     border-bottom: 1px dotted #222; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a:hover, a:active { | ||||||
|  |     color: #811610; | ||||||
|  |     border-bottom: 1px dotted #222; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section.nav, section.styles { | ||||||
|  |     color: #666; | ||||||
|  |     background: #EEE; | ||||||
|  |     border-bottom: 1px solid #CCC; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section.nav a, section.styles a { | ||||||
|  |     color: #666; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table, th, td { | ||||||
|  |     border: 1px solid #ccc; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | th { | ||||||
|  |     background: #eee; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | blockquote { | ||||||
|  |     background-color: rgba(120, 120, 120, 0.1); | ||||||
|  |     border: 1px solid #CCC; | ||||||
|  | } | ||||||
| @ -1,31 +1,19 @@ | |||||||
| html { | html { | ||||||
|     font-family: sans-serif; |     font-family: sans-serif; | ||||||
|     padding: 0; |     padding: 0; | ||||||
|     color: #222; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| body { | body { | ||||||
|     background: #888; |  | ||||||
| 
 | 
 | ||||||
|     margin: 0; |     margin: 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .site-wrap { | .site-wrap { | ||||||
|     background: white; |  | ||||||
| 
 |  | ||||||
|     max-width: 70pc; |     max-width: 70pc; | ||||||
|     min-height: 100vh; |     min-height: 100vh; | ||||||
|     margin: 0; |     margin: 0; | ||||||
|     margin-left: auto; |     margin-left: auto; | ||||||
|     margin-right: auto; |     margin-right: auto; | ||||||
| 
 |  | ||||||
|     border: 1px solid #ddd; |  | ||||||
|     border-top: none; |  | ||||||
|     border-bottom: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| h1,h2,h3,h4,h5,h6 { |  | ||||||
|     color: #811610; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| h1 { | h1 { | ||||||
| @ -53,46 +41,39 @@ h6 { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| a:link { | a:link { | ||||||
|     color: #222; |  | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
|     border-bottom: 1px dotted #222; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| a:visited { | a:visited { | ||||||
|     color: #222; |  | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
|     border-bottom: 1px dotted #222; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| a:hover { | a:hover { | ||||||
|     color: #811610; |  | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
|     border-bottom: 1px dotted #222; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| a:active { | a:active { | ||||||
|     color: #811610; |  | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
|     border-bottom: 1px dotted #222; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| section.nav { | section.nav, section.styles { | ||||||
|     color: #666; |  | ||||||
|     background: #eee; |  | ||||||
|     font-size: 0.75em; |     font-size: 0.75em; | ||||||
|     border-bottom: 1px solid #ccc; |     border-bottom: 1px solid #444; | ||||||
|     padding: 0.25em 0.5em; |     padding: 0.25em 0.5em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| section.nav a { | section.nav a, section.styles a { | ||||||
|     color: #666; |  | ||||||
|     border-bottom: none; |     border-bottom: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | section.styles { | ||||||
|  |     float: right; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| section.content { | section.content { | ||||||
|     font-size: 11pt; |     font-size: 11pt; | ||||||
| 
 | 
 | ||||||
| @ -119,11 +100,7 @@ table, th, td { | |||||||
|     margin-bottom: 15px; |     margin-bottom: 15px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| th { |  | ||||||
|     background: #eee; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| blockquote { | blockquote { | ||||||
|     background-color: rgba(120, 120, 120, 0.1); |     background-color: rgba(120, 120, 120, 0.3); | ||||||
|     padding: 1px 10px; |     padding: 1px 10px; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,8 +1,13 @@ | |||||||
| <!doctype html> | <!doctype html> | ||||||
| <title>{{ title }}{% if title %} - {% endif %}{{ config.TITLE_SUFFIX }}</title> | <title>{{ title }}{% if title %} - {% endif %}{{ config.TITLE_SUFFIX }}</title> | ||||||
| <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> | <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> | ||||||
|  | <link rel="stylesheet" href="{{ url_for('static', filename=user_style) }}"> | ||||||
| <link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}"> | <link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}"> | ||||||
| <section class="site-wrap"> | <section class="site-wrap"> | ||||||
|  |     <section class="styles"> | ||||||
|  |         <a href="?style=dark">[dark]</a> | ||||||
|  |         <a href="?style=light">[light]</a> | ||||||
|  |     </section> | ||||||
|     <section class="nav"> |     <section class="nav"> | ||||||
|         {% for nav in navs %} |         {% for nav in navs %} | ||||||
|         <a href="{{ nav.1 }}">{{ nav.0 }}</a> |         <a href="{{ nav.1 }}">{{ nav.0 }}</a> | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								tests/instance/pages/mdash-or-triple-dash.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tests/instance/pages/mdash-or-triple-dash.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | Test page | ||||||
|  | 
 | ||||||
|  | word --- word | ||||||
| @ -1,5 +1,7 @@ | |||||||
| """Unit test helper methods.""" | """Unit test helper methods.""" | ||||||
| from incorporealcms.pages import generate_parent_navs, is_file_path_actually_dir_path, resolve_page_file | from werkzeug.http import dump_cookie | ||||||
|  | 
 | ||||||
|  | from incorporealcms.pages import generate_parent_navs, is_file_path_actually_dir_path, render, resolve_page_file | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_resolve_page_file_dir_to_index(): | def test_resolve_page_file_dir_to_index(): | ||||||
| @ -74,3 +76,25 @@ def test_is_file_path_actually_dir_path_nonsense_dir_is_no(app): | |||||||
|     """Test that a directory request is a directory request even if the dir doesn't exist.""" |     """Test that a directory request is a directory request even if the dir doesn't exist.""" | ||||||
|     with app.app_context(): |     with app.app_context(): | ||||||
|         assert not is_file_path_actually_dir_path('/antphnathpnthapnthsnthax/') |         assert not is_file_path_actually_dir_path('/antphnathpnthapnthsnthax/') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_render_with_user_dark_theme(app): | ||||||
|  |     """Test that a request with the dark theme selected renders the dark theme.""" | ||||||
|  |     cookie = dump_cookie("user-style", 'dark') | ||||||
|  |     with app.test_request_context(headers={'COOKIE': cookie}): | ||||||
|  |         assert b'dark.css' in render('base.html').data | ||||||
|  |         assert b'light.css' not in render('base.html').data | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_render_with_user_light_theme(app): | ||||||
|  |     """Test that a request with the light theme selected renders the light theme.""" | ||||||
|  |     with app.test_request_context(): | ||||||
|  |         assert b'light.css' in render('base.html').data | ||||||
|  |         assert b'dark.css' not in render('base.html').data | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_render_with_no_user_theme(app): | ||||||
|  |     """Test that a request with no theme set renders the light theme.""" | ||||||
|  |     with app.test_request_context(): | ||||||
|  |         assert b'light.css' in render('base.html').data | ||||||
|  |         assert b'dark.css' not in render('base.html').data | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user