Files
simplenote-web/public/js/api.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

169 lines
3.5 KiB
JavaScript

// API Client for SimpleNote Web
const API_BASE = '/api/v1';
class ApiClient {
constructor() {
this.token = localStorage.getItem('sn_token');
}
setToken(token) {
this.token = token;
if (token) {
localStorage.setItem('sn_token', token);
} else {
localStorage.removeItem('sn_token');
}
}
getHeaders() {
const headers = {
'Content-Type': 'application/json'
};
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
return headers;
}
async request(method, path, body = null) {
const options = {
method,
headers: this.getHeaders()
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${API_BASE}${path}`, options);
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Request failed' }));
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}
get(path) { return this.request('GET', path); }
post(path, body) { return this.request('POST', path, body); }
put(path, body) { return this.request('PUT', path, body); }
delete(path) { return this.request('DELETE', path); }
// Auth
async login(token) {
try {
this.setToken(token); // Set token BEFORE making request
const data = await this.get('/auth/verify');
return data;
} catch (e) {
this.setToken(null);
throw e;
}
}
// Documents
getDocuments(params = {}) {
const query = new URLSearchParams(params).toString();
return this.get(`/documents${query ? '?' + query : ''}`);
}
getDocument(id) {
return this.get(`/documents/${id}`);
}
createDocument(data) {
return this.post('/documents', data);
}
updateDocument(id, data) {
return this.put(`/documents/${id}`, data);
}
deleteDocument(id) {
return this.delete(`/documents/${id}`);
}
exportDocument(id) {
return fetch(`${API_BASE}/documents/${id}/export`, {
headers: this.getHeaders()
}).then(r => r.text());
}
// Libraries
getLibraries() {
return this.get('/libraries');
}
getLibrary(id) {
return this.get(`/libraries/${id}`);
}
createLibrary(data) {
return this.post('/libraries', data);
}
updateLibrary(id, data) {
return this.put(`/libraries/${id}`, data);
}
deleteLibrary(id) {
return this.delete(`/libraries/${id}`);
}
// Tags
getTags() {
return this.get('/tags');
}
// ===== PROJECTS =====
getProjects() {
return this.get('/projects');
}
getProject(id) {
return this.get(`/projects/${id}`);
}
createProject(data) {
return this.post('/projects', data);
}
updateProject(id, data) {
return this.put(`/projects/${id}`, data);
}
deleteProject(id) {
return this.delete(`/projects/${id}`);
}
getProjectTree(id) {
return this.get(`/projects/${id}/tree`);
}
// ===== FOLDERS =====
getFolders(projectId, parentId = null) {
const query = parentId ? `?parentId=${parentId}` : '';
return this.get(`/projects/${projectId}/folders${query}`);
}
createFolder(data) {
return this.post('/folders', data);
}
updateFolder(id, data) {
return this.put(`/folders/${id}`, data);
}
deleteFolder(id) {
return this.delete(`/folders/${id}`);
}
// ===== Move document to folder =====
moveDocumentToFolder(documentId, folderId) {
return this.put(`/documents/${documentId}`, { folderId });
}
}
export const api = new ApiClient();