From d988e0b1124db2160cc4a75f3db54680fc136329 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 3 Mar 2026 14:26:04 -0800 Subject: [PATCH] refactor: slim Note toolbar, move rApps to Embed/Creative Remove Card, Rich Note, rNotes, rBooks, rPubs, rForum from the Note toolbar group. Move rNotes/rBooks/rForum to Embed as interactive embeds (data port-ins). Move rPubs to Creative. Remove Card and Obsidian Note from MI tool schema. Co-Authored-By: Claude Opus 4.6 --- lib/mi-tool-schema.ts | 2 - website/canvas.html | 419 +++++++++++++++++++++++++++++++++--------- 2 files changed, 336 insertions(+), 85 deletions(-) diff --git a/lib/mi-tool-schema.ts b/lib/mi-tool-schema.ts index 22aedc6..440da63 100644 --- a/lib/mi-tool-schema.ts +++ b/lib/mi-tool-schema.ts @@ -12,7 +12,6 @@ export interface ToolHint { const TOOL_HINTS: ToolHint[] = [ { tagName: "folk-markdown", label: "Note", icon: "πŸ“", keywords: ["note", "text", "markdown", "write", "document"] }, - { tagName: "folk-wrapper", label: "Card", icon: "πŸ“‹", keywords: ["card", "wrapper", "container", "group"] }, { tagName: "folk-slide", label: "Slide", icon: "πŸ–ΌοΈ", keywords: ["slide", "presentation", "deck"] }, { tagName: "folk-chat", label: "Chat", icon: "πŸ’¬", keywords: ["chat", "message", "conversation", "talk"] }, { tagName: "folk-embed", label: "Embed", icon: "πŸ”—", keywords: ["embed", "iframe", "website", "url", "link"] }, @@ -23,7 +22,6 @@ const TOOL_HINTS: ToolHint[] = [ { tagName: "folk-prompt", label: "AI Chat", icon: "πŸ€–", keywords: ["ai", "prompt", "llm", "assistant", "gpt"] }, { tagName: "folk-transcription", label: "Transcribe", icon: "πŸŽ™οΈ", keywords: ["transcribe", "audio", "speech", "voice", "record"] }, { tagName: "folk-video-chat", label: "Video Call", icon: "πŸ“Ή", keywords: ["video call", "webcam", "meeting"] }, - { tagName: "folk-obs-note", label: "Obsidian Note", icon: "πŸ““", keywords: ["obsidian", "note", "vault"] }, { tagName: "folk-workflow-block", label: "Workflow", icon: "βš™οΈ", keywords: ["workflow", "automation", "block", "process"] }, { tagName: "folk-social-post", label: "Social Post", icon: "πŸ“£", keywords: ["social", "post", "twitter", "instagram", "campaign"] }, { tagName: "folk-splat", label: "3D Gaussian", icon: "πŸ’Ž", keywords: ["3d", "splat", "gaussian", "point cloud"] }, diff --git a/website/canvas.html b/website/canvas.html index 5e2d48a..a672c25 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -261,6 +261,163 @@ padding: 4px 6px !important; } + /* ── Bottom toolbar β€” basic canvas tools ── */ + #bottom-toolbar { + position: fixed; + bottom: 16px; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + gap: 2px; + padding: 6px 10px; + background: var(--rs-toolbar-bg); + border-radius: 14px; + box-shadow: var(--rs-shadow-md); + z-index: 1000; + } + + #bottom-toolbar .tool-btn { + display: flex; + align-items: center; + justify-content: center; + width: 38px; + height: 38px; + border: none; + border-radius: 8px; + background: transparent; + color: var(--rs-toolbar-btn-text); + cursor: pointer; + font-size: 16px; + transition: background 0.15s, color 0.15s; + position: relative; + } + + #bottom-toolbar .tool-btn:hover { + background: var(--rs-toolbar-btn-hover); + } + + #bottom-toolbar .tool-btn.active { + background: var(--rs-accent); + color: white; + } + + #bottom-toolbar .tool-btn svg { + pointer-events: none; + } + + #bottom-toolbar .tool-sep { + width: 1px; + height: 24px; + background: var(--rs-toolbar-sep); + margin: 0 4px; + flex-shrink: 0; + } + + /* Recent tools area */ + #recent-tools { + display: flex; + align-items: center; + gap: 2px; + } + + #recent-tools:empty + #recent-sep { + display: none !important; + } + + #recent-tools .recent-tool-btn { + display: flex; + align-items: center; + justify-content: center; + width: 38px; + height: 38px; + border: none; + border-radius: 8px; + background: transparent; + color: var(--rs-toolbar-btn-text); + cursor: pointer; + font-size: 14px; + transition: background 0.15s; + opacity: 0.7; + } + + #recent-tools .recent-tool-btn:hover { + background: var(--rs-toolbar-btn-hover); + opacity: 1; + } + + /* Plus button + favorites menu */ + .tool-plus-wrap { + position: relative; + } + + #tool-plus-menu { + display: none; + position: absolute; + bottom: calc(100% + 10px); + right: 0; + width: 220px; + max-height: 320px; + overflow-y: auto; + background: var(--rs-toolbar-panel-bg); + border-radius: 12px; + box-shadow: var(--rs-shadow-lg); + padding: 8px; + z-index: 1002; + flex-direction: column; + gap: 2px; + } + + #tool-plus-menu.open { + display: flex; + } + + #tool-plus-menu .menu-heading { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--rs-text-muted); + padding: 4px 8px 2px; + } + + #tool-plus-menu button { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 7px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--rs-toolbar-btn-text); + cursor: pointer; + font-size: 12px; + text-align: left; + white-space: nowrap; + transition: background 0.15s; + } + + #tool-plus-menu button:hover { + background: var(--rs-toolbar-btn-hover); + } + + #tool-plus-menu button .pin-icon { + margin-left: auto; + font-size: 11px; + opacity: 0.3; + } + + #tool-plus-menu button.pinned .pin-icon { + opacity: 1; + color: var(--rs-accent); + } + + /* Feed mode: hide bottom toolbar */ + #canvas.feed-mode ~ #bottom-toolbar { + display: none; + } + #community-info { display: none; } @@ -1312,6 +1469,22 @@ border-radius: 16px; max-height: 50vh; } + + /* Bottom toolbar: compact on mobile */ + #bottom-toolbar { + bottom: 8px; + padding: 4px 6px; + gap: 1px; + } + + #bottom-toolbar .tool-btn { + width: 36px; + height: 36px; + } + + #bottom-toolbar .tool-sep { + margin: 0 2px; + } } /* ── Feed sort bar ── */ @@ -1545,30 +1718,12 @@
-
- -
- - - - - - -
-
-
- - - - - -
@@ -1584,6 +1739,7 @@ +
@@ -1606,6 +1762,9 @@ + + + @@ -1650,7 +1809,6 @@ - @@ -1675,6 +1833,48 @@
+
+ + + + + + + + + + + + + +
+ +
+ +
+
+
+

πŸ’­ Memory

@@ -2219,7 +2419,7 @@ } catch { return { notes: [], fetchedAt: Date.now() }; } } - const NOTE_BTN_IDS = ["new-markdown", "new-obs-note"]; + const NOTE_BTN_IDS = ["new-markdown"]; async function updateNoteToolbarState() { const data = await fetchNotesData(); @@ -3130,12 +3330,15 @@ ghostEl.style.left = (window.innerWidth / 2) + "px"; ghostEl.style.top = (window.innerHeight / 2) + "px"; } + + if (typeof syncBottomToolbar === "function") syncBottomToolbar(); } function clearPendingTool() { pendingTool = null; canvas.style.cursor = ""; if (ghostEl) { ghostEl.remove(); ghostEl = null; } + if (typeof syncBottomToolbar === "function") syncBottomToolbar(); } // Track ghost position @@ -3146,12 +3349,36 @@ } }); - // ESC returns to default selector tool + // Keyboard shortcuts for canvas tools document.addEventListener("keydown", (e) => { + // Skip when typing in inputs + if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA" || e.target.isContentEditable) return; + if (e.key === "Escape") { if (wbTool) setWbTool(null); if (pendingTool) clearPendingTool(); + if (connectMode) { + connectMode = false; + newArrowBtn?.classList.remove("active"); + canvas.classList.remove("connect-mode"); + if (connectSource) { connectSource.classList.remove("connect-source"); connectSource = null; } + } + if (typeof syncBottomToolbar === "function") syncBottomToolbar(); + return; } + + if (e.ctrlKey || e.metaKey || e.altKey) return; + + const key = e.key.toLowerCase(); + if (key === "v") { document.getElementById("tool-select")?.click(); } + else if (key === "p") { document.getElementById("tool-pencil")?.click(); } + else if (key === "l") { document.getElementById("tool-line")?.click(); } + else if (key === "r") { document.getElementById("tool-rect")?.click(); } + else if (key === "c") { document.getElementById("tool-circle")?.click(); } + else if (key === "s") { document.getElementById("tool-sticky")?.click(); } + else if (key === "t") { document.getElementById("tool-text")?.click(); } + else if (key === "a") { document.getElementById("tool-arrow")?.click(); } + else if (key === "e") { document.getElementById("tool-eraser")?.click(); } }); // Create a shape, add to canvas, and register for sync. @@ -3212,23 +3439,6 @@ setPendingTool("folk-markdown", { content: note.content || `# ${note.title}\n\n${note.content_plain || ""}` }); }); - document.getElementById("new-wrapper").addEventListener("click", () => { - const colors = ["#14b8a6", "#8b5cf6", "#f59e0b", "#ef4444", "#3b82f6", "#22c55e"]; - const icons = ["πŸ“‹", "πŸ’‘", "πŸ“Œ", "πŸ”—", "πŸ“", "⭐"]; - setPendingTool("folk-wrapper", { - title: "New Card", - icon: icons[Math.floor(Math.random() * icons.length)], - primaryColor: colors[Math.floor(Math.random() * colors.length)], - __postCreate: (shape) => { - const content = document.createElement("div"); - content.style.padding = "16px"; - content.style.color = "#374151"; - content.innerHTML = "

