Browse Source

Merge branch 'master' into 'master'

Add an estimated reading time to Link texts - Issue 700

See merge request tildes/tildes!138
merge-requests/138/merge
Joe H 2 months ago
parent
commit
b015645b3f
  1. 24
      tildes/alembic/versions/132ed4b3e988_words_per_minute.py
  2. 1
      tildes/scripts/clean_private_data.py
  3. 32
      tildes/templates/settings_wpm.jinja2
  4. 3
      tildes/tildes/models/user/user.py
  5. 1
      tildes/tildes/routes.py
  6. 5
      tildes/tildes/schemas/user.py
  7. 4
      tildes/tildes/templates/settings.jinja2
  8. 32
      tildes/tildes/templates/settings_wpm.jinja2
  9. 43
      tildes/tildes/templates/topic.jinja2
  10. 16
      tildes/tildes/views/api/web/user.py
  11. 6
      tildes/tildes/views/settings.py
  12. 6
      tildes/tildes/views/topic.py

24
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")

1
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,
)

32
tildes/templates/settings_wpm.jinja2

@ -0,0 +1,32 @@
{# Copyright (c) 2023 Tildes contributors <code@tildes.net> #}
{# 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 %}
<p>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.</p>
<div class="divider"></div>
<form
method="post"
name="wpm-change"
autocomplete="off"
data-ic-patch-to="{{ request.route_url('ic_user', username=request.user.username) }}"
data-ic-trigger-name="user-wpm"
data-js-confirm-leave-page-unsaved
>
<div class="form-group">
<label class="form-label" for="wordsPerMinute">Words Per Minute (WPM)</label>
<input type="number" class="form-input" id="wordsPerMinute" name="words_per_minute" value="{{ request.user.words_per_minute }}" min="0" max="9999" required>
</div>
<div class="form-buttons">
<button type="submit" class="btn btn-primary">Save WPM</button>
</div>
</form>
{% endblock %}

3
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",

1
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)

5
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:

4
tildes/tildes/templates/settings.jinja2

@ -260,6 +260,10 @@
<a href="/settings/bio">Edit your user bio</a>
<div class="text-small text-secondary">Tell others about yourself with a short bio on your user page</div>
</li>
<li>
<a href="/settings/wpm">Edit your reading speed</a>
<div class="text-small text-secondary">Customize your reading speed for personalized reading time estimates</div>
</li>
<li>
<a href="/settings/account_recovery">Set up account recovery</a>
<div class="text-small text-secondary">To be able to regain access in case of lost password, compromise, etc.</div>

32
tildes/tildes/templates/settings_wpm.jinja2

@ -0,0 +1,32 @@
{# Copyright (c) 2023 Tildes contributors <code@tildes.net> #}
{# 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 %}
<p>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.</p>
<div class="divider"></div>
<form
method="post"
name="wpm-change"
autocomplete="off"
data-ic-patch-to="{{ request.route_url('ic_user', username=request.user.username) }}"
data-ic-trigger-name="user-wpm"
data-js-confirm-leave-page-unsaved
>
<div class="form-group">
<label class="form-label" for="wordsPerMinute">Words Per Minute (WPM)</label>
<input type="number" class="form-input" id="wordsPerMinute" name="words_per_minute" value="{{ request.user.words_per_minute }}" min="0" max="9999" required>
</div>
<div class="form-buttons">
<button type="submit" class="btn btn-primary">Save WPM</button>
</div>
</form>
{% endblock %}

43
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 @@
</dl>
{% endblock %}
{% macro topic_content_metadata(content_metadata) %}
{% macro topic_content_metadata(content_metadata, words_per_minute) %}
<section class="topic-full-content-metadata">
<h2>Link information</h2>
<p class="text-small text-secondary">This data is scraped automatically and may be incorrect.</p>
<dl>
{% for field, value in content_metadata.items() %}
<dt>{{ field }}</dt>
<dd>{{ value }}</dd>
{% endfor %}
</dl>
<h2>Link information</h2>
<p class="text-small text-secondary">This data is scraped automatically and may be incorrect.</p>
<dl>
{% for field, value in content_metadata.items() %}
<dt>{{ field }}</dt>
<dd>{{ value }}</dd>
{% endfor %}
{% if words_per_minute > 0 %}
<dt>Reading time</dt>
<dd>
{% set word_count = content_metadata['Word count'].split()[0]|int %}
{% set reading_time = (word_count / words_per_minute)|round(0, 'ceil')|int %}
{% if reading_time >= 60 %}
{% set hours = (reading_time // 60)|int %}
{% set minutes = (reading_time % 60)|int %}
~{{ hours }} hour{{ 's' if hours > 1 }} and {{ minutes }} minute{{ 's' if minutes > 1 }}
{% else %}
{% if reading_time == 1 %}
~1 minute
{% else %}
~{{ reading_time }} minutes
{% endif %}
{% endif %}
</dd>
{% endif %}
</dl>
</section>
{% endmacro %}

16
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",

6
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 {}

6
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,
}

Loading…
Cancel
Save