Files
simplenote-web/public/js/app.js
Hiro 9496fc8e36 feat: Add Projects and Folders UI (SimpleNote v2)
- New Projects view (projects.js): Lists all projects with cards
- New ProjectView (projectView.js): Project dashboard with folder tree
- Updated API client: Projects and Folders CRUD methods
- New modals: NewProjectModal, NewFolderModal, MoveToFolderModal
- Edit/Delete project functionality
- Updated navigation: ProjectList -> ProjectView -> FolderView
- Consistent dark theme styling

Changes:
- public/js/views/projects.js (NEW)
- public/js/views/projectView.js (NEW)
- public/js/api.js (added Projects/Folders API methods)
- public/js/app.js (added navigation routes)
- public/js/components/sidebar.js (added Projects link)
- public/css/style.css (added project/folder styles)
2026-03-28 13:03:23 +00:00

161 lines
4.3 KiB
JavaScript

// SimpleNote Web - Main Application
import { api } from './api.js';
import { renderLogin, initLoginHandlers } from './views/login.js';
import { renderProjects } from './views/projects.js';
import { renderProjectView } from './views/projectView.js';
import { renderDashboard } from './views/dashboard.js';
import { renderDocument } from './views/document.js';
import { renderEditor } from './views/editor.js';
class App {
constructor() {
this.currentView = null;
this.state = {
token: localStorage.getItem('sn_token'),
view: 'projects', // Default to projects view
params: {}
};
}
async init() {
if (!this.state.token) {
this.renderLogin();
return;
}
api.setToken(this.state.token);
try {
await api.login(this.state.token);
this.render();
} catch (e) {
this.state.token = null;
localStorage.removeItem('sn_token');
this.renderLogin();
}
}
renderLogin() {
const app = document.getElementById('app');
app.innerHTML = renderLogin();
initLoginHandlers(async (token) => {
try {
await api.login(token);
this.state.token = token;
this.state.view = 'projects';
this.render();
} catch (e) {
return 'Invalid token';
}
});
}
async render() {
const app = document.getElementById('app');
switch (this.state.view) {
case 'projects':
await renderProjects(this);
break;
case 'project':
await renderProjectView(this);
break;
case 'dashboard':
await renderDashboard(this);
break;
case 'document':
await renderDocument(this);
break;
case 'editor':
renderEditor(this);
break;
default:
await renderProjects(this);
}
}
navigate(view, params = {}) {
this.state.view = view;
this.state.params = params;
this.render();
}
showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const escapedMessage = this.escapeHtml(message);
toast.innerHTML = `
<span class="toast-message">${escapedMessage}</span>
<button class="toast-close" onclick="this.parentElement.remove()">✕</button>
`;
container.appendChild(toast);
setTimeout(() => toast.remove(), 4000);
}
escapeHtml(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
async confirmDelete(message) {
return new Promise((resolve) => {
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop';
const escapedMessage = this.escapeHtml(message);
backdrop.innerHTML = `
<div class="modal">
<div class="modal-header">
<span>⚠️</span>
<h3>Confirm Delete</h3>
</div>
<div class="modal-body">
<p>${escapedMessage}</p>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" id="cancel-btn">Cancel</button>
<button class="btn btn-danger" id="confirm-btn">Delete</button>
</div>
</div>
`;
document.body.appendChild(backdrop);
backdrop.querySelector('#cancel-btn').onclick = () => {
backdrop.remove();
resolve(false);
};
backdrop.querySelector('#confirm-btn').onclick = () => {
backdrop.remove();
resolve(true);
};
backdrop.onclick = (e) => {
if (e.target === backdrop) {
backdrop.remove();
resolve(false);
}
};
});
}
}
window.app = new App();
app.init();
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && window.app.state.view === 'editor') {
window.app.navigate('project', { id: window.app.state.params.projectId });
}
if ((e.ctrlKey || e.metaKey) && e.key === 'n' && (window.app.state.view === 'projects' || window.app.state.view === 'project')) {
e.preventDefault();
if (window.app.state.view === 'project') {
window.showNewDocModal(window.app.state.params.id, '');
} else {
window.showNewProjectModal();
}
}
});