eyecandy and delete
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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');
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user