Skip to content

Commit

Permalink
ff-220 Improved GUI of the "mark read/unread" operation. Added "undo …
Browse files Browse the repository at this point in the history
…mark as read" button (#314)
  • Loading branch information
Tiendil authored Jan 15, 2025
1 parent 561aec5 commit 54c341a
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 39 deletions.
1 change: 1 addition & 0 deletions changes/unreleased.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion ffun/ffun/api/http_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
1 change: 1 addition & 0 deletions ffun/ffun/api/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_")

Expand Down
52 changes: 40 additions & 12 deletions site/src/components/EntryForList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,30 @@
<div
ref="entryTop"
class="flex text-lg">
<div class="flex-shrink-0 w-8 text-right pr-1">
<div :class="['flex-shrink-0', 'text-right', {'ml-8': isRead}]">
<input-marker
class="w-7 mr-2"
:marker="e.Marker.Read"
:entry-id="entryId">
<template v-slot:marked>
<span
class="text-green-700 no-underline"
title="Mark as unread">
<i class="ti ti-chevrons-left" />
</span>
</template>

<template v-slot:unmarked>
<span
class="text-orange-700 no-underline"
title="Mark as read">
<i class="ti ti-chevrons-right" />
</span>
</template>
</input-marker>
</div>

<div class="flex-shrink-0 w-8 text-center pr-1">
<value-score
:value="entry.score"
:entry-id="entry.id" />
Expand All @@ -14,15 +37,6 @@
class="w-5 h-5 align-text-bottom mx-1 inline" />
</div>

<div class="flex-shrink-0 text-right">
<input-marker
class="w-7 mr-2"
:marker="e.Marker.Read"
:entry-id="entryId"
on-text="read"
off-text="new" />
</div>

<div class="flex-grow">
<a
:href="entry.url"
Expand Down Expand Up @@ -73,7 +87,7 @@

<script lang="ts" setup>
import _ from "lodash";
import {computed, ref, useTemplateRef} from "vue";
import {computed, ref, useTemplateRef, onMounted} from "vue";
import type * as t from "@/logic/types";
import * as events from "@/logic/events";
import * as e from "@/logic/enums";
Expand Down Expand Up @@ -156,11 +170,25 @@
await entriesStore.displayEntry({entryId: entry.value.id});
if (topElement.value) {
topElement.value.scrollIntoView({behavior: "instant"});
const rect = topElement.value.getBoundingClientRect();
const isVisible =
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth);
if (!isVisible) {
topElement.value.scrollIntoView({behavior: "instant"});
}
}
}
} else {
await newsLinkOpenedEvent();
}
}
onMounted(() => {
entriesStore.requestFullEntry({entryId: properties.entryId});
});
</script>
28 changes: 8 additions & 20 deletions site/src/inputs/Marker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,25 @@
<template v-if="hasMarker">
<a
href="#"
class="marked"
@click.prevent="unmark()"
>{{ onText }}</a
>
@click.prevent="unmark()">
<slot name="marked" />
</a>
</template>

<template v-else>
<a
href="#"
class="unmarked"
@click.prevent="mark()"
>{{ offText }}</a
>
@click.prevent="mark()">
<slot name="unmarked" />
</a>
</template>
</div>
</template>

<script lang="ts" setup>
import {computed, ref} from "vue";
import * as api from "@/logic/api";
import type * as e from "@/logic/enums";
import * as e from "@/logic/enums";
import type * as t from "@/logic/types";
import {useEntriesStore} from "@/stores/entries";
Expand All @@ -32,8 +30,6 @@
const properties = defineProps<{
marker: e.Marker;
entryId: t.EntryId;
onText: string;
offText: string;
}>();
const hasMarker = computed(() => {
Expand All @@ -55,12 +51,4 @@
}
</script>

<style scoped>
.marked {
@apply text-green-700 no-underline;
}
.unmarked {
@apply text-orange-700 font-bold no-underline;
}
</style>
<style scoped></style>
35 changes: 32 additions & 3 deletions site/src/stores/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<t.EntryId | null>(null);
const readHistory = ref<t.EntryId[]>([]);

const canUndoMarkRead = computed(() => readHistory.value.length > 0);

function registerEntry(entry: t.Entry) {
if (entry.id in entries.value) {
Expand Down Expand Up @@ -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}) {
Expand All @@ -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,
Expand All @@ -126,6 +153,8 @@ export const useEntriesStore = defineStore("entriesStore", () => {
loadedEntriesReport,
displayedEntryId,
displayEntry,
hideEntry
hideEntry,
undoMarkRead,
canUndoMarkRead
};
});
2 changes: 1 addition & 1 deletion site/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion site/src/values/Score.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<div
class="inline-block cursor-pointer font-semibold text-purple-700"
title="Click to see score details"
class="inline-block cursor-pointer text-purple-700"
@click.prevent="onClick()">
{{ value }}
</div>
Expand Down
11 changes: 10 additions & 1 deletion site/src/views/NewsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,21 @@
</template>

<template #side-menu-item-3>
Show read:
Show read

<config-flag
style="min-width: 2.5rem"
v-model:flag="globalSettings.showRead"
on-text="no"
off-text="yes" />

<button
class="ffun-form-button py-0 ml-1"
title='Undo last "mark read" operation'
:disabled="!entriesStore.canUndoMarkRead"
@click="entriesStore.undoMarkRead()">
</button>
</template>

<template #side-footer>
Expand Down

0 comments on commit 54c341a

Please sign in to comment.