diff --git a/modules/rnotes/components/folk-notes-app.ts b/modules/rnotes/components/folk-notes-app.ts index ed9c952..8edcf2e 100644 --- a/modules/rnotes/components/folk-notes-app.ts +++ b/modules/rnotes/components/folk-notes-app.ts @@ -477,38 +477,39 @@ Gear: EUR 400 (10%)
Maya is tracking expenses in rF
private demoSearchNotes(query: string) {
if (!query.trim()) {
this.searchResults = [];
- this.render();
+ this.renderNav();
return;
}
const q = query.toLowerCase();
- const all = this.demoNotebooks.flatMap(nb => nb.notes);
- this.searchResults = all.filter(n =>
- n.title.toLowerCase().includes(q) ||
- n.content_plain.toLowerCase().includes(q) ||
- (n.tags && n.tags.some(t => t.toLowerCase().includes(q)))
- );
- this.render();
- }
-
- private demoLoadNotebook(id: string) {
- const nb = this.demoNotebooks.find(n => n.id === id);
- if (nb) {
- this.selectedNotebook = { ...nb };
- } else {
- this.error = "Notebook not found";
+ const results: Note[] = [];
+ for (const nb of this.demoNotebooks) {
+ for (const n of nb.notes) {
+ if (n.title.toLowerCase().includes(q) ||
+ n.content_plain.toLowerCase().includes(q) ||
+ (n.tags && n.tags.some(t => t.toLowerCase().includes(q)))) {
+ results.push(Object.assign({}, n, { notebook_id: nb.id }) as any);
+ }
+ }
}
- this.loading = false;
- this.render();
+ this.searchResults = results;
+ this.renderNav();
}
private demoLoadNote(id: string) {
- const allNotes = this.demoNotebooks.flatMap(nb => nb.notes);
- this.selectedNote = allNotes.find(n => n.id === id) || null;
- if (this.selectedNote) {
- this.view = "note";
- this.renderNav();
- this.renderMeta();
- this.mountEditor(this.selectedNote);
+ // Find the note and its parent notebook
+ for (const nb of this.demoNotebooks) {
+ const note = nb.notes.find(n => n.id === id);
+ if (note) {
+ this.selectedNote = note;
+ this.selectedNotebook = { ...nb };
+ if (!this.expandedNotebooks.has(nb.id)) {
+ this.expandedNotebooks.add(nb.id);
+ }
+ this.renderNav();
+ this.renderMeta();
+ this.mountEditor(this.selectedNote);
+ return;
+ }
}
}
@@ -529,12 +530,11 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
} as any;
this.demoNotebooks.push(nb);
this.notebooks = this.demoNotebooks.map(({ notes, ...rest }) => rest as Notebook);
+ this.notebookNotes.set(nbId, [newNote]);
+ this.expandedNotebooks.add(nbId);
this.selectedNotebook = { ...nb };
- this.view = "notebook";
- this.render();
// Auto-open the note for editing
this.selectedNote = newNote;
- this.view = "note";
this.renderNav();
this.renderMeta();
this.mountEditor(newNote);
@@ -555,15 +555,21 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
duration: opts.duration ?? null,
created_at: new Date(now).toISOString(), updated_at: new Date(now).toISOString(),
};
- const demoNb = this.demoNotebooks.find(n => n.id === this.selectedNotebook!.id);
+ const nbId = this.selectedNotebook.id;
+ const demoNb = this.demoNotebooks.find(n => n.id === nbId);
if (demoNb) {
demoNb.notes.push(newNote);
demoNb.note_count = String(demoNb.notes.length);
}
this.selectedNotebook.notes.push(newNote);
this.selectedNotebook.note_count = String(this.selectedNotebook.notes.length);
+ // Update sidebar cache
+ const cached = this.notebookNotes.get(nbId) || [];
+ cached.unshift(newNote);
+ this.notebookNotes.set(nbId, cached);
+ const nbIdx = this.notebooks.findIndex(n => n.id === nbId);
+ if (nbIdx >= 0) this.notebooks[nbIdx].note_count = String(cached.length);
this.selectedNote = newNote;
- this.view = "note";
this.renderNav();
this.renderMeta();
this.mountEditor(newNote);
@@ -671,8 +677,13 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
notes,
};
- // If viewing a note and editor is mounted, update editor content from remote
- if (this.view === "note" && this.selectedNote && this.editor && this.editorNoteId === this.selectedNote.id) {
+ // Update sidebar note cache
+ this.notebookNotes.set(nb.id, notes);
+ const nbIdx = this.notebooks.findIndex(n => n.id === nb.id);
+ if (nbIdx >= 0) this.notebooks[nbIdx].note_count = String(notes.length);
+
+ // If editor is mounted for a note, update editor content from remote
+ if (this.selectedNote && this.editor && this.editorNoteId === this.selectedNote.id) {
const noteItem = items?.[this.selectedNote.id];
if (noteItem) {
this.selectedNote = {
@@ -733,8 +744,8 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
}
}
- // If viewing a specific note without editor mounted, update selectedNote
- if (this.view === "note" && this.selectedNote) {
+ // If a note is selected but editor not mounted yet, update selectedNote
+ if (this.selectedNote) {
const noteItem = items?.[this.selectedNote.id];
if (noteItem) {
this.selectedNote = {
@@ -821,7 +832,6 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
duration: opts.duration ?? null,
created_at: new Date(now).toISOString(), updated_at: new Date(now).toISOString(),
};
- this.view = "note";
this.renderNav();
this.renderMeta();
this.mountEditor(this.selectedNote);
@@ -969,15 +979,13 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
}
private async loadNotebook(id: string) {
- this.loading = true;
- this.render();
-
this.unsubscribeNotebook();
- this.subscribeNotebook(id);
+ await this.subscribeNotebook(id);
this.broadcastPresence();
+ // REST fallback if Automerge doc is empty after 5s
setTimeout(() => {
- if (this.loading && this.view === "notebook") {
+ if (this.subscribedDocId && (!this.doc?.items || Object.keys(this.doc.items).length === 0)) {
this.loadNotebookREST(id);
}
}, 5000);
@@ -987,7 +995,11 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
try {
const base = this.getApiBase();
const res = await fetch(`${base}/api/notebooks/${id}`, { headers: this.authHeaders() });
- this.selectedNotebook = await res.json();
+ const data = await res.json();
+ this.selectedNotebook = data;
+ if (data?.notes) {
+ this.notebookNotes.set(id, data.notes);
+ }
} catch {
this.error = "Failed to load notebook";
}
@@ -995,6 +1007,100 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
this.render();
}
+ /** Fetch notes for a notebook (sidebar display). */
+ private async fetchNotebookNotes(id: string) {
+ try {
+ const base = this.getApiBase();
+ const res = await fetch(`${base}/api/notebooks/${id}`, { headers: this.authHeaders() });
+ const data = await res.json();
+ if (data?.notes) {
+ this.notebookNotes.set(id, data.notes);
+ const nbIdx = this.notebooks.findIndex(n => n.id === id);
+ if (nbIdx >= 0) this.notebooks[nbIdx].note_count = String(data.notes.length);
+ }
+ } catch {
+ // Silently fail — sidebar stays empty for this notebook
+ }
+ this.renderNav();
+ }
+
+ /** Toggle notebook expansion in sidebar. */
+ private expandNotebook(id: string) {
+ if (this.expandedNotebooks.has(id)) {
+ this.expandedNotebooks.delete(id);
+ this.renderNav();
+ return;
+ }
+ this.expandedNotebooks.add(id);
+ if (!this.notebookNotes.has(id)) {
+ if (this.space === "demo") {
+ const nb = this.demoNotebooks.find(n => n.id === id);
+ if (nb) this.notebookNotes.set(id, nb.notes);
+ this.renderNav();
+ } else {
+ this.fetchNotebookNotes(id);
+ }
+ } else {
+ this.renderNav();
+ }
+ }
+
+ /** Open a note for editing from the sidebar. */
+ private async openNote(noteId: string, notebookId: string) {
+ const isDemo = this.space === "demo";
+
+ // Expand notebook if not expanded
+ if (!this.expandedNotebooks.has(notebookId)) {
+ this.expandedNotebooks.add(notebookId);
+ }
+
+ if (isDemo) {
+ this.demoLoadNote(noteId);
+ return;
+ }
+
+ // Set selected notebook
+ const nb = this.notebooks.find(n => n.id === notebookId);
+ if (nb) {
+ this.selectedNotebook = { ...nb, notes: this.notebookNotes.get(notebookId) || [] };
+ }
+
+ // Subscribe to Automerge if needed
+ const needSubscribe = !this.subscribedDocId || !this.subscribedDocId.endsWith(`:${notebookId}`);
+ if (needSubscribe) {
+ await this.loadNotebook(notebookId);
+ }
+
+ this.loadNote(noteId);
+ }
+
+ /** Add a new note to a notebook via the sidebar '+' button. */
+ private async addNoteToNotebook(notebookId: string) {
+ const isDemo = this.space === "demo";
+
+ // Ensure expanded
+ if (!this.expandedNotebooks.has(notebookId)) {
+ this.expandedNotebooks.add(notebookId);
+ }
+
+ // Set selected notebook
+ const nb = this.notebooks.find(n => n.id === notebookId);
+ if (!nb) return;
+ this.selectedNotebook = { ...nb, notes: this.notebookNotes.get(notebookId) || [] };
+
+ if (isDemo) {
+ this.demoCreateNote();
+ return;
+ }
+
+ // Ensure subscribed
+ const needSubscribe = !this.subscribedDocId || !this.subscribedDocId.endsWith(`:${notebookId}`);
+ if (needSubscribe) {
+ await this.loadNotebook(notebookId);
+ }
+ this.createNoteViaSync();
+ }
+
private loadNote(id: string) {
// Note is already in the Automerge doc
if (this.doc?.items?.[id]) {
@@ -1020,6 +1126,14 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
this.selectedNote = this.selectedNotebook.notes.find(n => n.id === id) || null;
}
+ // Fallback: try sidebar note cache
+ if (!this.selectedNote) {
+ for (const [, notes] of this.notebookNotes) {
+ const found = notes.find(n => n.id === id);
+ if (found) { this.selectedNote = found; break; }
+ }
+ }
+
if (this.selectedNote) {
this.renderNav();
this.renderMeta();
@@ -1031,7 +1145,7 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
private async searchNotes(query: string) {
if (!query.trim()) {
this.searchResults = [];
- this.render();
+ this.renderNav();
return;
}
try {
@@ -1042,7 +1156,7 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
} catch {
this.searchResults = [];
}
- this.render();
+ this.renderNav();
}
private async createNotebook() {
@@ -1055,8 +1169,10 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
});
const nb = await res.json();
if (nb?.id) {
- this.view = "notebook";
- await this.loadNotebook(nb.id);
+ await this.loadNotebooks(); // Refresh list
+ this.notebookNotes.set(nb.id, []);
+ this.expandedNotebooks.add(nb.id);
+ this.render();
} else {
await this.loadNotebooks();
}
@@ -1372,27 +1488,25 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
});
}
- /** Patch presence dots onto notebook cards and note items in the DOM. */
+ /** Patch presence dots onto sidebar notebook headers and note items. */
private renderPresenceIndicators() {
// Remove all existing presence dots
this.shadow.querySelectorAll('.presence-dots').forEach(el => el.remove());
- // Notebook cards
- this.shadow.querySelectorAll Maya is tracking expenses in rF
private render() {
this.renderNav();
- if (this.view === 'note' && this.editor && this.editorNoteId) {
+ if (this.selectedNote && this.editor && this.editorNoteId === this.selectedNote.id) {
// Editor already mounted — don't touch contentZone
} else {
this.renderContent();
}
this.renderMeta();
- this.attachListeners();
this.renderPresenceIndicators();
this._tour.renderOverlay();
}
@@ -2345,112 +2458,84 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
startTour() { this._tour.start(); }
private renderNav() {
- const isDemo = this.space === "demo";
+ const isSearching = this.searchQuery.trim().length > 0;
- if (this.view === "note" && this.selectedNote) {
- // Nav is handled by mountEditor's content, or we just show back button
- this.navZone.innerHTML = `
- Maya is tracking expenses in rF
return;
}
- if (this.view === "notebook" && this.selectedNotebook) {
- const nb = this.selectedNotebook;
- const filtered = this.typeFilter
- ? nb.notes.filter(n => n.type === this.typeFilter)
- : nb.notes;
- this.contentZone.innerHTML = filtered.length > 0
- ? filtered.map((n) => this.renderNoteItem(n)).join("")
- : ` Select a note from the sidebar Maya is tracking expenses in rF
}
}
- private renderNoteItem(n: Note): string {
- let typeDetail = '';
- switch (n.type) {
- case 'BOOKMARK':
- case 'CLIP': {
- if (n.url) {
- try { typeDetail = `${new URL(n.url).hostname}`; } catch { typeDetail = ''; }
- }
- break;
- }
- case 'CODE':
- if (n.language) typeDetail = `${this.esc(n.language)}`;
- break;
- case 'AUDIO':
- if (n.duration) {
- const m = Math.floor(n.duration / 60);
- const s = String(n.duration % 60).padStart(2, '0');
- typeDetail = `${m}:${s}`;
- }
- break;
- }
-
- const typeBorder = this.getTypeBorderColor(n.type);
-
- // Sync status indicator
- let syncBadge = '';
- if (n.source_ref) {
- const status = n.source_ref.syncStatus || 'synced';
- const statusColors: Record Maya is tracking expenses in rF
}, 300);
});
- // Notebook cards
- this.shadow.querySelectorAll("[data-notebook]").forEach((el) => {
- el.addEventListener("click", () => {
- const id = (el as HTMLElement).dataset.notebook!;
- this._history.push(this.view);
- this._history.push("notebook", { notebookId: id });
- this.view = "notebook";
- isDemo ? this.demoLoadNotebook(id) : this.loadNotebook(id);
+ // Create notebook
+ this.shadow.getElementById("create-notebook")?.addEventListener("click", () => {
+ isDemo ? this.demoCreateNotebook() : this.createNotebook();
+ });
+
+ // Import / Export
+ this.shadow.getElementById("btn-import-export")?.addEventListener("click", () => {
+ this.openImportExportDialog();
+ });
+
+ // Tour
+ this.shadow.getElementById("btn-tour")?.addEventListener("click", () => this.startTour());
+
+ // Toggle notebooks
+ this.shadow.querySelectorAll("[data-toggle-notebook]").forEach(el => {
+ el.addEventListener("click", (e) => {
+ if ((e.target as HTMLElement).closest('[data-add-note]')) return;
+ const id = (el as HTMLElement).dataset.toggleNotebook!;
+ this.expandNotebook(id);
});
});
- // Note items
- this.shadow.querySelectorAll("[data-note]").forEach((el) => {
- el.addEventListener("click", () => {
- const id = (el as HTMLElement).dataset.note!;
- this._history.push(this.view);
- this._history.push("note", { noteId: id });
- this.view = "note";
- isDemo ? this.demoLoadNote(id) : this.loadNote(id);
+ // Add note to notebook
+ this.shadow.querySelectorAll("[data-add-note]").forEach(el => {
+ el.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const nbId = (el as HTMLElement).dataset.addNote!;
+ this.addNoteToNotebook(nbId);
});
});
- // Make notes draggable for calendar reminders
- makeDraggableAll(this.shadow, "[data-note]", (el) => {
- const title = el.querySelector(".note-item__title")?.textContent?.replace("📌 ", "") || "";
+ // Click note in tree
+ this.shadow.querySelectorAll(".sbt-note[data-note]").forEach(el => {
+ el.addEventListener("click", () => {
+ const noteId = (el as HTMLElement).dataset.note!;
+ const nbId = (el as HTMLElement).dataset.notebook!;
+ this.openNote(noteId, nbId);
+ });
+ });
+
+ // Click search result
+ this.shadow.querySelectorAll(".sidebar-search-result[data-note]").forEach(el => {
+ el.addEventListener("click", () => {
+ const noteId = (el as HTMLElement).dataset.note!;
+ const nbId = (el as HTMLElement).dataset.notebook!;
+ this.openNote(noteId, nbId);
+ });
+ });
+
+ // Make sidebar notes draggable
+ makeDraggableAll(this.shadow, ".sbt-note[data-note]", (el) => {
+ const title = el.querySelector(".sbt-note-title")?.textContent || "";
const id = el.dataset.note || "";
return title ? { title, module: "rnotes", entityId: id, label: "Note", color: "#f59e0b" } : null;
});
-
- // Back buttons (for notebook view)
- this.navZone.querySelectorAll("[data-back]").forEach((el) => {
- el.addEventListener("click", (e) => {
- e.stopPropagation();
- this.goBack();
- });
- });
- }
-
- private goBack() {
- this.destroyEditor();
- const prev = this._history.back();
- if (!prev) return;
- this.view = prev.view;
- if (prev.view === "notebooks") {
- if (this.space !== "demo") this.unsubscribeNotebook();
- this.selectedNotebook = null;
- this.selectedNote = null;
- } else if (prev.view === "notebook") {
- this.selectedNote = null;
- }
- this.render();
- this.broadcastPresence();
}
private demoUpdateNoteField(noteId: string, field: string, value: string) {
@@ -2806,109 +2791,141 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
* { box-sizing: border-box; }
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
+ /* ── Sidebar Layout ── */
+ #notes-layout {
+ display: grid;
+ grid-template-columns: 260px 1fr;
+ min-height: 400px;
+ height: calc(100vh - 120px);
+ }
+ .notes-sidebar {
+ display: flex;
+ flex-direction: column;
+ border-right: 1px solid var(--rs-border-subtle);
+ background: var(--rs-bg-surface);
+ overflow: hidden;
+ height: 100%;
+ }
+ .sidebar-header { padding: 12px 12px 8px; }
+ .sidebar-search {
+ width: 100%; padding: 8px 12px; border-radius: 6px;
+ border: 1px solid var(--rs-input-border); background: var(--rs-input-bg);
+ color: var(--rs-input-text); font-size: 13px; font-family: inherit;
+ transition: border-color 0.15s;
+ }
+ .sidebar-search:focus { border-color: var(--rs-primary); outline: none; }
+ .sidebar-tree { flex: 1; overflow-y: auto; padding: 4px 0; }
+ .sidebar-btn-new-nb {
+ display: flex; align-items: center; justify-content: center; gap: 6px;
+ width: calc(100% - 24px); margin: 0 12px 8px; padding: 7px;
+ border-radius: 6px; border: 1px dashed var(--rs-border);
+ background: transparent; color: var(--rs-text-secondary);
+ font-size: 13px; font-family: inherit; cursor: pointer; transition: all 0.15s;
+ }
+ .sidebar-btn-new-nb:hover { border-color: var(--rs-primary); color: var(--rs-primary); background: rgba(99, 102, 241, 0.05); }
+
+ /* Notebook tree */
+ .sbt-notebook-header {
+ display: flex; align-items: center; gap: 6px;
+ padding: 6px 12px; cursor: pointer; user-select: none;
+ transition: background 0.1s; font-size: 13px;
+ }
+ .sbt-notebook-header:hover { background: var(--rs-bg-hover); }
+ .sbt-toggle {
+ width: 16px; text-align: center; font-size: 10px;
+ color: var(--rs-text-muted); flex-shrink: 0;
+ transition: transform 0.15s;
+ }
+ .sbt-toggle.expanded { transform: rotate(90deg); }
+ .sbt-nb-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
+ .sbt-nb-title {
+ flex: 1; font-weight: 500; color: var(--rs-text-primary);
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+ }
+ .sbt-nb-count { font-size: 11px; color: var(--rs-text-muted); flex-shrink: 0; }
+ .sbt-nb-add {
+ opacity: 0; border: none; background: none;
+ color: var(--rs-text-muted); cursor: pointer;
+ font-size: 16px; line-height: 1; padding: 0 4px;
+ border-radius: 3px; transition: all 0.15s; flex-shrink: 0;
+ }
+ .sbt-notebook-header:hover .sbt-nb-add { opacity: 1; }
+ .sbt-nb-add:hover { color: var(--rs-primary); background: var(--rs-bg-surface-raised); }
+ .sbt-notes { padding-left: 20px; }
+ .sbt-note {
+ display: flex; align-items: center; gap: 8px;
+ padding: 5px 12px 5px 8px; cursor: pointer;
+ font-size: 13px; color: var(--rs-text-secondary);
+ border-radius: 4px; margin: 1px 8px 1px 0;
+ transition: all 0.1s; overflow: hidden;
+ }
+ .sbt-note:hover { background: var(--rs-bg-hover); color: var(--rs-text-primary); }
+ .sbt-note.active { background: var(--rs-primary); color: #fff; }
+ .sbt-note-icon { font-size: 14px; flex-shrink: 0; }
+ .sbt-note-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+ .sbt-note-pin { font-size: 10px; flex-shrink: 0; }
+
+ .sidebar-footer {
+ padding: 8px 12px; border-top: 1px solid var(--rs-border-subtle);
+ display: flex; gap: 6px;
+ }
+ .sidebar-footer-btn {
+ padding: 5px 10px; border-radius: 5px;
+ border: 1px solid var(--rs-border); background: transparent;
+ color: var(--rs-text-secondary); font-size: 11px;
+ font-family: inherit; cursor: pointer; transition: all 0.15s;
+ }
+ .sidebar-footer-btn:hover { border-color: var(--rs-border-strong); color: var(--rs-text-primary); }
+
+ /* Sidebar search results */
+ .sidebar-search-results { padding: 4px 0; }
+ .sidebar-search-result {
+ display: flex; align-items: center; gap: 8px;
+ padding: 6px 12px; cursor: pointer; font-size: 13px;
+ color: var(--rs-text-secondary); transition: background 0.1s;
+ }
+ .sidebar-search-result:hover { background: var(--rs-bg-hover); color: var(--rs-text-primary); }
+ .sidebar-search-result-nb { font-size: 10px; color: var(--rs-text-muted); margin-left: auto; }
+
+ /* Right column */
+ .notes-right-col {
+ display: flex; flex-direction: column; overflow: hidden; height: 100%;
+ }
+ .notes-right-col #content-zone { flex: 1; overflow-y: auto; padding: 20px; }
+ .notes-right-col #meta-zone { padding: 0 20px 12px; }
+
+ /* Empty state */
+ .editor-empty-state {
+ display: flex; flex-direction: column; align-items: center;
+ justify-content: center; height: 100%; min-height: 300px;
+ color: var(--rs-text-muted); gap: 12px;
+ }
+ .editor-empty-state svg { width: 48px; height: 48px; opacity: 0.4; }
+ .editor-empty-state p { font-size: 14px; }
+
+ /* Mobile sidebar */
+ .mobile-sidebar-toggle {
+ display: none; position: fixed; bottom: 20px; left: 20px; z-index: 198;
+ width: 44px; height: 44px; border-radius: 50%; border: none;
+ background: var(--rs-primary); color: #fff; font-size: 20px;
+ cursor: pointer; box-shadow: var(--rs-shadow-md);
+ align-items: center; justify-content: center;
+ }
+ .sidebar-overlay {
+ display: none; position: fixed; inset: 0;
+ background: rgba(0,0,0,0.4); z-index: 199;
+ }
+ .sidebar-overlay.open { display: block; }
+
/* ── Navigation ── */
- .rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
- .rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid var(--rs-border); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 13px; transition: all 0.15s; }
- .rapp-nav__back:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); }
- .rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: var(--rs-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: var(--rs-primary); color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; transition: background 0.15s; display: flex; align-items: center; gap: 4px; }
.rapp-nav__btn:hover { background: var(--rs-primary-hover); }
- .rapp-nav__btn--secondary { background: transparent; border: 1px solid var(--rs-border); color: var(--rs-text-secondary); font-weight: 500; }
- .rapp-nav__btn--secondary:hover { border-color: var(--rs-border-strong); color: var(--rs-text-primary); background: transparent; }
-
- /* ── Search ── */
- .search-bar {
- width: 100%; padding: 10px 14px; border-radius: 8px;
- border: 1px solid var(--rs-input-border); background: var(--rs-input-bg); color: var(--rs-input-text);
- font-size: 14px; margin-bottom: 16px; transition: border-color 0.15s;
- }
- .search-bar:focus { border-color: var(--rs-primary); outline: none; }
- .search-results-info { margin-bottom: 12px; font-size: 13px; color: var(--rs-text-secondary); }
-
- /* ── Notebook Grid ── */
- .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
- .notebook-card {
- position: relative; overflow: hidden;
- border-radius: 12px; padding: 16px 16px 16px 20px; cursor: pointer;
- border: 1px solid var(--rs-card-border); background: var(--rs-card-bg);
- transition: border-color 0.2s, box-shadow 0.2s, transform 0.15s;
- min-height: 120px; display: flex; flex-direction: column; justify-content: space-between;
- }
- .notebook-card:hover { border-color: var(--rs-border); box-shadow: var(--rs-shadow-sm); transform: translateY(-1px); }
- .notebook-card__accent { position: absolute; top: 0; left: 0; width: 4px; height: 100%; border-radius: 12px 0 0 12px; }
- .notebook-card__body { flex: 1; }
- .notebook-card__title { font-size: 15px; font-weight: 600; margin-bottom: 4px; color: var(--rs-text-primary); }
- .notebook-card__desc { font-size: 12px; color: var(--rs-text-muted); line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
- .notebook-card__footer { display: flex; justify-content: space-between; font-size: 11px; color: var(--rs-text-muted); margin-top: 8px; }
/* ── Presence Indicators ── */
.presence-dots { display: inline-flex; gap: 2px; align-items: center; margin-left: auto; }
.presence-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; border: 1px solid var(--rs-bg-surface, #fff); flex-shrink: 0; }
.presence-dot-more { font-size: 10px; color: var(--rs-text-muted); margin-left: 2px; }
- /* ── Note Items ── */
- .note-item {
- background: var(--rs-bg-surface); border: 1px solid var(--rs-border-subtle); border-radius: 10px;
- padding: 14px 16px; margin-bottom: 8px; cursor: pointer;
- transition: border-color 0.15s, box-shadow 0.15s;
- display: flex; gap: 12px; align-items: flex-start;
- }
- .note-item:hover { border-color: var(--rs-border); box-shadow: var(--rs-shadow-sm); }
- .note-item__icon {
- font-size: 18px; flex-shrink: 0; width: 32px; height: 32px;
- display: flex; align-items: center; justify-content: center;
- background: var(--rs-bg-surface-raised); border-radius: 8px;
- }
- .note-item__body { flex: 1; min-width: 0; }
- .note-item__title { font-size: 14px; font-weight: 600; color: var(--rs-text-primary); display: flex; align-items: center; gap: 6px; }
- .note-item__pin { color: var(--rs-warning); }
- .note-sync-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
- .note-item__preview {
- font-size: 12px; color: var(--rs-text-muted); margin-top: 3px; line-height: 1.4;
- overflow: hidden; text-overflow: ellipsis;
- display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
- }
- .note-item__meta { font-size: 11px; color: var(--rs-text-muted); margin-top: 6px; display: flex; gap: 8px; align-items: center; }
- .tag { display: inline-block; padding: 1px 6px; border-radius: 3px; background: var(--rs-bg-surface-raised); color: var(--rs-text-secondary); font-size: 10px; }
-
- /* ── Type Filter Bar ── */
- .type-filter-bar { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
- .type-filter-pill {
- padding: 4px 12px; border-radius: 16px; border: 1px solid var(--rs-border);
- background: transparent; color: var(--rs-text-secondary); font-size: 12px;
- cursor: pointer; transition: all 0.15s; font-family: inherit;
- }
- .type-filter-pill:hover { border-color: var(--rs-border-strong); color: var(--rs-text-primary); }
- .type-filter-pill.active { background: var(--rs-primary); color: #fff; border-color: var(--rs-primary); }
-
- /* ── New Note Split Button ── */
- .new-note-split { display: flex; position: relative; }
- .new-note-split .rapp-nav__btn:first-child { border-radius: 6px 0 0 6px; }
- .new-note-dropdown-btn {
- border-radius: 0 6px 6px 0 !important; padding: 6px 8px !important;
- border-left: 1px solid rgba(255,255,255,0.2) !important; min-width: 28px;
- }
- .new-note-dropdown {
- position: absolute; top: 100%; right: 0; margin-top: 4px; z-index: 50;
- background: var(--rs-bg-surface); border: 1px solid var(--rs-border);
- border-radius: 8px; box-shadow: var(--rs-shadow-md); min-width: 180px;
- overflow: hidden;
- }
- .new-note-dropdown-item {
- padding: 8px 14px; cursor: pointer; font-size: 13px;
- color: var(--rs-text-primary); transition: background 0.1s;
- }
- .new-note-dropdown-item:hover { background: var(--rs-bg-hover); }
-
- /* ── Note Item Type Badges ── */
- .note-item__badge {
- display: inline-block; padding: 1px 6px; border-radius: 3px;
- font-size: 10px; font-weight: 500;
- }
- .badge-url { background: rgba(245, 158, 11, 0.15); color: #d97706; }
- .badge-lang { background: rgba(16, 185, 129, 0.15); color: #059669; }
- .badge-duration { background: rgba(239, 68, 68, 0.15); color: #dc2626; }
-
/* ── Code Editor ── */
.code-editor-controls { padding: 4px 12px; display: flex; gap: 8px; align-items: center; }
.code-textarea {
@@ -3176,21 +3193,19 @@ Gear: EUR 400 (10%) Maya is tracking expenses in rF
.tiptap-container .tiptap u { text-underline-offset: 3px; }
@media (max-width: 768px) {
- .grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; }
- .notebook-card { min-height: 100px; padding: 12px 12px 12px 16px; }
+ #notes-layout { grid-template-columns: 1fr; }
+ .notes-sidebar {
+ position: fixed; left: 0; top: 0; bottom: 0; width: 280px;
+ z-index: 200; transform: translateX(-100%);
+ transition: transform 0.25s ease; box-shadow: var(--rs-shadow-lg);
+ }
+ .notes-sidebar.open { transform: translateX(0); }
+ .mobile-sidebar-toggle { display: flex !important; }
.editor-wrapper .editable-title { padding: 12px 14px 0; }
.tiptap-container .tiptap { padding: 14px 16px; }
- .note-item { padding: 10px 12px; gap: 8px; }
- }
- @media (max-width: 600px) {
- .grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
}
@media (max-width: 480px) {
- .grid { grid-template-columns: 1fr; }
- .rapp-nav { gap: 4px; }
.rapp-nav__btn { padding: 5px 10px; font-size: 12px; }
- .search-bar { font-size: 13px; padding: 8px 10px; }
- .notebook-card__title { font-size: 14px; }
.editable-title { font-size: 18px; }
.tiptap-container .tiptap { font-size: 14px; padding: 12px 14px; min-height: 200px; }
.editor-toolbar { padding: 3px 4px; gap: 1px; }
diff --git a/modules/rnotes/components/notes.css b/modules/rnotes/components/notes.css
index afdc266..3b41591 100644
--- a/modules/rnotes/components/notes.css
+++ b/modules/rnotes/components/notes.css
@@ -2,6 +2,6 @@
folk-notes-app {
display: block;
min-height: 400px;
- padding: 20px;
+ padding: 0;
position: relative;
}