From 54c341aaf7d43d99419a6bb87e185ba9021ddb2b Mon Sep 17 00:00:00 2001 From: Aliaksei Yaletski Date: Wed, 15 Jan 2025 16:34:25 +0100 Subject: [PATCH] ff-220 Improved GUI of the "mark read/unread" operation. Added "undo mark as read" button (#314) --- changes/unreleased.md | 1 + ffun/ffun/api/http_handlers.py | 2 +- ffun/ffun/api/settings.py | 1 + site/src/components/EntryForList.vue | 52 +++++++++++++++++++++------- site/src/inputs/Marker.vue | 28 +++++---------- site/src/stores/entries.ts | 35 +++++++++++++++++-- site/src/style.css | 2 +- site/src/values/Score.vue | 3 +- site/src/views/NewsView.vue | 11 +++++- 9 files changed, 96 insertions(+), 39 deletions(-) diff --git a/changes/unreleased.md b/changes/unreleased.md index 5c460294..4535d4f5 100644 --- a/changes/unreleased.md +++ b/changes/unreleased.md @@ -1,2 +1,3 @@ - ff-219 — Fixed sending User-Agent header in requests. +- ff-220 — Improved GUI of the "mark read/unread" operation. Added "undo mark as read" button. diff --git a/ffun/ffun/api/http_handlers.py b/ffun/ffun/api/http_handlers.py index 96cbb0bf..1abf2031 100644 --- a/ffun/ffun/api/http_handlers.py +++ b/ffun/ffun/api/http_handlers.py @@ -131,7 +131,7 @@ async def api_get_entries_by_ids( ) -> entities.GetEntriesByIdsResponse: # TODO: check if belongs to user - if len(request.ids) > 10: + if len(request.ids) > settings.max_entries_details_requests: # TODO: better error processing raise fastapi.HTTPException(status_code=400, detail="Too many ids") diff --git a/ffun/ffun/api/settings.py b/ffun/ffun/api/settings.py index 6851d33c..55a08086 100644 --- a/ffun/ffun/api/settings.py +++ b/ffun/ffun/api/settings.py @@ -7,6 +7,7 @@ class Settings(BaseSettings): max_returned_entries: int = 10000 max_feeds_suggestions_for_site: int = 100 max_entries_suggestions_for_site: int = 3 + max_entries_details_requests: int = 100 model_config = pydantic_settings.SettingsConfigDict(env_prefix="FFUN_API_") diff --git a/site/src/components/EntryForList.vue b/site/src/components/EntryForList.vue index bab89e5e..75120598 100644 --- a/site/src/components/EntryForList.vue +++ b/site/src/components/EntryForList.vue @@ -2,7 +2,30 @@
-
+
+ + + + + +
+ +
@@ -14,15 +37,6 @@ class="w-5 h-5 align-text-bottom mx-1 inline" />
-
- -
- @@ -23,7 +21,7 @@ - + diff --git a/site/src/stores/entries.ts b/site/src/stores/entries.ts index 15ca1c9b..873c4de1 100644 --- a/site/src/stores/entries.ts +++ b/site/src/stores/entries.ts @@ -16,6 +16,9 @@ export const useEntriesStore = defineStore("entriesStore", () => { const entries = ref<{[key: t.EntryId]: t.Entry}>({}); const requestedEntries = ref<{[key: t.EntryId]: boolean}>({}); const displayedEntryId = ref(null); + const readHistory = ref([]); + + const canUndoMarkRead = computed(() => readHistory.value.length > 0); function registerEntry(entry: t.Entry) { if (entry.id in entries.value) { @@ -82,19 +85,33 @@ export const useEntriesStore = defineStore("entriesStore", () => { requestedEntriesTimer.start(); async function setMarker({entryId, marker}: {entryId: t.EntryId; marker: e.Marker}) { - await api.setMarker({entryId: entryId, marker: marker}); + if (marker === e.Marker.Read) { + readHistory.value.push(entryId); + } + // This code must be before the actual API request + // to guarantee smooth UI transition to the new state + // otherwise the UI will be updated two times which leads to flickering if (entryId in entries.value) { entries.value[entryId].setMarker(marker); } + + await api.setMarker({entryId: entryId, marker: marker}); } async function removeMarker({entryId, marker}: {entryId: t.EntryId; marker: e.Marker}) { - await api.removeMarker({entryId: entryId, marker: marker}); + if (marker === e.Marker.Read) { + _.pull(readHistory.value, entryId); + + hideEntry({entryId: entryId}); + } + // This code must be before the actual API request, see comment above if (entryId in entries.value) { entries.value[entryId].removeMarker(marker); } + + await api.removeMarker({entryId: entryId, marker: marker}); } async function displayEntry({entryId}: {entryId: t.EntryId}) { @@ -118,6 +135,16 @@ export const useEntriesStore = defineStore("entriesStore", () => { } } + function undoMarkRead() { + if (readHistory.value.length === 0) { + return; + } + + const entryId = readHistory.value.pop() as t.EntryId; + + removeMarker({entryId: entryId, marker: e.Marker.Read}); + } + return { entries, requestFullEntry, @@ -126,6 +153,8 @@ export const useEntriesStore = defineStore("entriesStore", () => { loadedEntriesReport, displayedEntryId, displayEntry, - hideEntry + hideEntry, + undoMarkRead, + canUndoMarkRead }; }); diff --git a/site/src/style.css b/site/src/style.css index 8d7f6f93..d1d03294 100644 --- a/site/src/style.css +++ b/site/src/style.css @@ -81,7 +81,7 @@ } .ffun-form-button { - @apply ffun-input-common border-blue-300 disabled:bg-blue-700/75 py-1; + @apply ffun-input-common border-blue-300 disabled:bg-blue-100/25 py-1; } .ffun-file-button { diff --git a/site/src/values/Score.vue b/site/src/values/Score.vue index 10f8eeb1..46211b3a 100644 --- a/site/src/values/Score.vue +++ b/site/src/values/Score.vue @@ -1,6 +1,7 @@