Click to edit this card...

"; - shape.appendChild(content); - } - }); - }); - document.getElementById("new-slide").addEventListener("click", () => { setPendingTool("folk-slide", { label: `Slide ${shapeCounter}` }); }); @@ -3248,19 +3458,6 @@ document.getElementById("new-prompt").addEventListener("click", () => setPendingTool("folk-prompt")); document.getElementById("new-transcription").addEventListener("click", () => setPendingTool("folk-transcription")); document.getElementById("new-video-chat").addEventListener("click", () => setPendingTool("folk-video-chat")); - document.getElementById("new-obs-note").addEventListener("click", async () => { - const data = await fetchNotesData(); - if (data.notes.length === 0) { - setPendingTool("folk-obs-note"); - return; - } - const note = pickFromList(data.notes, n => n.title || "Untitled", "Select a note"); - if (!note) return; - setPendingTool("folk-obs-note", { - title: note.title || "Untitled", - content: note.content || note.content_plain || "", - }); - }); document.getElementById("new-workflow").addEventListener("click", () => { setPendingTool("folk-workflow-block", { blockType: "trigger", @@ -3600,19 +3797,7 @@ // Arrow connection mode let connectMode = false; let connectSource = null; - const newArrowBtn = document.getElementById("new-arrow"); - - newArrowBtn.addEventListener("click", () => { - if (wbTool) setWbTool(null); // clear whiteboard tool - connectMode = !connectMode; - newArrowBtn.classList.toggle("active", connectMode); - canvas.classList.toggle("connect-mode", connectMode); - - if (!connectMode && connectSource) { - connectSource.classList.remove("connect-source"); - connectSource = null; - } - }); + const newArrowBtn = document.getElementById("tool-arrow"); // Handle shape clicks for connection mode canvas.addEventListener("click", (e) => { @@ -3647,6 +3832,7 @@ connectMode = false; newArrowBtn.classList.remove("active"); canvas.classList.remove("connect-mode"); + if (typeof syncBottomToolbar === "function") syncBottomToolbar(); } }); @@ -3666,14 +3852,10 @@ canvasContent.appendChild(wbOverlay); function setWbTool(tool) { - const prev = wbTool; wbTool = wbTool === tool ? null : tool; - // Update button active states - document.querySelectorAll("[id^='wb-']").forEach(b => b.classList.remove("active")); if (wbTool) { - document.getElementById("wb-" + wbTool)?.classList.add("active"); - canvas.style.cursor = wbTool === "eraser" ? "crosshair" : "crosshair"; + canvas.style.cursor = "crosshair"; } else { canvas.style.cursor = ""; } @@ -3681,11 +3863,71 @@ // Disable shape interaction when whiteboard tool is active canvasContent.style.pointerEvents = wbTool ? "none" : ""; wbOverlay.style.pointerEvents = wbTool ? "all" : "none"; + + syncBottomToolbar(); } - document.getElementById("wb-pencil")?.addEventListener("click", () => setWbTool("pencil")); - document.getElementById("wb-sticky")?.addEventListener("click", () => { - setWbTool(null); + // ── Bottom toolbar wiring ── + const bottomToolbar = document.getElementById("bottom-toolbar"); + const bottomToolBtns = bottomToolbar.querySelectorAll(".tool-btn"); + + function syncBottomToolbar() { + bottomToolBtns.forEach(b => b.classList.remove("active")); + + if (wbTool) { + const map = { pencil: "tool-pencil", line: "tool-line", rect: "tool-rect", circle: "tool-circle", eraser: "tool-eraser" }; + document.getElementById(map[wbTool])?.classList.add("active"); + } else if (connectMode) { + document.getElementById("tool-arrow")?.classList.add("active"); + } else if (pendingTool) { + // Check if pending tool maps to a bottom toolbar button + if (pendingTool.tagName === "folk-markdown" && pendingTool.props?.__postCreate) { + document.getElementById("tool-sticky")?.classList.add("active"); + } else if (pendingTool.tagName === "folk-markdown") { + document.getElementById("tool-text")?.classList.add("active"); + } else { + // Sidebar tool active β€” select button stays inactive + } + } else { + document.getElementById("tool-select")?.classList.add("active"); + } + } + + document.getElementById("tool-select").addEventListener("click", () => { + if (wbTool) setWbTool(null); + if (pendingTool) clearPendingTool(); + if (connectMode) { + connectMode = false; + newArrowBtn.classList.remove("active"); + canvas.classList.remove("connect-mode"); + if (connectSource) { connectSource.classList.remove("connect-source"); connectSource = null; } + } + syncBottomToolbar(); + }); + + document.getElementById("tool-pencil").addEventListener("click", () => { + if (pendingTool) clearPendingTool(); + setWbTool("pencil"); + }); + document.getElementById("tool-line").addEventListener("click", () => { + if (pendingTool) clearPendingTool(); + setWbTool("line"); + }); + document.getElementById("tool-rect").addEventListener("click", () => { + if (pendingTool) clearPendingTool(); + setWbTool("rect"); + }); + document.getElementById("tool-circle").addEventListener("click", () => { + if (pendingTool) clearPendingTool(); + setWbTool("circle"); + }); + document.getElementById("tool-eraser").addEventListener("click", () => { + if (pendingTool) clearPendingTool(); + setWbTool("eraser"); + }); + + document.getElementById("tool-sticky").addEventListener("click", () => { + if (wbTool) setWbTool(null); setPendingTool("folk-markdown", { content: "# Sticky Note\n\nClick to edit...", __postCreate: (shape) => { @@ -3696,11 +3938,27 @@ shape.style.boxShadow = "2px 2px 8px rgba(0,0,0,0.15)"; }, }); + syncBottomToolbar(); + }); + + document.getElementById("tool-text").addEventListener("click", () => { + if (wbTool) setWbTool(null); + setPendingTool("folk-markdown", { content: "# New Note\n\nStart typing..." }); + syncBottomToolbar(); + }); + + document.getElementById("tool-arrow").addEventListener("click", () => { + if (wbTool) setWbTool(null); + if (pendingTool) clearPendingTool(); + connectMode = !connectMode; + newArrowBtn.classList.toggle("active", connectMode); + canvas.classList.toggle("connect-mode", connectMode); + if (!connectMode && connectSource) { + connectSource.classList.remove("connect-source"); + connectSource = null; + } + syncBottomToolbar(); }); - document.getElementById("wb-rect")?.addEventListener("click", () => setWbTool("rect")); - document.getElementById("wb-circle")?.addEventListener("click", () => setWbTool("circle")); - document.getElementById("wb-line")?.addEventListener("click", () => setWbTool("line")); - document.getElementById("wb-eraser")?.addEventListener("click", () => setWbTool("eraser")); // Whiteboard pointer handlers on the SVG overlay wbOverlay.addEventListener("pointerdown", (e) => { @@ -4250,8 +4508,7 @@ const btn = e.target.closest("button"); if (!btn) return; // Keep open for connect, memory, group toggles, collapse, whiteboard tools - const keepOpen = ["new-arrow", "toggle-memory", "toggle-hide-forgotten", "toggle-theme", "zoom-in", "zoom-out", "reset-view", "toolbar-collapse", - "wb-pencil", "wb-rect", "wb-circle", "wb-line", "wb-eraser"]; + const keepOpen = ["toggle-memory", "toggle-hide-forgotten", "toggle-theme", "zoom-in", "zoom-out", "reset-view", "toolbar-collapse"]; if (btn.classList.contains("toolbar-group-toggle")) return; if (!keepOpen.includes(btn.id)) { toolbarEl.classList.remove("mobile-open"); @@ -4281,11 +4538,7 @@ clone.addEventListener("click", (e) => { e.stopPropagation(); btn.click(); - // Close panel after tool is selected (unless it's a whiteboard toggle) - const keepOpen = ["wb-pencil", "wb-rect", "wb-circle", "wb-line", "wb-eraser"]; - if (!keepOpen.includes(btn.id)) { - closeToolbarPanel(); - } + closeToolbarPanel(); }); toolbarPanelBody.appendChild(clone); }