mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 02:59:40 +02:00
feat(kontext,notes): cross-module handoff — save Kontext as a Note
Wires the "Als Notiz speichern" action at the bottom of the Kontext
widget (UI itself landed in 003f75f7e) to actually open Notes next
to Kontext and focus the new note:
- workbench-scenes: new addAppAfter(appId, anchorAppId). addApp()
always appended, which pushed Notes to the far end of the
carousel; addAppAfter inserts directly after the anchor (Kontext)
and no-ops if the target is already open so the user's current
position isn't yanked around.
- notes/stores/selection: new transient in-memory focus signal
(focusedNoteId) that cross-module callers populate. Kept
non-persistent intentionally — surviving a remount would re-open
random notes after page loads.
- notes/ListView: $effect reads focusedNoteId, waits for the
Dexie liveQuery to surface the just-created row, opens it in
the inline editor, clears the focus signal, then scrolls the
matching data-note-id element into view via queueMicrotask so
the DOM has rendered the editor variant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cb384bc7ef
commit
6acb044230
3 changed files with 67 additions and 0 deletions
|
|
@ -5,6 +5,7 @@
|
|||
<script lang="ts">
|
||||
import { useAllNotes, searchNotes, getPreview, formatRelativeTime } from './queries';
|
||||
import { notesStore } from './stores/notes.svelte';
|
||||
import { notesSelectionStore } from './stores/selection.svelte';
|
||||
import type { Note } from './types';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import { ContextMenu, type ContextMenuItem } from '@mana/shared-ui';
|
||||
|
|
@ -59,6 +60,24 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Cross-module focus signal (e.g. Kontext → "Als Notiz speichern").
|
||||
// The caller populates selectionStore.focusedNoteId; we wait until
|
||||
// the underlying Dexie row is visible via liveQuery (the just-created
|
||||
// note may not be in `notes` on the first effect run because liveQuery
|
||||
// is async), then open it in the editor and scroll it into view.
|
||||
$effect(() => {
|
||||
const id = notesSelectionStore.focusedNoteId;
|
||||
if (!id) return;
|
||||
const target = notes.find((n) => n.id === id);
|
||||
if (!target) return;
|
||||
startEdit(target);
|
||||
notesSelectionStore.clearFocus();
|
||||
queueMicrotask(() => {
|
||||
const el = document.querySelector(`[data-note-id="${id}"]`);
|
||||
el?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
});
|
||||
});
|
||||
|
||||
async function saveEdit() {
|
||||
if (!editingId) return;
|
||||
await notesStore.updateNote(editingId, {
|
||||
|
|
@ -131,6 +150,7 @@
|
|||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="note-item editing"
|
||||
data-note-id={note.id}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Escape') saveEdit();
|
||||
}}
|
||||
|
|
@ -158,6 +178,7 @@
|
|||
<!-- Note row -->
|
||||
<button
|
||||
class="note-item"
|
||||
data-note-id={note.id}
|
||||
onclick={() => startEdit(note)}
|
||||
oncontextmenu={(e) => ctxMenu.open(e, note)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Notes focus signal — transient, in-memory only.
|
||||
*
|
||||
* Used by cross-module actions (e.g. "Kontext → Als Notiz speichern")
|
||||
* to ask the Notes ListView to open a specific note in its inline
|
||||
* editor and scroll it into view. Cleared by the ListView once
|
||||
* handled so the signal doesn't survive remounts.
|
||||
*/
|
||||
|
||||
function createNotesSelectionStore() {
|
||||
let focusedNoteId = $state<string | null>(null);
|
||||
|
||||
return {
|
||||
get focusedNoteId() {
|
||||
return focusedNoteId;
|
||||
},
|
||||
focusNote(id: string) {
|
||||
focusedNoteId = id;
|
||||
},
|
||||
clearFocus() {
|
||||
focusedNoteId = null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const notesSelectionStore = createNotesSelectionStore();
|
||||
|
|
@ -257,6 +257,26 @@ export const workbenchScenesStore = {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert `appId` directly after `anchorAppId` in the active scene.
|
||||
* Used by cross-module actions like "Kontext → Als Notiz speichern",
|
||||
* where the target widget should land next to the source rather than
|
||||
* at the end of the carousel. If the app is already open, its
|
||||
* position is left untouched (we don't want to yank a widget the
|
||||
* user is already interacting with). If the anchor isn't in the
|
||||
* scene, falls back to appending.
|
||||
*/
|
||||
async addAppAfter(appId: string, anchorAppId: string) {
|
||||
await patchActiveScene((apps) => {
|
||||
if (apps.some((a) => a.appId === appId)) return apps;
|
||||
const anchorIdx = apps.findIndex((a) => a.appId === anchorAppId);
|
||||
if (anchorIdx === -1) return [...apps, { appId }];
|
||||
const next = [...apps];
|
||||
next.splice(anchorIdx + 1, 0, { appId });
|
||||
return next;
|
||||
});
|
||||
},
|
||||
|
||||
async removeApp(appId: string) {
|
||||
await patchActiveScene((apps) => apps.filter((a) => a.appId !== appId));
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue