added UI pane for backlinks!

This commit is contained in:
Gamosoft 2026-03-24 12:02:34 +01:00
parent aef79a0285
commit efe199b463
13 changed files with 197 additions and 4 deletions

View File

@ -236,7 +236,10 @@ function noteApp() {
// Outline (TOC) state // Outline (TOC) state
outline: [], // [{level: 1, text: 'Heading', slug: 'heading'}, ...] outline: [], // [{level: 1, text: 'Heading', slug: 'heading'}, ...]
// Backlinks state
backlinks: [], // [{path: 'note.md', name: 'Note', references: [{line_number: 5, context: '...', type: 'wikilink'}]}]
// Scroll sync state // Scroll sync state
isScrolling: false, isScrolling: false,
@ -524,6 +527,7 @@ function noteApp() {
this.noteContent = ''; this.noteContent = '';
this.currentNoteName = ''; this.currentNoteName = '';
this.outline = []; this.outline = [];
this.backlinks = [];
this.shareInfo = null; // Reset share info this.shareInfo = null; // Reset share info
document.title = this.appName; document.title = this.appName;
@ -1315,6 +1319,7 @@ function noteApp() {
extractOutline(content) { extractOutline(content) {
if (!content) { if (!content) {
this.outline = []; this.outline = [];
this.backlinks = [];
return; return;
} }
@ -1429,6 +1434,11 @@ function noteApp() {
} }
} }
}, },
// Navigate to a backlink (note that links to current note)
navigateToBacklink(backlinkPath) {
this.loadNote(backlinkPath);
},
// Unified filtering logic combining tags and text search // Unified filtering logic combining tags and text search
async applyFilters() { async applyFilters() {
@ -2750,7 +2760,10 @@ function noteApp() {
// Extract outline for TOC panel // Extract outline for TOC panel
this.extractOutline(data.content); this.extractOutline(data.content);
// Store backlinks from API response
this.backlinks = data.backlinks || [];
// Initialize undo/redo history for this note (with cursor at start) // Initialize undo/redo history for this note (with cursor at start)
this.undoHistory = [{ content: data.content, cursorPos: 0 }]; this.undoHistory = [{ content: data.content, cursorPos: 0 }];
this.redoHistory = []; this.redoHistory = [];
@ -5625,6 +5638,7 @@ function noteApp() {
this.noteContent = ''; this.noteContent = '';
this.currentMedia = ''; this.currentMedia = '';
this.outline = []; this.outline = [];
this.backlinks = [];
document.title = this.appName; document.title = this.appName;
// Invalidate cache to force recalculation // Invalidate cache to force recalculation
@ -5647,6 +5661,7 @@ function noteApp() {
this.noteContent = ''; this.noteContent = '';
this.currentMedia = ''; this.currentMedia = '';
this.outline = []; this.outline = [];
this.backlinks = [];
this.mobileSidebarOpen = false; this.mobileSidebarOpen = false;
document.title = this.appName; document.title = this.appName;

View File

@ -1453,6 +1453,28 @@
x-text="outline.length > 9 ? '9+' : outline.length" x-text="outline.length > 9 ? '9+' : outline.length"
></span> ></span>
</button> </button>
<!-- Backlinks -->
<button
class="icon-rail-btn relative"
:class="{'active': activePanel === 'backlinks'}"
@click="activePanel = 'backlinks'"
:title="t('backlinks.title')"
:aria-label="t('backlinks.title')"
>
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 4h6m0 0v6m0-6L10 14"></path>
</svg>
<!-- Backlinks count badge -->
<span
x-show="backlinks.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="backlinks.length > 9 ? '9+' : backlinks.length"
></span>
</button>
<div class="icon-rail-spacer"></div> <div class="icon-rail-spacer"></div>
@ -1847,7 +1869,75 @@
</div> </div>
</div> </div>
<!-- END OUTLINE PANEL --> <!-- END OUTLINE PANEL -->
<!-- ==================== BACKLINKS PANEL ==================== -->
<div x-show="activePanel === 'backlinks'" 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">
<!-- Backlinks Header -->
<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('backlinks.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="backlinks.length"></span>
</div>
<!-- Scrollable content: Backlinks items -->
<div class="flex-1 overflow-y-auto custom-scrollbar">
<div class="py-2">
<!-- When note is open and has backlinks -->
<template x-if="currentNote && backlinks.length > 0">
<div>
<template x-for="(bl, index) in backlinks" :key="index">
<button
@click="navigateToBacklink(bl.path)"
class="hover-accent w-full text-left px-3 py-2 text-sm border-b"
style="color: var(--text-primary); border-color: var(--border-primary);"
>
<!-- Note name -->
<div class="flex items-center gap-2 mb-1">
<svg class="w-4 h-4 flex-shrink-0" style="color: var(--accent-primary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<span class="font-medium truncate" x-text="bl.name"></span>
</div>
<!-- References/Context snippets -->
<template x-for="(ref, refIdx) in bl.references.slice(0, 2)" :key="refIdx">
<div class="ml-6 text-xs truncate" style="color: var(--text-tertiary);">
<span class="opacity-60" x-text="'L' + ref.line_number + ':'"></span>
<span x-text="ref.context"></span>
</div>
</template>
<template x-if="bl.references.length > 2">
<div class="ml-6 text-xs" style="color: var(--text-tertiary);">
<span x-text="'+' + (bl.references.length - 2) + ' ' + t('backlinks.more_refs')"></span>
</div>
</template>
</button>
</template>
</div>
</template>
<!-- When note is open but no backlinks -->
<template x-if="currentNote && backlinks.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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 4h6m0 0v6m0-6L10 14"></path>
</svg>
<p class="text-sm mb-2" style="color: var(--text-tertiary);" x-text="t('backlinks.no_backlinks')"></p>
<p class="text-xs" style="color: var(--text-tertiary);" x-text="t('backlinks.hint')"></p>
</div>
</template>
<!-- When no note is open -->
<template x-if="!currentNote">
<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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<p class="text-sm" style="color: var(--text-tertiary);" x-text="t('backlinks.select_note')"></p>
</div>
</template>
</div>
</div>
</div>
<!-- END BACKLINKS 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

@ -156,6 +156,14 @@
"hint": "Überschriften mit # hinzufügen" "hint": "Überschriften mit # hinzufügen"
}, },
"backlinks": {
"title": "Rückverweise",
"no_backlinks": "Keine Rückverweise gefunden",
"hint": "Andere Notizen erscheinen hier, wenn sie auf diese Notiz mit [[Wikilinks]] verweisen",
"select_note": "Wählen Sie eine Notiz um Rückverweise zu sehen",
"more_refs": "mehr"
},
"stats": { "stats": {
"words": "Wörter", "words": "Wörter",
"reading_time": "~{{minutes}} Min. Lesezeit", "reading_time": "~{{minutes}} Min. Lesezeit",

View File

@ -155,6 +155,14 @@
"hint": "Add headings using # syntax" "hint": "Add headings using # syntax"
}, },
"backlinks": {
"title": "Backlinks",
"no_backlinks": "No backlinks found",
"hint": "Other notes will appear here when they link to this note using [[wikilinks]]",
"select_note": "Select a note to see backlinks",
"more_refs": "more"
},
"stats": { "stats": {
"words": "words", "words": "words",
"reading_time": "~{{minutes}}m read", "reading_time": "~{{minutes}}m read",

View File

@ -156,6 +156,14 @@
"hint": "Add headings using # syntax" "hint": "Add headings using # syntax"
}, },
"backlinks": {
"title": "Backlinks",
"no_backlinks": "No backlinks found",
"hint": "Other notes will appear here when they link to this note using [[wikilinks]]",
"select_note": "Select a note to see backlinks",
"more_refs": "more"
},
"stats": { "stats": {
"words": "words", "words": "words",
"reading_time": "~{{minutes}}m read", "reading_time": "~{{minutes}}m read",

View File

@ -156,6 +156,14 @@
"hint": "Añade encabezados usando sintaxis #" "hint": "Añade encabezados usando sintaxis #"
}, },
"backlinks": {
"title": "Backlinks",
"no_backlinks": "No se encontraron backlinks",
"hint": "Otras notas aparecerán aquí cuando enlacen a esta nota usando [[wikilinks]]",
"select_note": "Selecciona una nota para ver backlinks",
"more_refs": "más"
},
"stats": { "stats": {
"words": "palabras", "words": "palabras",
"reading_time": "~{{minutes}}m de lectura", "reading_time": "~{{minutes}}m de lectura",

View File

@ -156,6 +156,14 @@
"hint": "Ajoutez des titres avec la syntaxe #" "hint": "Ajoutez des titres avec la syntaxe #"
}, },
"backlinks": {
"title": "Rétroliens",
"no_backlinks": "Aucun rétrolien trouvé",
"hint": "D'autres notes apparaîtront ici lorsqu'elles feront référence à cette note avec des [[wikilinks]]",
"select_note": "Sélectionnez une note pour voir les rétroliens",
"more_refs": "de plus"
},
"stats": { "stats": {
"words": "mots", "words": "mots",
"reading_time": "~{{minutes}}m de lecture", "reading_time": "~{{minutes}}m de lecture",

View File

@ -156,6 +156,14 @@
"hint": "Fejléc hozzáadása a következővel: # " "hint": "Fejléc hozzáadása a következővel: # "
}, },
"backlinks": {
"title": "Visszahivatkozások",
"no_backlinks": "Nincsenek visszahivatkozások",
"hint": "Más jegyzetek itt jelennek meg, ha [[wikilink]]-kel hivatkoznak erre a jegyzetre",
"select_note": "Válasszon jegyzetet a visszahivatkozások megtekintéséhez",
"more_refs": "további"
},
"stats": { "stats": {
"words": "szavak", "words": "szavak",
"reading_time": "~{{minutes}} percnyi olvasás", "reading_time": "~{{minutes}} percnyi olvasás",

View File

@ -155,6 +155,14 @@
"hint": "Aggiungi titoli usando la sintassi #" "hint": "Aggiungi titoli usando la sintassi #"
}, },
"backlinks": {
"title": "Backlink",
"no_backlinks": "Nessun backlink trovato",
"hint": "Altre note appariranno qui quando fanno riferimento a questa nota usando [[wikilinks]]",
"select_note": "Seleziona una nota per vedere i backlink",
"more_refs": "altri"
},
"stats": { "stats": {
"words": "parole", "words": "parole",
"reading_time": "~{{minutes}}m lettura", "reading_time": "~{{minutes}}m lettura",

View File

@ -155,6 +155,14 @@
"hint": "# 記法で見出しを追加" "hint": "# 記法で見出しを追加"
}, },
"backlinks": {
"title": "バックリンク",
"no_backlinks": "バックリンクが見つかりません",
"hint": "他のノートが [[リンク]] でこのノートにリンクすると、ここに表示されます",
"select_note": "バックリンクを表示するノートを選択",
"more_refs": "その他"
},
"stats": { "stats": {
"words": "語", "words": "語",
"reading_time": "約{{minutes}}分で読了", "reading_time": "約{{minutes}}分で読了",

View File

@ -155,6 +155,14 @@
"hint": "Добавьте заголовки с помощью #" "hint": "Добавьте заголовки с помощью #"
}, },
"backlinks": {
"title": "Обратные ссылки",
"no_backlinks": "Обратные ссылки не найдены",
"hint": "Другие заметки появятся здесь, когда они ссылаются на эту заметку с помощью [[вики-ссылок]]",
"select_note": "Выберите заметку, чтобы увидеть обратные ссылки",
"more_refs": "ещё"
},
"stats": { "stats": {
"words": "слов", "words": "слов",
"reading_time": "~{{minutes}} мин. чтения", "reading_time": "~{{minutes}} мин. чтения",

View File

@ -155,6 +155,14 @@
"hint": "Dodajte naslove s sintakso #" "hint": "Dodajte naslove s sintakso #"
}, },
"backlinks": {
"title": "Povratne povezave",
"no_backlinks": "Ni najdenih povratnih povezav",
"hint": "Druge beležke se bodo pojavile tukaj, ko se bodo nanašale na to beležko z [[wikilinks]]",
"select_note": "Izberite beležko za ogled povratnih povezav",
"more_refs": "več"
},
"stats": { "stats": {
"words": "besed", "words": "besed",
"reading_time": "~{{minutes}} min branja", "reading_time": "~{{minutes}} min branja",

View File

@ -151,10 +151,18 @@
"outline": { "outline": {
"title": "大纲", "title": "大纲",
"no_headings": "未找到标题", "no_headings": "未找到标题",
"hint": "使用 # 语法添加标题" "hint": "使用 # 语法添加标题"
}, },
"backlinks": {
"title": "反向链接",
"no_backlinks": "未找到反向链接",
"hint": "当其他笔记使用 [[链接]] 链接到此笔记时,它们将显示在这里",
"select_note": "选择一个笔记以查看反向链接",
"more_refs": "更多"
},
"stats": { "stats": {
"words": "字数", "words": "字数",
"reading_time": "~{{minutes}}分钟阅读", "reading_time": "~{{minutes}}分钟阅读",