Compare commits
No commits in common. "v0.6.0" and "v0.5.1" have entirely different histories.
5 changed files with 18 additions and 166 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -9,16 +9,6 @@ former name, "Daybook".)
|
|||
|
||||
_Nothing yet._
|
||||
|
||||
## [0.6.0] — 2026-09-07
|
||||
|
||||
### Added
|
||||
- **Per-artifact actions menu.** A **⋯** button appears on row hover, and
|
||||
**right-clicking** a feed row opens the same floating menu — **Keep /
|
||||
Open / Copy link / Delete**. Dismisses on click-away or Escape, edge-flips
|
||||
to stay on-screen. The standalone one-click Delete icon stays; the Trash
|
||||
view keeps its dedicated Undelete. (Share and Add-label will join the menu
|
||||
in later slices.)
|
||||
|
||||
## [0.5.1] — 2026-09-07
|
||||
|
||||
### Added
|
||||
|
|
@ -205,8 +195,7 @@ First tagged release.
|
|||
- Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a
|
||||
runbook (`deploy/cloud_agent_deploy.md`) and health check.
|
||||
|
||||
[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.6.0...HEAD
|
||||
[0.6.0]: https://git.cygnus.systems/peter/daybook/compare/v0.5.1...v0.6.0
|
||||
[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.5.1...HEAD
|
||||
[0.5.1]: https://git.cygnus.systems/peter/daybook/compare/v0.5.0...v0.5.1
|
||||
[0.5.0]: https://git.cygnus.systems/peter/daybook/compare/v0.4.1...v0.5.0
|
||||
[0.4.1]: https://git.cygnus.systems/peter/daybook/compare/v0.4.0...v0.4.1
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Single source of truth for the app version. Bump this, commit, then tag the
|
||||
# commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in
|
||||
# GET /api/capabilities ("version").
|
||||
__version__ = "0.6.0"
|
||||
__version__ = "0.5.1"
|
||||
|
|
|
|||
|
|
@ -1060,22 +1060,12 @@ kbd {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.row-actions {
|
||||
.row-action {
|
||||
position: absolute;
|
||||
right: 0.4rem;
|
||||
right: 0.5rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
gap: 0.15rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.artifact-row:hover .row-actions,
|
||||
.artifact-row:focus-within .row-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.row-action {
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1;
|
||||
|
|
@ -1085,6 +1075,11 @@ kbd {
|
|||
color: var(--muted);
|
||||
}
|
||||
|
||||
.artifact-row:hover .row-action,
|
||||
.artifact-row:focus-within .row-action {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.row-action:hover {
|
||||
background: var(--surface-alt);
|
||||
color: var(--text);
|
||||
|
|
@ -1094,43 +1089,6 @@ kbd {
|
|||
color: var(--danger-text-plain);
|
||||
}
|
||||
|
||||
/* Per-row actions menu (opened by the ⋯ trigger or right-click) */
|
||||
.row-menu {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
min-width: 9rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--control-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14);
|
||||
padding: 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.row-menu[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.row-menu button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.4rem 0.6rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.row-menu button:hover {
|
||||
background: var(--surface-alt);
|
||||
}
|
||||
|
||||
.row-menu button.danger {
|
||||
color: var(--danger-text-plain);
|
||||
}
|
||||
|
||||
/* System folders (Keep / Deleted) at the bottom of the sidebar */
|
||||
.source-folders {
|
||||
border-top: 1px solid var(--grid-line);
|
||||
|
|
|
|||
|
|
@ -30,13 +30,10 @@
|
|||
{% endif %}
|
||||
</span>
|
||||
<span class="artifact-source">{{ artifact.source }}</span>
|
||||
<span class="row-actions">
|
||||
{% if in_trash %}
|
||||
<span class="row-action row-undelete" role="button" tabindex="0" data-undelete="{{ artifact.id }}" title="Restore from Trash" aria-label="Restore {{ artifact.title }}">↩</span>
|
||||
{% else %}
|
||||
<span class="row-action row-delete" role="button" tabindex="0" data-delete="{{ artifact.id }}" title="Delete" aria-label="Delete {{ artifact.title }}">🗑</span>
|
||||
<span class="row-action row-menu-trigger" role="button" tabindex="0" data-menu-trigger title="More actions" aria-label="More actions for {{ artifact.title }}">⋯</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -147,13 +147,6 @@
|
|||
</section>
|
||||
</main>
|
||||
|
||||
<div class="row-menu" hidden data-row-menu>
|
||||
<button type="button" data-menu-action="keep">Keep</button>
|
||||
<button type="button" data-menu-action="open">Open</button>
|
||||
<button type="button" data-menu-action="copy">Copy link</button>
|
||||
<button type="button" data-menu-action="delete" class="danger">Delete</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const feed = document.querySelector(".feed");
|
||||
|
|
@ -535,85 +528,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
// --- Per-row actions menu (⋯ trigger + right-click) ---
|
||||
const rowMenu = document.querySelector("[data-row-menu]");
|
||||
let menuRowId = null;
|
||||
|
||||
const currentMenuRow = () =>
|
||||
menuRowId ? artifactList.querySelector(`.artifact-row[data-artifact-id="${menuRowId}"]`) : null;
|
||||
|
||||
const closeRowMenu = () => {
|
||||
if (rowMenu) rowMenu.hidden = true;
|
||||
menuRowId = null;
|
||||
};
|
||||
|
||||
const openRowMenu = (row, x, y) => {
|
||||
if (!rowMenu || !row) return;
|
||||
menuRowId = row.dataset.artifactId;
|
||||
const keepBtn = rowMenu.querySelector('[data-menu-action="keep"]');
|
||||
if (keepBtn) keepBtn.textContent = row.dataset.kept === "1" ? "Remove from Keep" : "Keep";
|
||||
rowMenu.hidden = false;
|
||||
const rect = rowMenu.getBoundingClientRect();
|
||||
const left = Math.max(8, Math.min(x, window.innerWidth - rect.width - 8));
|
||||
const top = Math.max(8, Math.min(y, window.innerHeight - rect.height - 8));
|
||||
rowMenu.style.left = `${left}px`;
|
||||
rowMenu.style.top = `${top}px`;
|
||||
};
|
||||
|
||||
const copyText = async (text) => {
|
||||
try { await navigator.clipboard.writeText(text); } catch (e) { /* clipboard unavailable */ }
|
||||
};
|
||||
|
||||
const keepFromMenu = async (row, id) => {
|
||||
try {
|
||||
const response = await fetch(`/a/${id}/keep`, {method: "POST", headers: {"X-CSRFToken": csrfToken}});
|
||||
if (!response.ok) return;
|
||||
const data = await response.json();
|
||||
row.dataset.kept = data.is_kept ? "1" : "0";
|
||||
row.classList.toggle("kept", data.is_kept);
|
||||
const badge = row.querySelector("[data-kept-badge]");
|
||||
if (badge) badge.hidden = !data.is_kept;
|
||||
bumpFolderCount("keep", data.is_kept ? 1 : -1);
|
||||
if (feed.dataset.folder === "keep" && !data.is_kept) removeRowFromList(row);
|
||||
const keepButton = document.querySelector("[data-keep-button]");
|
||||
if (keepButton && keepButton.dataset.artifactId === id) {
|
||||
keepButton.classList.toggle("kept", data.is_kept);
|
||||
keepButton.textContent = data.is_kept ? "✓ Kept" : "Keep";
|
||||
}
|
||||
} catch (e) { /* leave as-is */ }
|
||||
};
|
||||
|
||||
if (rowMenu) {
|
||||
rowMenu.addEventListener("click", (event) => {
|
||||
const btn = event.target.closest("[data-menu-action]");
|
||||
if (!btn) return;
|
||||
const row = currentMenuRow();
|
||||
const id = menuRowId;
|
||||
closeRowMenu();
|
||||
if (!row || !id) return;
|
||||
const action = btn.dataset.menuAction;
|
||||
if (action === "delete") softDeleteRow(id);
|
||||
else if (action === "keep") keepFromMenu(row, id);
|
||||
else if (action === "open") window.open(row.dataset.renderedUrl, "_blank", "noopener");
|
||||
else if (action === "copy") copyText(window.location.origin + row.dataset.renderedUrl);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (rowMenu && !rowMenu.hidden && !rowMenu.contains(event.target)) closeRowMenu();
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") closeRowMenu();
|
||||
});
|
||||
|
||||
// Right-click a feed row opens the same menu at the cursor.
|
||||
artifactList.addEventListener("contextmenu", (event) => {
|
||||
const row = event.target.closest(".artifact-row");
|
||||
if (!row || feed.dataset.folder) return; // normal feed only
|
||||
event.preventDefault();
|
||||
openRowMenu(row, event.clientX, event.clientY);
|
||||
});
|
||||
|
||||
const renderPreview = (row, pushState = true) => {
|
||||
navigationMode = "artifacts";
|
||||
markRead(row);
|
||||
|
|
@ -649,19 +563,13 @@
|
|||
|
||||
// Delegated so rows appended by infinite scroll work without rebinding.
|
||||
artifactList.addEventListener("click", (event) => {
|
||||
// Row action icons (delete/undelete/menu) intercept before selection.
|
||||
const action = event.target.closest("[data-delete], [data-undelete], [data-menu-trigger]");
|
||||
// Row action icons (delete/undelete) intercept before selection.
|
||||
const action = event.target.closest("[data-delete], [data-undelete]");
|
||||
if (action) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (action.hasAttribute("data-menu-trigger")) {
|
||||
const rect = action.getBoundingClientRect();
|
||||
openRowMenu(action.closest(".artifact-row"), rect.right, rect.bottom);
|
||||
} else if (action.hasAttribute("data-delete")) {
|
||||
softDeleteRow(action.getAttribute("data-delete"));
|
||||
} else {
|
||||
undeleteRow(action.getAttribute("data-undelete"));
|
||||
}
|
||||
if (action.hasAttribute("data-delete")) softDeleteRow(action.getAttribute("data-delete"));
|
||||
else undeleteRow(action.getAttribute("data-undelete"));
|
||||
return;
|
||||
}
|
||||
const row = event.target.closest(".artifact-row");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue