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'] | ||||
| 
 | ||||
|     PAGE_STYLES = { | ||||
|         'DEFAULT': 'css/light.css', | ||||
|         'dark': 'css/dark.css', | ||||
|         'light': 'css/light.css', | ||||
|     } | ||||
| 
 | ||||
|     TITLE_SUFFIX = 'incorporeal.org' | ||||
|     MEDIA_DIR = 'media' | ||||
|  | ||||
| @ -5,7 +5,7 @@ import os | ||||
| 
 | ||||
| from flask import Blueprint, Markup, abort | ||||
| 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 | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| @ -35,8 +35,27 @@ def display_page(path): | ||||
|         content = Markup(app.config['md'].convert(entry)) | ||||
|         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 "" | ||||
|         return render_template('base.html', title=title, content=content, navs=parent_navs, | ||||
|                                mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z')) | ||||
|         return render('base.html', title=title, content=content, navs=parent_navs, | ||||
|                       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): | ||||
|  | ||||
							
								
								
									
										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 { | ||||
|     font-family: sans-serif; | ||||
|     padding: 0; | ||||
|     color: #222; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|     background: #888; | ||||
| 
 | ||||
|     margin: 0; | ||||
| } | ||||
| 
 | ||||
| .site-wrap { | ||||
|     background: white; | ||||
| 
 | ||||
|     max-width: 70pc; | ||||
|     min-height: 100vh; | ||||
|     margin: 0; | ||||
|     margin-left: auto; | ||||
|     margin-right: auto; | ||||
| 
 | ||||
|     border: 1px solid #ddd; | ||||
|     border-top: none; | ||||
|     border-bottom: none; | ||||
| } | ||||
| 
 | ||||
| h1,h2,h3,h4,h5,h6 { | ||||
|     color: #811610; | ||||
| } | ||||
| 
 | ||||
| h1 { | ||||
| @ -53,46 +41,39 @@ h6 { | ||||
| } | ||||
| 
 | ||||
| a:link { | ||||
|     color: #222; | ||||
|     font-weight: bold; | ||||
|     text-decoration: none; | ||||
|     border-bottom: 1px dotted #222; | ||||
| } | ||||
| 
 | ||||
| a:visited { | ||||
|     color: #222; | ||||
|     font-weight: bold; | ||||
|     text-decoration: none; | ||||
|     border-bottom: 1px dotted #222; | ||||
| } | ||||
| 
 | ||||
| a:hover { | ||||
|     color: #811610; | ||||
|     font-weight: bold; | ||||
|     text-decoration: none; | ||||
|     border-bottom: 1px dotted #222; | ||||
| } | ||||
| 
 | ||||
| a:active { | ||||
|     color: #811610; | ||||
|     font-weight: bold; | ||||
|     text-decoration: none; | ||||
|     border-bottom: 1px dotted #222; | ||||
| } | ||||
| 
 | ||||
| section.nav { | ||||
|     color: #666; | ||||
|     background: #eee; | ||||
| section.nav, section.styles { | ||||
|     font-size: 0.75em; | ||||
|     border-bottom: 1px solid #ccc; | ||||
|     border-bottom: 1px solid #444; | ||||
|     padding: 0.25em 0.5em; | ||||
| } | ||||
| 
 | ||||
| section.nav a { | ||||
|     color: #666; | ||||
| section.nav a, section.styles a { | ||||
|     border-bottom: none; | ||||
| } | ||||
| 
 | ||||
| section.styles { | ||||
|     float: right; | ||||
| } | ||||
| 
 | ||||
| section.content { | ||||
|     font-size: 11pt; | ||||
| 
 | ||||
| @ -119,11 +100,7 @@ table, th, td { | ||||
|     margin-bottom: 15px; | ||||
| } | ||||
| 
 | ||||
| th { | ||||
|     background: #eee; | ||||
| } | ||||
| 
 | ||||
| blockquote { | ||||
|     background-color: rgba(120, 120, 120, 0.1); | ||||
|     background-color: rgba(120, 120, 120, 0.3); | ||||
|     padding: 1px 10px; | ||||
| } | ||||
|  | ||||
| @ -1,8 +1,13 @@ | ||||
| <!doctype html> | ||||
| <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=user_style) }}"> | ||||
| <link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}"> | ||||
| <section class="site-wrap"> | ||||
|     <section class="styles"> | ||||
|         <a href="?style=dark">[dark]</a> | ||||
|         <a href="?style=light">[light]</a> | ||||
|     </section> | ||||
|     <section class="nav"> | ||||
|         {% for nav in navs %} | ||||
|         <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.""" | ||||
| 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(): | ||||
| @ -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.""" | ||||
|     with app.app_context(): | ||||
|         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