Changeset 0.21.5 (#91)

This commit is contained in:
2026-03-01 20:35:10 +00:00
parent d67cfd37c2
commit aadba77887
15 changed files with 1499 additions and 14 deletions

View File

@@ -240,6 +240,12 @@ function showChatContextMenu(e, chatId) {
<span class="ctx-icon">+</span> New project…
</button>`;
// Workspace override (v0.21.5)
items += '<div class="ctx-divider"></div>';
items += `<button class="ctx-item" onclick="showWorkspacePicker('${chatId}');dismissChatContextMenu()">
<span class="ctx-icon">📁</span> Set workspace…
</button>`;
menu.innerHTML = items;
// Position near cursor
@@ -392,6 +398,16 @@ function _registerProjectPanel() {
<label class="project-panel-label">Notes</label>
<div id="projectNoteList" class="project-panel-list"></div>
</div>
<div class="project-panel-section">
<label class="project-panel-label">Workspace</label>
<div class="project-panel-ws-row" id="projectWsRow">
<select id="projectWsSelect" class="project-panel-select">
<option value="">None</option>
</select>
<button class="btn-small" id="projectWsCreate" title="Create workspace">+ New</button>
</div>
<div class="project-panel-hint">Workspace files are available to AI in all project chats. Enables the Editor surface.</div>
</div>
<div class="project-panel-section project-panel-footer">
<label class="project-panel-checkbox">
<input type="checkbox" id="projectArchiveToggle">
@@ -413,6 +429,10 @@ function _registerProjectPanel() {
// Wire archive toggle (v0.19.2)
el.querySelector('#projectArchiveToggle').addEventListener('change', _toggleProjectArchive);
// Wire workspace select + create (v0.21.5)
el.querySelector('#projectWsSelect').addEventListener('change', _saveProjectWorkspace);
el.querySelector('#projectWsCreate').addEventListener('click', _createProjectWorkspace);
PanelRegistry.register('project', {
element: el,
label: 'Project',
@@ -457,6 +477,9 @@ async function _loadProjectPanel(projectId) {
// Load notes
await _renderProjectNotes(projectId);
// Load workspace picker (v0.21.5)
await _renderProjectWorkspace(projectId);
}
async function _saveProjectPrompt() {
@@ -643,6 +666,143 @@ async function _toggleProjectArchive() {
}
}
// ── Workspace picker (v0.21.5) ───────────────
async function _renderProjectWorkspace(projectId) {
const sel = document.getElementById('projectWsSelect');
if (!sel) return;
// Fetch current project state + available workspaces in parallel
const [proj, workspaces] = await Promise.all([
API.getProject(projectId).catch(() => null),
API.listWorkspaces().catch(() => []),
]);
const currentWsId = proj?.workspace_id || '';
const wsList = Array.isArray(workspaces) ? workspaces : (workspaces?.data || []);
sel.innerHTML = '<option value="">None</option>';
for (const ws of wsList) {
const opt = document.createElement('option');
opt.value = ws.id;
opt.textContent = ws.name || ws.id.slice(0, 8);
if (ws.id === currentWsId) opt.selected = true;
sel.appendChild(opt);
}
}
async function _saveProjectWorkspace() {
if (!_projectPanelId) return;
const wsId = document.getElementById('projectWsSelect').value;
try {
await API.updateProject(_projectPanelId, { workspace_id: wsId || null });
UI.toast(wsId ? 'Workspace linked' : 'Workspace unlinked', 'success');
// Trigger editor surface check
if (typeof EditorMode !== 'undefined') EditorMode.check();
} catch (e) {
UI.toast('Failed to update workspace', 'error');
}
}
async function _createProjectWorkspace() {
if (!_projectPanelId) return;
const proj = App.projects.find(p => p.id === _projectPanelId);
const name = prompt('Workspace name:', (proj?.name || 'Project') + ' Workspace');
if (!name || !name.trim()) return;
try {
const ws = await API.createWorkspace({
name: name.trim(),
owner_type: 'user',
owner_id: API.user?.id || '',
});
// Auto-bind to project
await API.updateProject(_projectPanelId, { workspace_id: ws.id });
UI.toast('Workspace created and linked', 'success');
// Refresh the picker
await _renderProjectWorkspace(_projectPanelId);
// Trigger editor surface check
if (typeof EditorMode !== 'undefined') EditorMode.check();
} catch (e) {
UI.toast('Failed to create workspace: ' + (e.message || e), 'error');
}
}
// ── Channel workspace picker (v0.21.5) ───────
async function showWorkspacePicker(chatId) {
let workspaces = [];
try {
const resp = await API.listWorkspaces();
workspaces = Array.isArray(resp) ? resp : (resp?.data || []);
} catch (_) {}
// Get current channel workspace
let currentWsId = '';
try {
const ch = await API.getChannel(chatId);
currentWsId = ch.workspace_id || '';
} catch (_) {}
// Build options
const options = [{ value: '', label: 'None (inherit from project)' }];
for (const ws of workspaces) {
options.push({ value: ws.id, label: ws.name || ws.id.slice(0, 8) });
}
options.push({ value: '__new__', label: '+ Create new workspace…' });
// Use simple select via popup
const overlay = document.createElement('div');
overlay.className = 'editor-quick-open'; // reuse overlay style
overlay.innerHTML = `
<div class="editor-qo-dialog" style="width:340px">
<div style="padding:10px 14px; font-weight:600; border-bottom:1px solid var(--border)">Set Workspace</div>
<div class="editor-qo-results" id="wsPickerList"></div>
</div>`;
const list = overlay.querySelector('#wsPickerList');
for (const opt of options) {
const row = document.createElement('div');
row.className = 'editor-qo-row' + (opt.value === currentWsId ? ' active' : '');
row.style.fontWeight = opt.value === currentWsId ? '600' : '';
row.textContent = opt.label;
row.addEventListener('click', async () => {
overlay.remove();
if (opt.value === '__new__') {
const name = prompt('Workspace name:');
if (!name || !name.trim()) return;
try {
const ws = await API.createWorkspace({
name: name.trim(),
owner_type: 'user',
owner_id: API.user?.id || '',
});
await API.updateChannel(chatId, { workspace_id: ws.id });
UI.toast('Workspace created and linked', 'success');
if (typeof EditorMode !== 'undefined') EditorMode.check();
} catch (e) {
UI.toast('Failed: ' + (e.message || e), 'error');
}
} else {
try {
await API.updateChannel(chatId, { workspace_id: opt.value || '' });
UI.toast(opt.value ? 'Workspace linked' : 'Workspace unlinked', 'success');
if (typeof EditorMode !== 'undefined') EditorMode.check();
} catch (e) {
UI.toast('Failed: ' + (e.message || e), 'error');
}
}
});
list.appendChild(row);
}
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.remove();
});
document.body.appendChild(overlay);
}
// ═══════════════════════════════════════════
// CHANNEL REORDER WITHIN PROJECT (v0.19.2)
// ═══════════════════════════════════════════