eyecandy and delete

This commit is contained in:
2026-08-26 10:04:23 +02:00
parent 2e5610555e
commit 9deb28330a
9 changed files with 92 additions and 7 deletions
+12
View File
@@ -640,3 +640,15 @@ Only print the `from` line if there is no title. Put the title directly behind t
### Assistant outcome
Updated Mastodon formatting to place the title directly after `From my #LinkLog:`, omit the `from:` line when no title is available, and place tags on a separate final line. Added no-title regression coverage. The focused tests passed and the complete Dockerized backend suite passed with 23 tests.
### User
Do not show edit buttons on the home page. On `/<user>/`, show the edit button on the right and add a delete button.
### Assistant outcome
Added an owner-scoped `DELETE /api/links/{link_id}` endpoint. Feed edit/delete controls now render only on user-specific pages, never on the home feed, and are right-aligned in an entry action group. Added owner-only deletion regression coverage and updated the feed cache key. The complete Dockerized backend suite passed with 23 tests, and the running container is healthy.
### User
Put the buttons on the right-hand side of the link item, make them smaller, and keep the same color scheme.
### Assistant outcome
Changed the entry action group to float at the right edge of user-specific link items, allowing content to wrap around it. Reduced Edit and Delete button padding, radius, and font size while preserving Catppuccin surface and danger colors.
+2
View File
@@ -117,6 +117,8 @@
111. LINKLOG_PUBLIC_URL and TRAEFIK_HOST are the same and can be merged. (use LINKLOG_PUBLIC_URL), fix docker-compose to use said variable
112. Format mastodon posts like From my #LinkLog: Title, optional comment, and from: URL
113. Only print the 'from' line if there is no title. Put the title directly behind the colon, and put all tags on the last line.
114. In the frontend do not show edit buttons on the home page even when a user is logged in. On the /<user>/ page show the edit button on the right of the entry. Also add a delete button.
115. Put the buttons on the right hand side of the link item, make the buttons smaller and same color scheme
## Future entries
+16 -1
View File
@@ -5,7 +5,7 @@ from fastapi import APIRouter, Header, HTTPException, status
import logging
from pydantic import BaseModel
from backend.app.services.link_service import create_link, list_public_links, list_tags, update_link
from backend.app.services.link_service import create_link, delete_link, list_public_links, list_tags, update_link
from backend.app.services.plugin_manager import plugin_manager
from backend.app.services.token_service import validate_token
@@ -73,6 +73,21 @@ def update_link_endpoint(
return record
@router.delete('/links/{link_id}')
def delete_link_endpoint(
link_id: str,
authorization: str | None = Header(default=None),
):
if not authorization or not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail='Missing or invalid Authorization header')
info = validate_token(authorization.replace('Bearer ', '', 1))
if info is None:
raise HTTPException(status_code=401, detail='Token expired or invalid')
if not delete_link(link_id, info['user_id']):
raise HTTPException(status_code=404, detail='Link not found or not owned by user')
return {'status': 'deleted', 'id': link_id}
@router.get('/links')
def list_links():
return list_public_links()
+10
View File
@@ -157,6 +157,16 @@ def update_link(
return record
def delete_link(link_id: str, user_id: str) -> bool:
with get_connection() as conn:
cursor = conn.execute(
'DELETE FROM links WHERE id = ? AND user_id = ?',
(link_id, user_id),
)
conn.commit()
return cursor.rowcount > 0
def list_public_users():
with get_connection() as conn:
rows = conn.execute(
+7 -1
View File
@@ -334,6 +334,11 @@ def test_only_link_owner_can_edit_link():
})
assert denied.status_code == 404
deleted = client.delete(f'/api/links/{link_id}', headers=owner_headers)
assert deleted.status_code == 200
assert client.delete(f'/api/links/{link_id}', headers=owner_headers).status_code == 404
assert client.delete(f'/api/links/{link_id}', headers=login_headers('bob')).status_code == 404
def test_logout_revokes_token_and_admin_can_list_plugins():
headers = login_headers()
@@ -396,7 +401,8 @@ def test_public_and_admin_pages_render_html():
assert '<a id="auth-username" class="user-name" href="/">' in admin_page
assert 'admin.js?v=5' in admin_page
feed_script = client.get('/static/feed.js?v=7').text
assert 'if (item.is_owner)' in feed_script
assert 'if (item.is_owner && !showIdentity)' in feed_script
assert 'deleteEntry(item, deleteButton)' in feed_script
assert 'tag.toLowerCase() === pref.tag.toLowerCase()' in feed_script
assert 'return `${date.getFullYear()} ${months[date.getMonth()]} ${date.getDate()} - ${hours}:${minutes}`' in feed_script
assert 'edit-tag-options' in feed_script
+24 -2
View File
@@ -117,13 +117,21 @@ function renderFeed(items, showIdentity = true) {
meta.className = 'meta';
meta.textContent = formatDate(item.created_at);
if (item.is_owner) {
if (item.is_owner && !showIdentity) {
const actions = document.createElement('div');
actions.className = 'entry-actions';
const editButton = document.createElement('button');
editButton.type = 'button';
editButton.className = 'edit-button';
editButton.textContent = 'Edit';
editButton.addEventListener('click', () => showEditForm(article, item));
article.appendChild(editButton);
const deleteButton = document.createElement('button');
deleteButton.type = 'button';
deleteButton.className = 'delete-button';
deleteButton.textContent = 'Delete';
deleteButton.addEventListener('click', () => deleteEntry(item, deleteButton));
actions.append(editButton, deleteButton);
article.appendChild(actions);
}
if (showIdentity) {
@@ -144,6 +152,20 @@ function renderFeed(items, showIdentity = true) {
});
}
async function deleteEntry(item, button) {
if (!window.confirm(`Delete this link?`)) return;
button.disabled = true;
const response = await fetch(`/api/links/${encodeURIComponent(item.id)}`, {
method: 'DELETE',
headers: {Authorization: `Bearer ${accessToken}`},
});
if (response.ok) {
await loadFeed();
} else {
button.disabled = false;
}
}
function showEditForm(article, item) {
if (article.querySelector('.edit-form')) return;
const form = document.createElement('form');
+20 -2
View File
@@ -420,11 +420,29 @@ button:disabled {
.edit-button {
min-width: 0;
margin-top: 14px;
padding: 8px 12px;
padding: 5px 9px;
border-radius: 6px;
background: var(--surface-1);
border-color: var(--surface-2);
color: var(--text);
font-size: 0.82rem;
}
.entry-actions {
display: flex;
float: right;
gap: 8px;
margin: 0 0 8px 16px;
}
.delete-button {
min-width: 0;
padding: 5px 9px;
border-radius: 6px;
border-color: rgba(243, 139, 168, 0.45);
background: transparent;
color: var(--red);
font-size: 0.82rem;
}
.edit-form {
+1 -1
View File
@@ -79,6 +79,6 @@
<script src="/static/auth-header.js?v=3"></script>
<script src="/static/logout.js?v=3"></script>
<script src="/static/feed.js?v=7"></script>
<script src="/static/feed.js?v=8"></script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB