Tried to fix a broke filter.
Build LinkLog Development Image / development-image (push) Successful in 10s

This commit is contained in:
2026-08-27 22:54:22 +02:00
parent 9494d2b119
commit 7bd64b870c
8 changed files with 56 additions and 42 deletions
+4
View File
@@ -13,6 +13,7 @@
* Made every web header logo link to the home page * Made every web header logo link to the home page
* Added a high-contrast Red color theme * Added a high-contrast Red color theme
* Brightened the Red theme surfaces and deepened its crimson accents * Brightened the Red theme surfaces and deepened its crimson accents
* Allow every user to filter the feed using every available tag or label
### Fixed ### Fixed
* Fixed element ID conflict on the Admin page so Available Themes load correctly * Fixed element ID conflict on the Admin page so Available Themes load correctly
* Positioned new-entry tag checkboxes after their label text * Positioned new-entry tag checkboxes after their label text
@@ -20,6 +21,9 @@
* Prevented the new-entry form grid from placing tag checkboxes above their labels * Prevented the new-entry form grid from placing tag checkboxes above their labels
* Kept unconfigured New Entry Mastodon publishing controls hidden * Kept unconfigured New Entry Mastodon publishing controls hidden
* Restored the home-page header layout, keeping the action controls above the filter toolbar * Restored the home-page header layout, keeping the action controls above the filter toolbar
* Made the tag filter refresh the feed directly when its selection changes
* Cache-busted the feed script to ensure browsers load the responsive tag filter
* Fixed feed initialization so tag filtering does not receive promise results as a selected tag [Still no complete fix]
## Version v0.1.1 ## Version v0.1.1
### Features ### Features
+24
View File
@@ -1394,3 +1394,27 @@ Since checkin d433a305f5cd8ccdafde8a0f7764462e8c167162 on the home page the filt
### Assistant outcome ### Assistant outcome
Confirmed the referenced commit did not change the feed header, then identified the current template had the toolbar outside its `.header-tools` wrapper. Restored the prior header structure: the New Entry and menu controls remain grouped above the filter toolbar on the right side of the home-page header. Added a rendered-page regression assertion for `.header-tools`. Confirmed the referenced commit did not change the feed header, then identified the current template had the toolbar outside its `.header-tools` wrapper. Restored the prior header structure: the New Entry and menu controls remain grouped above the filter toolbar on the right side of the home-page header. Added a rendered-page regression assertion for `.header-tools`.
### User
I want every user to filter on every available tag/label
### Assistant outcome
Separated feed filtering from label management: `/api/tags` now returns every tag/label to all callers, allowing complete filtering of the public feed. `/api/user/labels` remains ownership-scoped, so users still cannot manage other users' labels. Updated tag visibility regression coverage for authenticated and anonymous callers.
### User
tag filter doesn't seem to respond when pressed
### Assistant outcome
Updated the tag-filter change handler to pass the newly selected tag directly to the feed refresh instead of relying on the browser to persist and immediately reread the preference cookie. This makes filtering work even when cookie persistence is unavailable or delayed. Cache-busted the feed script to `feed.js?v=10` and added a regression assertion for the direct refresh path.
### User
The tag filter still does not allow me to select tags, even though the raw html contains the correct content. Also the user filter and the sort functionality work too
### Assistant outcome
Verified the live running container with Chromium and Playwright: selecting `#AI` changes the tag dropdown value, writes the preference cookie, and renders only matching feed entries. The deployed script already contains the direct-refresh handler, so cache-busted it again to `feed.js?v=11` and rebuilt the Compose service. Confirmed the live page serves the new asset version.
### User
I believe tag selection was still working for v0.1.0 - it is still broken
### Assistant outcome
Found the actual regression in the direct-refresh update: `Promise.all([loadUsers(), loadTags()]).then(loadFeed)` passed its results array into the new `selectedTag` parameter. The feed then attempted to call `toLowerCase()` on that array during initialization. Restored the v0.1.0 callback shape with `.then(() => loadFeed())`, retained direct selected-tag refreshes, added a regression assertion, and cache-busted the feed asset to `feed.js?v=12`.
+4
View File
@@ -248,6 +248,10 @@
229. Add a red collor theme 229. Add a red collor theme
230. Make the theme brighter and darker red 230. Make the theme brighter and darker red
231. Since checkin d433a305f5cd8ccdafde8a0f7764462e8c167162 on the home page the filter toolbar moved so that the menu button and new entry are not in the top left anylonger - revert to the previous layout 231. Since checkin d433a305f5cd8ccdafde8a0f7764462e8c167162 on the home page the filter toolbar moved so that the menu button and new entry are not in the top left anylonger - revert to the previous layout
232. I want every user to filter on every available tag/label
233. tag filter doesn't seem to respond when pressed
234. The tag filter still does not allow me to select tags, even though the raw html contains the correct content. Also the user filter and the sort functionality work too
235. I believe tag selection was still working for v0.1.0 - it is still broken
## Future entries ## Future entries
+2 -4
View File
@@ -6,7 +6,6 @@ from fastapi import APIRouter, Depends, Header, HTTPException, Response, status
import logging import logging
from pydantic import BaseModel from pydantic import BaseModel
from backend.app.api.dependencies import get_optional_current_user
from backend.app.services.link_service import create_link, delete_link, find_owned_link_by_title_url, get_link_tags, get_owned_link, list_public_links, list_tags, mark_mastodon_posted, update_link from backend.app.services.link_service import create_link, delete_link, find_owned_link_by_title_url, get_link_tags, get_owned_link, list_public_links, list_tags, mark_mastodon_posted, update_link
from backend.app.database import get_connection from backend.app.database import get_connection
from backend.app.services.plugin_manager import plugin_manager from backend.app.services.plugin_manager import plugin_manager
@@ -35,9 +34,8 @@ class LinkUpdate(BaseModel):
@router.get('/tags') @router.get('/tags')
def available_tags(user: dict | None = Depends(get_optional_current_user)): def available_tags():
user_id = user['id'] if user else None return list_tags()
return list_tags(user_id=user_id)
@router.get('/scrape') @router.get('/scrape')
+2 -25
View File
@@ -227,32 +227,9 @@ def list_public_users():
return [row['username'] for row in rows] return [row['username'] for row in rows]
def list_tags(user_id: str | None = None): def list_tags():
with get_connection() as conn: with get_connection() as conn:
if user_id: rows = conn.execute('SELECT name FROM tags ORDER BY name').fetchall()
rows = conn.execute(
'''
SELECT tags.name
FROM tags
LEFT JOIN users ON users.id = tags.created_by
WHERE tags.created_by IS NULL
OR tags.created_by = ?
OR users.is_admin = 1
ORDER BY tags.name
''',
(user_id,),
).fetchall()
else:
rows = conn.execute(
'''
SELECT tags.name
FROM tags
LEFT JOIN users ON users.id = tags.created_by
WHERE tags.created_by IS NULL
OR users.is_admin = 1
ORDER BY tags.name
''',
).fetchall()
tags = [] tags = []
seen = set() seen = set()
for row in rows: for row in rows:
+10 -6
View File
@@ -564,17 +564,17 @@ def test_label_visibility_isolation_and_grandfathering():
assert '#Cybersecurity' in charlie_label_names assert '#Cybersecurity' in charlie_label_names
assert '#BobOnlyLabel' not in charlie_label_names assert '#BobOnlyLabel' not in charlie_label_names
# Bob views /api/tags (authenticated): sees Bob tag & default/grandfathered, NOT Charlie tag # Every user can filter by every available tag, including another user's label.
bob_tags = client.get('/api/tags', headers=bob_headers).json() bob_tags = client.get('/api/tags', headers=bob_headers).json()
assert '#BobOnlyLabel' in bob_tags assert '#BobOnlyLabel' in bob_tags
assert '#GrandfatheredLabel' in bob_tags assert '#GrandfatheredLabel' in bob_tags
assert '#CharlieOnlyLabel' not in bob_tags assert '#CharlieOnlyLabel' in bob_tags
# Anonymous views /api/tags: sees default/grandfathered, NOT Bob or Charlie tag # The public feed filter has the same complete tag catalog.
anon_tags = client.get('/api/tags').json() anon_tags = client.get('/api/tags').json()
assert '#GrandfatheredLabel' in anon_tags assert '#GrandfatheredLabel' in anon_tags
assert '#BobOnlyLabel' not in anon_tags assert '#BobOnlyLabel' in anon_tags
assert '#CharlieOnlyLabel' not in anon_tags assert '#CharlieOnlyLabel' in anon_tags
# Bob cannot edit or delete grandfathered label # Bob cannot edit or delete grandfathered label
assert client.put(f"/api/user/labels/{grandfathered_id}", headers=bob_headers, json={'name': '#RenamedGrandfathered'}).status_code == 404 assert client.put(f"/api/user/labels/{grandfathered_id}", headers=bob_headers, json={'name': '#RenamedGrandfathered'}).status_code == 404
@@ -828,7 +828,11 @@ def test_public_and_admin_pages_render_html():
assert 'if (item.is_owner && !showIdentity)' in feed_script assert 'if (item.is_owner && !showIdentity)' in feed_script
assert 'deleteEntry(item, deleteButton)' in feed_script assert 'deleteEntry(item, deleteButton)' in feed_script
assert 'postToMastodon(item, mastodonButton)' in feed_script assert 'postToMastodon(item, mastodonButton)' in feed_script
assert 'tag.toLowerCase() === pref.tag.toLowerCase()' in feed_script assert 'tag.toLowerCase() === activeTag.toLowerCase()' in feed_script
assert 'loadFeed(event.target.value)' in feed_script
assert 'Promise.all([loadUsers(), loadTags()]).then(() => loadFeed())' in feed_script
assert "fetch('/api/tags', {" in feed_script
assert 'Authorization: `Bearer ${accessToken}`' in feed_script
assert 'return `${date.getFullYear()} ${months[date.getMonth()]} ${date.getDate()} - ${hours}:${minutes}`' in feed_script assert 'return `${date.getFullYear()} ${months[date.getMonth()]} ${date.getDate()} - ${hours}:${minutes}`' in feed_script
assert "entryMeta.className = 'entry-meta'" in feed_script assert "entryMeta.className = 'entry-meta'" in feed_script
assert "meta.className = 'meta'" in feed_script assert "meta.className = 'meta'" in feed_script
+9 -6
View File
@@ -73,7 +73,9 @@ async function loadUsers() {
} }
async function loadTags() { async function loadTags() {
const response = await fetch('/api/tags'); const response = await fetch('/api/tags', {
headers: accessToken ? {Authorization: `Bearer ${accessToken}`} : {},
});
if (!response.ok) throw new Error('Could not load tags'); if (!response.ok) throw new Error('Could not load tags');
const tags = await response.json(); const tags = await response.json();
availableTags = tags; availableTags = tags;
@@ -254,7 +256,7 @@ function showEditForm(article, item) {
article.appendChild(form); article.appendChild(form);
} }
async function loadFeed() { async function loadFeed(selectedTag = null) {
const routeUser = document.body.dataset.userFilter; const routeUser = document.body.dataset.userFilter;
const endpoint = routeUser const endpoint = routeUser
? `/api/public/feed/${encodeURIComponent(routeUser)}` ? `/api/public/feed/${encodeURIComponent(routeUser)}`
@@ -266,13 +268,14 @@ async function loadFeed() {
let items = data || []; let items = data || [];
const pref = readPreferences(); const pref = readPreferences();
const activeTag = selectedTag ?? pref.tag;
if (pref.user && !routeUser) { if (pref.user && !routeUser) {
items = items.filter((item) => (item.user?.username || '').toLowerCase() === pref.user.toLowerCase()); items = items.filter((item) => (item.user?.username || '').toLowerCase() === pref.user.toLowerCase());
} }
if (pref.tag) { if (activeTag) {
items = items.filter((item) => item.tags?.some((tag) => tag.toLowerCase() === pref.tag.toLowerCase())); items = items.filter((item) => item.tags?.some((tag) => tag.toLowerCase() === activeTag.toLowerCase()));
} }
if (pref.sort === 'oldest') { if (pref.sort === 'oldest') {
@@ -304,9 +307,9 @@ function syncPreferences() {
tagFilter.addEventListener('change', (event) => { tagFilter.addEventListener('change', (event) => {
const next = { ...readPreferences(), tag: event.target.value }; const next = { ...readPreferences(), tag: event.target.value };
writePreferences(next); writePreferences(next);
loadFeed(); loadFeed(event.target.value);
}); });
} }
syncPreferences(); syncPreferences();
Promise.all([loadUsers(), loadTags()]).then(loadFeed).catch(() => loadFeed()); Promise.all([loadUsers(), loadTags()]).then(() => loadFeed()).catch(() => loadFeed());
+1 -1
View File
@@ -86,6 +86,6 @@
<script src="/static/auth-header.js?v=3"></script> <script src="/static/auth-header.js?v=3"></script>
<script src="/static/logout.js?v=3"></script> <script src="/static/logout.js?v=3"></script>
<script src="/static/theme.js?v=1"></script> <script src="/static/theme.js?v=1"></script>
<script src="/static/feed.js?v=9"></script> <script src="/static/feed.js?v=12"></script>
</body> </body>
</html> </html>