mirror of
https://gitlab.com/tildes/tildes.git
synced 2026-04-17 14:59:11 +02:00
Add a Theme Preview page in Settings
This commit is contained in:
@@ -28,3 +28,19 @@
|
||||
border-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-preview-blocks {
|
||||
display: flex;
|
||||
max-width: 40rem;
|
||||
flex-wrap: wrap;
|
||||
// The colors are assigned by a mixin from _theme_base.scss called in each theme file
|
||||
span {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
min-width: 6rem;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
font-weight: bold;
|
||||
margin: .3rem;
|
||||
}
|
||||
}
|
||||
@@ -47,3 +47,6 @@ $theme-atom-one-dark: (
|
||||
body.theme-atom-one-dark {
|
||||
@include use-theme($theme-atom-one-dark);
|
||||
}
|
||||
body {
|
||||
@include theme-preview-block($theme-atom-one-dark, "atom-one-dark");
|
||||
}
|
||||
@@ -26,3 +26,6 @@ $theme-black: (
|
||||
body.theme-black {
|
||||
@include use-theme($theme-black);
|
||||
}
|
||||
body {
|
||||
@include theme-preview-block($theme-black, "black");
|
||||
}
|
||||
@@ -31,3 +31,6 @@ $default-theme: (
|
||||
body {
|
||||
@include use-theme($default-theme);
|
||||
}
|
||||
body {
|
||||
@include theme-preview-block($default-theme, "white");
|
||||
}
|
||||
@@ -49,3 +49,6 @@ $theme-dracula: (
|
||||
body.theme-dracula {
|
||||
@include use-theme($theme-dracula);
|
||||
}
|
||||
body {
|
||||
@include theme-preview-block($theme-dracula, "dracula");
|
||||
}
|
||||
|
||||
@@ -110,6 +110,9 @@ $gruvbox-dark: (
|
||||
body.theme-gruvbox-dark {
|
||||
@include use-theme(map-merge($gruvbox-base, $gruvbox-dark));
|
||||
}
|
||||
body {
|
||||
@include theme-preview-block(map-merge($gruvbox-base, $gruvbox-dark), "gruvbox-dark");
|
||||
}
|
||||
|
||||
// Light theme definition
|
||||
$gruvbox-light: (
|
||||
@@ -130,3 +133,6 @@ $gruvbox-light: (
|
||||
body.theme-gruvbox-light {
|
||||
@include use-theme(map-merge($gruvbox-base, $gruvbox-light));
|
||||
}
|
||||
body {
|
||||
@include theme-preview-block(map-merge($gruvbox-base, $gruvbox-light), "gruvbox-light");
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ $solarized-dark: (
|
||||
body.theme-solarized-dark {
|
||||
@include use-theme(map-merge($solarized-base, $solarized-dark));
|
||||
}
|
||||
body {
|
||||
@include theme-preview-block(map-merge($solarized-base, $solarized-dark), "solarized-dark");
|
||||
}
|
||||
|
||||
// Light theme definition
|
||||
$solarized-light: (
|
||||
@@ -84,3 +87,6 @@ $solarized-light: (
|
||||
body.theme-solarized-light {
|
||||
@include use-theme(map-merge($solarized-base, $solarized-light));
|
||||
}
|
||||
body {
|
||||
@include theme-preview-block(map-merge($solarized-base, $solarized-light), "solarized-light");
|
||||
}
|
||||
|
||||
@@ -917,3 +917,13 @@
|
||||
"topic-tag-spoiler": $topic-tag-spoiler,
|
||||
));
|
||||
}
|
||||
|
||||
@mixin theme-preview-block($theme, $name) {
|
||||
.theme-preview-blocks {
|
||||
.theme-preview-block-#{$name} {
|
||||
background-color: map-get($theme, "background-primary");
|
||||
color: map-get($theme, "foreground-primary");
|
||||
border: 1px solid map-get($theme, "background-secondary");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,3 +39,6 @@ $theme-zenburn: (
|
||||
body.theme-zenburn {
|
||||
@include use-theme($theme-zenburn);
|
||||
}
|
||||
body {
|
||||
@include theme-preview-block($theme-zenburn, "zenburn");
|
||||
}
|
||||
@@ -18,6 +18,7 @@ from tildes.resources.user import user_by_username
|
||||
|
||||
def includeme(config: Configurator) -> None:
|
||||
"""Set up application routes."""
|
||||
# pylint: disable=too-many-statements
|
||||
config.add_route("home", "/")
|
||||
|
||||
config.add_route("search", "/search")
|
||||
@@ -99,6 +100,9 @@ def includeme(config: Configurator) -> None:
|
||||
config.add_route(
|
||||
"settings_password_change", "/password_change", factory=LoggedInFactory
|
||||
)
|
||||
config.add_route(
|
||||
"settings_theme_previews", "/theme_previews", factory=LoggedInFactory
|
||||
)
|
||||
|
||||
config.add_route("bookmarks", "/bookmarks", factory=LoggedInFactory)
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<a class="btn btn-primary" href="{{ request.route_url('settings_theme_previews') }}">Preview themes</a>
|
||||
|
||||
<button id="button-set-default-theme" class="btn btn-link d-none">
|
||||
Set as account default
|
||||
</button>
|
||||
|
||||
77
tildes/tildes/templates/settings_theme_previews.jinja2
Normal file
77
tildes/tildes/templates/settings_theme_previews.jinja2
Normal file
@@ -0,0 +1,77 @@
|
||||
{# Copyright (c) 2018 Tildes contributors <code@tildes.net> #}
|
||||
{# SPDX-License-Identifier: AGPL-3.0-or-later #}
|
||||
|
||||
{% from 'macros/comments.jinja2' import render_comment_tree, comment_label_options_template, comment_reply_template with context %}
|
||||
{% from 'macros/topics.jinja2' import render_topic_for_listing with context %}
|
||||
|
||||
{% extends 'base_settings.jinja2' %}
|
||||
|
||||
{% block main_classes %}{% endblock %}
|
||||
|
||||
{% block title %}Theme previews{% endblock %}
|
||||
|
||||
{% block main_heading %}Theme previews{% endblock %}
|
||||
|
||||
{% block settings %}
|
||||
<label for="theme">Choose a display theme:</label>
|
||||
<form
|
||||
class="form-inline"
|
||||
name="account-default-theme"
|
||||
data-ic-patch-to="{{ request.route_url(
|
||||
'ic_user',
|
||||
username=request.user.username
|
||||
) }}"
|
||||
>
|
||||
<select class="form-select col-8 col-sm-12" name="theme" id="theme" data-js-theme-selector>
|
||||
{% for theme, description in theme_options.items() %}
|
||||
<option
|
||||
value="{{ theme }}"
|
||||
{{ 'selected' if theme == current_theme else '' }}
|
||||
>
|
||||
{{ description }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<a class="btn btn-primary" href="{{ request.route_url('settings') }}">Return to Settings</a>
|
||||
|
||||
<button id="button-set-default-theme" class="btn btn-link d-none">
|
||||
Set as account default
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<section class="settings-section">
|
||||
<h2>Quick overview</h2>
|
||||
<div class="theme-preview-blocks">
|
||||
{% for theme, description in theme_options.items() %}
|
||||
{# The theme dict includes various (default) texts in the descriptions, cut them off #}
|
||||
{# Also, replace all spaces with NBSPs so the theme names don't get linewrapped #}
|
||||
<span class="theme-preview-block-{{ theme }}">{{ description.split(" (")[0]|replace(" ", "\u00a0") }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-section">
|
||||
<h2>Topic listings</h2>
|
||||
<ol class="topic-listing">
|
||||
{% for fake_topic in fake_topics %}
|
||||
<li>
|
||||
{{ render_topic_for_listing(fake_topic, show_group=true) }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</section>
|
||||
<section class="settings-section">
|
||||
<h2>Comments</h2>
|
||||
<ol class="comment-tree" id="comments">
|
||||
{{ render_comment_tree(fake_comment_tree, mark_newer_than=last_visit, is_individual_comment=False) }}
|
||||
</ol>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block templates %}
|
||||
{% if request.user %}
|
||||
{{ comment_reply_template() }}
|
||||
{{ comment_label_options_template(comment_label_options) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -4,6 +4,7 @@
|
||||
"""Views related to user settings."""
|
||||
|
||||
from io import BytesIO
|
||||
from typing import List, Optional
|
||||
|
||||
import pyotp
|
||||
import qrcode
|
||||
@@ -13,7 +14,13 @@ from pyramid.response import Response
|
||||
from pyramid.view import view_config
|
||||
from webargs.pyramidparser import use_kwargs
|
||||
|
||||
from tildes.enums import CommentLabelOption, CommentTreeSortOption
|
||||
from tildes.lib.string import separate_string
|
||||
from tildes.lib.datetime import utc_from_timestamp, utc_now
|
||||
from tildes.models.comment import Comment, CommentLabel, CommentTree
|
||||
from tildes.models.group import Group
|
||||
from tildes.models.topic import Topic
|
||||
from tildes.models.user import User
|
||||
from tildes.schemas.user import (
|
||||
BIO_MAX_LENGTH,
|
||||
EMAIL_ADDRESS_NOTE_MAX_LENGTH,
|
||||
@@ -27,6 +34,11 @@ PASSWORD_FIELD = UserSchema(only=("password",)).fields["password"]
|
||||
@view_config(route_name="settings", renderer="settings.jinja2")
|
||||
def get_settings(request: Request) -> dict:
|
||||
"""Generate the user settings page."""
|
||||
return generate_theme_chooser_dict(request)
|
||||
|
||||
|
||||
def generate_theme_chooser_dict(request: Request) -> dict:
|
||||
"""Generate the partial response dict necessary for the settings theme selector."""
|
||||
site_default_theme = "white"
|
||||
user_default_theme = request.user.theme_default or site_default_theme
|
||||
|
||||
@@ -144,3 +156,128 @@ def post_settings_password_change(
|
||||
request.user.change_password(old_password, new_password)
|
||||
|
||||
return Response("Your password has been updated")
|
||||
|
||||
|
||||
@view_config(
|
||||
route_name="settings_theme_previews", renderer="settings_theme_previews.jinja2"
|
||||
)
|
||||
def get_settings_theme_previews(request: Request) -> dict:
|
||||
"""Generate the theme preview page.
|
||||
|
||||
On the site, the following data must not point to real data users could
|
||||
inadvertently affect with the demo widgets:
|
||||
- The user @Tildes
|
||||
- The group ~groupname
|
||||
- Topic ID 42_000_000_000
|
||||
- Comment IDs 42_000_000_000 through 42_000_000_003
|
||||
"""
|
||||
|
||||
fake_old_timestamp = utc_from_timestamp(int(utc_now().timestamp() - 60 * 60 * 24))
|
||||
fake_last_visit_timestamp = utc_from_timestamp(
|
||||
int(utc_now().timestamp() - 60 * 60 * 12)
|
||||
)
|
||||
|
||||
fake_user = User("Tildes", "a_very_safe_password")
|
||||
fake_user.user_id = 0
|
||||
fake_user_not_op = User("Tildes", "another_very_safe_password")
|
||||
fake_user_not_op.user_id = -1
|
||||
fake_group = Group("groupname")
|
||||
fake_group.is_user_treated_as_topic_source = False
|
||||
|
||||
fake_topics: List[Topic] = [
|
||||
Topic.create_link_topic(
|
||||
fake_group, fake_user, "Example Link Topic", "https://tildes.net/"
|
||||
),
|
||||
Topic.create_text_topic(
|
||||
fake_group, fake_user, "Example Text Topic", "empty string"
|
||||
),
|
||||
]
|
||||
|
||||
for fake_topic in fake_topics:
|
||||
fake_topic.topic_id = 42_000_000_000
|
||||
fake_topic.tags = ["a tag", "another tag"]
|
||||
fake_topic.created_time = utc_now()
|
||||
fake_topic.group = fake_group
|
||||
fake_topic.num_comments = 0
|
||||
fake_topic.num_votes = 0
|
||||
fake_topic.content_metadata = {
|
||||
"excerpt": """Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit. Nunc auctor purus at diam tempor,
|
||||
id viverra nunc vulputate.""",
|
||||
"word_count": 42,
|
||||
}
|
||||
|
||||
def make_comment(
|
||||
markdown: str, comment_id: int, parent_id: Optional[int], is_op: bool
|
||||
) -> Comment:
|
||||
"""Create a fake comment with enough data to make the template render fine."""
|
||||
fake_comment = Comment(fake_topics[0], fake_user_not_op, markdown)
|
||||
fake_comment.comment_id = comment_id
|
||||
if parent_id:
|
||||
fake_comment.parent_comment_id = parent_id
|
||||
fake_comment.created_time = fake_old_timestamp
|
||||
fake_comment.num_votes = 0
|
||||
if is_op:
|
||||
fake_comment.user = fake_user
|
||||
return fake_comment
|
||||
|
||||
fake_comments: List[Comment] = []
|
||||
|
||||
fake_comments.append(
|
||||
make_comment(
|
||||
"""This is a regular comment, written by yourself. \
|
||||
It has **formatting** and a [link](https://tildes.net).""",
|
||||
42_000_000_000,
|
||||
None,
|
||||
False,
|
||||
)
|
||||
)
|
||||
fake_comments[-1].user = request.user
|
||||
fake_comments.append(
|
||||
make_comment(
|
||||
"""This is a reply written by the topic's OP. \
|
||||
It's new and has the *Mark New Comments* stripe on its left, \
|
||||
even if you didn't enable that feature.""",
|
||||
42_000_000_001,
|
||||
42_000_000_000,
|
||||
True,
|
||||
)
|
||||
)
|
||||
fake_comments[-1].created_time = utc_now()
|
||||
fake_comments.append(
|
||||
make_comment(
|
||||
"""This reply is Exemplary. It also has a blockquote:\
|
||||
\n> Hello World!""",
|
||||
42_000_000_002,
|
||||
42_000_000_000,
|
||||
False,
|
||||
)
|
||||
)
|
||||
fake_comments[-1].labels.append(
|
||||
CommentLabel(fake_comments[-1], fake_user, CommentLabelOption.EXEMPLARY, 1.0)
|
||||
)
|
||||
fake_comments.append(
|
||||
make_comment(
|
||||
"""This is a regular reply with a code block in it:\
|
||||
\n```js\
|
||||
\nfunction foo() {\
|
||||
\n ['1', '2', '3'].map(parseInt);\
|
||||
\n}\
|
||||
\n```""",
|
||||
42_000_000_003,
|
||||
42_000_000_000,
|
||||
False,
|
||||
)
|
||||
)
|
||||
|
||||
fake_tree = CommentTree(
|
||||
fake_comments, CommentTreeSortOption.RELEVANCE, request.user
|
||||
)
|
||||
|
||||
return {
|
||||
**generate_theme_chooser_dict(request),
|
||||
"fake_topics": fake_topics,
|
||||
"fake_comment_tree": fake_tree,
|
||||
"comment_label_options": [label for label in CommentLabelOption],
|
||||
"last_visit": fake_last_visit_timestamp,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user