Skip to content

Commit 79587eb

Browse files
committed
feat: Exact URL backlinking via Obsidian URI custom protocol
1 parent 5f4ed17 commit 79587eb

4 files changed

Lines changed: 47 additions & 9 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ This removes all Anki note references from the file and deletes the correspondin
134134
Embed `.excalidraw` drawings seamlessly. They are automatically converted to images on sync.
135135
- **Math auto-formatting**
136136
Dollar-delimited LaTeX in Obsidian becomes nicely rendered in Anki.
137-
- **Automatic deletion**
137+
- [x] Automatic deletion
138138
Delete a card in Obsidian and the corresponding Anki card is removed on sync. **IMPORTANT: don't delete the Anki-ID below manually**
139-
- **Backlinks**
140-
Each card carries a URL back to its source note for easy context retrieval.
139+
- **Precise Backlinks**
140+
Each card carries a URL back to its source note. Clicking it from Anki will open Obsidian and scroll you down directly to the flashcard's exact row!
141141
- **Anki-Tags**
142142
Each card in Anki has a assigned tag `obsidian_simple_anki_sync_created` to easely filter for this automatically created cards.
143143

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "simple-anki-sync",
33
"name": "Simple Anki Sync",
4-
"version": "1.5.0",
4+
"version": "1.5.1",
55
"minAppVersion": "0.15.0",
66
"description": "The simplest way of syncing simple Flashchards with Anki.",
77
"author": "Lukas Mayr",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "simple-anki-sync",
3-
"version": "1.5.0",
3+
"version": "1.5.1",
44
"description": "Personal Anki Sync Plugin for Obsidian.",
55
"main": "main.js",
66
"scripts": {

src/main.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Plugin, TFile, MarkdownView, Notice, arrayBufferToBase64 } from 'obsidian';
1+
import { Plugin, TFile, MarkdownView, Notice, arrayBufferToBase64, ObsidianProtocolData } from 'obsidian';
22
import { AnkiService } from './anki-service';
33
import { ObsidianNote, ProcessedMediaResult } from './types';
44
import { DEFAULT_SETTINGS, SimpleAnkiSyncSettingTab, SimpleAnkiSyncSettings } from './settings';
@@ -114,6 +114,40 @@ export default class SimpleAnkiSyncPlugin extends Plugin {
114114
return;
115115
},
116116
});
117+
118+
this.registerObsidianProtocolHandler('simple-anki-sync', this.handleCustomProtocol.bind(this));
119+
}
120+
121+
private async handleCustomProtocol(params: ObsidianProtocolData) {
122+
if (params.action === 'simple-anki-sync') {
123+
const { file, noteId } = params;
124+
if (!file || !noteId) return;
125+
126+
const abstractFile = this.app.vault.getAbstractFileByPath(file);
127+
if (!(abstractFile instanceof TFile)) return;
128+
129+
const leaf = this.app.workspace.getLeaf(false);
130+
await leaf.openFile(abstractFile);
131+
132+
const content = await this.app.vault.read(abstractFile);
133+
const lines = content.split('\n');
134+
135+
const searchFor = `<!--ANKI_NOTE_ID:${noteId}-->`;
136+
const lineNumber = lines.findIndex(line => line.includes(searchFor));
137+
138+
if (lineNumber !== -1) {
139+
// Find where the trailing Anki comment or `<br>` tags start, so we can place the cursor exactly there
140+
const line = lines[lineNumber];
141+
const match = line.match(/(?:<br\s*\/?>\s*)*<!--ANKI_NOTE_ID:\d+-->/i);
142+
const column = match ? match.index || 0 : 0;
143+
144+
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
145+
if (view && view.editor) { // only if in edit mode
146+
view.editor.setCursor(lineNumber, column);
147+
view.editor.scrollIntoView({ from: { line: lineNumber, ch: 0 }, to: { line: lineNumber, ch: 0 } }, true);
148+
}
149+
}
150+
}
117151
}
118152

119153
onunload() {
@@ -472,9 +506,13 @@ export default class SimpleAnkiSyncPlugin extends Plugin {
472506

473507
// Obsidian-Link
474508
const vault = this.app.vault.getName();
475-
const url = `obsidian://open?vault=${encodeURIComponent(vault)}&file=${encodeURIComponent(
476-
file.path
477-
)}`;
509+
let url = '';
510+
if (note.noteId) {
511+
url = `obsidian://simple-anki-sync?vault=${encodeURIComponent(vault)}&file=${encodeURIComponent(file.path)}&noteId=${note.noteId}`;
512+
} else {
513+
url = `obsidian://open?vault=${encodeURIComponent(vault)}&file=${encodeURIComponent(file.path)}`;
514+
}
515+
478516
backHtml += `<br><small><a href="${url}" style="text-decoration:none;color:grey;font-size:0.8em;">Obsidian Note</a></small>`;
479517

480518
const fields = { Front: frontHtml, Back: backHtml };

0 commit comments

Comments
 (0)