Add dragdrop grouping
This commit is contained in:
parent
a6589fb1f3
commit
7d9d3aef0e
128
dist/index.html
vendored
128
dist/index.html
vendored
@ -5538,6 +5538,20 @@ async function searchEntries(query, options = {}) {
|
||||
return entries.filter((e) => e.title.toLowerCase().includes(lower) || e.username.toLowerCase().includes(lower) || e.url && e.url.toLowerCase().includes(lower) || e.notes && e.notes.toLowerCase().includes(lower));
|
||||
}
|
||||
/**
|
||||
* Move an entry to a different group (or ungroup it by passing empty string).
|
||||
* @param {string} entryId
|
||||
* @param {string} groupId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function moveEntryToGroup(entryId, groupId) {
|
||||
const db = await getDb();
|
||||
const entry = await db.get("entries", entryId);
|
||||
if (!entry) throw new Error("Entry not found");
|
||||
entry.groupId = groupId;
|
||||
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
||||
await db.put("entries", entry);
|
||||
}
|
||||
/**
|
||||
* Count entries per group.
|
||||
* @returns {Promise<Map<string, number>>}
|
||||
*/
|
||||
@ -5818,12 +5832,12 @@ var SearchStore = class {
|
||||
var search = new SearchStore();
|
||||
//#endregion
|
||||
//#region src/components/Sidebar.svelte
|
||||
var root_1$7 = /* @__PURE__ */ from_html(`<div class="group-row svelte-181dlmc"><button><span class="group-color svelte-181dlmc"></span> <span class="group-name svelte-181dlmc"> </span></button> <div class="group-actions svelte-181dlmc"><button class="group-action-btn svelte-181dlmc" title="Edit group">✏️</button> <button class="group-action-btn svelte-181dlmc" title="Delete group">🗑</button></div></div>`);
|
||||
var root_1$7 = /* @__PURE__ */ from_html(`<div class="group-row svelte-181dlmc"><button><span class="group-color svelte-181dlmc"></span> <span class="group-name svelte-181dlmc"> </span> <span class="drop-icon svelte-181dlmc">📥</span></button> <div class="group-actions svelte-181dlmc"><button class="group-action-btn svelte-181dlmc" title="Edit group">✏️</button> <button class="group-action-btn svelte-181dlmc" title="Delete group">🗑</button></div></div>`);
|
||||
var root_3$5 = /* @__PURE__ */ from_html(`<div class="error-banner svelte-181dlmc"> </div>`);
|
||||
var root_4$5 = /* @__PURE__ */ from_html(`<button></button>`);
|
||||
var root_2$5 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-181dlmc" role="presentation"><div class="modal svelte-181dlmc" role="dialog" aria-modal="true" aria-label="Group settings" tabindex="-1"><h3 class="svelte-181dlmc"> </h3> <!> <div class="form-group"><label for="group-name">Group Name</label> <input id="group-name" type="text" placeholder="e.g. Work, Personal"/></div> <div class="form-group"><span class="field-label">Color</span> <div class="color-picker svelte-181dlmc"></div></div> <div class="modal-actions svelte-181dlmc"><button class="btn btn-primary"> </button> <button class="btn btn-ghost">Cancel</button></div></div></div>`);
|
||||
var root_5$4 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-181dlmc" role="presentation"><div class="modal svelte-181dlmc" role="dialog" aria-modal="true" aria-label="Delete group confirmation" tabindex="-1"><h3 class="svelte-181dlmc">Delete Group</h3> <p class="svelte-181dlmc">Delete "<strong> </strong>"? Entries in this group will become ungrouped.</p> <div class="modal-actions svelte-181dlmc"><button class="btn btn-danger">Yes, delete</button> <button class="btn btn-ghost">Cancel</button></div></div></div>`);
|
||||
var root$6 = /* @__PURE__ */ from_html(`<div class="sidebar-content svelte-181dlmc"><div class="sidebar-header svelte-181dlmc"><h2 class="svelte-181dlmc">🔐 Vault</h2></div> <div class="search-box svelte-181dlmc"><input type="text" placeholder="Search entries..." class="svelte-181dlmc"/></div> <nav class="groups-nav svelte-181dlmc"><button><span class="group-icon svelte-181dlmc">📋</span> <span class="group-name svelte-181dlmc">All Entries</span></button> <!></nav> <div class="sidebar-footer svelte-181dlmc"><button class="btn btn-ghost btn-sm w-full">+ New Group</button></div> <!> <!></div>`);
|
||||
var root_2$5 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-181dlmc" role="presentation"><div class="modal svelte-181dlmc" role="dialog" aria-modal="true" aria-label="Group settings" tabindex="-1"><h3 class="svelte-181dlmc"> </h3> <!> <div class="form-group svelte-181dlmc"><label for="group-name" class="svelte-181dlmc">Group Name</label> <input id="group-name" type="text" placeholder="e.g. Work, Personal" class="svelte-181dlmc"/></div> <div class="form-group svelte-181dlmc"><span class="field-label svelte-181dlmc">Color</span> <div class="color-picker svelte-181dlmc"></div></div> <div class="modal-actions svelte-181dlmc"><button class="btn btn-primary svelte-181dlmc"> </button> <button class="btn btn-ghost svelte-181dlmc">Cancel</button></div></div></div>`);
|
||||
var root_5$4 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-181dlmc" role="presentation"><div class="modal svelte-181dlmc" role="dialog" aria-modal="true" aria-label="Delete group confirmation" tabindex="-1"><h3 class="svelte-181dlmc">Delete Group</h3> <p class="svelte-181dlmc">Delete "<strong class="svelte-181dlmc"> </strong>"? Entries in this group will become ungrouped.</p> <div class="modal-actions svelte-181dlmc"><button class="btn btn-danger svelte-181dlmc">Yes, delete</button> <button class="btn btn-ghost svelte-181dlmc">Cancel</button></div></div></div>`);
|
||||
var root$6 = /* @__PURE__ */ from_html(`<div class="sidebar-content svelte-181dlmc"><div class="sidebar-header svelte-181dlmc"><h2 class="svelte-181dlmc">🔐 Vault</h2></div> <div class="search-box svelte-181dlmc"><input type="text" placeholder="Search entries..." class="svelte-181dlmc"/></div> <nav class="groups-nav svelte-181dlmc"><button><span class="group-icon svelte-181dlmc">📋</span> <span class="group-name svelte-181dlmc">All Entries</span></button> <!></nav> <div class="sidebar-footer svelte-181dlmc"><button class="btn btn-ghost btn-sm w-full svelte-181dlmc">+ New Group</button></div> <!> <!></div>`);
|
||||
function Sidebar($$anchor, $$props) {
|
||||
push($$props, true);
|
||||
let groups = /* @__PURE__ */ state(proxy([]));
|
||||
@ -5835,6 +5849,22 @@ 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 dragOverGroupId = /* @__PURE__ */ state(null);
|
||||
let droppedGroupId = /* @__PURE__ */ state(null);
|
||||
async function handleDrop(groupId, entryId) {
|
||||
try {
|
||||
await moveEntryToGroup(entryId, groupId);
|
||||
set(droppedGroupId, groupId, true);
|
||||
setTimeout(() => {
|
||||
set(droppedGroupId, null);
|
||||
}, 600);
|
||||
await loadData();
|
||||
search.refresh();
|
||||
} catch (e) {}
|
||||
}
|
||||
function canDrop(groupId) {
|
||||
return groupId !== search.activeGroupId;
|
||||
}
|
||||
const GROUP_COLORS = [
|
||||
"#6c63ff",
|
||||
"#e5484d",
|
||||
@ -5918,6 +5948,7 @@ function Sidebar($$anchor, $$props) {
|
||||
var span_1 = sibling(span, 2);
|
||||
var text = child(span_1, true);
|
||||
reset(span_1);
|
||||
next(2);
|
||||
reset(button_1);
|
||||
var div_3 = sibling(button_1, 2);
|
||||
var button_2 = child(div_3);
|
||||
@ -5925,11 +5956,29 @@ function Sidebar($$anchor, $$props) {
|
||||
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(dragOverGroupId) === get(group).id ? "drag-over" : ""} ${get(droppedGroupId) === get(group).id ? "dropped" : ""}`, "svelte-181dlmc");
|
||||
set_style(span, `background-color: ${(get(group).color || "#6c63ff") ?? ""}`);
|
||||
set_text(text, get(group).name);
|
||||
});
|
||||
delegated("click", button_1, () => search.activeGroupId = get(group).id);
|
||||
event("dragover", button_1, (e) => {
|
||||
if (canDrop(get(group).id)) {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
set(dragOverGroupId, get(group).id, true);
|
||||
}
|
||||
});
|
||||
event("dragleave", button_1, () => {
|
||||
if (get(dragOverGroupId) === get(group).id) set(dragOverGroupId, null);
|
||||
});
|
||||
event("drop", button_1, (e) => {
|
||||
e.preventDefault();
|
||||
set(dragOverGroupId, null);
|
||||
if (canDrop(get(group).id)) {
|
||||
const entryId = e.dataTransfer.getData("text/plain");
|
||||
if (entryId) handleDrop(get(group).id, entryId);
|
||||
}
|
||||
});
|
||||
delegated("click", button_2, () => openGroupForm(get(group)));
|
||||
delegated("click", button_3, () => set(showDeleteGroupConfirm, get(group).id, true));
|
||||
append($$anchor, div_2);
|
||||
@ -6042,7 +6091,7 @@ var root_2$4 = /* @__PURE__ */ from_html(`<div class="error-banner svelte-13s7gu
|
||||
var root_4$4 = /* @__PURE__ */ from_html(`<button class="btn btn-primary mt-3">+ New Entry</button>`);
|
||||
var root_3$4 = /* @__PURE__ */ from_html(`<div class="empty-state svelte-13s7gu4"><p class="empty-icon svelte-13s7gu4"> </p> <p class="empty-text svelte-13s7gu4"> </p> <p class="empty-hint svelte-13s7gu4"> </p> <!></div>`);
|
||||
var root_6$4 = /* @__PURE__ */ from_html(`matching "<strong> </strong>"`, 1);
|
||||
var root_7$2 = /* @__PURE__ */ from_html(`<tr class="entry-row svelte-13s7gu4"><td class="svelte-13s7gu4"><span class="entry-title svelte-13s7gu4"> </span></td><td class="svelte-13s7gu4"><span class="entry-username svelte-13s7gu4"> </span></td><td class="svelte-13s7gu4"><span class="entry-url truncate svelte-13s7gu4"> </span></td></tr>`);
|
||||
var root_7$2 = /* @__PURE__ */ from_html(`<tr><td class="svelte-13s7gu4"><span class="drag-handle svelte-13s7gu4" aria-hidden="true">⠿</span> <span class="entry-title svelte-13s7gu4"> </span></td><td class="svelte-13s7gu4"><span class="entry-username svelte-13s7gu4"> </span></td><td class="svelte-13s7gu4"><span class="entry-url truncate svelte-13s7gu4"> </span></td></tr>`);
|
||||
var root_5$3 = /* @__PURE__ */ from_html(`<div class="results-info svelte-13s7gu4"><span class="text-sm text-muted"> <!></span></div> <table class="entries-table svelte-13s7gu4"><thead><tr><th class="svelte-13s7gu4">Title</th><th class="svelte-13s7gu4">Username</th><th class="svelte-13s7gu4">URL</th></tr></thead><tbody></tbody></table>`, 1);
|
||||
var root$5 = /* @__PURE__ */ from_html(`<div class="entry-list"><!></div>`);
|
||||
function EntryList($$anchor, $$props) {
|
||||
@ -6051,6 +6100,7 @@ function EntryList($$anchor, $$props) {
|
||||
let loading = /* @__PURE__ */ state(true);
|
||||
let error = /* @__PURE__ */ state("");
|
||||
let resultCount = /* @__PURE__ */ state(0);
|
||||
let dragging = /* @__PURE__ */ state(false);
|
||||
async function loadEntries() {
|
||||
set(loading, true);
|
||||
set(error, "");
|
||||
@ -6138,8 +6188,9 @@ function EntryList($$anchor, $$props) {
|
||||
var tbody = sibling(child(table));
|
||||
each(tbody, 21, () => get(entries), (entry) => entry.id, ($$anchor, entry) => {
|
||||
var tr = root_7$2();
|
||||
set_attribute(tr, "draggable", true);
|
||||
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);
|
||||
@ -6155,11 +6206,20 @@ function EntryList($$anchor, $$props) {
|
||||
reset(td_2);
|
||||
reset(tr);
|
||||
template_effect(() => {
|
||||
set_class(tr, 1, `entry-row ${get(dragging) ? "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) => {
|
||||
set(dragging, true);
|
||||
e.dataTransfer.setData("text/plain", get(entry).id);
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
});
|
||||
event("dragend", tr, () => {
|
||||
set(dragging, false);
|
||||
});
|
||||
append($$anchor, tr);
|
||||
});
|
||||
reset(tbody);
|
||||
@ -7330,6 +7390,35 @@ label {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.group-item.drag-over.svelte-181dlmc {
|
||||
background: rgba(108, 99, 255, 0.2);
|
||||
border: 2px dashed var(--color-primary);
|
||||
outline: 2px solid rgba(108, 99, 255, 0.25);
|
||||
outline-offset: -4px;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.group-item.drag-over.svelte-181dlmc .drop-icon:where(.svelte-181dlmc) {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.group-item.dropped.svelte-181dlmc {
|
||||
animation: svelte-181dlmc-dropFlash 600ms ease;
|
||||
}
|
||||
|
||||
@keyframes svelte-181dlmc-dropFlash {
|
||||
0% { background: rgba(52, 211, 153, 0.35); }
|
||||
100% { background: transparent; }
|
||||
}
|
||||
|
||||
.drop-icon.svelte-181dlmc {
|
||||
opacity: 0;
|
||||
font-size: 0.85rem;
|
||||
transition: opacity 150ms, transform 150ms;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.group-icon.svelte-181dlmc {
|
||||
font-size: 1rem;
|
||||
}
|
||||
@ -7504,20 +7593,41 @@ label {
|
||||
}
|
||||
|
||||
.entry-row.svelte-13s7gu4 {
|
||||
cursor: pointer;
|
||||
transition: background-color 150ms;
|
||||
cursor: grab;
|
||||
transition: background-color 150ms, opacity 150ms;
|
||||
}
|
||||
|
||||
.entry-row.svelte-13s7gu4:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.entry-row.svelte-13s7gu4:hover {
|
||||
background: var(--color-surface-hover);
|
||||
}
|
||||
|
||||
.entry-row.dragging.svelte-13s7gu4 {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.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);
|
||||
opacity: 0.3;
|
||||
margin-right: 6px;
|
||||
font-size: 0.9rem;
|
||||
user-select: none;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
|
||||
.entry-row.svelte-13s7gu4:hover .drag-handle:where(.svelte-13s7gu4) {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.entry-title.svelte-13s7gu4 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
let loading = $state(true)
|
||||
let error = $state('')
|
||||
let resultCount = $state(0)
|
||||
let dragging = $state(false)
|
||||
|
||||
let { onSelect, onAdd } = $props()
|
||||
|
||||
@ -82,8 +83,15 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each entries as entry (entry.id)}
|
||||
<tr onclick={() => onSelect(entry.id)} class="entry-row">
|
||||
<tr
|
||||
draggable={true}
|
||||
onclick={() => onSelect(entry.id)}
|
||||
ondragstart={(e) => { dragging = true; e.dataTransfer.setData('text/plain', entry.id); e.dataTransfer.effectAllowed = 'move'; }}
|
||||
ondragend={() => { dragging = false; }}
|
||||
class="entry-row {dragging ? 'dragging' : ''}"
|
||||
>
|
||||
<td>
|
||||
<span class="drag-handle" aria-hidden="true">⠿</span>
|
||||
<span class="entry-title">{entry.title}</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -153,20 +161,41 @@
|
||||
}
|
||||
|
||||
.entry-row {
|
||||
cursor: pointer;
|
||||
transition: background-color 150ms;
|
||||
cursor: grab;
|
||||
transition: background-color 150ms, opacity 150ms;
|
||||
}
|
||||
|
||||
.entry-row:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.entry-row:hover {
|
||||
background: var(--color-surface-hover);
|
||||
}
|
||||
|
||||
.entry-row.dragging {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.entry-row td {
|
||||
padding: 10px 12px;
|
||||
font-size: 0.875rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
color: var(--color-text-muted);
|
||||
opacity: 0.3;
|
||||
margin-right: 6px;
|
||||
font-size: 0.9rem;
|
||||
user-select: none;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
|
||||
.entry-row:hover .drag-handle {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.entry-title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { getGroups, addGroup, updateGroup, deleteGroup, getEntryCountsByGroup } from '../lib/storage/db.js'
|
||||
import { getGroups, addGroup, updateGroup, deleteGroup, getEntryCountsByGroup, moveEntryToGroup } from '../lib/storage/db.js'
|
||||
import { createGroup, validateGroup } from '../lib/models/schema.js'
|
||||
import { search as searchStore } from '../lib/stores/search.svelte.js'
|
||||
import { autofocus } from '../lib/autofocus.js'
|
||||
@ -16,6 +16,26 @@
|
||||
let showDeleteGroupConfirm = $state(null) // groupId being confirmed for deletion
|
||||
let deletingGroup = $derived(groups.find(g => g.id === showDeleteGroupConfirm))
|
||||
|
||||
// Drag-and-drop state
|
||||
let dragOverGroupId = $state(null)
|
||||
let droppedGroupId = $state(null)
|
||||
|
||||
async function handleDrop(groupId, entryId) {
|
||||
try {
|
||||
await moveEntryToGroup(entryId, groupId)
|
||||
droppedGroupId = groupId
|
||||
setTimeout(() => { droppedGroupId = null }, 600)
|
||||
await loadData()
|
||||
searchStore.refresh()
|
||||
} catch (e) {
|
||||
// silent fail
|
||||
}
|
||||
}
|
||||
|
||||
function canDrop(groupId) {
|
||||
return groupId !== searchStore.activeGroupId
|
||||
}
|
||||
|
||||
const GROUP_COLORS = [
|
||||
'#6c63ff', '#e5484d', '#34d399', '#fbbf24', '#3b82f6',
|
||||
'#ec4899', '#8b5cf6', '#14b8a6', '#f97316', '#06b6d4',
|
||||
@ -112,11 +132,15 @@
|
||||
{#each groups as group}
|
||||
<div class="group-row">
|
||||
<button
|
||||
class="group-item {searchStore.activeGroupId === group.id ? 'active' : ''}"
|
||||
class="group-item {searchStore.activeGroupId === group.id ? 'active' : ''} {dragOverGroupId === group.id ? 'drag-over' : ''} {droppedGroupId === group.id ? 'dropped' : ''}"
|
||||
onclick={() => searchStore.activeGroupId = group.id}
|
||||
ondragover={(e) => { if (canDrop(group.id)) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; dragOverGroupId = group.id; } }}
|
||||
ondragleave={() => { if (dragOverGroupId === group.id) dragOverGroupId = null; }}
|
||||
ondrop={(e) => { e.preventDefault(); dragOverGroupId = null; if (canDrop(group.id)) { const entryId = e.dataTransfer.getData('text/plain'); if (entryId) handleDrop(group.id, entryId); } }}
|
||||
>
|
||||
<span class="group-color" style="background-color: {group.color || '#6c63ff'}"></span>
|
||||
<span class="group-name">{group.name}</span>
|
||||
<span class="drop-icon">📥</span>
|
||||
</button>
|
||||
<div class="group-actions">
|
||||
<button class="group-action-btn" onclick={() => openGroupForm(group)} title="Edit group">✏️</button>
|
||||
@ -250,6 +274,35 @@
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.group-item.drag-over {
|
||||
background: rgba(108, 99, 255, 0.2);
|
||||
border: 2px dashed var(--color-primary);
|
||||
outline: 2px solid rgba(108, 99, 255, 0.25);
|
||||
outline-offset: -4px;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.group-item.drag-over .drop-icon {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.group-item.dropped {
|
||||
animation: dropFlash 600ms ease;
|
||||
}
|
||||
|
||||
@keyframes dropFlash {
|
||||
0% { background: rgba(52, 211, 153, 0.35); }
|
||||
100% { background: transparent; }
|
||||
}
|
||||
|
||||
.drop-icon {
|
||||
opacity: 0;
|
||||
font-size: 0.85rem;
|
||||
transition: opacity 150ms, transform 150ms;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.group-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@ -261,6 +261,21 @@ export async function searchEntries(query, options = {}) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an entry to a different group (or ungroup it by passing empty string).
|
||||
* @param {string} entryId
|
||||
* @param {string} groupId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function moveEntryToGroup(entryId, groupId) {
|
||||
const db = await getDb()
|
||||
const entry = await db.get('entries', entryId)
|
||||
if (!entry) throw new Error('Entry not found')
|
||||
entry.groupId = groupId
|
||||
entry.updatedAt = new Date().toISOString()
|
||||
await db.put('entries', entry)
|
||||
}
|
||||
|
||||
/**
|
||||
* Count entries per group.
|
||||
* @returns {Promise<Map<string, number>>}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user