-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Creates activity feed on main "My Books" page #11645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3aba75f
bb06872
340d00b
2aa856d
efafac0
5188e2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -176,3 +176,16 @@ def most_followed(cls, limit=100): | |
| vars={'limit': limit}, | ||
| ) | ||
| return top_publishers | ||
|
|
||
| @classmethod | ||
| def is_following(cls, username): | ||
| oldb = db.get_db() | ||
| query = """ | ||
| SELECT EXISTS( | ||
| SELECT 1 | ||
| FROM follows | ||
| WHERE subscriber=$subscriber | ||
| ) | ||
| """ | ||
| result = oldb.query(query, vars={'subscriber': username}) | ||
| return result and result[0].get('exists', False) | ||
|
Comment on lines
+180
to
+191
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,9 +2,10 @@ | |||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||
| from collections import namedtuple | ||||||||||||||||||||||||||||||||||||
| from collections.abc import Callable, Iterable | ||||||||||||||||||||||||||||||||||||
| from datetime import date, datetime | ||||||||||||||||||||||||||||||||||||
| from typing import Any, cast | ||||||||||||||||||||||||||||||||||||
| from typing import Any, Literal, cast | ||||||||||||||||||||||||||||||||||||
| from urllib.parse import urlsplit | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import babel | ||||||||||||||||||||||||||||||||||||
|
|
@@ -142,8 +143,12 @@ def datestr( | |||||||||||||||||||||||||||||||||||
| now: datetime | None = None, | ||||||||||||||||||||||||||||||||||||
| lang: str | None = None, | ||||||||||||||||||||||||||||||||||||
| relative: bool = True, | ||||||||||||||||||||||||||||||||||||
| format: Literal['compact', 'long'] = 'long', | ||||||||||||||||||||||||||||||||||||
| ) -> str: | ||||||||||||||||||||||||||||||||||||
| """Internationalized version of web.datestr.""" | ||||||||||||||||||||||||||||||||||||
| if format == 'compact' and relative: | ||||||||||||||||||||||||||||||||||||
| return _datestr_compact(then, now) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| lang = lang or web.ctx.lang | ||||||||||||||||||||||||||||||||||||
| if relative: | ||||||||||||||||||||||||||||||||||||
| if now is None: | ||||||||||||||||||||||||||||||||||||
|
|
@@ -156,6 +161,36 @@ def datestr( | |||||||||||||||||||||||||||||||||||
| return format_date(then, lang=lang) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| TimeDeltaUnit = namedtuple( | ||||||||||||||||||||||||||||||||||||
| 'TimeDeltaUnit', ['long_name', 'short_name', 'seconds_per_unit'] | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| TIME_DELTA_UNITS = ( | ||||||||||||||||||||||||||||||||||||
| TimeDeltaUnit('year', 'y', 3600 * 24 * 365), | ||||||||||||||||||||||||||||||||||||
| TimeDeltaUnit('month', 'm', 3600 * 24 * 30), | ||||||||||||||||||||||||||||||||||||
| TimeDeltaUnit('week', 'w', 3600 * 24 * 7), | ||||||||||||||||||||||||||||||||||||
| TimeDeltaUnit('day', 'd', 3600 * 24), | ||||||||||||||||||||||||||||||||||||
| TimeDeltaUnit('hour', 'h', 3600), | ||||||||||||||||||||||||||||||||||||
| TimeDeltaUnit('minute', 'm', 60), | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+166
to
+174
|
||||||||||||||||||||||||||||||||||||
| TimeDeltaUnit('second', 's', 1), | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| def _datestr_compact(then: datetime, now: datetime | None = None) -> str: | ||||||||||||||||||||||||||||||||||||
| if now is None: | ||||||||||||||||||||||||||||||||||||
| now = datetime.now() | ||||||||||||||||||||||||||||||||||||
| delta = now - then | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| time_delta_unit = TIME_DELTA_UNITS[-1] | ||||||||||||||||||||||||||||||||||||
| for tdu in TIME_DELTA_UNITS: | ||||||||||||||||||||||||||||||||||||
| if delta.total_seconds() > tdu.seconds_per_unit: | ||||||||||||||||||||||||||||||||||||
| time_delta_unit = tdu | ||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| result = f'{int(delta.total_seconds()) // time_delta_unit.seconds_per_unit}{time_delta_unit.short_name}' | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+183
to
+190
|
||||||||||||||||||||||||||||||||||||
| time_delta_unit = TIME_DELTA_UNITS[-1] | |
| for tdu in TIME_DELTA_UNITS: | |
| if delta.total_seconds() > tdu.seconds_per_unit: | |
| time_delta_unit = tdu | |
| break | |
| result = f'{int(delta.total_seconds()) // time_delta_unit.seconds_per_unit}{time_delta_unit.short_name}' | |
| total_seconds = int(delta.total_seconds()) | |
| time_delta_unit = TIME_DELTA_UNITS[-1] | |
| for tdu in TIME_DELTA_UNITS: | |
| if total_seconds >= tdu.seconds_per_unit: | |
| time_delta_unit = tdu | |
| break | |
| result = f'{total_seconds // time_delta_unit.seconds_per_unit}{time_delta_unit.short_name}' |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,52 @@ | ||||||||||||||||||||||
| import { buildPartialsUrl } from '../utils' | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Fetches and displays the activity feed for a "My Books" page. | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * @param {HTMLElement} elem - Container for the activity feed | ||||||||||||||||||||||
| * @returns {Promise<void>} | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * @see `/openlibrary/templates/account/activity_feed.html` for activity feed template | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export async function initActivityFeedRequest(elem) { | ||||||||||||||||||||||
| const fullPath = window.location.pathname | ||||||||||||||||||||||
| const splitPath = fullPath.split('/') | ||||||||||||||||||||||
| const username = splitPath[2] // Assumes an activity feed can only appear on the patron's "My Books" page | ||||||||||||||||||||||
|
||||||||||||||||||||||
| const username = splitPath[2] // Assumes an activity feed can only appear on the patron's "My Books" page | |
| // Expected path structure for a "My Books" activity feed page: | |
| // "/people/{username}/books" | |
| // After splitting on "/", this yields: | |
| // splitPath[0] === "" (leading slash) | |
| // splitPath[1] === "people" | |
| // splitPath[2] === "{username}" | |
| // splitPath[3] === "books" | |
| const USERNAME_PATH_INDEX = 2 | |
| const username = splitPath[USERNAME_PATH_INDEX] // Assumes an activity feed can only appear on the patron's "My Books" page |
Copilot
AI
Dec 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing preventDefault() call in the click event handler. When the retry link is clicked, the default anchor behavior should be prevented to avoid page navigation to '#'.
| retryButton.addEventListener('click', async () => { | |
| retryButton.addEventListener('click', async (event) => { | |
| event.preventDefault() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SQL query uses a bare SELECT EXISTS which returns a boolean, but the result access pattern expects a dictionary with an 'exists' key. This may work in the database library being used, but it's worth verifying that
result[0].get('exists', False)will work correctly with the query result format. Consider explicitly naming the column with AS for clarity.