diff --git a/dist/index.html b/dist/index.html index 4a8c710..60dff24 100644 --- a/dist/index.html +++ b/dist/index.html @@ -5760,8 +5760,9 @@ var root_1$7 = /* @__PURE__ */ from_html(`
var root_3$5 = /* @__PURE__ */ from_html(`
`); var root_4$5 = /* @__PURE__ */ from_html(``); var root_2$5 = /* @__PURE__ */ from_html(``); -var root_5$4 = /* @__PURE__ */ from_html(``); -var root$6 = /* @__PURE__ */ from_html(``); +var root_5$4 = /* @__PURE__ */ from_html(`
Drop here to move
`); +var root_6$5 = /* @__PURE__ */ from_html(``); +var root$6 = /* @__PURE__ */ from_html(``); function Sidebar($$anchor, $$props) { push($$props, true); let groups = /* @__PURE__ */ state(proxy([])); @@ -5773,6 +5774,7 @@ function Sidebar($$anchor, $$props) { let groupError = /* @__PURE__ */ state(""); let showDeleteGroupConfirm = /* @__PURE__ */ state(null); let deletingGroup = /* @__PURE__ */ user_derived(() => get(groups).find((g) => g.id === get(showDeleteGroupConfirm))); + let dropTargetGroupId = /* @__PURE__ */ state(null); const GROUP_COLORS = [ "#6c63ff", "#e5484d", @@ -5842,65 +5844,96 @@ function Sidebar($$anchor, $$props) { set(groupError, "Failed to delete group: " + e.message); } } + function onDragOverGroup(e, groupId) { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + set(dropTargetGroupId, groupId, true); + } + function onDragLeaveGroup() { + set(dropTargetGroupId, null); + } + async function onDropGroup(groupId, e) { + set(dropTargetGroupId, null); + const entryId = e.dataTransfer.getData("text/plain"); + if (!entryId) return; + try { + const entry = await getEntryById(entryId); + if (!entry) return; + const newGroupId = groupId === "all" ? "" : groupId; + await updateEntry({ + ...entry, + groupId: newGroupId, + updatedAt: (/* @__PURE__ */ new Date()).toISOString() + }); + await loadData(); + } catch (err) { + console.error("Failed to move entry:", err); + } + } var div = root$6(); var div_1 = sibling(child(div), 2); var input = child(div_1); remove_input_defaults(input); reset(div_1); var nav = sibling(div_1, 2); - var button = child(nav); - each(sibling(button, 2), 17, () => get(groups), index, ($$anchor, group) => { - var div_2 = root_1$7(); - var button_1 = child(div_2); + var div_2 = child(nav); + var button = child(div_2); + reset(div_2); + each(sibling(div_2, 2), 17, () => get(groups), index, ($$anchor, group) => { + var div_3 = root_1$7(); + var button_1 = child(div_3); var span = child(button_1); var span_1 = sibling(span, 2); var text = child(span_1, true); reset(span_1); reset(button_1); - var div_3 = sibling(button_1, 2); - var button_2 = child(div_3); + var div_4 = sibling(button_1, 2); + var button_2 = child(div_4); var button_3 = sibling(button_2, 2); + reset(div_4); reset(div_3); - reset(div_2); template_effect(() => { - set_class(button_1, 1, `group-item ${search.activeGroupId === get(group).id ? "active" : ""}`, "svelte-181dlmc"); + set_class(button_1, 1, `group-item ${search.activeGroupId === get(group).id ? "active" : ""} ${get(dropTargetGroupId) === get(group).id ? "drop-target" : ""}`, "svelte-181dlmc"); set_style(span, `background-color: ${(get(group).color || "#6c63ff") ?? ""}`); set_text(text, get(group).name); }); + event("DragOver", div_3, (e) => onDragOverGroup(e, get(group).id)); + event("DragLeave", div_3, onDragLeaveGroup); + event("Drop", div_3, (e) => onDropGroup(get(group).id, e)); delegated("click", button_1, () => search.activeGroupId = get(group).id); delegated("click", button_2, () => openGroupForm(get(group))); delegated("click", button_3, () => set(showDeleteGroupConfirm, get(group).id, true)); - append($$anchor, div_2); + append($$anchor, div_3); }); reset(nav); - var div_4 = sibling(nav, 2); - var button_4 = child(div_4); - reset(div_4); - var node_1 = sibling(div_4, 2); + var div_5 = sibling(nav, 2); + var button_4 = child(div_5); + reset(div_5); + var node_1 = sibling(div_5, 2); var consequent_1 = ($$anchor) => { - var div_5 = root_2$5(); - var div_6 = child(div_5); - var h3 = child(div_6); + var div_6 = root_2$5(); + var div_7 = child(div_6); + var h3 = child(div_7); var text_1 = child(h3, true); reset(h3); var node_2 = sibling(h3, 2); var consequent = ($$anchor) => { - var div_7 = root_3$5(); - var text_2 = child(div_7, true); - reset(div_7); + var div_8 = root_3$5(); + var text_2 = child(div_8, true); + reset(div_8); template_effect(() => set_text(text_2, get(groupError))); - append($$anchor, div_7); + append($$anchor, div_8); }; if_block(node_2, ($$render) => { if (get(groupError)) $$render(consequent); }); - var div_8 = sibling(node_2, 2); - var input_1 = sibling(child(div_8), 2); + var div_9 = sibling(node_2, 2); + var input_1 = sibling(child(div_9), 2); remove_input_defaults(input_1); - reset(div_8); - var div_9 = sibling(div_8, 2); - var div_10 = sibling(child(div_9), 2); - each(div_10, 21, () => GROUP_COLORS, index, ($$anchor, color) => { + reset(div_9); + var div_10 = sibling(div_9, 2); + var div_11 = sibling(child(div_10), 2); + each(div_11, 21, () => GROUP_COLORS, index, ($$anchor, color) => { var button_5 = root_4$5(); template_effect(() => { set_class(button_5, 1, `color-swatch ${get(groupColor) === get(color) ? "selected" : ""}`, "svelte-181dlmc"); @@ -5910,62 +5943,72 @@ function Sidebar($$anchor, $$props) { delegated("click", button_5, () => set(groupColor, get(color), true)); append($$anchor, button_5); }); + reset(div_11); reset(div_10); - reset(div_9); - var div_11 = sibling(div_9, 2); - var button_6 = child(div_11); + var div_12 = sibling(div_10, 2); + var button_6 = child(div_12); var text_3 = child(button_6, true); reset(button_6); var button_7 = sibling(button_6, 2); - reset(div_11); + reset(div_12); + reset(div_7); reset(div_6); - reset(div_5); template_effect(() => { set_text(text_1, get(editingGroupId) ? "Edit Group" : "New Group"); set_text(text_3, get(editingGroupId) ? "Update" : "Create"); }); - delegated("click", div_5, () => set(showGroupForm, false)); - delegated("click", div_6, (e) => e.stopPropagation()); + delegated("click", div_6, () => set(showGroupForm, false)); + delegated("click", div_7, (e) => e.stopPropagation()); bind_value(input_1, () => get(groupName), ($$value) => set(groupName, $$value)); delegated("click", button_6, saveGroup); delegated("click", button_7, () => set(showGroupForm, false)); - append($$anchor, div_5); + append($$anchor, div_6); }; if_block(node_1, ($$render) => { if (get(showGroupForm)) $$render(consequent_1); }); var node_3 = sibling(node_1, 2); var consequent_2 = ($$anchor) => { - var div_12 = root_5$4(); - var div_13 = child(div_12); - var p = sibling(child(div_13), 2); + append($$anchor, root_5$4()); + }; + if_block(node_3, ($$render) => { + if (get(dropTargetGroupId)) $$render(consequent_2); + }); + var node_4 = sibling(node_3, 2); + var consequent_3 = ($$anchor) => { + var div_14 = root_6$5(); + var div_15 = child(div_14); + var p = sibling(child(div_15), 2); var strong = sibling(child(p)); var text_4 = child(strong, true); reset(strong); next(); reset(p); - var div_14 = sibling(p, 2); - var button_8 = child(div_14); + var div_16 = sibling(p, 2); + var button_8 = child(div_16); var button_9 = sibling(button_8, 2); + reset(div_16); + reset(div_15); reset(div_14); - reset(div_13); - reset(div_12); template_effect(() => set_text(text_4, get(deletingGroup).name)); - delegated("click", div_12, () => set(showDeleteGroupConfirm, null)); - delegated("click", div_13, (e) => e.stopPropagation()); + delegated("click", div_14, () => set(showDeleteGroupConfirm, null)); + delegated("click", div_15, (e) => e.stopPropagation()); delegated("click", button_8, () => confirmDeleteGroup(get(deletingGroup).id)); delegated("click", button_9, () => set(showDeleteGroupConfirm, null)); - append($$anchor, div_12); + append($$anchor, div_14); }; - if_block(node_3, ($$render) => { - if (get(deletingGroup)) $$render(consequent_2); + if_block(node_4, ($$render) => { + if (get(deletingGroup)) $$render(consequent_3); }); reset(div); template_effect(() => { set_value(input, search.query); - set_class(button, 1, `group-item ${search.activeGroupId === "all" ? "active" : ""}`, "svelte-181dlmc"); + set_class(button, 1, `group-item ${search.activeGroupId === "all" ? "active" : ""} ${get(dropTargetGroupId) === "all" ? "drop-target" : ""}`, "svelte-181dlmc"); }); event("Input", input, (e) => search.query = e.target.value); + event("DragOver", div_2, (e) => onDragOverGroup(e, "all")); + event("DragLeave", div_2, onDragLeaveGroup); + event("Drop", div_2, (e) => onDropGroup("all", e)); delegated("click", button, () => search.activeGroupId = "all"); delegated("click", button_4, () => openGroupForm(null)); append($$anchor, div); @@ -5979,7 +6022,7 @@ var root_2$4 = /* @__PURE__ */ from_html(`
+ New Entry`); var root_3$4 = /* @__PURE__ */ from_html(`

`); var root_6$4 = /* @__PURE__ */ from_html(`matching " "`, 1); -var root_7$2 = /* @__PURE__ */ from_html(` `); +var root_7$2 = /* @__PURE__ */ from_html(` `); var root_5$3 = /* @__PURE__ */ from_html(`
TitleUsernameURL
`, 1); var root$5 = /* @__PURE__ */ from_html(`
`); function EntryList($$anchor, $$props) { @@ -5988,6 +6031,13 @@ function EntryList($$anchor, $$props) { let loading = /* @__PURE__ */ state(true); let error = /* @__PURE__ */ state(""); let resultCount = /* @__PURE__ */ state(0); + let draggedEntryId = /* @__PURE__ */ state(null); + function handleDragStart(entryId) { + set(draggedEntryId, entryId, true); + } + function handleDragEnd() { + set(draggedEntryId, null); + } async function loadEntries() { set(loading, true); set(error, ""); @@ -6076,7 +6126,7 @@ function EntryList($$anchor, $$props) { each(tbody, 21, () => get(entries), (entry) => entry.id, ($$anchor, entry) => { var tr = root_7$2(); var td = child(tr); - var span_1 = child(td); + var span_1 = sibling(child(td), 2); var text_6 = child(span_1, true); reset(span_1); reset(td); @@ -6092,11 +6142,18 @@ function EntryList($$anchor, $$props) { reset(td_2); reset(tr); template_effect(() => { + set_class(tr, 1, `entry-row ${get(draggedEntryId) === get(entry).id ? "dragging" : ""}`, "svelte-13s7gu4"); set_text(text_6, get(entry).title); set_text(text_7, get(entry).username); set_text(text_8, get(entry).url || "—"); }); delegated("click", tr, () => $$props.onSelect(get(entry).id)); + event("DragStart", tr, (e) => { + e.dataTransfer.setData("text/plain", get(entry).id); + e.dataTransfer.effectAllowed = "move"; + handleDragStart(get(entry).id); + }); + event("DragEnd", tr, handleDragEnd); append($$anchor, tr); }); reset(tbody); @@ -7261,6 +7318,17 @@ label { color: var(--color-primary); } + .group-item.drop-target.svelte-181dlmc { + background: rgba(108, 99, 255, 0.25); + color: var(--color-primary); + border: 1px dashed var(--color-primary); + } + + .group-wrapper.svelte-181dlmc { + border-radius: var(--radius-md); + transition: background-color 150ms; + } + .group-icon.svelte-181dlmc { font-size: 1rem; } @@ -7310,6 +7378,27 @@ label { border-top: 1px solid var(--color-border); } + .drop-indicator.svelte-181dlmc { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + padding: 8px 16px; + background: var(--color-primary); + color: #fff; + border-radius: var(--radius-md); + font-size: 0.8rem; + font-weight: 500; + z-index: 100; + pointer-events: none; + animation: svelte-181dlmc-fadeIn 150ms ease; + } + + @keyframes svelte-181dlmc-fadeIn { + from { opacity: 0; transform: translateX(-50%) translateY(10px); } + to { opacity: 1; transform: translateX(-50%) translateY(0); } + } + /* Modal */ .modal-overlay.svelte-181dlmc { position: fixed; @@ -7435,7 +7524,7 @@ label { } .entry-row.svelte-13s7gu4 { - cursor: pointer; + cursor: grab; transition: background-color 150ms; } @@ -7443,12 +7532,32 @@ label { background: var(--color-surface-hover); } + .entry-row.dragging.svelte-13s7gu4 { + opacity: 0.4; + } + + .entry-row.svelte-13s7gu4:active { + cursor: grabbing; + } + .entry-row.svelte-13s7gu4 td:where(.svelte-13s7gu4) { padding: 10px 12px; font-size: 0.875rem; border-bottom: 1px solid var(--color-border); } + .drag-handle.svelte-13s7gu4 { + color: var(--color-text-muted); + margin-right: 6px; + user-select: none; + opacity: 0.5; + transition: opacity 150ms; + } + + .entry-row.svelte-13s7gu4:hover .drag-handle:where(.svelte-13s7gu4) { + opacity: 1; + } + .entry-title.svelte-13s7gu4 { font-weight: 500; } diff --git a/src/components/EntryList.svelte b/src/components/EntryList.svelte index 97dc646..55cc3b2 100644 --- a/src/components/EntryList.svelte +++ b/src/components/EntryList.svelte @@ -9,6 +9,16 @@ let { onSelect, onAdd } = $props() + let draggedEntryId = $state(null) + + function handleDragStart(entryId) { + draggedEntryId = entryId + } + + function handleDragEnd() { + draggedEntryId = null + } + async function loadEntries() { loading = true error = '' @@ -82,8 +92,19 @@ {#each entries as entry (entry.id)} - onSelect(entry.id)} class="entry-row"> + onSelect(entry.id)} + class="entry-row {draggedEntryId === entry.id ? 'dragging' : ''}" + draggable="true" + onDragStart={(e) => { + e.dataTransfer.setData('text/plain', entry.id) + e.dataTransfer.effectAllowed = 'move' + handleDragStart(entry.id) + }} + onDragEnd={handleDragEnd} + > + {entry.title} @@ -153,7 +174,7 @@ } .entry-row { - cursor: pointer; + cursor: grab; transition: background-color 150ms; } @@ -161,12 +182,32 @@ background: var(--color-surface-hover); } + .entry-row.dragging { + opacity: 0.4; + } + + .entry-row:active { + cursor: grabbing; + } + .entry-row td { padding: 10px 12px; font-size: 0.875rem; border-bottom: 1px solid var(--color-border); } + .drag-handle { + color: var(--color-text-muted); + margin-right: 6px; + user-select: none; + opacity: 0.5; + transition: opacity 150ms; + } + + .entry-row:hover .drag-handle { + opacity: 1; + } + .entry-title { font-weight: 500; } diff --git a/src/components/Sidebar.svelte b/src/components/Sidebar.svelte index afcfbaa..8a6ae01 100644 --- a/src/components/Sidebar.svelte +++ b/src/components/Sidebar.svelte @@ -1,5 +1,5 @@
{#each groups as group} -
+
onDragOverGroup(e, group.id)} + onDragLeave={onDragLeaveGroup} + onDrop={(e) => onDropGroup(group.id, e)} + >
{/if} + + {#if dropTargetGroupId} +
Drop here to move
+ {/if} + {#if deletingGroup}