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(`
Delete Group
Delete " "? Entries in this group will become ungrouped.
`);
-var root$6 = /* @__PURE__ */ from_html(``);
+var root_5$4 = /* @__PURE__ */ from_html(`
Drop here to move
`);
+var root_6$5 = /* @__PURE__ */ from_html(`
Delete Group
Delete " "? Entries in this group will become ungrouped.
`);
+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(`
`, 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 @@
|