mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:21:10 +02:00
refactor(mana/web): extract addTagId / removeTagIdWithUndo helpers
Four ListViews (calendar, contacts, places, todo) reimplemented the same drag-drop tag append logic, and four matching DetailViews reimplemented the same "remove tag with undo toast" logic. Extract both into pure helpers in $lib/data/tag-mutations.ts that take a store-agnostic update function — works for the standard tagIds modules and for todo's metadata.labelIds via tasksStore.updateLabels. Side win: places/views/DetailView's removeTag had no undo toast (every other module did). Consolidating fixes the inconsistency. zitare is the outlier — its drag target is a Quote, but the tags live on a (possibly-not-yet-existing) Favorite record. Stays custom. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1f26aa4f2f
commit
3dc16bb885
9 changed files with 96 additions and 39 deletions
67
apps/mana/apps/web/src/lib/data/tag-mutations.ts
Normal file
67
apps/mana/apps/web/src/lib/data/tag-mutations.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Tag mutation helpers — shared add/remove logic for entity.tagIds fields.
|
||||
*
|
||||
* Every module that supports tagging on its records (calendar events,
|
||||
* contacts, places, todo tasks, …) reimplemented the same two operations:
|
||||
*
|
||||
* - "append a tag if not already present" (drag-drop from tag strip)
|
||||
* - "remove a tag, with undo toast" (click on a tag pill in the detail view)
|
||||
*
|
||||
* These helpers stay store-agnostic — the caller passes the current
|
||||
* `tagIds` array and an `update` function that knows which store/field to
|
||||
* write to. That keeps them usable for tasks (which write `metadata.labelIds`
|
||||
* via `tasksStore.updateLabels`) as well as the standard `tagIds` modules.
|
||||
*/
|
||||
|
||||
import { toastStore } from '@mana/shared-ui/toast';
|
||||
|
||||
type UpdateFn = (next: string[]) => Promise<void> | void;
|
||||
|
||||
/**
|
||||
* Append `tagId` to `current` and call `update` with the result.
|
||||
* No-op if the tag is already present.
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* use:dropTarget={{
|
||||
* accepts: ['tag'],
|
||||
* onDrop: (p) => addTagId(
|
||||
* contact.tagIds ?? [],
|
||||
* (p.data as TagDragData).id,
|
||||
* (next) => contactsStore.updateTagIds(contact.id, next),
|
||||
* ),
|
||||
* }}
|
||||
* ```
|
||||
*/
|
||||
export async function addTagId(current: string[], tagId: string, update: UpdateFn): Promise<void> {
|
||||
if (current.includes(tagId)) return;
|
||||
await update([...current, tagId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove `tagId` from `current`, call `update` with the filtered list,
|
||||
* and show a toast with an undo action that reinstates the original list.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* async function removeTag(tagId: string) {
|
||||
* await removeTagIdWithUndo(
|
||||
* contact.tagIds ?? [],
|
||||
* tagId,
|
||||
* (next) => contactsStore.updateTagIds(contactId, next),
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export async function removeTagIdWithUndo(
|
||||
current: string[],
|
||||
tagId: string,
|
||||
update: UpdateFn,
|
||||
undoLabel = 'Tag entfernt'
|
||||
): Promise<void> {
|
||||
const removed = current.filter((id) => id !== tagId);
|
||||
await update(removed);
|
||||
toastStore.undo(undoLabel, () => {
|
||||
void update(current);
|
||||
});
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
import { dropTarget, dragSource } from '@mana/shared-ui/dnd';
|
||||
import type { TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import { addTagId } from '$lib/data/tag-mutations';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
@ -23,10 +24,9 @@
|
|||
function handleTagDrop(eventId: string, tagData: TagDragData) {
|
||||
const event = allItems.find((e) => e.id === eventId);
|
||||
if (!event) return;
|
||||
const current = event.tagIds ?? [];
|
||||
if (!current.includes(tagData.id)) {
|
||||
eventsStore.updateTagIds(eventId, [...current, tagData.id]);
|
||||
}
|
||||
void addTagId(event.tagIds ?? [], tagData.id, (next) =>
|
||||
eventsStore.updateTagIds(eventId, next)
|
||||
);
|
||||
}
|
||||
|
||||
const itemsQuery = useAllCalendarItems();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import LinkedItems from '$lib/components/links/LinkedItems.svelte';
|
||||
import { toastStore } from '@mana/shared-ui/toast';
|
||||
import { removeTagIdWithUndo } from '$lib/data/tag-mutations';
|
||||
|
||||
let { navigate, params, goBack }: ViewProps = $props();
|
||||
let eventId = $derived(params.eventId as string);
|
||||
|
|
@ -68,12 +69,9 @@
|
|||
let eventTags = $derived(getTagsByIds(allTags, detail.entity?.tagIds ?? []));
|
||||
|
||||
async function removeTag(tagId: string) {
|
||||
const current = detail.entity?.tagIds ?? [];
|
||||
const removed = current.filter((id) => id !== tagId);
|
||||
await eventsStore.updateTagIds(eventId, removed);
|
||||
toastStore.undo('Tag entfernt', () => {
|
||||
eventsStore.updateTagIds(eventId, current);
|
||||
});
|
||||
await removeTagIdWithUndo(detail.entity?.tagIds ?? [], tagId, (next) =>
|
||||
eventsStore.updateTagIds(eventId, next)
|
||||
);
|
||||
}
|
||||
|
||||
async function saveField() {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
import { dropTarget, dragSource } from '@mana/shared-ui/dnd';
|
||||
import type { TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import { addTagId } from '$lib/data/tag-mutations';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
@ -23,10 +24,9 @@
|
|||
function handleTagDrop(contactId: string, tagData: TagDragData) {
|
||||
const contact = contacts.find((c) => c.id === contactId);
|
||||
if (!contact) return;
|
||||
const current = contact.tagIds ?? [];
|
||||
if (!current.includes(tagData.id)) {
|
||||
contactsStore.updateTagIds(contactId, [...current, tagData.id]);
|
||||
}
|
||||
void addTagId(contact.tagIds ?? [], tagData.id, (next) =>
|
||||
contactsStore.updateTagIds(contactId, next)
|
||||
);
|
||||
}
|
||||
|
||||
let contacts$ = useLiveQueryWithDefault(async () => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
import type { LocalContact } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import LinkedItems from '$lib/components/links/LinkedItems.svelte';
|
||||
import { toastStore } from '@mana/shared-ui/toast';
|
||||
import { removeTagIdWithUndo } from '$lib/data/tag-mutations';
|
||||
|
||||
let { navigate, params, goBack }: ViewProps = $props();
|
||||
let contactId = $derived(params.contactId as string);
|
||||
|
|
@ -58,12 +58,9 @@
|
|||
let contactTags = $derived(getTagsByIds(allTags, detail.entity?.tagIds ?? []));
|
||||
|
||||
async function removeTag(tagId: string) {
|
||||
const current = detail.entity?.tagIds ?? [];
|
||||
const removed = current.filter((id) => id !== tagId);
|
||||
await contactsStore.updateTagIds(contactId, removed);
|
||||
toastStore.undo('Tag entfernt', () => {
|
||||
contactsStore.updateTagIds(contactId, current);
|
||||
});
|
||||
await removeTagIdWithUndo(detail.entity?.tagIds ?? [], tagId, (next) =>
|
||||
contactsStore.updateTagIds(contactId, next)
|
||||
);
|
||||
}
|
||||
|
||||
function initials(c: LocalContact): string {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
import { dropTarget, dragSource } from '@mana/shared-ui/dnd';
|
||||
import type { TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import { addTagId } from '$lib/data/tag-mutations';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
@ -23,10 +24,9 @@
|
|||
function handleTagDrop(placeId: string, tagData: TagDragData) {
|
||||
const place = places.find((p) => p.id === placeId);
|
||||
if (!place) return;
|
||||
const current = place.tagIds ?? [];
|
||||
if (!current.includes(tagData.id)) {
|
||||
placesStore.updateTagIds(placeId, [...current, tagData.id]);
|
||||
}
|
||||
void addTagId(place.tagIds ?? [], tagData.id, (next) =>
|
||||
placesStore.updateTagIds(placeId, next)
|
||||
);
|
||||
}
|
||||
|
||||
let places = $state<LocalPlace[]>([]);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
import type { LocalPlace, PlaceCategory, LocalLocationLog } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import LinkedItems from '$lib/components/links/LinkedItems.svelte';
|
||||
import { removeTagIdWithUndo } from '$lib/data/tag-mutations';
|
||||
|
||||
let { navigate, params, goBack }: ViewProps = $props();
|
||||
let placeId = $derived(params.placeId as string);
|
||||
|
|
@ -64,10 +65,8 @@
|
|||
];
|
||||
|
||||
async function removeTag(tagId: string) {
|
||||
const current = detail.entity?.tagIds ?? [];
|
||||
await placesStore.updateTagIds(
|
||||
placeId,
|
||||
current.filter((id) => id !== tagId)
|
||||
await removeTagIdWithUndo(detail.entity?.tagIds ?? [], tagId, (next) =>
|
||||
placesStore.updateTagIds(placeId, next)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
import { dropTarget, dragSource } from '@mana/shared-ui/dnd';
|
||||
import type { TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import { addTagId } from '$lib/data/tag-mutations';
|
||||
import VoiceCaptureBar from '$lib/components/voice/VoiceCaptureBar.svelte';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
@ -34,10 +35,7 @@
|
|||
function handleTagDrop(taskId: string, tagData: TagDragData) {
|
||||
const task = tasks.find((t) => t.id === taskId);
|
||||
if (!task) return;
|
||||
const current = getTaskTagIds(task);
|
||||
if (!current.includes(tagData.id)) {
|
||||
tasksStore.updateLabels(taskId, [...current, tagData.id]);
|
||||
}
|
||||
void addTagId(getTaskTagIds(task), tagData.id, (next) => tasksStore.updateLabels(taskId, next));
|
||||
}
|
||||
|
||||
type ViewFilter = 'inbox' | 'today' | 'overdue';
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import LinkedItems from '$lib/components/links/LinkedItems.svelte';
|
||||
import { toastStore } from '@mana/shared-ui/toast';
|
||||
import { removeTagIdWithUndo } from '$lib/data/tag-mutations';
|
||||
|
||||
let { navigate, params, goBack }: ViewProps = $props();
|
||||
let taskId = $derived(params.taskId as string);
|
||||
|
|
@ -73,12 +74,9 @@
|
|||
let taskTags = $derived(getTagsByIds(allTags, getTaskTagIds()));
|
||||
|
||||
async function removeTag(tagId: string) {
|
||||
const current = getTaskTagIds();
|
||||
const removed = current.filter((id) => id !== tagId);
|
||||
await tasksStore.updateLabels(taskId, removed);
|
||||
toastStore.undo('Tag entfernt', () => {
|
||||
tasksStore.updateLabels(taskId, current);
|
||||
});
|
||||
await removeTagIdWithUndo(getTaskTagIds(), tagId, (next) =>
|
||||
tasksStore.updateLabels(taskId, next)
|
||||
);
|
||||
}
|
||||
|
||||
async function saveField() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue