Unify all New Page / New Folder buttons across the app
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

- Add global window.FlowDeck namespace with createPage() and showCreateFolderModal()
- createPage() creates a Notion-style page via /board/api/pages (same as sidebar bottom-right button)
- showCreateFolderModal() opens a global themed modal (dark/light) instead of browser prompt()
- All New Page buttons now call window.FlowDeck.createPage()
- All New Folder buttons now call window.FlowDeck.showCreateFolderModal()
- Global New Folder modal uses existing .flowdeck-modal CSS classes for theme consistency
- Updated Ctrl+N keyboard shortcut to use unified createPage()
This commit is contained in:
2026-07-19 21:40:39 -04:00
parent 74883688ef
commit 9100da74f3
4 changed files with 101 additions and 20 deletions
+89 -8
View File
@@ -123,8 +123,8 @@
<span>{% if gitea_workspace %}📁 {{ active_ws_name }}{% elif workspace_key and '/' in workspace_key %}🔗{% else %}📁{% endif %} {{ active_ws_name if not gitea_workspace else '' }}</span>
</div>
<div class="sidebar-section-actions">
<button class="section-action-btn" title="New Page" @click.stop="newPageInWorkspace()">📄</button>
<button class="section-action-btn" title="New Folder" @click.stop="newFolderInWorkspace()">📁</button>
<button class="section-action-btn" title="New Page" @click.stop="window.FlowDeck.createPage()">📄</button>
<button class="section-action-btn" title="New Folder" @click.stop="window.FlowDeck.showCreateFolderModal()">📁</button>
</div>
</div>
{% from '_workspace_tree_macro.html' import render_workspace_tree %}
@@ -170,7 +170,7 @@
<span>Meetings</span>
</div>
<div class="sidebar-section-actions">
<button class="section-action-btn" title="Add" @click.stop="newPage('Meetings')">+</button>
<button class="section-action-btn" title="Add" @click.stop="window.FlowDeck.createPage()">+</button>
</div>
</div>
<div class="sidebar-section-items" x-show="sectionsOpen.meetings" x-transition>
@@ -332,7 +332,7 @@
<span style="font-size:14px;">💬</span> New chat
<span class="shortcut">Ctrl+O</span>
</button>
<button class="sidebar-footer-btn" style="width:36px; justify-content:center; background:var(--bg-hover); border-radius:8px;" @click="newPage('Private')" title="New page">
<button class="sidebar-footer-btn" style="width:36px; justify-content:center; background:var(--bg-hover); border-radius:8px;" onclick="window.FlowDeck.createPage()" title="New page">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
</button>
</div>
@@ -425,10 +425,10 @@
@click.outside="wsCtx.visible = false">
<template x-if="wsCtx.isFolder">
<div class="context-menu-section">
<div class="context-menu-item" @click="wsCtxAction('new-file')">
<div class="context-menu-item" onclick="window.FlowDeck.createPage()">
<span class="menu-icon">📄</span> New File
</div>
<div class="context-menu-item" @click="wsCtxAction('new-folder')">
<div class="context-menu-item" onclick="window.FlowDeck.showCreateFolderModal()">
<span class="menu-icon">📁</span> New Folder
</div>
</div>
@@ -488,6 +488,70 @@
});
})();
// ── Global FlowDeck namespace ────────────────────────────────
// All "New Page" and "New Folder" buttons across the app call
// these functions for consistent behaviour.
window.FlowDeck = {
getCsrfToken: function() {
var m = document.cookie.match(/csrf_token=([^;]+)/);
return m ? m[1] : '';
},
/** Create a new Notion-style page (same as sidebar bottom-right button). */
createPage: function() {
var project = (window.appState && window.appState.workspaceKey) || '';
var url = '/board/api/pages?section=Private&project=' + encodeURIComponent(project);
fetch(url, {
method: 'POST',
headers: { 'X-CSRF-Token': this.getCsrfToken() }
})
.then(function(r) { if (!r.ok) throw new Error('CSRF or API error'); return r.json(); })
.then(function(data) { window.location.href = '/pages/' + data.id; })
.catch(function(err) {
if (window.appState && window.appState.toast) window.appState.toast('Failed to create page: ' + err.message, 'error');
else alert('Failed to create page: ' + err.message);
});
},
/** Show the global New Folder modal. */
showCreateFolderModal: function() {
var modal = document.getElementById('fd-global-folder-modal');
if (!modal) return;
modal.style.display = 'flex';
var input = document.getElementById('fd-global-folder-input');
if (input) { input.value = ''; setTimeout(function() { input.focus(); }, 100); }
},
/** Called by the global New Folder modal's Create button. */
_confirmCreateFolder: function() {
var input = document.getElementById('fd-global-folder-input');
var name = (input && input.value || '').trim();
if (!name) return;
var modal = document.getElementById('fd-global-folder-modal');
if (modal) modal.style.display = 'none';
var self = this;
fetch('/api/local-workspace/items', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': this.getCsrfToken() },
body: JSON.stringify({ name: name, type: 'folder' })
})
.then(function(r) { if (!r.ok) throw new Error('Failed'); return r.json(); })
.then(function() {
// Refresh sidebar tree
if (window.appState && window.appState.refreshSidebarTree) window.appState.refreshSidebarTree();
// Notify workspace page
document.dispatchEvent(new CustomEvent('flowdeck:workspace-changed'));
if (window._wsData && window._wsData._reloadAfterAction) window._wsData._reloadAfterAction();
// Show toast
if (window.appState && window.appState.toast) window.appState.toast('Folder created', 'success');
})
.catch(function(err) {
if (window.appState && window.appState.toast) window.appState.toast('Failed: ' + err.message, 'error');
else alert('Failed to create folder: ' + err.message);
});
}
};
function appState() {
return {
init() {
@@ -1164,8 +1228,7 @@
if (e.ctrlKey && e.key === 'n') {
e.preventDefault();
if (data && data.newPageInWorkspace) data.newPageInWorkspace();
else if (data && data.newPage) data.newPage('Private');
window.FlowDeck.createPage();
}
else if (e.key === 'Enter' && !editable) {
var activeSidebar = document.querySelector('.sidebar-item.active');
@@ -1237,5 +1300,23 @@
.flowdeck-modal-btn.confirm:hover{background:#2C8CEB;}
</style>
<!-- Global New Folder Modal -->
<div id="fd-global-folder-modal" class="flowdeck-modal-overlay" style="display:none" onclick="if(event.target===this)this.style.display='none'">
<div class="flowdeck-modal">
<div class="flowdeck-modal-header">
<h3>📁 New Folder</h3>
<button class="flowdeck-modal-close" onclick="document.getElementById('fd-global-folder-modal').style.display='none'">&times;</button>
</div>
<div class="flowdeck-modal-body">
<p>Enter a name for the new folder.</p>
<input class="flowdeck-modal-input" id="fd-global-folder-input" type="text" placeholder="Folder name" onkeydown="if(event.key==='Enter')window.FlowDeck._confirmCreateFolder()">
</div>
<div class="flowdeck-modal-footer">
<button class="flowdeck-modal-btn cancel" onclick="document.getElementById('fd-global-folder-modal').style.display='none'">Cancel</button>
<button class="flowdeck-modal-btn confirm" onclick="window.FlowDeck._confirmCreateFolder()">Create</button>
</div>
</div>
</div>
</body>
</html>
+1 -1
View File
@@ -779,7 +779,7 @@ document.addEventListener('alpine:init', () => {
<div class="gw-content">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
<h3>🔒 Private Pages</h3>
<button class="btn btn-primary" @click="newPrivate()">+ New Page</button>
<button class="btn btn-primary" onclick="window.FlowDeck.createPage()">+ New Page</button>
</div>
<template x-if="privatePages.length === 0">
<div class="empty-state" style="padding:40px 20px;">
+3 -3
View File
@@ -186,7 +186,7 @@
<div class="lib-header">
<h1>Library</h1>
<div class="lib-header-actions">
<button class="lib-btn-primary" @click="newPage()">
<button class="lib-btn-primary" onclick="window.FlowDeck.createPage()">
<svg 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 page
</button>
@@ -326,7 +326,7 @@
<!-- "+ Add new" (root level) -->
<div class="add-new-row" x-show="!loading && tab !== 'favorites' && tab !== 'shared' && tab !== 'published' && tab !== 'repository'"
@click="addNewPage(0)">
onclick="window.FlowDeck.createPage()">
<svg 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>
Add new
</div>
@@ -541,7 +541,7 @@ function libraryPage() {
// ── Synthetic "+ Add new" placeholder row for an expanded parent ──
if (item._isAddNew) {
var pad = 30 + (item.depth || 0) * 20;
html += '<div class="add-new-under" style="padding-left:' + pad + 'px" onclick="window._libData.addNewPage(' + item._parentId + ')">';
html += '<div class="add-new-under" style="padding-left:' + pad + 'px" onclick="window.FlowDeck.createPage()">';
html += '<svg 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> Add new</div>';
continue;
}
+8 -8
View File
@@ -1072,10 +1072,10 @@ window._wsInitData = (function() {
switch(action) {
case 'new-file':
this.openModal('file', {db_id: id, name: name});
window.FlowDeck.createPage();
break;
case 'new-folder':
this.openModal('folder', {db_id: id, name: name});
window.FlowDeck.showCreateFolderModal();
break;
case 'rename':
this.startRename({db_id: id, name: name});
@@ -2183,8 +2183,8 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
</select>
<button class="btn-icon" @click="expandAll()" title="Expand" x-show="viewMode==='tree'">⊞</button>
<button class="btn-icon" @click="collapseAll()" title="Collapse" x-show="viewMode==='tree'">⊟</button>
<button class="btn-icon btn-create" @click="openModal('file')" title="New file">📄</button>
<button class="btn-icon btn-create" @click="openModal('folder')" title="New folder">📁</button>
<button class="btn-icon btn-create" onclick="window.FlowDeck.createPage()" title="New file">📄</button>
<button class="btn-icon btn-create" onclick="window.FlowDeck.showCreateFolderModal()" title="New folder">📁</button>
</div>
<!-- Bulk actions bar -->
@@ -2274,8 +2274,8 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
<div class="actions">
<template x-if="node.is_folder">
<span style="display:flex;gap:2px">
<button class="ws-act" @click.stop="openModal('file',node)" title="New file">📄</button>
<button class="ws-act" @click.stop="openModal('folder',node)" title="New folder">📁</button>
<button class="ws-act" @click.stop="window.FlowDeck.createPage()" title="New file">📄</button>
<button class="ws-act" @click.stop="window.FlowDeck.showCreateFolderModal()" title="New folder">📁</button>
</span>
</template>
<button class="ws-act" @click.stop="startRename(node)" title="Rename">✏️</button>
@@ -2480,8 +2480,8 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
<h2 x-text="currentFolder > 0 ? 'Empty folder' : 'Welcome to your Workspace'"></h2>
<p x-text="currentFolder > 0 ? 'Create a new file or folder to get started' : 'Drag & drop files from your computer, or create new ones.'"></p>
<div class="empty-actions">
<button class="btn-icon" @click="openModal('file')" title="New file">📄</button>
<button class="btn-icon" @click="openModal('folder')" title="New folder">📁</button>
<button class="btn-icon" onclick="window.FlowDeck.createPage()" title="New file">📄</button>
<button class="btn-icon" onclick="window.FlowDeck.showCreateFolderModal()" title="New folder">📁</button>
</div>
</div>