From 8ba181f63396e4b048f75b068996eaf7139d8e07 Mon Sep 17 00:00:00 2001 From: Harm te Molder Date: Fri, 17 Feb 2023 09:41:51 +0100 Subject: [PATCH 1/2] Update test vault to include Hot Reload And manifest.json, and exclude Obsidian settings. Also add EditorConfig --- .editorconfig | 14 +++ .gitignore | 67 +++++----- .vscode/settings.json | 3 + docs/CONTRIBUTING.md | 8 +- docs/test-vault/.obsidian/app.json | 4 - docs/test-vault/.obsidian/appearance.json | 3 - docs/test-vault/.obsidian/core-plugins.json | 15 --- docs/test-vault/.obsidian/hotkeys.json | 1 - .../.hotreload | 0 .../.obsidian/plugins/flashcards/data.json | 13 -- .../.obsidian/plugins/hot-reload/main.js | 115 ++++++++++++++++++ package.json | 2 +- 12 files changed, 174 insertions(+), 71 deletions(-) create mode 100755 .editorconfig create mode 100644 .vscode/settings.json delete mode 100644 docs/test-vault/.obsidian/app.json delete mode 100644 docs/test-vault/.obsidian/appearance.json delete mode 100644 docs/test-vault/.obsidian/core-plugins.json delete mode 100644 docs/test-vault/.obsidian/hotkeys.json rename docs/test-vault/.obsidian/plugins/{flashcards => flashcards-obsidian}/.hotreload (100%) delete mode 100644 docs/test-vault/.obsidian/plugins/flashcards/data.json create mode 100644 docs/test-vault/.obsidian/plugins/hot-reload/main.js diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..a3ebc62 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 80 +quote_style = single +trim_trailing_whitespace = true + +[{*.js, *.json, *.ts}] +quote_style = double diff --git a/.gitignore b/.gitignore index da93af9..c679350 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,34 @@ -# Intellij -*.iml -.idea - -# npm -node_modules -package-lock.json - -# build -main.js -*.js.map -docs/test-vault/.obsidian/plugins/flashcards/main.js -docs/test-vault/.obsidian/plugins/flashcards/manifest.json - -# scripts -move.sh - -# Visual Studio Code -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Github -.DS_Store - -# Test-vault -docs/test-vault/.obsidian/workspace +# Obsidian +data.json + +# IntelliJ +.idea/ +*.iml + +# npm +node_modules/ +package-lock.json + +# Build +main.js + +# Scripts +move.sh + +# Visual Studio Code +.vscode/ +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/ + +# macOS +.DS_Store + +# Test vault +docs/test-vault/.obsidian/*.json +!docs/test-vault/.obsidian/community-plugins.json +docs/test-vault/.obsidian/plugins/flashcards-obsidian/* +!docs/test-vault/.obsidian/plugins/flashcards-obsidian/.hotreload +!docs/test-vault/.obsidian/plugins/hot-reload/* diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5e97a9e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "standard.enable": false +} diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 0508d30..b826fbe 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,10 +1,16 @@ # Contributing + Contributions via bug reports, bug fixes, are welcome. If you have ideas about features to be implemented, please open an issue so we can discuss the best way to implement it. ## How to build? + You need to pull the repository, install the dependencies with `node` and then build with the command `npm run dev`. It will automatically move the files into the `docs/test-vault` and hot reload the plugin. $ git clone git@github.com:reuseman/flashcards-obsidian.git $ cd flashcards-obsidian ~/flashcards-obsidian$ npm install - ~/flashcards-obsidian$ npm run dev \ No newline at end of file + ~/flashcards-obsidian$ npm run dev + +### Hot Reload + +You need to download `main.js` and `manifest.json` from https://github.com/pjeby/hot-reload into `docs/test-vault/.obsidian/plugins/hot-reload` if you want to use the hot reload functionality mentioned above. diff --git a/docs/test-vault/.obsidian/app.json b/docs/test-vault/.obsidian/app.json deleted file mode 100644 index bf21f23..0000000 --- a/docs/test-vault/.obsidian/app.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "legacyEditor": false, - "livePreview": true -} \ No newline at end of file diff --git a/docs/test-vault/.obsidian/appearance.json b/docs/test-vault/.obsidian/appearance.json deleted file mode 100644 index 990f337..0000000 --- a/docs/test-vault/.obsidian/appearance.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "baseFontSize": 16 -} \ No newline at end of file diff --git a/docs/test-vault/.obsidian/core-plugins.json b/docs/test-vault/.obsidian/core-plugins.json deleted file mode 100644 index ab1d511..0000000 --- a/docs/test-vault/.obsidian/core-plugins.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - "file-explorer", - "global-search", - "switcher", - "graph", - "backlink", - "page-preview", - "note-composer", - "command-palette", - "editor-status", - "markdown-importer", - "word-count", - "open-with-default-app", - "file-recovery" -] \ No newline at end of file diff --git a/docs/test-vault/.obsidian/hotkeys.json b/docs/test-vault/.obsidian/hotkeys.json deleted file mode 100644 index 9e26dfe..0000000 --- a/docs/test-vault/.obsidian/hotkeys.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/docs/test-vault/.obsidian/plugins/flashcards/.hotreload b/docs/test-vault/.obsidian/plugins/flashcards-obsidian/.hotreload similarity index 100% rename from docs/test-vault/.obsidian/plugins/flashcards/.hotreload rename to docs/test-vault/.obsidian/plugins/flashcards-obsidian/.hotreload diff --git a/docs/test-vault/.obsidian/plugins/flashcards/data.json b/docs/test-vault/.obsidian/plugins/flashcards/data.json deleted file mode 100644 index 5701d93..0000000 --- a/docs/test-vault/.obsidian/plugins/flashcards/data.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "contextAwareMode": true, - "sourceSupport": false, - "codeHighlightSupport": false, - "inlineID": false, - "contextSeparator": " > ", - "deck": "Default", - "flashcardsTag": "card", - "inlineSeparator": "::", - "inlineSeparatorReverse": ":::", - "defaultAnkiTag": "obsidian", - "ankiConnectPermission": false -} \ No newline at end of file diff --git a/docs/test-vault/.obsidian/plugins/hot-reload/main.js b/docs/test-vault/.obsidian/plugins/hot-reload/main.js new file mode 100644 index 0000000..0502508 --- /dev/null +++ b/docs/test-vault/.obsidian/plugins/hot-reload/main.js @@ -0,0 +1,115 @@ +const {Plugin, Notice} = require("obsidian"); +const fs = require("fs"); + +const watchNeeded = window.process.platform !== "darwin" && window.process.platform !== "win32"; + +module.exports = class HotReload extends Plugin { + + statCache = new Map(); // path -> Stat + + onload() { this.app.workspace.onLayoutReady( this._onload.bind(this) ); } + + async _onload() { + this.pluginReloaders = {}; + this.inProgress = null; + await this.getPluginNames(); + this.reindexPlugins = this.debouncedMethod(500, this.getPluginNames); + this.registerEvent( this.app.vault.on("raw", this.onFileChange.bind(this)) ); + this.watch(".obsidian/plugins"); + await this.checkVersions(); + this.addCommand({ + id: "scan-for-changes", + name: "Check plugins for changes and reload them", + callback: () => this.checkVersions() + }) + } + + watch(path) { + if (this.app.vault.adapter.watchers.hasOwnProperty(path)) return; + const realPath = [this.app.vault.adapter.basePath, path].join("/"); + const lstat = fs.lstatSync(realPath); + if (lstat && (watchNeeded || lstat.isSymbolicLink()) && fs.statSync(realPath).isDirectory()) { + this.app.vault.adapter.startWatchPath(path, false); + } + } + + async checkVersions() { + const base = this.app.plugins.getPluginFolder(); + for (const dir of Object.keys(this.pluginNames)) { + for (const file of ["manifest.json", "main.js", "styles.css", ".hotreload"]) { + const path = `${base}/${dir}/${file}`; + const stat = await app.vault.adapter.stat(path); + if (stat) { + if (this.statCache.has(path) && stat.mtime !== this.statCache.get(path).mtime) { + this.onFileChange(path); + } + this.statCache.set(path, stat); + } + } + } + } + + async getPluginNames() { + const plugins = {}, enabled = new Set(); + for (const {id, dir} of Object.values(app.plugins.manifests)) { + this.watch(dir); + plugins[dir.split("/").pop()] = id; + if ( + await this.app.vault.exists(dir+"/.git") || + await this.app.vault.exists(dir+"/.hotreload") + ) enabled.add(id); + } + this.pluginNames = plugins; + this.enabledPlugins = enabled; + } + + onFileChange(filename) { + if (!filename.startsWith(this.app.plugins.getPluginFolder()+"/")) return; + const path = filename.split("/"); + const base = path.pop(), dir = path.pop(); + if (path.length === 1 && dir === "plugins") return this.watch(filename); + if (path.length != 2) return; + const plugin = dir && this.pluginNames[dir]; + if (base === "manifest.json" || base === ".hotreload" || base === ".git" || !plugin) return this.reindexPlugins(); + if (base !== "main.js" && base !== "styles.css") return; + if (!this.enabledPlugins.has(plugin)) return; + const reloader = this.pluginReloaders[plugin] || ( + this.pluginReloaders[plugin] = this.debouncedMethod(750, this.requestReload, plugin) + ); + reloader(); + } + + requestReload(plugin) { + const next = this.inProgress = this.reload(plugin, this.inProgress); + next.finally(() => {if (this.inProgress === next) this.inProgress = null;}) + } + + async reload(plugin, previous) { + const plugins = this.app.plugins; + try { + // Wait for any other queued/in-progress reloads to finish + await previous; + await plugins.disablePlugin(plugin); + console.debug("disabled", plugin); + // Ensure sourcemaps are loaded (Obsidian 14+) + const oldDebug = localStorage.getItem("debug-plugin"); + localStorage.setItem("debug-plugin", "1"); + try { + await plugins.enablePlugin(plugin); + } finally { + // Restore previous setting + if (oldDebug === null) localStorage.removeItem("debug-plugin"); else localStorage.setItem("debug-plugin", oldDebug); + } + console.debug("enabled", plugin); + new Notice(`Plugin "${plugin}" has been reloaded`); + } catch(e) {} + } + + debouncedMethod(ms, func, ...args) { + var timeout; + return () => { + if (timeout) clearTimeout(timeout); + timeout = setTimeout( () => { timeout = null; func.apply(this, args); }, ms); + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index b58eeae..d86a0e9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "An Anki integration.", "main": "main.js", "scripts": { - "dev": "rollup --config rollup.config.js -w --environment BUILD:dev", + "dev": "cp manifest.json docs/test-vault/.obsidian/plugins/flashcards-obsidian/ && rollup --config rollup.config.js -w --environment BUILD:dev", "build": "rollup --config rollup.config.js --environment BUILD:production", "test": "jest" }, From e715bc1bb603e95c9e40f819df08348784a7d59f Mon Sep 17 00:00:00 2001 From: Harm te Molder Date: Fri, 17 Feb 2023 10:32:39 +0100 Subject: [PATCH 2/2] Add check if notes exist in Anki before querying them --- src/services/anki.ts | 8 ++++++-- src/services/cards.ts | 13 +++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/services/anki.ts b/src/services/anki.ts index 3f04a4a..7109f9e 100644 --- a/src/services/anki.ts +++ b/src/services/anki.ts @@ -139,7 +139,11 @@ export class Anki { return await this.invoke("cardsInfo", 6, { cards: ids }); } - public async getCards(ids: number[]) { + public async findNotes(deckName: string) { + return await this.invoke("findNotes", 6, { query: `deck:"${deckName}"` }); + } + + public async notesInfo(ids: number[]) { return await this.invoke("notesInfo", 6, { notes: ids }); } @@ -304,7 +308,7 @@ export class Anki { }, ], }, - + } const obsidianSpaced = { diff --git a/src/services/cards.ts b/src/services/cards.ts index 238ea86..678b4e6 100644 --- a/src/services/cards.ts +++ b/src/services/cards.ts @@ -60,7 +60,10 @@ export class CardsService { let deckName = ""; if (parseFrontMatterEntry(frontmatter, "cards-deck")) { deckName = parseFrontMatterEntry(frontmatter, "cards-deck"); - } else if (this.settings.folderBasedDeck && activeFile.parent.path !== "/") { + } else if ( + this.settings.folderBasedDeck && + activeFile.parent.path !== "/" + ) { // If the current file is in the path "programming/java/strings.md" then the deck name is "programming::java" deckName = activeFile.parent.path.split("/").join("::"); } else { @@ -81,8 +84,14 @@ export class CardsService { globalTags = this.parseGlobalTags(this.file); // TODO with empty check that does not call ankiCards line const ankiBlocks = this.parser.getAnkiIDsBlocks(this.file); + const ankiIDs = this.getAnkiIDs(ankiBlocks); + const ankiNotes = await this.anki.findNotes(deckName); + // const inObsidianNotAnki = ankiIDs.filter((x) => !ankiNotes.includes(x)); + // const inAnkiNotObsidian = ankiNotes.filter((x) => !ankiIDs.includes(x)); + const inBoth = ankiIDs.filter((x) => ankiNotes.includes(x)); + const ankiCards = ankiBlocks - ? await this.anki.getCards(this.getAnkiIDs(ankiBlocks)) + ? await this.anki.notesInfo(inBoth) : undefined; const cards: Card[] = this.parser.generateFlashcards(