Changeset 0.25.0 (#160)
This commit is contained in:
@@ -16,6 +16,11 @@
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/panels.css?v={{.Version}}">
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/surfaces.css?v={{.Version}}">
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/splash.css?v={{.Version}}">
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/pane-container.css?v={{.Version}}">
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/chat-pane.css?v={{.Version}}">
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/user-menu.css?v={{.Version}}">
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/tool-grants.css?v={{.Version}}">
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/admin-surfaces.css?v={{.Version}}">
|
||||
{{if eq .Surface "chat"}}{{template "css-chat" .}}{{end}}
|
||||
{{if eq .Surface "editor"}}{{template "css-editor" .}}{{end}}
|
||||
<style>
|
||||
@@ -74,6 +79,7 @@
|
||||
window.__SURFACE__ = '{{.Surface}}';
|
||||
window.__PAGE_DATA__ = {{.Data | toJSON}};
|
||||
window.__USER__ = {{.User | toJSON}};
|
||||
{{if .Manifest}}window.__MANIFEST__ = {{.Manifest | toJSON}};{{end}}
|
||||
</script>
|
||||
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/app-state.js?v={{.Version}}"></script>
|
||||
@@ -87,6 +93,13 @@
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-primitives-additions.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-core.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/pages.js?v={{.Version}}"></script>
|
||||
{{/* v0.25.0: Component scripts — available on all surfaces */}}
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/user-menu.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/model-selector.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/file-tree.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/code-editor.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/note-editor.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/pane-container.js?v={{.Version}}"></script>
|
||||
|
||||
{{if eq .Surface "chat"}}{{template "scripts-chat" .}}{{end}}
|
||||
{{if eq .Surface "admin"}}{{template "scripts-admin" .}}{{end}}
|
||||
|
||||
@@ -3,12 +3,27 @@
|
||||
Usage: {{template "chat-pane" dict "ID" "main"}}
|
||||
Creates mount points: {ID}ChatMessages, {ID}ChatInput, {ID}SendBtn, {ID}ModelSel
|
||||
ChatPane.create() in chat-pane.js binds to these IDs.
|
||||
|
||||
The header bar ({ID}ChatHeader) is hidden by default.
|
||||
Standalone panes (editor assist) show it for chat switching + model selection.
|
||||
*/}}
|
||||
{{define "chat-pane"}}
|
||||
<div class="chat-pane" id="{{.ID}}ChatPane">
|
||||
<div class="chat-pane-header" id="{{.ID}}ChatHeader" style="display:none;">
|
||||
<div class="chat-pane-header-left">
|
||||
<select class="chat-pane-chat-select" id="{{.ID}}ChatSelect" title="Switch chat">
|
||||
<option value="">New conversation</option>
|
||||
</select>
|
||||
<button class="chat-pane-new-btn" id="{{.ID}}ChatNewBtn" title="New chat">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="chat-pane-header-right">
|
||||
<div class="chat-pane-model-sel" id="{{.ID}}ModelSel"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-pane-messages" id="{{.ID}}ChatMessages"></div>
|
||||
<div class="chat-pane-input-bar">
|
||||
<div class="chat-pane-model-sel" id="{{.ID}}ModelSel"></div>
|
||||
<div class="chat-pane-input-wrap">
|
||||
<div class="chat-pane-input" id="{{.ID}}ChatInput"></div>
|
||||
<button class="chat-pane-send" id="{{.ID}}SendBtn" title="Send">
|
||||
|
||||
30
server/pages/templates/components/code-editor.html
Normal file
30
server/pages/templates/components/code-editor.html
Normal file
@@ -0,0 +1,30 @@
|
||||
{{/*
|
||||
Code Editor Component — tabbed CM6 editor with tab bar, save, and status bar.
|
||||
Usage: {{template "code-editor" dict "ID" "editor"}}
|
||||
Creates mount points: {ID}EditorTabs, {ID}EditorContent, {ID}EditorWelcome,
|
||||
{ID}EditorStatus, {ID}EditorStatusFile, {ID}EditorStatusLang, {ID}EditorStatusBranch.
|
||||
CodeEditor.create() in code-editor.js binds to these IDs.
|
||||
*/}}
|
||||
{{define "code-editor"}}
|
||||
<div class="code-editor" id="{{.ID}}CodeEditor">
|
||||
<div class="code-editor-tabs" id="{{.ID}}EditorTabs">
|
||||
{{/* Populated by CodeEditor.openFile() */}}
|
||||
</div>
|
||||
<div class="code-editor-content" id="{{.ID}}EditorContent">
|
||||
<div class="code-editor-welcome" id="{{.ID}}EditorWelcome">
|
||||
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-3);">
|
||||
<div style="text-align:center;">
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="opacity:0.12;display:inline-block;margin-bottom:10px;"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>
|
||||
<p style="margin:0;font-size:14px;">Open a file to start editing</p>
|
||||
<p style="margin:6px 0 0;font-size:12px;">Select from the file tree or Ctrl+P</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="code-editor-statusbar" id="{{.ID}}EditorStatus">
|
||||
<span id="{{.ID}}EditorStatusFile"></span>
|
||||
<span id="{{.ID}}EditorStatusLang"></span>
|
||||
<span id="{{.ID}}EditorStatusBranch"></span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
20
server/pages/templates/components/file-tree.html
Normal file
20
server/pages/templates/components/file-tree.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{{/*
|
||||
File Tree Component — workspace file browser with directory expand/collapse.
|
||||
Usage: {{template "file-tree" dict "ID" "editor"}}
|
||||
Creates mount points: {ID}FileTree, {ID}TreeHeader, {ID}TreeItems, {ID}TreeNewFile.
|
||||
FileTree.create() in file-tree.js binds to these IDs.
|
||||
*/}}
|
||||
{{define "file-tree"}}
|
||||
<div class="file-tree" id="{{.ID}}FileTree">
|
||||
<div class="file-tree-header" id="{{.ID}}TreeHeader">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||||
<span class="file-tree-title">Files</span>
|
||||
<button class="icon-btn" id="{{.ID}}TreeNewFile" title="New file">
|
||||
<svg width="12" height="12" 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>
|
||||
<div class="file-tree-items" id="{{.ID}}TreeItems">
|
||||
{{/* Populated by FileTree.refresh() */}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
19
server/pages/templates/components/model-selector.html
Normal file
19
server/pages/templates/components/model-selector.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{/*
|
||||
Model Selector Component — chat model + persona dropdown with capability badges.
|
||||
Usage: {{template "model-selector" dict "ID" ""}}
|
||||
With empty ID, generates existing IDs: modelDropdown, modelDropdownBtn, etc.
|
||||
With prefix: {{template "model-selector" dict "ID" "editor"}} → editorModelDropdown, etc.
|
||||
ModelSelector.create() in model-selector.js binds to these IDs.
|
||||
*/}}
|
||||
{{define "model-selector"}}
|
||||
<div id="{{.ID}}modelDropdown" class="model-dropdown">
|
||||
<button id="{{.ID}}modelDropdownBtn" class="model-dropdown-btn">
|
||||
<span id="{{.ID}}modelDropdownLabel" class="model-dropdown-label">Select model…</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<div id="{{.ID}}modelDropdownMenu" class="model-dropdown-menu">
|
||||
{{/* Populated by ModelSelector.update() */}}
|
||||
</div>
|
||||
</div>
|
||||
<span id="{{.ID}}modelCaps" class="model-caps"></span>
|
||||
{{end}}
|
||||
86
server/pages/templates/components/note-editor.html
Normal file
86
server/pages/templates/components/note-editor.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{{/*
|
||||
Note Editor Component — notes list + editor/reader with folders, search, backlinks.
|
||||
Usage: {{template "note-editor" dict "ID" "main"}}
|
||||
Creates mount points for list view, editor view, and graph view.
|
||||
NoteEditor.create() in note-editor.js binds to these IDs.
|
||||
|
||||
When used in the notes surface (standalone), notes.js wires event listeners
|
||||
and integrates with PanelRegistry. When used in the editor surface (tabbed pane),
|
||||
NoteEditor.create() provides independent lifecycle and event wiring.
|
||||
*/}}
|
||||
{{define "note-editor"}}
|
||||
<div class="note-editor" id="{{.ID}}NoteEditor">
|
||||
{{/* ── List View ───────────────────────── */}}
|
||||
<div class="note-editor-list-view" id="{{.ID}}NotesListView">
|
||||
<div class="notes-toolbar">
|
||||
<button id="{{.ID}}NotesNewBtn" class="btn-small" title="New note">+ New</button>
|
||||
<button id="{{.ID}}NotesTodayBtn" class="btn-small" title="Open daily note">📅 Today</button>
|
||||
<button id="{{.ID}}NotesGraphBtn" class="btn-small" title="Note graph">🕸 Graph</button>
|
||||
<button id="{{.ID}}NotesSelectModeBtn" class="btn-small" title="Select mode">☑ Select</button>
|
||||
</div>
|
||||
<div class="notes-search-row">
|
||||
<input type="text" id="{{.ID}}NotesSearchInput" class="notes-search-input" placeholder="Search notes…" autocomplete="off">
|
||||
</div>
|
||||
<div class="notes-filter-row">
|
||||
<select id="{{.ID}}NotesFolderFilter" class="notes-filter-select"><option value="">All folders</option></select>
|
||||
<select id="{{.ID}}NotesSortSelect" class="notes-filter-select">
|
||||
<option value="updated_desc">Last updated</option>
|
||||
<option value="created_desc">Created</option>
|
||||
<option value="alpha">Alphabetical</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="{{.ID}}NotesList" class="notes-list"></div>
|
||||
<div id="{{.ID}}NotesSelectionBar" class="notes-selection-bar" style="display:none;">
|
||||
<label class="notes-select-all"><input type="checkbox" id="{{.ID}}NotesSelectAll"> All</label>
|
||||
<span id="{{.ID}}NotesSelectedCount">0</span> selected
|
||||
<button id="{{.ID}}NotesDeleteSelectedBtn" class="btn-small btn-danger">Delete</button>
|
||||
<button id="{{.ID}}NotesCancelSelectBtn" class="btn-small">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* ── Editor View ─────────────────────── */}}
|
||||
<div class="note-editor-editor-view" id="{{.ID}}NotesEditorView" style="display:none;">
|
||||
<div class="notes-editor-header">
|
||||
<button id="{{.ID}}NotesBackBtn" class="btn-small" title="Back to list">← Back</button>
|
||||
<div style="flex:1"></div>
|
||||
<button id="{{.ID}}NoteEditBtn" class="btn-small" style="display:none;">Edit</button>
|
||||
<button id="{{.ID}}NotePreviewBtn" class="btn-small" style="display:none;">Preview</button>
|
||||
<button id="{{.ID}}NoteCancelEditBtn" class="btn-small" style="display:none;">Cancel</button>
|
||||
<button id="{{.ID}}NoteDeleteBtn" class="btn-small btn-danger" style="display:none;">Delete</button>
|
||||
<button id="{{.ID}}NoteSaveBtn" class="btn-small btn-primary">Save</button>
|
||||
</div>
|
||||
|
||||
{{/* Edit mode */}}
|
||||
<div id="{{.ID}}NoteEditMode" class="notes-editor">
|
||||
<input type="text" id="{{.ID}}NoteEditorTitle" class="notes-title-input" placeholder="Note title">
|
||||
<div class="notes-meta-row">
|
||||
<input type="text" id="{{.ID}}NoteEditorFolder" class="notes-folder-input" placeholder="Folder (e.g. work/project)">
|
||||
<input type="text" id="{{.ID}}NoteEditorTags" class="notes-tags-input" placeholder="Tags (comma-separated)">
|
||||
</div>
|
||||
<div id="{{.ID}}NoteEditorContentContainer" class="notes-content-container">
|
||||
<textarea id="{{.ID}}NoteEditorContent" rows="12" class="notes-content-input" placeholder="Write your note in markdown…"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Read mode */}}
|
||||
<div id="{{.ID}}NoteReadMode" style="display:none;">
|
||||
<div class="notes-read-view">
|
||||
<h3 id="{{.ID}}NoteReadTitle" class="note-read-title"></h3>
|
||||
<div id="{{.ID}}NoteReadMeta" class="note-read-meta"></div>
|
||||
<div id="{{.ID}}NoteReadContent" class="note-read-content msg-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Backlinks */}}
|
||||
<div id="{{.ID}}NoteBacklinks" class="note-backlinks" style="display:none;">
|
||||
<div class="note-backlinks-header">
|
||||
Backlinks <span id="{{.ID}}NoteBacklinksCount" class="badge">0</span>
|
||||
</div>
|
||||
<div id="{{.ID}}NoteBacklinksList" class="note-backlinks-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* ── Graph View ──────────────────────── */}}
|
||||
<div class="note-editor-graph-view" id="{{.ID}}NotesGraphView" style="display:none;"></div>
|
||||
</div>
|
||||
{{end}}
|
||||
40
server/pages/templates/components/user-menu.html
Normal file
40
server/pages/templates/components/user-menu.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{{/*
|
||||
User Menu Component — reusable user avatar + flyout dropdown.
|
||||
Usage: {{template "user-menu" dict "ID" ""}}
|
||||
With empty ID, generates existing IDs: userMenuBtn, userAvatar, etc.
|
||||
With prefix: {{template "user-menu" dict "ID" "editor"}} → editorUserMenuBtn, etc.
|
||||
UserMenu.create() in user-menu.js binds to these IDs.
|
||||
*/}}
|
||||
{{define "user-menu"}}
|
||||
<div class="user-menu-wrap" id="{{.ID}}userMenuWrap">
|
||||
<button id="{{.ID}}userMenuBtn" class="user-btn">
|
||||
<div id="{{.ID}}userAvatar" class="user-avatar">
|
||||
<span id="{{.ID}}avatarLetter">?</span>
|
||||
</div>
|
||||
<span id="{{.ID}}userName" class="sb-label">User</span>
|
||||
</button>
|
||||
<div id="{{.ID}}userFlyout" class="user-flyout">
|
||||
<button id="{{.ID}}menuSettings" class="flyout-item">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.32 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
<button id="{{.ID}}menuAdmin" class="flyout-item" style="display:none;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||
<span>Admin</span>
|
||||
</button>
|
||||
<button id="{{.ID}}menuTeamAdmin" class="flyout-item" style="display:none;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||
<span>Team Admin</span>
|
||||
</button>
|
||||
<button id="{{.ID}}menuDebug" class="flyout-item" style="display:none;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"/><path d="M12 6V12L16 14"/></svg>
|
||||
<span>Debug Log</span>
|
||||
</button>
|
||||
<hr class="flyout-divider">
|
||||
<button id="{{.ID}}menuSignout" class="flyout-item flyout-danger">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
||||
<span>Sign Out</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -32,7 +32,7 @@
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
Routing
|
||||
</a>
|
||||
<a href="{{$base}}/admin/settings" class="admin-cat-btn{{if eq $section "settings"}} active{{else if eq $section "storage"}} active{{else if eq $section "extensions"}} active{{end}}" data-cat="system">
|
||||
<a href="{{$base}}/admin/settings" class="admin-cat-btn{{if eq $section "settings"}} active{{else if eq $section "storage"}} active{{else if eq $section "extensions"}} active{{else if eq $section "channels"}} active{{else if eq $section "surfaces"}} active{{end}}" data-cat="system">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09"/></svg>
|
||||
System
|
||||
</a>
|
||||
@@ -241,4 +241,5 @@
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/memory-ui.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notifications.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-scaffold.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-surfaces.js?v={{.Version}}"></script>
|
||||
{{end}}
|
||||
|
||||
@@ -177,45 +177,20 @@ window.addEventListener('unhandledrejection', function(e) {
|
||||
|
||||
{{/* User / bottom */}}
|
||||
<div class="sidebar-bottom">
|
||||
{{if .SurfaceEnabled "notes"}}
|
||||
<button id="notesBtn" class="sb-btn sidebar-notes-btn">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||
<span class="sb-label">Notes</span>
|
||||
</button>
|
||||
{{/* Editor shortcut */}}
|
||||
{{end}}
|
||||
{{if .SurfaceEnabled "editor"}}
|
||||
<a href="{{.BasePath}}/editor" class="sb-btn sidebar-editor-btn" title="Editor">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
||||
<span class="sb-label">Editor</span>
|
||||
</a>
|
||||
{{end}}
|
||||
<div class="sidebar-bottom-divider"></div>
|
||||
<button id="userMenuBtn" class="user-btn">
|
||||
<div id="userAvatar" class="user-avatar">
|
||||
<span id="avatarLetter">?</span>
|
||||
</div>
|
||||
<span id="userName" class="sb-label">User</span>
|
||||
</button>
|
||||
<div id="userFlyout" class="user-flyout">
|
||||
<button id="menuSettings" class="flyout-item">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.32 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
<button id="menuAdmin" class="flyout-item" style="display:none;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||
<span>Admin</span>
|
||||
</button>
|
||||
<button id="menuTeamAdmin" class="flyout-item" style="display:none;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||
<span>Team Admin</span>
|
||||
</button>
|
||||
<button id="menuDebug" class="flyout-item" style="display:none;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"/><path d="M12 6V12L16 14"/></svg>
|
||||
<span>Debug Log</span>
|
||||
</button>
|
||||
<hr class="flyout-divider">
|
||||
<button id="menuSignout" class="flyout-item flyout-danger">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
||||
<span>Sign Out</span>
|
||||
</button>
|
||||
</div>
|
||||
{{template "user-menu" dict "ID" ""}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -233,16 +208,7 @@ window.addEventListener('unhandledrejection', function(e) {
|
||||
|
||||
{{/* Chat header: model selector + caps + tokens + panel + notif */}}
|
||||
<div class="chat-header">
|
||||
<div id="modelDropdown" class="model-dropdown">
|
||||
<button id="modelDropdownBtn" class="model-dropdown-btn">
|
||||
<span id="modelDropdownLabel" class="model-dropdown-label">Select model…</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<div id="modelDropdownMenu" class="model-dropdown-menu">
|
||||
{{/* Populated by UI.updateModelSelector() */}}
|
||||
</div>
|
||||
</div>
|
||||
<span id="modelCaps" class="model-caps"></span>
|
||||
{{template "model-selector" dict "ID" ""}}
|
||||
<div class="chat-header-right">
|
||||
<span id="chatTokenCount" class="token-count" title="Estimated token count"></span>
|
||||
<span id="summarizedHistory" class="summarized-badge" style="display:none;" title="Earlier messages are summarized">📋 Summarized</span>
|
||||
|
||||
@@ -1,127 +1,104 @@
|
||||
{{/*
|
||||
Editor surface — matches switchboard-prototype-editor.jsx.
|
||||
IDE-like layout: topbar + file tree + tabs + code + chat pane.
|
||||
editor-mode.js builds CodeMirror, file tree, etc.
|
||||
Editor surface — v0.25.0 rebuild.
|
||||
Three-pane layout: files | code-editor | <chat, notes>
|
||||
Uses PaneContainer for resizable splits and tabbed right pane.
|
||||
|
||||
Components are SERVER-RENDERED via Go template partials into hidden
|
||||
containers (#editorComponents). The boot script moves them into
|
||||
pane slots created by PaneContainer. This ensures full DOM parity
|
||||
with the chat surface — same buttons, same features, same behavior.
|
||||
*/}}
|
||||
|
||||
{{define "surface-editor"}}
|
||||
<div class="surface-editor">
|
||||
<div class="surface-editor" id="editorSurface">
|
||||
|
||||
{{/* Top Bar */}}
|
||||
<div class="editor-topbar">
|
||||
<a href="{{.BasePath}}/" class="editor-topbar-back">
|
||||
{{/* ── Top Bar ─────────────────────────── */}}
|
||||
<div class="editor-topbar" id="editorTopbar">
|
||||
<a href="{{.BasePath}}/" class="editor-topbar-back" title="Back to chat">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
|
||||
Back
|
||||
</a>
|
||||
<div style="width:1px;height:18px;background:var(--border);"></div>
|
||||
<div class="editor-topbar-sep"></div>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--accent);"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
||||
<span style="font-size:13px;font-weight:600;" id="editorWorkspaceName">
|
||||
{{if .Data}}{{.Data.WorkspaceName}}{{else}}Editor{{end}}
|
||||
</span>
|
||||
<div style="display:flex;align-items:center;gap:4px;background:var(--purple-dim);padding:2px 8px;border-radius:4px;">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" stroke-width="2"><circle cx="12" cy="18" r="3"/><circle cx="12" cy="6" r="3"/><line x1="12" y1="9" x2="12" y2="15"/></svg>
|
||||
<span style="font-size:11px;font-weight:600;color:var(--purple);font-family:var(--mono);">main</span>
|
||||
</div>
|
||||
<div style="flex:1;"></div>
|
||||
<button class="icon-btn" id="editorNewFileBtn" title="New File">
|
||||
<svg width="14" height="14" 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>
|
||||
<button class="icon-btn" id="editorRefreshBtn" title="Refresh">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="editorSearchBtn" title="Search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
</button>
|
||||
<div style="width:1px;height:18px;background:var(--border);"></div>
|
||||
<button class="icon-btn" id="editorToggleTree" title="Toggle file tree">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="editorToggleChat" title="Toggle chat">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="15" y1="3" x2="15" y2="21"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{/* Body: tree + editor + chat */}}
|
||||
<div class="editor-body" id="editorBody">
|
||||
{{/* File Tree */}}
|
||||
<div class="editor-tree" id="editorFileTree">
|
||||
<div class="editor-tree-header">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||||
<span style="font-size:11px;font-weight:600;color:var(--text-2);text-transform:uppercase;letter-spacing:0.4px;flex:1;">Files</span>
|
||||
<button class="icon-btn" id="treeNewFileBtn" title="New file">
|
||||
{{/* Workspace selector dropdown */}}
|
||||
<div class="editor-ws-selector" id="editorWsSelector">
|
||||
<button class="editor-ws-selector-btn" id="editorWsSelectorBtn">
|
||||
<span id="editorWorkspaceName">{{if .Data}}{{.Data.WorkspaceName}}{{else}}Editor{{end}}</span>
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
<div class="editor-ws-dropdown" id="editorWsDropdown">
|
||||
<div id="editorWsList" class="editor-ws-list">
|
||||
{{/* Populated by JS */}}
|
||||
</div>
|
||||
<div class="editor-ws-dropdown-divider"></div>
|
||||
<button class="editor-ws-dropdown-item editor-ws-new" id="editorWsNewBtn">
|
||||
<svg width="12" height="12" 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>
|
||||
New Workspace
|
||||
</button>
|
||||
</div>
|
||||
<div class="editor-tree-files" id="editorTreeFiles">
|
||||
{{/* Populated by editor-mode.js */}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Main Editor Area */}}
|
||||
<div class="editor-main" id="editorMain">
|
||||
{{/* Tabs */}}
|
||||
<div class="editor-tabs" id="editorTabs">
|
||||
{{/* Populated by editor-mode.js */}}
|
||||
</div>
|
||||
|
||||
{{/* Code Content */}}
|
||||
<div class="editor-content" id="editorContent">
|
||||
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-3);">
|
||||
<div style="text-align:center;">
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="opacity:0.12;display:inline-block;margin-bottom:10px;"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>
|
||||
<p style="margin:0;font-size:14px;">Open a file to start editing</p>
|
||||
<p style="margin:6px 0 0;font-size:12px;">Select from the file tree or Ctrl+P</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Status Bar */}}
|
||||
<div class="editor-statusbar" id="editorStatusbar">
|
||||
<span>Ready</span>
|
||||
</div>
|
||||
<div class="editor-topbar-branch" id="editorBranchBadge" style="display:none;">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" stroke-width="2"><circle cx="12" cy="18" r="3"/><circle cx="12" cy="6" r="3"/><line x1="12" y1="9" x2="12" y2="15"/></svg>
|
||||
<span id="editorBranchName" class="editor-topbar-branch-text">main</span>
|
||||
</div>
|
||||
<div style="flex:1;"></div>
|
||||
<button class="icon-btn" id="editorRefreshBtn" title="Refresh files">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
||||
</button>
|
||||
{{template "user-menu" dict "ID" ""}}
|
||||
</div>
|
||||
|
||||
{{/* Chat Pane (resizable) */}}
|
||||
<div id="editorSplitHandle" style="width:5px;background:var(--border);cursor:col-resize;flex-shrink:0;display:none;"></div>
|
||||
<div class="editor-chat" id="editorChatPane" style="display:none;">
|
||||
<div class="editor-chat-header">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<span style="font-size:12px;font-weight:600;flex:1;">AI Chat</span>
|
||||
<span class="badge badge-accent" id="editorChatModel">claude-sonnet-4-5</span>
|
||||
{{/* ── Pane Body ───────────────────────── */}}
|
||||
<div class="editor-body" id="editorBody"></div>
|
||||
|
||||
{{/* ── Bootstrap (no workspace) ────────── */}}
|
||||
<div class="editor-bootstrap" id="editorBootstrap" style="display:none;">
|
||||
<div class="editor-bootstrap-card">
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="1.5" style="opacity:0.6;margin-bottom:12px;">
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
|
||||
</svg>
|
||||
<h3 style="margin:0 0 16px;font-size:16px;">Open a Workspace</h3>
|
||||
<div id="editorBootstrapList" style="margin-bottom:16px;">
|
||||
<div style="font-size:12px;color:var(--text-3);">Loading workspaces…</div>
|
||||
</div>
|
||||
<div class="editor-chat-messages" id="editorChatMessages">
|
||||
{{/* Populated by editor-mode.js */}}
|
||||
</div>
|
||||
<div class="editor-chat-input">
|
||||
<div style="display:flex;gap:8px;align-items:flex-end;">
|
||||
<textarea id="editorChatInput" placeholder="Ask about this code…" rows="2"
|
||||
style="flex:1;background:var(--input-bg,var(--bg));border:1px solid var(--border);color:var(--text);padding:8px 10px;border-radius:8px;font-size:12px;font-family:inherit;outline:none;resize:none;"></textarea>
|
||||
<button id="editorChatSendBtn" style="background:var(--accent);color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;">Send</button>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;margin-top:6px;font-size:10px;color:var(--text-3);">
|
||||
<span id="editorChatContext">Context: none</span>
|
||||
<span>·</span>
|
||||
<span>Cmd+L to focus</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
|
||||
<div style="flex:1;height:1px;background:var(--border);"></div>
|
||||
<span style="font-size:11px;color:var(--text-3);text-transform:uppercase;">or create new</span>
|
||||
<div style="flex:1;height:1px;background:var(--border);"></div>
|
||||
</div>
|
||||
<input type="text" id="editorBootstrapName" class="editor-bootstrap-input" placeholder="Workspace name" value="workspace">
|
||||
<button id="editorBootstrapBtn" class="editor-bootstrap-btn">Create Workspace</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* ── Server-Rendered Components ──────── */}}
|
||||
{{/* Hidden until moved into pane slots by editor-surface.js.
|
||||
Full DOM from Go template partials = feature parity with chat surface. */}}
|
||||
<div id="editorComponents" style="display:none;">
|
||||
{{template "file-tree" dict "ID" "ed"}}
|
||||
{{template "code-editor" dict "ID" "ed"}}
|
||||
{{template "chat-pane" dict "ID" "ed"}}
|
||||
{{template "note-editor" dict "ID" "edNotes"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toastContainer" class="toast-container"></div>
|
||||
{{end}}
|
||||
|
||||
{{/* CSS: loaded via base.html (variables, layout, primitives, modals, chat, panels, surfaces, splash) */}}
|
||||
{{/* ── CSS ─────────────────────────────────── */}}
|
||||
{{define "css-editor"}}
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/editor-mode.css?v={{.Version}}">
|
||||
<link rel="stylesheet" href="{{.BasePath}}/css/editor-surface.css?v={{.Version}}">
|
||||
{{end}}
|
||||
|
||||
{{/* Scripts */}}
|
||||
{{/* ── Scripts ─────────────────────────────── */}}
|
||||
{{define "scripts-editor"}}
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/vendor/marked.min.js"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/vendor/purify.min.js"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/vendor/codemirror/codemirror.bundle.js?v={{.Version}}" onerror="console.warn('[CM6] Bundle not available')"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-format.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/editor-mode.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/chat-pane.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/editor-surface.js?v={{.Version}}"></script>
|
||||
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/app.js?v={{.Version}}"></script>
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user