added display of currently shared notes

This commit is contained in:
Gamosoft 2026-04-22 15:38:16 +02:00
parent 4061633daa
commit 142a3a59cd
13 changed files with 123 additions and 14 deletions

View File

@ -19,6 +19,9 @@ const CONFIG = {
TOAST_DURATION_SUCCESS_MS: 3500, TOAST_DURATION_SUCCESS_MS: 3500,
}; };
/** Heroicons outline "share" (same d= as shared-note icon in the file tree) */
const SHARE_ICON_PATH = 'M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z';
// localStorage settings configuration - centralized definition of all persisted settings // localStorage settings configuration - centralized definition of all persisted settings
const LOCAL_SETTINGS = { const LOCAL_SETTINGS = {
// Boolean settings // Boolean settings
@ -231,7 +234,7 @@ function noteApp() {
sortMode: localStorage.getItem('sortMode') || 'a-z', sortMode: localStorage.getItem('sortMode') || 'a-z',
// Icon rail / panel state // Icon rail / panel state
activePanel: 'files', // 'files', 'search', 'tags', 'settings' activePanel: 'files', // 'files', 'search', 'tags', 'outline', 'backlinks', 'shared', 'settings'
// Folder state // Folder state
folderTree: [], folderTree: [],
@ -329,6 +332,7 @@ function noteApp() {
showShareQR: false, showShareQR: false,
shareLinkCopied: false, shareLinkCopied: false,
_sharedNotePaths: new Set(), // O(1) lookup for shared note indicators _sharedNotePaths: new Set(), // O(1) lookup for shared note indicators
_sharedNotePathsList: [], // sorted paths, mirrors Set for reactive sidebar panel
// Quick Switcher state (Ctrl+Alt+P) // Quick Switcher state (Ctrl+Alt+P)
showQuickSwitcher: false, showQuickSwitcher: false,
@ -2237,7 +2241,7 @@ function noteApp() {
// Share icon for shared notes // Share icon for shared notes
const isShared = !isMediaFile && this.isNoteShared(note.path); const isShared = !isMediaFile && this.isNoteShared(note.path);
const shareIcon = isShared ? '<svg aria-hidden="true" style="display: inline-block; width: 12px; height: 12px; vertical-align: middle; margin-right: 2px; opacity: 0.7;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg>' : ''; const shareIcon = isShared ? `<svg aria-hidden="true" style="display: inline-block; width: 12px; height: 12px; vertical-align: middle; margin-right: 2px; opacity: 0.7;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${SHARE_ICON_PATH}"></path></svg>` : '';
const icon = this.getMediaIcon(note.type); const icon = this.getMediaIcon(note.type);
const deleteTitle = !isMediaFile const deleteTitle = !isMediaFile
? this.t('toolbar.delete_note') ? this.t('toolbar.delete_note')
@ -6111,19 +6115,38 @@ function noteApp() {
// Share Functions // Share Functions
// ============================================================================ // ============================================================================
// Load list of shared note paths (for visual indicators) // Load list of shared note paths (for visual indicators and Shared sidebar panel)
async loadSharedNotePaths() { async loadSharedNotePaths() {
try { try {
const response = await fetch('/api/shared-notes'); const response = await fetch('/api/shared-notes');
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
this._sharedNotePaths = new Set(data.paths || []); this._sharedNotePaths = new Set(data.paths || []);
this._syncSharedNotePathsList();
} }
} catch (error) { } catch (error) {
console.error('Failed to load shared note paths:', error); console.error('Failed to load shared note paths:', error);
this._sharedNotePaths = new Set(); this._sharedNotePaths = new Set();
this._sharedNotePathsList = [];
} }
}, },
_syncSharedNotePathsList() {
this._sharedNotePathsList = [...this._sharedNotePaths].sort((a, b) =>
a.localeCompare(b, undefined, { sensitivity: 'base' })
);
},
/** Display rows for Shared notes panel: name + folder line (search-style) */
getSharedPanelItems() {
return this._sharedNotePathsList.map((path) => {
const noMd = path.replace(/\.md$/i, '');
const last = Math.max(noMd.lastIndexOf('/'), noMd.lastIndexOf('\\'));
const name = last < 0 ? noMd : noMd.slice(last + 1);
const folder = last < 0 ? '' : noMd.slice(0, last).replace(/\\/g, '/');
return { path, name, folder: folder || 'Root' };
});
},
// Check if a note is currently shared (O(1) lookup) // Check if a note is currently shared (O(1) lookup)
isNoteShared(notePath) { isNoteShared(notePath) {
@ -6284,6 +6307,7 @@ function noteApp() {
this.shareInfo.shared = true; this.shareInfo.shared = true;
// Update the shared paths set // Update the shared paths set
this._sharedNotePaths.add(this.currentNote); this._sharedNotePaths.add(this.currentNote);
this._syncSharedNotePathsList();
} else { } else {
const error = await response.json(); const error = await response.json();
this.toast(this.t('share.error_creating', { error: error.detail || 'Unknown error' }), { type: 'error' }); this.toast(this.t('share.error_creating', { error: error.detail || 'Unknown error' }), { type: 'error' });
@ -6342,6 +6366,7 @@ function noteApp() {
this.shareInfo = { shared: false }; this.shareInfo = { shared: false };
// Update the shared paths set // Update the shared paths set
this._sharedNotePaths.delete(this.currentNote); this._sharedNotePaths.delete(this.currentNote);
this._syncSharedNotePathsList();
} else { } else {
const error = await response.json(); const error = await response.json();
this.toast(this.t('share.error_revoking', { error: error.detail || 'Unknown error' }), { type: 'error' }); this.toast(this.t('share.error_revoking', { error: error.detail || 'Unknown error' }), { type: 'error' });

View File

@ -1521,6 +1521,26 @@
x-text="backlinks.length > 9 ? '9+' : backlinks.length" x-text="backlinks.length > 9 ? '9+' : backlinks.length"
></span> ></span>
</button> </button>
<!-- Shared notes (public links) -->
<button
class="icon-rail-btn relative"
:class="{'active': activePanel === 'shared'}"
@click="activePanel = 'shared'"
:title="t('share.panel_title')"
:aria-label="t('share.panel_title')"
>
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path>
</svg>
<span
x-show="_sharedNotePathsList.length > 0"
x-cloak
class="absolute -top-1 -right-1 w-4 h-4 text-[9px] font-bold rounded-full flex items-center justify-center"
style="background-color: var(--accent-primary); color: white;"
x-text="_sharedNotePathsList.length > 9 ? '9+' : _sharedNotePathsList.length"
></span>
</button>
<div class="icon-rail-spacer"></div> <div class="icon-rail-spacer"></div>
@ -1992,6 +2012,48 @@
</div> </div>
<!-- END BACKLINKS PANEL --> <!-- END BACKLINKS PANEL -->
<!-- ==================== SHARED NOTES PANEL ==================== -->
<div x-show="activePanel === 'shared'" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="flex flex-col h-full">
<div class="flex-shrink-0 px-3 py-2 border-b flex items-center justify-between" style="border-color: var(--border-primary);">
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);" x-text="t('share.panel_title')"></span>
<span class="text-xs px-1.5 py-0.5 rounded" style="background-color: var(--bg-tertiary); color: var(--text-tertiary);" x-text="_sharedNotePathsList.length"></span>
</div>
<div class="flex-1 overflow-y-auto custom-scrollbar">
<div class="py-2">
<template x-if="_sharedNotePathsList.length > 0">
<div class="px-2">
<template x-for="item in getSharedPanelItems()" :key="item.path">
<div
@click="openItem(item.path, 'note')"
class="hover-accent px-2 py-1.5 text-sm cursor-pointer mb-1 rounded"
:style="currentNote === item.path ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'"
>
<div class="flex items-start gap-2 mb-0.5">
<svg class="w-4 h-4 flex-shrink-0 mt-0.5" style="color: var(--accent-primary); opacity: 0.9;" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path>
</svg>
<div class="min-w-0 flex-1">
<div class="font-medium truncate" x-text="item.name" :title="item.name"></div>
<div class="text-xs truncate" style="color: var(--text-tertiary);" x-text="item.folder" :title="item.folder"></div>
</div>
</div>
</div>
</template>
</div>
</template>
<template x-if="_sharedNotePathsList.length === 0">
<div class="text-center py-6 px-3">
<svg class="w-12 h-12 mx-auto mb-3 opacity-40" style="color: var(--text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path>
</svg>
<p class="text-sm" style="color: var(--text-tertiary);" x-text="t('share.no_shared')"></p>
</div>
</template>
</div>
</div>
</div>
<!-- END SHARED NOTES PANEL -->
<!-- ==================== SETTINGS PANEL ==================== --> <!-- ==================== SETTINGS PANEL ==================== -->
<template x-if="activePanel === 'settings'"> <template x-if="activePanel === 'settings'">
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">

View File

@ -142,7 +142,9 @@
"error_creating": "Fehler beim Erstellen des Freigabelinks: {{error}}", "error_creating": "Fehler beim Erstellen des Freigabelinks: {{error}}",
"error_revoking": "Fehler beim Widerrufen des Freigabelinks: {{error}}", "error_revoking": "Fehler beim Widerrufen des Freigabelinks: {{error}}",
"show_qr": "QR-Code anzeigen", "show_qr": "QR-Code anzeigen",
"hide_qr": "QR-Code ausblenden" "hide_qr": "QR-Code ausblenden",
"panel_title": "Geteilt",
"no_shared": "Keine geteilten Notizen"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "Notizen suchen...", "placeholder": "Notizen suchen...",

View File

@ -141,7 +141,9 @@
"error_creating": "Failed to create share link: {{error}}", "error_creating": "Failed to create share link: {{error}}",
"error_revoking": "Failed to revoke share link: {{error}}", "error_revoking": "Failed to revoke share link: {{error}}",
"show_qr": "Show QR Code", "show_qr": "Show QR Code",
"hide_qr": "Hide QR Code" "hide_qr": "Hide QR Code",
"panel_title": "Shared",
"no_shared": "No shared notes"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "Type to search notes...", "placeholder": "Type to search notes...",

View File

@ -142,7 +142,9 @@
"error_creating": "Failed to create share link: {{error}}", "error_creating": "Failed to create share link: {{error}}",
"error_revoking": "Failed to revoke share link: {{error}}", "error_revoking": "Failed to revoke share link: {{error}}",
"show_qr": "Show QR Code", "show_qr": "Show QR Code",
"hide_qr": "Hide QR Code" "hide_qr": "Hide QR Code",
"panel_title": "Shared",
"no_shared": "No shared notes"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "Type to search notes...", "placeholder": "Type to search notes...",

View File

@ -142,7 +142,9 @@
"error_creating": "Error al crear el enlace: {{error}}", "error_creating": "Error al crear el enlace: {{error}}",
"error_revoking": "Error al revocar el enlace: {{error}}", "error_revoking": "Error al revocar el enlace: {{error}}",
"show_qr": "Mostrar código QR", "show_qr": "Mostrar código QR",
"hide_qr": "Ocultar código QR" "hide_qr": "Ocultar código QR",
"panel_title": "Compartidas",
"no_shared": "No hay notas compartidas"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "Escribe para buscar notas...", "placeholder": "Escribe para buscar notas...",

View File

@ -142,7 +142,9 @@
"error_creating": "Échec de la création du lien : {{error}}", "error_creating": "Échec de la création du lien : {{error}}",
"error_revoking": "Échec de la révocation du lien : {{error}}", "error_revoking": "Échec de la révocation du lien : {{error}}",
"show_qr": "Afficher le code QR", "show_qr": "Afficher le code QR",
"hide_qr": "Masquer le code QR" "hide_qr": "Masquer le code QR",
"panel_title": "Partagées",
"no_shared": "Aucune note partagée"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "Rechercher des notes...", "placeholder": "Rechercher des notes...",

View File

@ -142,7 +142,9 @@
"error_creating": "Hiba történt a megosztási link létrehozásánál: {{error}}", "error_creating": "Hiba történt a megosztási link létrehozásánál: {{error}}",
"error_revoking": "Nemsikerült visszavonni a megosztási linket: {{error}}", "error_revoking": "Nemsikerült visszavonni a megosztási linket: {{error}}",
"show_qr": "QR-Kód Megjelenítése", "show_qr": "QR-Kód Megjelenítése",
"hide_qr": "QR-Kód Elrejtése" "hide_qr": "QR-Kód Elrejtése",
"panel_title": "Megosztott",
"no_shared": "Nincs megosztott jegyzet"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "Írj a jegyzetek kereséséhez...", "placeholder": "Írj a jegyzetek kereséséhez...",

View File

@ -141,7 +141,9 @@
"error_creating": "Impossibile creare il link: {{error}}", "error_creating": "Impossibile creare il link: {{error}}",
"error_revoking": "Impossibile revocare il link: {{error}}", "error_revoking": "Impossibile revocare il link: {{error}}",
"show_qr": "Mostra codice QR", "show_qr": "Mostra codice QR",
"hide_qr": "Nascondi codice QR" "hide_qr": "Nascondi codice QR",
"panel_title": "Condivise",
"no_shared": "Nessuna nota condivisa"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "Cerca note...", "placeholder": "Cerca note...",

View File

@ -141,7 +141,9 @@
"error_creating": "共有リンクの作成に失敗しました: {{error}}", "error_creating": "共有リンクの作成に失敗しました: {{error}}",
"error_revoking": "共有リンクの取り消しに失敗しました: {{error}}", "error_revoking": "共有リンクの取り消しに失敗しました: {{error}}",
"show_qr": "QRコードを表示", "show_qr": "QRコードを表示",
"hide_qr": "QRコードを非表示" "hide_qr": "QRコードを非表示",
"panel_title": "共有",
"no_shared": "共有中のノートはありません"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "ノートを検索...", "placeholder": "ノートを検索...",

View File

@ -141,7 +141,9 @@
"error_creating": "Не удалось создать ссылку: {{error}}", "error_creating": "Не удалось создать ссылку: {{error}}",
"error_revoking": "Не удалось отозвать ссылку: {{error}}", "error_revoking": "Не удалось отозвать ссылку: {{error}}",
"show_qr": "Показать QR-код", "show_qr": "Показать QR-код",
"hide_qr": "Скрыть QR-код" "hide_qr": "Скрыть QR-код",
"panel_title": "Опубликовано",
"no_shared": "Нет опубликованных заметок"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "Поиск заметок...", "placeholder": "Поиск заметок...",

View File

@ -141,7 +141,9 @@
"error_creating": "Napaka pri ustvarjanju povezave: {{error}}", "error_creating": "Napaka pri ustvarjanju povezave: {{error}}",
"error_revoking": "Napaka pri preklicu povezave: {{error}}", "error_revoking": "Napaka pri preklicu povezave: {{error}}",
"show_qr": "Prikaži QR kodo", "show_qr": "Prikaži QR kodo",
"hide_qr": "Skrij QR kodo" "hide_qr": "Skrij QR kodo",
"panel_title": "V skupni rabi",
"no_shared": "Ni deljenih zapisov"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "Išči beležke...", "placeholder": "Išči beležke...",

View File

@ -141,7 +141,9 @@
"error_creating": "创建分享链接失败:{{error}}", "error_creating": "创建分享链接失败:{{error}}",
"error_revoking": "撤销分享链接失败:{{error}}", "error_revoking": "撤销分享链接失败:{{error}}",
"show_qr": "显示二维码", "show_qr": "显示二维码",
"hide_qr": "隐藏二维码" "hide_qr": "隐藏二维码",
"panel_title": "已分享",
"no_shared": "暂无已分享的笔记"
}, },
"quick_switcher": { "quick_switcher": {
"placeholder": "搜索笔记...", "placeholder": "搜索笔记...",