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:
140
public/js/app.js
Normal file
140
public/js/app.js
Normal file
@@ -0,0 +1,140 @@
|
||||
// SimpleNote Web - Main Application
|
||||
|
||||
import { api } from './api.js';
|
||||
import { renderLogin } from './views/login.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: 'dashboard',
|
||||
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({
|
||||
onLogin: async (token) => {
|
||||
try {
|
||||
await api.login(token);
|
||||
this.state.token = token;
|
||||
this.state.view = 'dashboard';
|
||||
this.render();
|
||||
} catch (e) {
|
||||
return 'Invalid token';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async render() {
|
||||
const app = document.getElementById('app');
|
||||
|
||||
switch (this.state.view) {
|
||||
case 'dashboard':
|
||||
await renderDashboard(this);
|
||||
break;
|
||||
case 'document':
|
||||
await renderDocument(this);
|
||||
break;
|
||||
case 'editor':
|
||||
renderEditor(this);
|
||||
break;
|
||||
default:
|
||||
await renderDashboard(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}`;
|
||||
toast.innerHTML = `
|
||||
<span class="toast-message">${message}</span>
|
||||
<button class="toast-close" onclick="this.parentElement.remove()">✕</button>
|
||||
`;
|
||||
container.appendChild(toast);
|
||||
setTimeout(() => toast.remove(), 4000);
|
||||
}
|
||||
|
||||
async confirmDelete(message) {
|
||||
return new Promise((resolve) => {
|
||||
const backdrop = document.createElement('div');
|
||||
backdrop.className = 'modal-backdrop';
|
||||
backdrop.innerHTML = `
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<span>⚠️</span>
|
||||
<h3>Confirm Delete</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>${message}</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('dashboard');
|
||||
}
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'n' && window.app.state.view === 'dashboard') {
|
||||
e.preventDefault();
|
||||
window.app.navigate('editor');
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user