UX: Clarify document/library creation flow
- Replace direct 'New Document' with modal wizard that asks for library first - Separate 'New Document' (📄) and 'New Library' (📁) buttons in header - Update sidebar quick links to show both options with clear icons - Add modal for library creation with name input - Add CSS for form-control and modal-close button styling - Minor improvements to document.js (type=button, event.stopPropagation) - Keyboard shortcut Ctrl+N now opens document creation modal
This commit is contained in:
@@ -932,6 +932,41 @@ ul, ol {
|
|||||||
border-top: 1px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--space-1);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
background: var(--color-hover);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--space-3);
|
||||||
|
background: var(--color-bg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control option {
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
/* === Login Screen === */
|
/* === Login Screen === */
|
||||||
.login-screen {
|
.login-screen {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|||||||
@@ -143,6 +143,6 @@ document.addEventListener('keydown', (e) => {
|
|||||||
}
|
}
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 'n' && window.app.state.view === 'dashboard') {
|
if ((e.ctrlKey || e.metaKey) && e.key === 'n' && window.app.state.view === 'dashboard') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.app.navigate('editor');
|
window.showNewDocModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ export function renderSidebar({ libraries, tags, selectedLibrary, selectedTag, o
|
|||||||
</div>
|
</div>
|
||||||
<div class="quick-links">
|
<div class="quick-links">
|
||||||
<a class="quick-link" data-action="home">📋 All Documents</a>
|
<a class="quick-link" data-action="home">📋 All Documents</a>
|
||||||
<a class="quick-link" onclick="window.app.navigate('editor')">+ New Document</a>
|
<a class="quick-link" onclick="window.showNewDocModal()">📄 New Document</a>
|
||||||
|
<a class="quick-link" onclick="window.showNewLibraryModal()">📁 New Library</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -66,18 +67,21 @@ export function renderSidebar({ libraries, tags, selectedLibrary, selectedTag, o
|
|||||||
(function() {
|
(function() {
|
||||||
var callbacks = window.__sidebarCallbacks;
|
var callbacks = window.__sidebarCallbacks;
|
||||||
document.querySelectorAll('[data-action="home"]').forEach(function(el) {
|
document.querySelectorAll('[data-action="home"]').forEach(function(el) {
|
||||||
el.addEventListener('click', function() {
|
el.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
if (callbacks && callbacks.onHome) callbacks.onHome();
|
if (callbacks && callbacks.onHome) callbacks.onHome();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
document.querySelectorAll('[data-action="library"]').forEach(function(el) {
|
document.querySelectorAll('[data-action="library"]').forEach(function(el) {
|
||||||
el.addEventListener('click', function() {
|
el.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
var id = this.getAttribute('data-library-id');
|
var id = this.getAttribute('data-library-id');
|
||||||
if (callbacks && callbacks.onSelectLibrary) callbacks.onSelectLibrary(id);
|
if (callbacks && callbacks.onSelectLibrary) callbacks.onSelectLibrary(id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
document.querySelectorAll('[data-action="tag"]').forEach(function(el) {
|
document.querySelectorAll('[data-action="tag"]').forEach(function(el) {
|
||||||
el.addEventListener('click', function() {
|
el.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
var tag = this.getAttribute('data-tag');
|
var tag = this.getAttribute('data-tag');
|
||||||
if (callbacks && callbacks.onSelectTag) callbacks.onSelectTag(tag);
|
if (callbacks && callbacks.onSelectTag) callbacks.onSelectTag(tag);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ export async function renderDashboard(app) {
|
|||||||
<input type="text" id="search-input" placeholder="Search documents..." value="${searchQuery}">
|
<input type="text" id="search-input" placeholder="Search documents..." value="${searchQuery}">
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button class="btn btn-ghost btn-icon-only" onclick="window.app.navigate('editor')" title="New Document">+</button>
|
<button class="btn btn-primary" onclick="window.showNewDocModal()">+ New Document</button>
|
||||||
<button class="btn btn-ghost btn-icon-only" onclick="window.app.navigate('editor', {libraryId: 'new'})" title="New Library">📁</button>
|
<button class="btn btn-ghost" onclick="window.showNewLibraryModal()">📁 New Library</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="app-layout">
|
<div class="app-layout">
|
||||||
@@ -99,7 +99,7 @@ export async function renderDashboard(app) {
|
|||||||
<div class="content-header">
|
<div class="content-header">
|
||||||
<h1>${selectedLibrary ? getLibraryName(libraries, selectedLibrary) : selectedTag ? `#${selectedTag}` : 'All Documents'}</h1>
|
<h1>${selectedLibrary ? getLibraryName(libraries, selectedLibrary) : selectedTag ? `#${selectedTag}` : 'All Documents'}</h1>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button class="btn btn-primary" onclick="window.app.navigate('editor')">+ New Document</button>
|
<button class="btn btn-primary" onclick="window.showNewDocModal()">+ New Document</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
@@ -108,7 +108,7 @@ export async function renderDashboard(app) {
|
|||||||
<div class="icon">📄</div>
|
<div class="icon">📄</div>
|
||||||
<h3>No documents found</h3>
|
<h3>No documents found</h3>
|
||||||
<p>${searchQuery || selectedTag ? 'Try adjusting your filters' : 'Create your first document'}</p>
|
<p>${searchQuery || selectedTag ? 'Try adjusting your filters' : 'Create your first document'}</p>
|
||||||
<button class="btn btn-primary" onclick="window.app.navigate('editor')">+ Create Document</button>
|
<button class="btn btn-primary" onclick="window.showNewDocModal()">+ Create Document</button>
|
||||||
</div>
|
</div>
|
||||||
` : `
|
` : `
|
||||||
<div class="doc-grid">
|
<div class="doc-grid">
|
||||||
@@ -174,3 +174,171 @@ function formatDate(dateStr) {
|
|||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global function: Show modal to create new document (with library selection)
|
||||||
|
window.showNewDocModal = async function() {
|
||||||
|
let libraries = [];
|
||||||
|
try {
|
||||||
|
const libResult = await api.getLibraries();
|
||||||
|
libraries = libResult.libraries || [];
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
let step = 1; // 1 = choose library, 2 = create new library
|
||||||
|
let newLibraryName = '';
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const backdrop = document.createElement('div');
|
||||||
|
backdrop.className = 'modal-backdrop';
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
backdrop.innerHTML = `
|
||||||
|
<div class="modal" style="min-width: 450px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<span>📄</span>
|
||||||
|
<h3>Create New Document</h3>
|
||||||
|
<button class="modal-close" onclick="this.closest('.modal-backdrop').remove()">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p style="color: var(--color-text-secondary); margin-bottom: 16px;">Choose a library for your document:</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="doc-library-select">Library</label>
|
||||||
|
<select id="doc-library-select" class="form-control">
|
||||||
|
<option value="">— No Library —</option>
|
||||||
|
${libraries.map(l => `<option value="${l.id}">📁 ${escapeHtml(l.name)}</option>`).join('')}
|
||||||
|
<option value="__new__">+ Create New Library</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-ghost" onclick="this.closest('.modal-backdrop').remove()">Cancel</button>
|
||||||
|
<button class="btn btn-primary" id="doc-next-btn">Next →</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
backdrop.innerHTML = `
|
||||||
|
<div class="modal" style="min-width: 450px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<span>📁</span>
|
||||||
|
<h3>Create New Library</h3>
|
||||||
|
<button class="modal-close" onclick="this.closest('.modal-backdrop').remove()">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new-library-name">Library Name</label>
|
||||||
|
<input type="text" id="new-library-name" class="form-control" placeholder="e.g., Backend Requirements" value="${escapeHtml(newLibraryName)}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-ghost" id="doc-back-btn">← Back</button>
|
||||||
|
<button class="btn btn-primary" id="doc-create-lib-btn">Create Library</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(backdrop);
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
const select = document.getElementById('doc-library-select');
|
||||||
|
const nextBtn = document.getElementById('doc-next-btn');
|
||||||
|
|
||||||
|
nextBtn.onclick = () => {
|
||||||
|
const value = select.value;
|
||||||
|
if (value === '__new__') {
|
||||||
|
step = 2;
|
||||||
|
backdrop.remove();
|
||||||
|
render();
|
||||||
|
} else {
|
||||||
|
backdrop.remove();
|
||||||
|
window.app.navigate('editor', { libraryId: value || null });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const backBtn = document.getElementById('doc-back-btn');
|
||||||
|
const createBtn = document.getElementById('doc-create-lib-btn');
|
||||||
|
const nameInput = document.getElementById('new-library-name');
|
||||||
|
|
||||||
|
backBtn.onclick = () => {
|
||||||
|
newLibraryName = nameInput.value;
|
||||||
|
step = 1;
|
||||||
|
backdrop.remove();
|
||||||
|
render();
|
||||||
|
};
|
||||||
|
|
||||||
|
createBtn.onclick = async () => {
|
||||||
|
const name = nameInput.value.trim();
|
||||||
|
if (!name) {
|
||||||
|
window.app.showToast('Please enter a library name', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await api.createLibrary({ name });
|
||||||
|
backdrop.remove();
|
||||||
|
window.app.showToast('Library created', 'success');
|
||||||
|
window.app.navigate('editor', { libraryId: result.id });
|
||||||
|
} catch (e) {
|
||||||
|
window.app.showToast('Failed to create library: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
backdrop.onclick = (e) => {
|
||||||
|
if (e.target === backdrop) backdrop.remove();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global function: Show modal to create new library
|
||||||
|
window.showNewLibraryModal = function() {
|
||||||
|
const backdrop = document.createElement('div');
|
||||||
|
backdrop.className = 'modal-backdrop';
|
||||||
|
backdrop.innerHTML = `
|
||||||
|
<div class="modal" style="min-width: 450px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<span>📁</span>
|
||||||
|
<h3>Create New Library</h3>
|
||||||
|
<button class="modal-close" onclick="this.closest('.modal-backdrop').remove()">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p style="color: var(--color-text-secondary); margin-bottom: 16px;">Libraries help you organize your documents.</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new-lib-name">Library Name</label>
|
||||||
|
<input type="text" id="new-lib-name" class="form-control" placeholder="e.g., Backend Requirements">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-ghost" onclick="this.closest('.modal-backdrop').remove()">Cancel</button>
|
||||||
|
<button class="btn btn-primary" id="create-lib-btn">Create Library</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(backdrop);
|
||||||
|
|
||||||
|
const nameInput = document.getElementById('new-lib-name');
|
||||||
|
const createBtn = document.getElementById('create-lib-btn');
|
||||||
|
|
||||||
|
createBtn.onclick = async () => {
|
||||||
|
const name = nameInput.value.trim();
|
||||||
|
if (!name) {
|
||||||
|
window.app.showToast('Please enter a library name', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await api.createLibrary({ name });
|
||||||
|
backdrop.remove();
|
||||||
|
window.app.showToast('Library created successfully', 'success');
|
||||||
|
window.app.navigate('dashboard');
|
||||||
|
} catch (e) {
|
||||||
|
window.app.showToast('Failed to create library: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backdrop.onclick = (e) => {
|
||||||
|
if (e.target === backdrop) backdrop.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
nameInput.focus();
|
||||||
|
};
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ export async function renderDocument(app) {
|
|||||||
|
|
||||||
appEl.innerHTML = `
|
appEl.innerHTML = `
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
<button class="btn btn-ghost" onclick="window.app.navigate('dashboard')">← Back</button>
|
<button type="button" class="btn btn-ghost" onclick="window.app.navigate('dashboard')">← Back</button>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button class="btn btn-ghost" onclick="window.app.navigate('editor', {id: '${doc.id}'})">✏️ Edit</button>
|
<button type="button" class="btn btn-ghost" onclick="window.app.navigate('editor', {id: '${doc.id}'})">✏️ Edit</button>
|
||||||
<button class="btn btn-ghost" onclick="exportDoc()">📥 Export</button>
|
<button type="button" class="btn btn-ghost" onclick="exportDoc()">📥 Export</button>
|
||||||
<button class="btn btn-ghost danger" onclick="deleteDoc()">🗑️ Delete</button>
|
<button type="button" class="btn btn-ghost danger" onclick="deleteDoc()">🗑️ Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
@@ -70,7 +70,7 @@ export async function renderDocument(app) {
|
|||||||
<div class="meta-section">
|
<div class="meta-section">
|
||||||
<div class="meta-header">Tags</div>
|
<div class="meta-header">Tags</div>
|
||||||
<div class="meta-body doc-tags">
|
<div class="meta-body doc-tags">
|
||||||
${doc.tags.map(t => `<span class="tag-pill" onclick="filterByTag('${escapeHtml(t)}')">${escapeHtml(t)}</span>`).join('')}
|
${doc.tags.map(t => `<span class="tag-pill" onclick="filterByTag('${escapeHtml(t)}'); event.stopPropagation();">${escapeHtml(t)}</span>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
|
|||||||
Reference in New Issue
Block a user