/// import { Cell, computed, handler, ifElse, lift, NAME, navigateTo, pattern, UI, wish, } from "commontools"; import { default as Note } from "../notes/note.tsx"; // Simple random ID generator (crypto.randomUUID not available in pattern env) const generateId = () => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`; import BacklinksIndex, { type MentionableCharm } from "./backlinks-index.tsx"; import OmniboxFAB from "./omnibox-fab.tsx"; import Notebook from "../notes/notebook.tsx"; import NotesImportExport from "../notes/notes-import-export.tsx"; type MinimalCharm = { [NAME]?: string; isHidden?: boolean; }; type CharmsListInput = void; // Recipe returns only UI, no data outputs (only symbol properties) interface CharmsListOutput { [key: string]: unknown; backlinksIndex: { mentionable: MentionableCharm[]; }; sidebarUI: unknown; fabUI: unknown; } const _visit = handler< Record, { charm: Cell } >((_, state) => { return navigateTo(state.charm); }, { proxy: true }); const removeCharm = handler< Record, { charm: Cell; allCharms: Cell; } >((_, state) => { const allCharmsValue = state.allCharms.get(); const index = allCharmsValue.findIndex((c: any) => state.charm.equals(c)); if (index !== -1) { const charmListCopy = [...allCharmsValue]; console.log("charmListCopy before", charmListCopy.length); charmListCopy.splice(index, 1); console.log("charmListCopy after", charmListCopy.length); state.allCharms.set(charmListCopy); } }); // Handler for dropping a note onto a notebook row const dropOntoNotebook = handler< { detail: { sourceCell: Cell } }, { notebook: Cell<{ notes?: unknown[] }> } >((event, { notebook }) => { const sourceCell = event.detail.sourceCell; const notesCell = notebook.key("notes"); const notesList = notesCell.get() ?? []; // Prevent duplicates using Cell.equals const alreadyExists = notesList.some((n) => Cell.equals(sourceCell, n as any) ); if (alreadyExists) return; // Hide from Patterns list sourceCell.key("isHidden").set(true); // Add to notebook - push cell reference, not value, to maintain charm identity notesCell.push(sourceCell); }); const toggleFab = handler }>( (_, { fabExpanded }) => { fabExpanded.set(!fabExpanded.get()); }, ); // Toggle dropdown menu const toggleMenu = handler }>( (_, { menuOpen }) => menuOpen.set(!menuOpen.get()), ); // Close dropdown menu (for backdrop click) const closeMenu = handler }>( (_, { menuOpen }) => menuOpen.set(false), ); // Menu: New Note const menuNewNote = handler }>( (_, { menuOpen }) => { menuOpen.set(false); return navigateTo(Note({ title: "New Note", content: "", noteId: generateId(), })); }, ); // Menu: New Notebook const menuNewNotebook = handler }>( (_, { menuOpen }) => { menuOpen.set(false); return navigateTo(Notebook({ title: "New Notebook" })); }, ); // Helper to find existing All Notes charm const findAllNotebooksCharm = (allCharms: Cell) => { const charms = allCharms.get(); return charms.find((charm: any) => { const name = charm?.[NAME]; return typeof name === "string" && name.startsWith("All Notes"); }); }; // Menu: All Notes const menuAllNotebooks = handler< void, { menuOpen: Cell; allCharms: Cell } >((_, { menuOpen, allCharms }) => { menuOpen.set(false); const existing = findAllNotebooksCharm(allCharms); if (existing) { return navigateTo(existing); } return navigateTo(NotesImportExport({ importMarkdown: "" })); }); export default pattern((_) => { const { allCharms } = wish<{ allCharms: MentionableCharm[] }>("/"); // Dropdown menu state const menuOpen = Cell.of(false); // Filter out hidden charms and charms without resolved NAME // (prevents transient hash-only pills during reactive updates) // NOTE: Use truthy check, not === true, because charm.isHidden is a proxy object const visibleCharms = computed(() => allCharms.filter((charm) => { if (charm.isHidden) return false; const name = (charm as any)?.[NAME]; return typeof name === "string" && name.length > 0; }) ); const index = BacklinksIndex({ allCharms }); const fab = OmniboxFAB({ mentionable: index.mentionable as unknown as Cell, }); return { backlinksIndex: index, [NAME]: computed(() => `DefaultCharmList (${visibleCharms.length})`), [UI]: (

Patterns

Notes ▾ {/* Backdrop to close menu when clicking outside */}
(menuOpen.get() ? "block" : "none")), position: "fixed", inset: "0", zIndex: "999", }} /> {/* Dropdown Menu */} (menuOpen.get() ? "flex" : "none")), position: "fixed", top: "112px", right: "16px", background: "var(--ct-color-bg, white)", border: "1px solid var(--ct-color-border, #e5e5e7)", borderRadius: "12px", boxShadow: "0 4px 12px rgba(0,0,0,0.15)", minWidth: "160px", zIndex: "1000", padding: "4px", }} > {"\u00A0\u00A0"}📝 New Note {"\u00A0\u00A0"}📓 New Notebook
{"\u00A0\u00A0"}📁 All Notes
{visibleCharms.map((charm) => { // Check if charm is a notebook by NAME prefix (isNotebook prop not reliable through proxy) const isNotebook = lift((args: { c: unknown }) => { const name = (args.c as any)?.[NAME]; const result = typeof name === "string" && name.startsWith("📓"); return result; })({ c: charm }); const dragHandle = ( ); const link = ( ); return ( {dragHandle} {ifElse( isNotebook, {link} , link, )} 🗑️ ); })} ), sidebarUI: undefined, fabUI: fab[UI], }; });