diff --git a/tildes/alembic/versions/132ed4b3e988_words_per_minute.py b/tildes/alembic/versions/132ed4b3e988_words_per_minute.py new file mode 100644 index 0000000..0060d1b --- /dev/null +++ b/tildes/alembic/versions/132ed4b3e988_words_per_minute.py @@ -0,0 +1,24 @@ +"""Words per Minute + +Revision ID: 132ed4b3e988 +Revises: 55f4c1f951d5 +Create Date: 2023-06-07 23:44:32.533936 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "132ed4b3e988" +down_revision = "55f4c1f951d5" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column("users", sa.Column("words_per_minute", sa.Integer(), nullable=True)) + + +def downgrade(): + op.drop_column("users", "words_per_minute") diff --git a/tildes/scripts/clean_private_data.py b/tildes/scripts/clean_private_data.py index 452b77e..604d197 100644 --- a/tildes/scripts/clean_private_data.py +++ b/tildes/scripts/clean_private_data.py @@ -207,6 +207,7 @@ class DataCleaner: "last_exemplary_label_time": DEFAULT, "_bio_markdown": DEFAULT, "bio_rendered_html": DEFAULT, + "words_per_minute": DEFAULT, }, synchronize_session=False, ) diff --git a/tildes/templates/settings_wpm.jinja2 b/tildes/templates/settings_wpm.jinja2 new file mode 100644 index 0000000..0d3b38f --- /dev/null +++ b/tildes/templates/settings_wpm.jinja2 @@ -0,0 +1,32 @@ +{# Copyright (c) 2023 Tildes contributors #} +{# SPDX-License-Identifier: AGPL-3.0-or-later #} + +{% extends 'base_settings.jinja2' %} + +{% block title %}Edit your reading speed{% endblock %} + +{% block main_heading %}Edit your reading speed{% endblock %} + +{% block settings %} +

Enter your estimated reading speed in words per minute (WPM). This will be used to calculate estimated reading times for posts. To disable reading time from displaying on Link topics, enter a value of 0.

+ +
+ +
+
+ + +
+ +
+ +
+
+{% endblock %} diff --git a/tildes/tildes/models/user/user.py b/tildes/tildes/models/user/user.py index eea2d91..e4fcf4c 100644 --- a/tildes/tildes/models/user/user.py +++ b/tildes/tildes/models/user/user.py @@ -135,6 +135,9 @@ class User(DatabaseModel): ) comment_label_weight: Optional[float] = Column(REAL) last_exemplary_label_time: Optional[datetime] = Column(TIMESTAMP(timezone=True)) + + words_per_minute: int = Column(Integer, default=0) + _bio_markdown: str = deferred( Column( "bio_markdown", diff --git a/tildes/tildes/routes.py b/tildes/tildes/routes.py index bd8b8f4..b4c8e62 100644 --- a/tildes/tildes/routes.py +++ b/tildes/tildes/routes.py @@ -105,6 +105,7 @@ def includeme(config: Configurator) -> None: config.add_route( "settings_theme_previews", "/theme_previews", factory=LoggedInFactory ) + config.add_route("settings_wpm", "/wpm", factory=LoggedInFactory) config.add_route("bookmarks", "/bookmarks", factory=LoggedInFactory) config.add_route("ignored_topics", "/ignored_topics", factory=LoggedInFactory) diff --git a/tildes/tildes/schemas/user.py b/tildes/tildes/schemas/user.py index 44bf55f..e587f63 100644 --- a/tildes/tildes/schemas/user.py +++ b/tildes/tildes/schemas/user.py @@ -8,8 +8,8 @@ from typing import Any from marshmallow import post_dump, pre_load, Schema, validates, validates_schema from marshmallow.exceptions import ValidationError -from marshmallow.fields import DateTime, Email, String -from marshmallow.validate import Length, Regexp +from marshmallow.fields import DateTime, Email, String, Integer +from marshmallow.validate import Length, Regexp, Range from tildes.lib.password import is_breached_password from tildes.schemas.fields import Markdown @@ -58,6 +58,7 @@ class UserSchema(Schema): email_address_note = String(validate=Length(max=EMAIL_ADDRESS_NOTE_MAX_LENGTH)) created_time = DateTime(dump_only=True) bio_markdown = Markdown(max_length=BIO_MAX_LENGTH, allow_none=True) + words_per_minute = Integer(validate=Range(min=0, max=9999)) @post_dump def anonymize_username(self, data: dict, many: bool) -> dict: diff --git a/tildes/tildes/templates/settings.jinja2 b/tildes/tildes/templates/settings.jinja2 index 6c3f283..659fd0a 100644 --- a/tildes/tildes/templates/settings.jinja2 +++ b/tildes/tildes/templates/settings.jinja2 @@ -260,6 +260,10 @@ Edit your user bio
Tell others about yourself with a short bio on your user page
+
  • + Edit your reading speed +
    Customize your reading speed for personalized reading time estimates
    +
  • Set up account recovery
    To be able to regain access in case of lost password, compromise, etc.
    diff --git a/tildes/tildes/templates/settings_wpm.jinja2 b/tildes/tildes/templates/settings_wpm.jinja2 new file mode 100644 index 0000000..0d3b38f --- /dev/null +++ b/tildes/tildes/templates/settings_wpm.jinja2 @@ -0,0 +1,32 @@ +{# Copyright (c) 2023 Tildes contributors #} +{# SPDX-License-Identifier: AGPL-3.0-or-later #} + +{% extends 'base_settings.jinja2' %} + +{% block title %}Edit your reading speed{% endblock %} + +{% block main_heading %}Edit your reading speed{% endblock %} + +{% block settings %} +

    Enter your estimated reading speed in words per minute (WPM). This will be used to calculate estimated reading times for posts. To disable reading time from displaying on Link topics, enter a value of 0.

    + +
    + +
    +
    + + +
    + +
    + +
    +
    +{% endblock %} diff --git a/tildes/tildes/templates/topic.jinja2 b/tildes/tildes/templates/topic.jinja2 index bfbd101..0a19b46 100644 --- a/tildes/tildes/templates/topic.jinja2 +++ b/tildes/tildes/templates/topic.jinja2 @@ -93,7 +93,7 @@ {% endif %} {% if content_metadata %} - {{ topic_content_metadata(content_metadata) }} + {{ topic_content_metadata(content_metadata, words_per_minute) }} {% endif %} {% endif %} {% endif %} @@ -364,15 +364,38 @@ {% endblock %} -{% macro topic_content_metadata(content_metadata) %} +{% macro topic_content_metadata(content_metadata, words_per_minute) %} {% endmacro %} + + + + + diff --git a/tildes/tildes/views/api/web/user.py b/tildes/tildes/views/api/web/user.py index a9b70f3..7fe18d1 100644 --- a/tildes/tildes/views/api/web/user.py +++ b/tildes/tildes/views/api/web/user.py @@ -8,7 +8,7 @@ import string from typing import Optional from marshmallow import ValidationError -from marshmallow.fields import String +from marshmallow.fields import String, Int from pyramid.httpexceptions import ( HTTPForbidden, HTTPUnauthorized, @@ -309,6 +309,20 @@ def patch_change_user_bio(request: Request, markdown: str) -> dict: return IC_NOOP +@ic_view_config( + route_name="user", + request_method="PATCH", + request_param="ic-trigger-name=wpm-change", + permission="change_settings", +) +@use_kwargs({"words_per_minute": Int()}, location="form") +def patch_change_wpm(request: Request, words_per_minute: int) -> dict: + """Change the user's words per minute (WPM) setting.""" + user = request.context + + user.words_per_minute = words_per_minute + + return IC_NOOP @ic_view_config( route_name="user_invite_code", diff --git a/tildes/tildes/views/settings.py b/tildes/tildes/views/settings.py index 0af7577..56da706 100644 --- a/tildes/tildes/views/settings.py +++ b/tildes/tildes/views/settings.py @@ -259,3 +259,9 @@ def get_settings_theme_previews(request: Request) -> dict: "fake_comment_tree": fake_tree, "last_visit": fake_last_visit_time, } + + +@view_config(route_name="settings_wpm", renderer="settings_wpm.jinja2") +def get_settings_wpm(request: Request) -> dict: + """Render the WPM settings page.""" + return {} diff --git a/tildes/tildes/views/topic.py b/tildes/tildes/views/topic.py index 376cd8e..3d4b182 100644 --- a/tildes/tildes/views/topic.py +++ b/tildes/tildes/views/topic.py @@ -481,7 +481,7 @@ def get_topic(request: Request, comment_order: CommentTreeSortOption) -> dict: ) tree.collapse_from_labels() - + user_wpm = 0 # Initialize user_wpm with a default value of 0 if request.user: request.db_session.add(TopicVisit(request.user, topic)) @@ -491,6 +491,9 @@ def get_topic(request: Request, comment_order: CommentTreeSortOption) -> dict: tree.uncollapse_new_comments(topic.last_visit_time) tree.finalize_collapsing_maximized() + # Get user's wpm setting or use a default if not set + user_wpm = request.user.words_per_minute or 0 + return { "topic": topic, "content_metadata": content_metadata, @@ -499,6 +502,7 @@ def get_topic(request: Request, comment_order: CommentTreeSortOption) -> dict: "comment_order": comment_order, "comment_order_options": CommentTreeSortOption, "comment_label_options": CommentLabelOption, + "words_per_minute": user_wpm, }