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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-03 14:26:04 -08:00
parent 63ac2a268e
commit d988e0b112
2 changed files with 336 additions and 85 deletions

View File

@ -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"] },

View File

@ -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 @@
</div>
<div id="toolbar">
<div class="toolbar-group">
<button class="toolbar-group-toggle">✏️ Draw</button>
<div class="toolbar-dropdown">
<button id="wb-pencil" title="Freehand Pencil">✏️ Pencil</button>
<button id="wb-sticky" title="Sticky Note">📌 Sticky Note</button>
<button id="wb-rect" title="Rectangle">▢ Rectangle</button>
<button id="wb-circle" title="Circle">○ Circle</button>
<button id="wb-line" title="Line"> Line</button>
<button id="wb-eraser" title="Eraser">🧹 Eraser</button>
</div>
</div>
<div class="toolbar-group">
<button class="toolbar-group-toggle">📝 Note</button>
<div class="toolbar-dropdown">
<button id="new-markdown" title="New Note">📝 Note</button>
<button id="new-wrapper" title="New Card">🗂️ Card</button>
<button id="new-slide" title="New Slide">🎞️ Slide</button>
<button id="new-obs-note" title="New Rich Note">📓 Rich Note</button>
<button id="new-chat" title="New Chat">💬 Chat</button>
<button id="embed-notes" title="Embed rNotes">📝 rNotes</button>
<button id="embed-books" title="Embed rBooks">📚 rBooks</button>
<button id="embed-pubs" title="Embed rPubs">📖 rPubs</button>
<button id="embed-forum" title="Embed rForum">💬 rForum</button>
</div>
</div>
@ -1584,6 +1739,7 @@
<button id="new-freecad" title="New FreeCAD">📐 FreeCAD</button>
<button id="new-kicad" title="New KiCAD PCB">🔌 KiCAD PCB</button>
<button id="embed-swag" title="Embed rSwag">🎨 rSwag</button>
<button id="embed-pubs" title="Embed rPubs">📖 rPubs</button>
</div>
</div>
@ -1606,6 +1762,9 @@
<button id="new-calendar" title="New Calendar">📅 Calendar</button>
<button id="new-map" title="New Map">🗺️ Map</button>
<button id="new-social-post" title="New Post">📱 Social Post</button>
<button id="embed-notes" title="Embed rNotes">📝 rNotes</button>
<button id="embed-books" title="Embed rBooks">📚 rBooks</button>
<button id="embed-forum" title="Embed rForum">💬 rForum</button>
<button id="embed-files" title="Embed rFiles">📁 rFiles</button>
<button id="embed-work" title="Embed rWork">📋 rWork</button>
<button id="embed-inbox" title="Embed rInbox">📧 rInbox</button>
@ -1650,7 +1809,6 @@
<span class="toolbar-sep"></span>
<button id="new-arrow" title="Connect rSpaces">↗️ Connect</button>
<button id="new-feed" title="New Feed from another layer">🔄 Feed</button>
<button id="toggle-memory" title="Forgotten rSpaces">💭 Memory</button>
<button id="toggle-hide-forgotten" title="Hide forgotten items">👁 Hide Faded</button>
@ -1675,6 +1833,48 @@
<div id="toolbar-panel-body"></div>
</div>
<div id="bottom-toolbar">
<button class="tool-btn active" id="tool-select" title="Select (V)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/><path d="M13 13l6 6"/></svg>
</button>
<span class="tool-sep"></span>
<button class="tool-btn" id="tool-pencil" title="Draw (P)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 3a2.83 2.83 0 114 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg>
</button>
<button class="tool-btn" id="tool-line" title="Line (L)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/></svg>
</button>
<button class="tool-btn" id="tool-rect" title="Rectangle (R)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>
</button>
<button class="tool-btn" id="tool-circle" title="Circle (C)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/></svg>
</button>
<span class="tool-sep"></span>
<button class="tool-btn" id="tool-sticky" title="Sticky Note (S)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15.5 3H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V8.5L15.5 3z"/><polyline points="14 2 14 8 21 8"/></svg>
</button>
<button class="tool-btn" id="tool-text" title="Note (T)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 7 4 4 20 4 20 7"/><line x1="12" y1="4" x2="12" y2="20"/><line x1="8" y1="20" x2="16" y2="20"/></svg>
</button>
<button class="tool-btn" id="tool-arrow" title="Connect (A)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/><polyline points="12 5 19 5 19 12"/></svg>
</button>
<span class="tool-sep"></span>
<button class="tool-btn" id="tool-eraser" title="Eraser (E)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 20H7L3 16a1 1 0 010-1.4l9.6-9.6a1 1 0 011.4 0l7 7a1 1 0 010 1.4L15 20"/><line x1="18" y1="13" x2="11" y2="6"/></svg>
</button>
<span class="tool-sep"></span>
<div id="recent-tools"></div>
<span class="tool-sep" id="recent-sep" style="display:none"></span>
<div class="tool-plus-wrap">
<button class="tool-btn" id="tool-plus" title="Add favorite tools">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<div id="tool-plus-menu"></div>
</div>
</div>
<div id="memory-panel">
<div id="memory-panel-header">
<h3>💭 Memory</h3>
@ -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 = "<p>Click to edit this card...</p>";
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);
}