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._
|
_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
|
## [0.5.1] — 2026-09-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
@ -205,8 +195,7 @@ First tagged release.
|
||||||
- Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a
|
- Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a
|
||||||
runbook (`deploy/cloud_agent_deploy.md`) and health check.
|
runbook (`deploy/cloud_agent_deploy.md`) and health check.
|
||||||
|
|
||||||
[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.6.0...HEAD
|
[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.5.1...HEAD
|
||||||
[0.6.0]: https://git.cygnus.systems/peter/daybook/compare/v0.5.1...v0.6.0
|
|
||||||
[0.5.1]: https://git.cygnus.systems/peter/daybook/compare/v0.5.0...v0.5.1
|
[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.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
|
[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
|
# 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
|
# commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in
|
||||||
# GET /api/capabilities ("version").
|
# GET /api/capabilities ("version").
|
||||||
__version__ = "0.6.0"
|
__version__ = "0.5.1"
|
||||||
|
|
|
||||||
|
|
@ -1060,22 +1060,12 @@ kbd {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-actions {
|
.row-action {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.4rem;
|
right: 0.5rem;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
display: flex;
|
|
||||||
gap: 0.15rem;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.artifact-row:hover .row-actions,
|
|
||||||
.artifact-row:focus-within .row-actions {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-action {
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
@ -1085,6 +1075,11 @@ kbd {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artifact-row:hover .row-action,
|
||||||
|
.artifact-row:focus-within .row-action {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.row-action:hover {
|
.row-action:hover {
|
||||||
background: var(--surface-alt);
|
background: var(--surface-alt);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
|
@ -1094,43 +1089,6 @@ kbd {
|
||||||
color: var(--danger-text-plain);
|
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 */
|
/* System folders (Keep / Deleted) at the bottom of the sidebar */
|
||||||
.source-folders {
|
.source-folders {
|
||||||
border-top: 1px solid var(--grid-line);
|
border-top: 1px solid var(--grid-line);
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,10 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<span class="artifact-source">{{ artifact.source }}</span>
|
<span class="artifact-source">{{ artifact.source }}</span>
|
||||||
<span class="row-actions">
|
|
||||||
{% if in_trash %}
|
{% 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>
|
<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 %}
|
{% 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-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 %}
|
{% endif %}
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
|
|
@ -147,13 +147,6 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</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>
|
<script>
|
||||||
(() => {
|
(() => {
|
||||||
const feed = document.querySelector(".feed");
|
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) => {
|
const renderPreview = (row, pushState = true) => {
|
||||||
navigationMode = "artifacts";
|
navigationMode = "artifacts";
|
||||||
markRead(row);
|
markRead(row);
|
||||||
|
|
@ -649,19 +563,13 @@
|
||||||
|
|
||||||
// Delegated so rows appended by infinite scroll work without rebinding.
|
// Delegated so rows appended by infinite scroll work without rebinding.
|
||||||
artifactList.addEventListener("click", (event) => {
|
artifactList.addEventListener("click", (event) => {
|
||||||
// Row action icons (delete/undelete/menu) intercept before selection.
|
// Row action icons (delete/undelete) intercept before selection.
|
||||||
const action = event.target.closest("[data-delete], [data-undelete], [data-menu-trigger]");
|
const action = event.target.closest("[data-delete], [data-undelete]");
|
||||||
if (action) {
|
if (action) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (action.hasAttribute("data-menu-trigger")) {
|
if (action.hasAttribute("data-delete")) softDeleteRow(action.getAttribute("data-delete"));
|
||||||
const rect = action.getBoundingClientRect();
|
else undeleteRow(action.getAttribute("data-undelete"));
|
||||||
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"));
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const row = event.target.closest(".artifact-row");
|
const row = event.target.closest(".artifact-row");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue