feat: add frontend UI for SimpleNote Web

- Vanilla JS frontend with dark theme
- Dashboard with sidebar (libraries tree, tags), document grid, search
- Document viewer with markdown rendering and metadata panel
- Document editor with split write/preview and formatting toolbar
- Login screen with token authentication
- All styled according to UI/UX specs (dark theme, accent #00d4aa)
- API client for all endpoints
- Responsive design
This commit is contained in:
Hiro
2026-03-28 11:44:42 +00:00
parent c3e48596f3
commit c4921c8e73
12 changed files with 2280 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
// Sidebar Component
export function renderSidebar({ libraries, tags, selectedLibrary, selectedTag, onSelectLibrary, onSelectTag, onHome }) {
const buildLibraryTree = (libs, parentId = null, depth = 0) => {
return libs
.filter(l => l.parentId === parentId)
.map(lib => {
const children = libs.filter(l => l.parentId === lib.id);
const hasChildren = children.length > 0;
const isSelected = selectedLibrary === lib.id;
return `
<div class="tree-node">
<div class="tree-item ${isSelected ? 'active' : ''}" onclick="handleSelectLibrary('${lib.id}')">
<span class="tree-toggle ${hasChildren ? 'expanded' : ''}" style="padding-left:${depth * 12}px">
${hasChildren ? '▶' : ''}
</span>
<span class="icon">📁</span>
<span class="label">${escapeHtml(lib.name)}</span>
</div>
${hasChildren ? `<div class="tree-children">${buildLibraryTree(libraries, lib.id, depth + 1)}</div>` : ''}
</div>
`;
})
.join('');
};
return `
<aside class="sidebar">
<div class="sidebar-scroll">
<div class="sidebar-section">
<h3>📚 Libraries</h3>
<div class="library-tree">
<div class="tree-item ${!selectedLibrary ? 'active' : ''}" onclick="handleHome()">
<span class="icon">🏠</span>
<span class="label">All Documents</span>
</div>
${buildLibraryTree(libraries)}
</div>
</div>
<div class="sidebar-section">
<h3>🏷️ Tags</h3>
<div class="tag-list">
${tags.map(tag => `
<div class="tag-item ${selectedTag === tag.name ? 'active' : ''}" onclick="handleSelectTag('${escapeHtml(tag.name)}')">
<span>#${escapeHtml(tag.name)}</span>
<span class="tag-count">${tag.count}</span>
</div>
`).join('')}
</div>
</div>
<div class="quick-links">
<a class="quick-link" onclick="handleHome()">📋 All Documents</a>
<a class="quick-link" onclick="window.app.navigate('editor')">+ New Document</a>
</div>
</div>
</aside>
<script>
window.handleSelectLibrary = (id) => {
${onSelectLibrary ? `window.document.dispatchEvent(new CustomEvent('select-library', {detail: '${''}'}))` : ''}
};
window.handleSelectTag = (tag) => {
${onSelectTag ? `window.document.dispatchEvent(new CustomEvent('select-tag', {detail: tag}))` : ''}
};
window.handleHome = () => {
${onHome ? `window.document.dispatchEvent(new CustomEvent('go-home'))` : ''}
};
window.document.addEventListener('select-library', (e) => {
${onSelectLibrary ? onSelectLibrary.toString().replace(/\s+/g, ' ') : ''}
});
window.document.addEventListener('select-tag', (e) => {
${onSelectTag ? onSelectTag.toString().replace(/\s+/g, ' ') : ''}
});
window.document.addEventListener('go-home', (e) => {
${onHome ? onHome.toString().replace(/\s+/g, ' ') : ''}
});
</script>
`;
}
function escapeHtml(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}