- 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)
161 lines
4.3 KiB
JavaScript
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();
|
|
}
|
|
}
|
|
});
|