mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-10 15:42:01 +03:00
sheet organization: pin and reorder with up/down arrows
This commit is contained in:
parent
f8f9095fca
commit
4b5381d7e7
4 changed files with 71 additions and 6 deletions
|
|
@ -58,6 +58,10 @@ const dict: Record<string, Record<string, string>> = {
|
|||
closeSheet: "Close this sheet?",
|
||||
close: "Close",
|
||||
cancel: "Cancel",
|
||||
pin: "Pin",
|
||||
unpin: "Unpin",
|
||||
moveUp: "Move up",
|
||||
moveDown: "Move down",
|
||||
},
|
||||
ru: {
|
||||
settings: "Настройки",
|
||||
|
|
@ -116,6 +120,10 @@ const dict: Record<string, Record<string, string>> = {
|
|||
closeSheet: "Закрыть этот лист?",
|
||||
close: "Закрыть",
|
||||
cancel: "Отмена",
|
||||
pin: "Закрепить",
|
||||
unpin: "Открепить",
|
||||
moveUp: "Выше",
|
||||
moveDown: "Ниже",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
51
src/main.ts
51
src/main.ts
|
|
@ -54,19 +54,65 @@ function syncTitleField(): void {
|
|||
if (document.activeElement !== field) field.value = activeDoc()?.title ?? "";
|
||||
}
|
||||
|
||||
function pinDoc(id: string): void {
|
||||
const doc = data.docs.find((d) => d.id === id);
|
||||
if (!doc) return;
|
||||
doc.pinned = !doc.pinned;
|
||||
if (doc.pinned) {
|
||||
// move to end of pinned group
|
||||
data.docs = data.docs.filter((d) => d.id !== id);
|
||||
let lastPinned = -1;
|
||||
for (let i = data.docs.length - 1; i >= 0; i--) { if (data.docs[i].pinned) { lastPinned = i; break; } }
|
||||
data.docs.splice(lastPinned + 1, 0, doc);
|
||||
} else {
|
||||
// move to start of non-pinned group
|
||||
data.docs = data.docs.filter((d) => d.id !== id);
|
||||
const firstNonPinned = data.docs.findIndex((d) => !d.pinned);
|
||||
data.docs.splice(firstNonPinned === -1 ? data.docs.length : firstNonPinned, 0, doc);
|
||||
}
|
||||
saveAppData(data);
|
||||
renderDocList();
|
||||
}
|
||||
|
||||
function moveDoc(id: string, dir: -1 | 1): void {
|
||||
const idx = data.docs.findIndex((d) => d.id === id);
|
||||
if (idx < 0) return;
|
||||
const doc = data.docs[idx];
|
||||
const target = data.docs[idx + dir];
|
||||
if (!target || !!target.pinned !== !!doc.pinned) return; // can't cross pin boundary
|
||||
data.docs.splice(idx, 1);
|
||||
data.docs.splice(idx + dir, 0, doc);
|
||||
saveAppData(data);
|
||||
renderDocList();
|
||||
}
|
||||
|
||||
function renderDocList(): void {
|
||||
const list = $("#doc-list");
|
||||
list.replaceChildren();
|
||||
for (const doc of data.docs) {
|
||||
const el = document.createElement("div");
|
||||
el.className = "doc-item" + (doc.id === data.activeId ? " active" : "");
|
||||
el.className = "doc-item" + (doc.id === data.activeId ? " active" : "") + (doc.pinned ? " pinned" : "");
|
||||
const name = document.createElement("span");
|
||||
name.className = "doc-name";
|
||||
name.textContent = doc.title || t("untitled");
|
||||
el.appendChild(name);
|
||||
|
||||
const mkBtn = (cls: string, text: string, title: string, cb: () => void): HTMLButtonElement => {
|
||||
const b = document.createElement("button");
|
||||
b.className = cls;
|
||||
b.textContent = text;
|
||||
b.title = title;
|
||||
b.addEventListener("click", (e) => { e.stopPropagation(); cb(); });
|
||||
return b;
|
||||
};
|
||||
|
||||
el.appendChild(mkBtn("pin-btn" + (doc.pinned ? " active" : ""), "📌", doc.pinned ? t("unpin") : t("pin"), () => pinDoc(doc.id)));
|
||||
el.appendChild(mkBtn("move-btn", "↑", t("moveUp"), () => moveDoc(doc.id, -1)));
|
||||
el.appendChild(mkBtn("move-btn", "↓", t("moveDown"), () => moveDoc(doc.id, 1)));
|
||||
|
||||
const del = document.createElement("button");
|
||||
del.className = "del";
|
||||
del.textContent = "✕";
|
||||
// two-step inline confirmation instead of the native dialog
|
||||
let confirmTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
del.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -81,7 +127,6 @@ function renderDocList(): void {
|
|||
return;
|
||||
}
|
||||
if (confirmTimer) clearTimeout(confirmTimer);
|
||||
// the sheet goes to backups/deleted/ — restore by dragging it back
|
||||
void backupDeletedSheet(settings.dataDir, doc.title, data.contents[doc.id] ?? "");
|
||||
delete data.contents[doc.id];
|
||||
data.docs = data.docs.filter((d) => d.id !== doc.id);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export interface DocMeta {
|
|||
title: string;
|
||||
/** user renamed the document — stop deriving the title from the first line */
|
||||
customTitle?: boolean;
|
||||
pinned?: boolean;
|
||||
}
|
||||
|
||||
export interface AppData {
|
||||
|
|
|
|||
|
|
@ -83,23 +83,34 @@ body {
|
|||
}
|
||||
.doc-item:hover { background: var(--hover); }
|
||||
.doc-item.active { background: var(--sel); }
|
||||
.doc-name { flex: 1; overflow: hidden; text-overflow: ellipsis; }
|
||||
.doc-item .pin-btn,
|
||||
.doc-item .move-btn,
|
||||
.doc-item .del {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
opacity: 0;
|
||||
color: var(--fg-muted);
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
padding: 2px 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
.doc-item:hover .pin-btn,
|
||||
.doc-item:hover .move-btn,
|
||||
.doc-item:hover .del { opacity: 0.7; }
|
||||
.doc-item .pin-btn.active { opacity: 1; }
|
||||
.doc-item:hover .pin-btn.active { opacity: 1; }
|
||||
.doc-item .del { font-size: 12px; padding: 2px 6px; }
|
||||
.doc-item:hover .del { opacity: 1; }
|
||||
.doc-item .del.confirm {
|
||||
opacity: 1;
|
||||
background: #d63a3a;
|
||||
color: #fff;
|
||||
}
|
||||
.doc-item.pinned .doc-name { font-weight: 500; }
|
||||
#new-doc {
|
||||
border: 1px dashed var(--border);
|
||||
background: none;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue