/* * Folders, the same everywhere (docs/library.md): like tags (a thing may be in several), * shown as a row of filter chips; assigned with a sheet of checkboxes; managed in another * (rename = merge onto an existing name, delete = the things stay, order by ↑ ↓). Each area * keeps its folders where it keeps them and calls these for the look and the dialogs: * * DioraFolders.chips(el, {folders: [{name, count}], active, none, lead, onPick, onManage}) * DioraFolders.assign({title, folders: [names], selected: [names], onDone(names)}) * DioraFolders.manage({title, folders: [names], onRename(old, new), onDelete(name), onOrder(names)}) * * Runs everywhere (web, desktop, both Android apps). Chrome-61-safe: no ?. / ??. */ (function () { 'use strict'; function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[c])); } // ---- the chip row ---------------------------------------------------------------------- // lead: chips before the folders (the library's Angefangen/Ungelesen …): [{id, label, count, on}], // picked with onPick({lead: id}). active: null = all, '' = without a folder, else a name. // The folders fold away (a "Ordner" chip), per area (the list's element id), remembered on // this device; folded, only the folder chosen stays in sight. Folded until opened. function areaKey(el) { const owner = el.id ? el : el.parentElement && el.parentElement.closest('[id]'); return 'diora_folders_folded_' + (owner ? owner.id : 'x'); } function folded(el) { try { return localStorage.getItem(areaKey(el)) !== '0'; } catch (e) { return true; } } function chips(el, o) { const lead = o.lead || []; const leadOn = lead.some(c => c.on); const folders = o.folders || []; const fold = folders.length > 0 && folded(el); let h = '
Noch keine Ordner.
') + 'Umbenennen auf einen vorhandenen Namen legt beide zusammen. Löschen entfernt nur den Ordner, nicht seinen Inhalt.
' : 'Noch keine Ordner. Sie entstehen beim Einordnen („In Ordner …“).
'; }; draw(); body.addEventListener('click', async e => { const b = e.target.closest('button'); if (!b) return; const ask = (msg, value) => typeof customPrompt === 'function' ? customPrompt(msg, value) : Promise.resolve(window.prompt(msg, value)); const sure = msg => typeof customConfirm === 'function' ? customConfirm(msg, {danger: true}) : Promise.resolve(window.confirm(msg)); if (b.dataset.up != null || b.dataset.down != null) { const i = Number(b.dataset.up != null ? b.dataset.up : b.dataset.down); const j = b.dataset.up != null ? i - 1 : i + 1; if (j < 0 || j >= names.length) return; const t = names[i]; names[i] = names[j]; names[j] = t; draw(); o.onOrder(names.slice()); } else if (b.dataset.rename != null) { const old = names[Number(b.dataset.rename)]; const value = await ask('Neuer Name für „' + old + '“:', old); const n = value == null ? '' : String(value).split(/\s+/).filter(Boolean).join(' ').slice(0, 100); if (!n || n === old) return; await o.onRename(old, n); const merged = names.filter(x => x.toLowerCase() === n.toLowerCase())[0]; names = merged ? names.filter(x => x !== old) : names.map(x => (x === old ? n : x)); draw(); } else if (b.dataset.delete != null) { const n = names[Number(b.dataset.delete)]; if (!(await sure('Ordner „' + n + '“ löschen? Was darin liegt, bleibt.'))) return; await o.onDelete(n); names = names.filter(x => x !== n); draw(); } }); sheet(o.title || 'Ordner verwalten', body); } window.DioraFolders = {chips, assign, manage, close}; // ---- styles ---------------------------------------------------------------------------------------- const style = document.createElement('style'); style.textContent = [ '.dfo-row { display: flex; gap: 6px; align-items: center; overflow-x: auto; padding: 2px 0 6px; scrollbar-width: none; }', '.dfo-row::-webkit-scrollbar { display: none; }', '@media (min-width: 721px) { .dfo-row { flex-wrap: wrap; overflow: visible; } }', '.dfo-chip { flex: none; min-height: 34px; padding: 0 12px; border-radius: 17px; border: 1px solid var(--border-strong, #444); background: none; color: inherit; font: inherit; font-size: 13px; cursor: pointer; white-space: nowrap; }', '.dfo-chip.on { background: var(--accent, #e63946); border-color: var(--accent, #e63946); color: var(--on-accent, #fff); }', '.dfo-count { opacity: .7; font-size: 12px; }', '.dfo-none { font-style: italic; }', '.dfo-manage { min-width: 34px; padding: 0 8px; }', '.dfo-fold { border-style: dashed; }', '.dfo-sep { flex: none; width: 1px; height: 20px; background: var(--border-strong, #444); margin: 0 2px; }', // Above the book view (900), below the page's dialogs (990): a name is asked for on top. '.dfo-scrim { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 950; background: var(--scrim, rgba(0,0,0,.5)); display: flex; align-items: flex-end; justify-content: center; }', '@media (min-width: 721px) { .dfo-scrim { align-items: center; } }', '.dfo-sheet { width: 100%; max-width: 480px; max-height: 80vh; overflow-y: auto; background: var(--panel, #161616); color: var(--fg, #eee); border-radius: 14px 14px 0 0; padding: 12px 16px 18px; box-sizing: border-box; }', '@media (min-width: 721px) { .dfo-sheet { border-radius: 14px; } }', '.dfo-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }', '.dfo-x, .dfo-icon { background: none; border: none; color: inherit; font: inherit; font-size: 17px; min-width: 38px; min-height: 38px; cursor: pointer; }', '.dfo-icon[disabled] { opacity: .3; cursor: default; }', '.dfo-check { display: flex; align-items: center; gap: 10px; padding: 9px 2px; border-bottom: 1px solid var(--border, #2a2a2a); cursor: pointer; }', '.dfo-check input { width: 18px; height: 18px; }', '.dfo-mrow { display: flex; align-items: center; border-bottom: 1px solid var(--border, #2a2a2a); }', '.dfo-mname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }', '.dfo-new { display: flex; gap: 8px; margin: 12px 0 4px; }', '.dfo-input { flex: 1; min-width: 0; padding: 9px 10px; border-radius: 6px; border: 1px solid var(--border, #2a2a2a); background: var(--bg, #000); color: inherit; font: inherit; }', '.dfo-btn { min-height: 38px; padding: 0 14px; border-radius: 19px; border: 1px solid var(--border-strong, #444); background: none; color: inherit; font: inherit; font-size: 14px; cursor: pointer; }', '.dfo-primary { background: var(--accent, #e63946); border-color: var(--accent, #e63946); color: var(--on-accent, #fff); }', '.dfo-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }', '.dfo-note { color: var(--muted, #9a9a9a); font-size: 13px; line-height: 1.45; }', 'body.eink .dfo-chip.on, body.eink .dfo-primary { background: #000; color: #fff; }', ].join('\n'); document.head.appendChild(style); })();