This commit is contained in:
th committed 2026-04-06 21:39:37 +02:00
1 parent 0d4d369870
commit 9c71e65041
9 files changed
+423 -16

No files matched your search

+113 -1
View File
@@ -36,6 +36,7 @@ body::after {
/* Cards */
.card-bar { image-rendering: pixelated; height: 3px; }
.board-card { min-height: 180px; }
.card-layer { position: relative; z-index: 1; }
.card-has-cover {
background-size: cover;
@@ -43,6 +44,9 @@ body::after {
background-repeat: no-repeat;
overflow: hidden;
}
.card-group.card-has-cover {
overflow: visible;
}
.card-cover-overlay {
position: absolute;
inset: 0;
@@ -74,7 +78,10 @@ body::after {
right: -6px;
min-width: 20px;
height: 20px;
border-radius: 10px;
padding: 0 6px;
border-radius: 999px;
box-sizing: border-box;
white-space: nowrap;
display: flex;
align-items: center;
justify-content: center;
@@ -185,6 +192,61 @@ body.detail-zen-open { overflow: hidden; }
.overlay:not(.open) { opacity: 0; pointer-events: none; }
.overlay.open { opacity: 1; }
/* Detail image fullscreen */
body.detail-image-fs-open { overflow: hidden; }
#detail-body img { cursor: zoom-in; }
.detail-image-fs-overlay {
position: fixed;
inset: 0;
z-index: 170;
display: none;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(2, 6, 23, 0.94);
}
.detail-image-fs-overlay.open { display: flex; }
.detail-image-fs-stage {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
}
.detail-image-fs-img {
max-width: min(96vw, 1900px);
max-height: 94vh;
object-fit: contain;
border: 1px solid var(--t-border);
box-shadow: 0 16px 60px rgba(0, 0, 0, 0.55);
cursor: grab;
user-select: none;
-webkit-user-drag: none;
will-change: transform;
}
.detail-image-fs-overlay.dragging .detail-image-fs-img { cursor: grabbing; }
.detail-image-fs-close {
position: fixed;
top: 12px;
right: 12px;
z-index: 171;
width: 34px;
height: 34px;
display: grid;
place-items: center;
padding: 0;
border: none;
background: transparent;
color: #fff;
font-family: inherit;
cursor: pointer;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
transition: color 0.12s, transform 0.12s;
}
.detail-image-fs-close:hover {
color: var(--t-accent);
transform: scale(1.06);
}
/* Theme picker */
.theme-picker { position: relative; }
.theme-menu { display: none; position: absolute; bottom: calc(100% + 8px); right: 0; z-index: 200; }
@@ -201,6 +263,14 @@ body.detail-zen-open { overflow: hidden; }
}
.bucket-tab.drag-over i,
.bucket-tab.drag-over span { color: inherit !important; }
.bucket-add.drag-over {
background: var(--t-accent) !important;
color: var(--t-bg) !important;
transform: scale(1.05);
transition: all 0.15s ease;
}
.bucket-add.drag-over i,
.bucket-add.drag-over span { color: inherit !important; }
.bucket-drop-left { border-left: 3px solid var(--t-accent) !important; }
.bucket-drop-right { border-right: 3px solid var(--t-accent) !important; }
@@ -321,3 +391,45 @@ body.detail-zen-open { overflow: hidden; }
.prose table { border-collapse: collapse; width: 100%; margin: 0.8em 0; }
.prose th, .prose td { border: 1px solid var(--t-border); padding: 0.4em 0.8em; text-align: left; }
.prose th { background: var(--t-raised); color: var(--t-bright); font-weight: 600; }
/* Temporary reading aid in open detail view (not persisted) */
#detail-body .detail-reading-resize {
position: relative;
padding-bottom: 1rem;
min-height: 1.8em;
}
#detail-body .detail-reading-resize:hover,
#detail-body .detail-reading-resize.resizing {
outline: 1px dashed var(--t-border);
outline-offset: 1px;
}
#detail-body .detail-font-dragline {
position: absolute;
left: 0.4rem;
right: 0.4rem;
bottom: 0.2rem;
height: 10px;
padding: 0;
border: none;
background: transparent;
cursor: ns-resize;
opacity: 0;
transition: opacity 0.12s ease;
}
#detail-body .detail-font-dragline::before {
content: '';
position: absolute;
left: 0;
right: 0;
top: 4px;
border-top: 1px dashed var(--t-border);
transition: border-color 0.12s ease;
}
#detail-body .detail-reading-resize:hover .detail-font-dragline,
#detail-body .detail-reading-resize.resizing .detail-font-dragline {
opacity: 1;
}
#detail-body .detail-font-dragline:hover::before,
#detail-body .detail-reading-resize.resizing .detail-font-dragline::before {
border-color: var(--t-accent);
}
+5 -2
View File
@@ -141,8 +141,11 @@ if (str_starts_with($route, '/api/')) {
$title = trim($input['title'] ?? '');
if ($title === '') { http_response_code(400); echo json_encode(['error' => 'title is required']); exit; }
$pos = $input['position'] ?? 0;
$db->prepare('INSERT INTO cards (title, body, color, bucket_id, position) VALUES (:title, :body, :color, :bucket_id, :position)')->execute([
'title' => $title, 'body' => $input['body'] ?? '', 'color' => validColor($input['color'] ?? null), 'bucket_id' => validInt($input['bucket_id'] ?? null, null), 'position' => validInt($pos),
$db->prepare('INSERT INTO cards (title, body, color, bucket_id, parent_id, position) VALUES (:title, :body, :color, :bucket_id, :parent_id, :position)')->execute([
'title' => $title, 'body' => $input['body'] ?? '', 'color' => validColor($input['color'] ?? null),
'bucket_id' => validInt($input['bucket_id'] ?? null, null),
'parent_id' => validInt($input['parent_id'] ?? null, null),
'position' => validInt($pos),
]);
echo json_encode($db->query('SELECT c.*, b.name as bucket_name FROM cards c LEFT JOIN buckets b ON c.bucket_id = b.id WHERE c.id = ' . $db->lastInsertId())->fetch());
exit;
+2 -1
View File
@@ -13,13 +13,14 @@ import { openModal, closeModal, saveCard } from './modules/editor.js';
import { openSearch, closeSearch } from './modules/search.js';
import { showContextMenu } from './modules/context-menu.js';
import { undo, canUndo } from './modules/undo.js';
import { closeGallery } from './modules/gallery.js';
import { openGallery, closeGallery } from './modules/gallery.js';
import { refreshTasksView, openTasks, closeTasks } from './modules/tasks.js';
// ── Global API (for inline onclick handlers and cross-module calls) ──
window._openDetail = openDetail;
window._openModal = openModal;
window._openGallery = openGallery;
window._closeSearch = closeSearch;
window._closeTasks = closeTasks;
window._renderBoard = renderBoard;
+2 -2
View File
@@ -79,11 +79,11 @@ export function renderBoard() {
const hasCover = !!coverImg;
return `
<div class="${isGroup ? 'card-group ' : ''}card-glow ${hasCover ? 'card-has-cover' : 'bg-t-surface'} border border-t-border cursor-pointer hover:border-t-muted transition-colors relative group"
<div class="board-card ${isGroup ? 'card-group ' : ''}card-glow ${hasCover ? 'card-has-cover' : 'bg-t-surface'} border border-t-border cursor-pointer hover:border-t-muted transition-colors relative group"
style="--glow-color:${c.color}22;${hasCover ? `background-image:url('${coverImg}')` : ''};${recent.includes(String(c.id)) ? 'border-bottom:3px solid ' + c.color : ''}" draggable="true" data-id="${c.id}">
${hasCover ? '<div class="card-cover-overlay"></div>' : ''}
<div class="card-bar w-full card-layer" style="background:${c.color}"></div>
${isGroup ? `<div class="card-group-badge font-pixel cursor-pointer hover:scale-110 transition-transform" style="background:${c.color};color:#000" data-group="${c.id}">${children.length + 1}</div>` : ''}
${isGroup ? `<div class="card-group-badge cursor-pointer hover:scale-110 transition-transform" style="background:${c.color};color:#000" data-group="${c.id}">${children.length + 1}</div>` : ''}
${thumbImg ? `<div class="card-thumb" style="background-image:url('${thumbImg}')"></div>` : ''}
<div class="p-4 card-layer ${thumbImg ? 'pr-16' : ''}">
<div class="flex items-start gap-2 mb-2">
+34 -2
View File
@@ -121,6 +121,15 @@ export function populateSelects() {
}
}
function autoBucketNameFromCard(card) {
const existing = new Set(state.buckets.map(b => String(b.name || '').trim().toLowerCase()).filter(Boolean));
const base = String(card?.title || '').trim().replace(/\s+/g, ' ').slice(0, 40) || 'bucket';
if (!existing.has(base.toLowerCase())) return base;
let i = 2;
while (existing.has(`${base} ${i}`.toLowerCase())) i++;
return `${base} ${i}`;
}
function updateHeaderTint() {
const tint = $('header-tint');
if (!tint) return;
@@ -145,7 +154,7 @@ function renderBar() {
const active = state.activeBucket == b.id;
return `<button class="${tab(active)}" data-bucket="${b.id}" draggable="true" data-bucket-drag="${b.id}" style="border-top:3px solid ${c};${active ? `border-color:${c};color:${c}` : ''}"><i class="ph ph-folder text-sm"></i><span class="font-pixel text-[10px]">${esc(b.name)}</span></button>`;
}).join('')}
<button id="add-bucket-btn" class="flex items-center gap-1 px-2 py-1 text-xs text-t-dim hover:text-t-accent transition-colors shrink-0"><i class="ph ph-plus text-sm"></i><span class="font-pixel text-[10px]">bucket</span></button>`;
<button id="add-bucket-btn" class="bucket-add flex items-center gap-1 px-2 py-1 text-xs text-t-dim hover:text-t-accent transition-colors shrink-0"><i class="ph ph-plus text-sm"></i><span class="font-pixel text-[10px]">bucket</span></button>`;
updateHeaderTint();
@@ -223,11 +232,34 @@ function renderBar() {
});
});
$('add-bucket-btn')?.addEventListener('click', async () => {
const addBtn = $('add-bucket-btn');
addBtn?.addEventListener('click', async () => {
const name = await showDialog({ title: 'new_bucket', input: true, placeholder: '> bucket name...' });
if (!name || !name.trim()) return;
await api('api/buckets', { method: 'POST', body: { name: name.trim() } });
await loadBuckets();
window._renderBoard();
});
const isCardDrag = e => Array.from(e.dataTransfer?.types || []).includes('text/plain');
addBtn?.addEventListener('dragover', e => {
if (!isCardDrag(e)) return;
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
addBtn.classList.add('drag-over');
});
addBtn?.addEventListener('dragleave', () => addBtn.classList.remove('drag-over'));
addBtn?.addEventListener('drop', async e => {
addBtn.classList.remove('drag-over');
const cardId = e.dataTransfer.getData('text/plain');
if (!cardId) return;
e.preventDefault();
const card = state.cards.find(c => String(c.id) === String(cardId));
const created = await api('api/buckets', { method: 'POST', body: { name: autoBucketNameFromCard(card) } });
if (!created?.id) return;
await api(`api/cards/${cardId}`, { method: 'PATCH', body: { bucket_id: created.id } });
await loadBuckets();
await window._reloadAll();
});
}
+241 -4
View File
@@ -13,6 +13,18 @@ const bodyEl = $('detail-body');
const metaEl = $('detail-meta');
let morphTimer = null;
let morphGhost = null;
let imageFsOverlay = null;
let imageFsImg = null;
let fsScale = 1;
let fsTx = 0;
let fsTy = 0;
let fsPanning = false;
let fsStartX = 0;
let fsStartY = 0;
let fsStartTx = 0;
let fsStartTy = 0;
const FS_MIN_SCALE = 0.35;
const FS_MAX_SCALE = 8;
// ── Zoom ──
@@ -41,7 +53,7 @@ scroll?.addEventListener('wheel', e => {
// ── Open / close ──
export function openDetail(id) {
export function openDetail(id, options = {}) {
const card = state.cards.find(c => c.id == id);
if (!card) return;
markRecent(id);
@@ -51,6 +63,11 @@ export function openDetail(id) {
if (!detail.classList.contains('open')) {
state.openedFromSearch = $('search')?.classList.contains('open') || false;
state.openedFromGallery = !!options.fromGallery;
state.galleryOrigin = options.fromGallery ? (options.galleryOrigin || null) : null;
} else if (options.fromGallery) {
state.openedFromGallery = true;
state.galleryOrigin = options.galleryOrigin || state.galleryOrigin || null;
}
state.viewingCard = card;
@@ -70,7 +87,139 @@ function clearMorphGhost() {
}
}
function ensureImageFullscreenOverlay() {
if (imageFsOverlay) return imageFsOverlay;
const overlay = document.createElement('div');
overlay.className = 'detail-image-fs-overlay';
overlay.innerHTML = `
<button type="button" class="detail-image-fs-close" aria-label="close image">
<i class="ph ph-x text-xl"></i>
</button>
<div class="detail-image-fs-stage">
<img class="detail-image-fs-img" alt="">
</div>
`;
overlay.addEventListener('click', e => {
if (e.target === overlay) closeImageFullscreen();
});
overlay.querySelector('.detail-image-fs-close')?.addEventListener('click', closeImageFullscreen);
document.body.appendChild(overlay);
imageFsOverlay = overlay;
imageFsImg = overlay.querySelector('.detail-image-fs-img');
if (imageFsImg) {
imageFsImg.draggable = false;
imageFsImg.addEventListener('mousedown', onImageFsMouseDown);
}
overlay.addEventListener('wheel', onImageFsWheel, { passive: false });
document.addEventListener('mousemove', onImageFsMouseMove);
document.addEventListener('mouseup', onImageFsMouseUp);
return overlay;
}
function applyImageFsTransform() {
if (!imageFsImg) return;
imageFsImg.style.transform = `translate(${fsTx}px, ${fsTy}px) scale(${fsScale})`;
}
function resetImageFsTransform() {
fsScale = 1;
fsTx = 0;
fsTy = 0;
fsPanning = false;
imageFsOverlay?.classList.remove('dragging');
applyImageFsTransform();
}
function onImageFsWheel(e) {
if (!imageFsOverlay?.classList.contains('open') || !imageFsImg) return;
e.preventDefault();
const old = fsScale;
const factor = Math.exp(-e.deltaY * 0.0015);
const next = Math.min(FS_MAX_SCALE, Math.max(FS_MIN_SCALE, old * factor));
if (Math.abs(next - old) < 0.0001) return;
const r = imageFsOverlay.getBoundingClientRect();
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
const px = e.clientX - cx;
const py = e.clientY - cy;
fsTx = px - ((px - fsTx) / old) * next;
fsTy = py - ((py - fsTy) / old) * next;
fsScale = next;
applyImageFsTransform();
}
function onImageFsMouseDown(e) {
if (!imageFsOverlay?.classList.contains('open')) return;
if (e.button !== 0) return;
e.preventDefault();
fsPanning = true;
fsStartX = e.clientX;
fsStartY = e.clientY;
fsStartTx = fsTx;
fsStartTy = fsTy;
imageFsOverlay.classList.add('dragging');
}
function onImageFsMouseMove(e) {
if (!fsPanning || !imageFsOverlay?.classList.contains('open')) return;
fsTx = fsStartTx + (e.clientX - fsStartX);
fsTy = fsStartTy + (e.clientY - fsStartY);
applyImageFsTransform();
}
function onImageFsMouseUp() {
if (!fsPanning) return;
fsPanning = false;
imageFsOverlay?.classList.remove('dragging');
}
function openImageFullscreen(src, alt = '') {
if (!src) return;
const overlay = ensureImageFullscreenOverlay();
resetImageFsTransform();
if (imageFsImg) {
imageFsImg.src = src;
imageFsImg.alt = alt || 'image';
}
overlay.classList.add('open');
document.body.classList.add('detail-image-fs-open');
}
function closeImageFullscreen() {
if (!imageFsOverlay) return;
fsPanning = false;
imageFsOverlay.classList.remove('dragging');
imageFsOverlay.classList.remove('open');
document.body.classList.remove('detail-image-fs-open');
if (imageFsImg) {
imageFsImg.removeAttribute('src');
imageFsImg.alt = '';
imageFsImg.style.transform = '';
}
fsScale = 1;
fsTx = 0;
fsTy = 0;
}
function resolveMorphTarget(cardId) {
if (state.openedFromGallery) {
const grid = $('gallery-grid');
if (grid) {
if (state.galleryOrigin) {
const exact = Array.from(grid.querySelectorAll('[data-gallery-origin]'))
.find(el => el.dataset.galleryOrigin === state.galleryOrigin);
if (exact) return exact;
}
const byCard = grid.querySelector(`[data-card-id="${cardId}"]`);
if (byCard) return byCard;
}
}
const board = $('board');
if (!board || !cardId) return null;
@@ -133,7 +282,9 @@ function startCloseMorph(cardId) {
const frame = document.createElement('div');
frame.className = 'detail-close-frame';
const targetCard = state.cards.find(c => c.id == target.dataset.id);
const targetCard = target.dataset.id
? state.cards.find(c => c.id == target.dataset.id)
: card;
Object.assign(frame.style, {
left: `${from.left}px`,
top: `${from.top}px`,
@@ -170,10 +321,12 @@ function startCloseMorph(cardId) {
export function closeDetail(options = {}) {
const animate = options?.animate !== false;
const restoreGallery = options?.restoreGallery !== false;
const cardId = state.viewingCard?.id;
if (animate) startCloseMorph(cardId);
else clearMorphGhost();
closeImageFullscreen();
detail.classList.remove('open', 'zen');
document.documentElement.classList.remove('detail-zen-open');
@@ -185,6 +338,12 @@ export function closeDetail(options = {}) {
state.openedFromSearch = false;
$('search-input')?.focus();
}
if (state.openedFromGallery) {
const galleryOpen = $('gallery')?.classList.contains('open');
state.openedFromGallery = false;
state.galleryOrigin = null;
if (restoreGallery && !galleryOpen) window._openGallery?.();
}
}
export function showDetailCard(id) {
@@ -241,6 +400,8 @@ function showContent(card) {
$('detail-color').style.background = card.color;
titleEl.textContent = card.title;
bodyEl.innerHTML = renderMd(card.body);
applyReadingResizeHandles(bodyEl);
bindDetailImageFullscreen(bodyEl);
// Interactive checkboxes
bodyEl.querySelectorAll('input[type="checkbox"]').forEach((cb, idx) => {
@@ -280,6 +441,68 @@ function showContent(card) {
}
}
function applyReadingResizeHandles(root) {
if (!root) return;
// Reading aid only: temporary per-block font-size scaling (not persisted).
root.querySelectorAll('p, blockquote, pre').forEach(el => {
el.classList.add('detail-reading-resize');
const line = document.createElement('button');
line.type = 'button';
line.className = 'detail-font-dragline';
line.title = 'drag to resize text';
line.setAttribute('aria-label', 'resize text');
el.appendChild(line);
line.addEventListener('mousedown', e => {
e.preventDefault();
e.stopPropagation();
const startY = e.clientY;
const startFont = parseFloat(getComputedStyle(el).fontSize) || 14;
const startLineHeightPx = parseFloat(getComputedStyle(el).lineHeight);
const lineRatio = Number.isFinite(startLineHeightPx) && startLineHeightPx > 0
? startLineHeightPx / startFont
: 1.65;
const prevSelect = document.body.style.userSelect;
const min = 12;
const max = 44;
el.classList.add('resizing');
document.body.style.userSelect = 'none';
const onMove = ev => {
const delta = ev.clientY - startY;
const next = Math.min(max, Math.max(min, startFont + delta * 0.08));
el.style.fontSize = `${next.toFixed(2)}px`;
el.style.lineHeight = `${(next * lineRatio).toFixed(2)}px`;
};
const onUp = () => {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
document.body.style.userSelect = prevSelect;
el.classList.remove('resizing');
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
});
}
function bindDetailImageFullscreen(root) {
if (!root) return;
root.querySelectorAll('img').forEach(img => {
img.title = 'double-click to open fullscreen';
img.addEventListener('dblclick', e => {
e.preventDefault();
e.stopPropagation();
openImageFullscreen(img.getAttribute('src'), img.getAttribute('alt') || 'image');
});
});
}
// ── Event listeners ──
// Delegated card-link clicks (replaces inline onclick)
@@ -290,6 +513,14 @@ detail.addEventListener('click', e => {
});
$('detail-close')?.addEventListener('click', closeDetail);
document.addEventListener('keydown', e => {
if (e.key !== 'Escape') return;
if (!imageFsOverlay?.classList.contains('open')) return;
e.preventDefault();
e.stopImmediatePropagation();
closeImageFullscreen();
}, true);
// Zen mode
$('detail-zen')?.addEventListener('click', () => {
detail.classList.toggle('zen');
@@ -303,8 +534,13 @@ $('detail-zen')?.addEventListener('click', () => {
$('detail-edit')?.addEventListener('click', () => {
if (!state.viewingCard) return;
const card = state.viewingCard;
const fromGallery = state.openedFromGallery;
state.openedFromSearch = false;
closeDetail({ animate: false });
state.openedFromGallery = false;
state.galleryOrigin = null;
state.returnToGalleryAfterEdit = fromGallery;
closeDetail({ animate: false, restoreGallery: false });
$('gallery')?.classList.remove('open');
window._closeSearch();
window._openModal(card);
});
@@ -315,7 +551,8 @@ $('detail-delete')?.addEventListener('click', async () => {
if (!ok) return;
pushCardDelete('delete', snapshotCard(state.viewingCard));
await api(`api/cards/${state.viewingCard.id}`, { method: 'DELETE' });
closeDetail({ animate: false });
state.galleryOrigin = null;
closeDetail({ animate: false, restoreGallery: false });
await window._reloadAll();
});
+16 -2
View File
@@ -59,7 +59,11 @@ export function openModal(card = null) {
inpTitle.focus();
}
export function closeModal() { modal.classList.remove('open'); state.editingId = null; }
export function closeModal() {
modal.classList.remove('open');
state.editingId = null;
state.returnToGalleryAfterEdit = false;
}
function updatePreview() {
if (state.previewVisible) previewContent.innerHTML = renderMd(body.value);
@@ -68,16 +72,26 @@ function updatePreview() {
export async function saveCard() {
const data = { title: inpTitle.value, body: body.value, color: inpColor.value, bucket_id: inpBucket.value || null };
if (!data.title.trim()) return;
const returnToGallery = state.returnToGalleryAfterEdit;
if (state.editingId) {
const prev = snapshotCard(state.cards.find(c => c.id == state.editingId));
if (prev) pushCardEdit('edit', state.editingId, { title: prev.title, body: prev.body, color: prev.color, bucket_id: prev.bucket_id });
await api(`api/cards/${state.editingId}`, { method: 'PATCH', body: data });
} else {
data.position = -1;
if (state.activeGroup) {
const parent = state.cards.find(c => c.id == state.activeGroup);
const groupCards = parent ? [parent, ...state.cards.filter(c => c.parent_id == parent.id)] : [];
const maxPos = groupCards.reduce((max, c) => Math.max(max, Number(c.position) || 0), -1);
data.parent_id = parent ? parent.id : state.activeGroup;
data.position = maxPos + 1;
} else {
data.position = -1;
}
await api('api/cards', { method: 'POST', body: data });
}
closeModal();
await window._reloadAll();
if (returnToGallery) window._openGallery?.();
}
// ── Markdown toolbar ──
+7 -2
View File
@@ -77,7 +77,7 @@ export async function openGallery() {
if (linked.length) {
if (orphaned.length) html += `<div class="col-span-full font-pixel text-[10px] text-t-muted flex items-center gap-1.5 pt-3"><i class="ph ph-link text-xs"></i>linked to cards</div>`;
html += linked.map(img => `
<div class="gallery-item group relative cursor-pointer" data-card-id="${img.cardId}">
<div class="gallery-item group relative cursor-pointer" data-card-id="${img.cardId}" data-gallery-origin="${encodeURIComponent(img.url)}">
<div class="aspect-square bg-t-bg border border-t-border rounded overflow-hidden">
<img src="${img.url}" class="w-full h-full object-cover" loading="lazy">
</div>
@@ -94,7 +94,12 @@ export async function openGallery() {
// Linked image clicks → open card
grid.querySelectorAll('[data-card-id]').forEach(item => {
item.addEventListener('click', () => { closeGallery(); window._openDetail(item.dataset.cardId); });
item.addEventListener('click', () => {
window._openDetail(item.dataset.cardId, {
fromGallery: true,
galleryOrigin: item.dataset.galleryOrigin || null,
});
});
});
// Orphan: create card from image
+3
View File
@@ -9,6 +9,9 @@ export const state = {
editingId: null,
viewingCard: null,
openedFromSearch: false,
openedFromGallery: false,
galleryOrigin: null,
returnToGalleryAfterEdit: false,
previewVisible: false,
};