From 70979b3350547b0f40f32a8f506e703b84de1cec Mon Sep 17 00:00:00 2001 From: Michiel Scholten Date: Mon, 12 May 2025 16:13:28 +0200 Subject: [PATCH] Support for filtering on tags --- src/digimarks/main.py | 11 ++++- src/digimarks/static/js/digimarks.js | 58 +++++++++++++++++++++---- src/digimarks/templates/user_index.html | 2 +- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/digimarks/main.py b/src/digimarks/main.py index 768fb04..40df9b5 100644 --- a/src/digimarks/main.py +++ b/src/digimarks/main.py @@ -17,7 +17,7 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates -from pydantic import AnyUrl, DirectoryPath, FilePath +from pydantic import AnyUrl, DirectoryPath, FilePath, computed_field from pydantic_settings import BaseSettings from sqlmodel import AutoString, Field, Session, SQLModel, create_engine, desc, select @@ -240,6 +240,15 @@ class Bookmark(SQLModel, table=True): status: int = Field(default=Visibility.VISIBLE) + @computed_field + @property + def tag_list(self) -> list: + """The tags but as a proper list.""" + if not self.tags: + # Not tags, return empty list instead of [''] that split returns in that case + return [] + return self.tags.split(',') + async def set_title_from_source(self, request: Request) -> str: """Request the title by requesting the source url.""" try: diff --git a/src/digimarks/static/js/digimarks.js b/src/digimarks/static/js/digimarks.js index 050ed59..a2a8f31 100644 --- a/src/digimarks/static/js/digimarks.js +++ b/src/digimarks/static/js/digimarks.js @@ -7,7 +7,6 @@ document.addEventListener('alpine:init', () => { cache: Alpine.$persist({}).as('cache'), bookmarks: [], - tags: [], /* Bookmark that is being edited, used to fill the form etc */ bookmark_to_edit: null, @@ -26,6 +25,10 @@ document.addEventListener('alpine:init', () => { /* Search filter */ search: '', + /* Show bookmarks with this tag/these tags */ + tags_filter: [], + /* Hide bookmarks with these tags */ + tags_to_hide: Alpine.$persist([]).as('tags_to_hide'), /* Sort on ~ */ sort_title_asc: Alpine.$persist(false).as('sort_title_asc'), @@ -61,9 +64,10 @@ document.addEventListener('alpine:init', () => { }, async loadCache() { + /* Load bookmarks and tags from cache */ if (this.userKey in this.cache) { console.log('Loading bookmarks from cache for user "' + this.userKey + '"'); - this.bookmarks = this.cache[this.userKey]['bookmarks'] || []; + this.filterBookmarksByTags(); } }, async getBookmarks() { @@ -89,34 +93,65 @@ document.addEventListener('alpine:init', () => { } console.log('Fetching latest bookmarks from backend for user "' + this.userKey + '"...'); + /* At the moment, request 'a lot' bookmarks; likely all of them in one go; paging tbd if needed */ let response = await fetch('/api/v1/' + this.userKey + '/bookmarks/?limit=10000'); - let result = await response.json(); - this.bookmarks = result; /* Cache the bookmarks to Local Storage */ - this.cache[this.userKey]['bookmarks'] = result; + this.cache[this.userKey]['bookmarks'] = await response.json(); let tags_response = await fetch('/api/v1/' + this.userKey + '/tags/'); this.cache[this.userKey]['tags'] = await tags_response.json(); + /* Filter bookmarks by (blacklisted) tags */ + await this.filterBookmarksByTags(); this.loading = false; }, + hasTag(tagList, filterList) { + /* Looks for the items in filterList and returns True when one appears on the tagList */ + if (tagList === undefined) { + return false; + } + for (let tag in filterList) { + if (tagList.includes(tag)) + return true; + } + return false; + }, + filterBookmarksByTags() { + /* Filter away bookmarks with a tag on the 'blacklist' */ + + /* First make a shallow copy of all bookmarks */ + let prefiltered_bookmarks = [...this.cache[this.userKey]['bookmarks']] || []; + if (this.tags_to_hide.length > 0) { + console.log('Filtering away bookmarks containing blacklisted tags'); + this.bookmarks = prefiltered_bookmarks.filter( + i => !this.hasTag(i.tag_list, this.tags_to_hide) + ) + } else { + this.bookmarks = prefiltered_bookmarks; + } + }, get filteredBookmarks() { - // return this.cache[this.userKey]['bookmarks'].filter( - // i => i.title.includes(this.search) - // ) - /* Use 'bookmarks' as it can already be pre-filtered */ + /* Get the bookmarks, optionally filtered by search text or tag black-/whitelists */ + + /* Use 'bookmarks' and not the cache, as it can already be pre-filtered */ + if (this.search === '') { + /* No need to filter, quickly return the set */ + return this.bookmarks; + } return this.bookmarks.filter( i => i.title.match(new RegExp(this.search, "i")) ) }, get filteredTags() { + /* Search in the list of all tags */ return this.cache[this.userKey].tags.filter( i => i.match(new RegExp(this.search, "i")) ) }, async sortAlphabetically(order = 'asc') { + /* Sort the bookmarks (reverse) alphabetically, based on 'asc' or 'desc' */ this.loading = true; this.sort_created_asc = false; this.sort_created_desc = false; @@ -132,6 +167,7 @@ document.addEventListener('alpine:init', () => { this.loading = false; }, async sortCreated(order = 'asc') { + /* Sort the bookmarks (reverse) chronologically, based on 'asc' or 'desc' */ this.loading = true; this.sort_created_asc = false; this.sort_created_desc = false; @@ -148,22 +184,26 @@ document.addEventListener('alpine:init', () => { }, async toggleTagPage() { + /* Show or hide the tag page instead of the bookmarks */ console.log('Toggle tag page'); this.show_bookmarks = !this.show_bookmarks; this.show_tags = !this.show_bookmarks; }, async toggleListOrGrid() { + /* Toggle between 'list' or 'grid' (cards) view */ this.show_bookmarks_list = !this.show_bookmarks_list; this.show_bookmarks_cards = !this.show_bookmarks_list; }, async startAddingBookmark() { + /* Open 'add bookmark' page */ console.log('Start adding bookmark'); this.bookmark_to_edit = { 'url': '' } }, async addBookmark() { + /* Post new bookmark to the backend */ // } }) diff --git a/src/digimarks/templates/user_index.html b/src/digimarks/templates/user_index.html index fbba9da..2539201 100644 --- a/src/digimarks/templates/user_index.html +++ b/src/digimarks/templates/user_index.html @@ -21,7 +21,7 @@
  • -
  • +