From 71c6b8e7ba964cfb11dbccbbd178abe62d3a7295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Niclas=20W=C3=A4chtler?= Date: Thu, 17 Apr 2025 23:11:21 +0200 Subject: [PATCH] add baseline pdf2img functionality (THIS DOES NOT WORK! wrong package :)) --- main.ts | 178 ++++++++++++++++++++++++++++++++++++++------------ manifest.json | 14 ++-- 2 files changed, 145 insertions(+), 47 deletions(-) diff --git a/main.ts b/main.ts index 2d07212..33f3f13 100644 --- a/main.ts +++ b/main.ts @@ -1,4 +1,16 @@ -import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; +import { + App, + Editor, + MarkdownView, + Modal, + Notice, + Plugin, + PluginSettingTab, + Setting, + TFile, + moment, +} from "obsidian"; +import { Options, pdf } from "pdf-to-img"; // Remember to rename these classes and interfaces! @@ -7,51 +19,64 @@ interface MyPluginSettings { } const DEFAULT_SETTINGS: MyPluginSettings = { - mySetting: 'default' -} + mySetting: "default", +}; export default class MyPlugin extends Plugin { settings: MyPluginSettings; async onload() { + console.log("loading plugin"); await this.loadSettings(); // This creates an icon in the left ribbon. - const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => { - // Called when the user clicks the icon. - new Notice('This is a notice!'); - }); + const ribbonIconEl = this.addRibbonIcon( + "dice", + "hier klicken!", + (evt: MouseEvent) => { + // Called when the user clicks the icon. + new Notice("ich liebe dich"); + } + ); // Perform additional things with the ribbon - ribbonIconEl.addClass('my-plugin-ribbon-class'); + ribbonIconEl.addClass("my-plugin-ribbon-class"); // This adds a status bar item to the bottom of the app. Does not work on mobile apps. const statusBarItemEl = this.addStatusBarItem(); - statusBarItemEl.setText('Status Bar Text'); + statusBarItemEl.setText("Status Bar Text"); // This adds a simple command that can be triggered anywhere this.addCommand({ - id: 'open-sample-modal-simple', - name: 'Open sample modal (simple)', + id: "open-sample-modal-simple", + name: "Open sample modal (simple)", callback: () => { new SampleModal(this.app).open(); - } + }, }); // This adds an editor command that can perform some operation on the current editor instance this.addCommand({ - id: 'sample-editor-command', - name: 'Sample editor command', - editorCallback: (editor: Editor, view: MarkdownView) => { - console.log(editor.getSelection()); - editor.replaceSelection('Sample Editor Command'); - } + id: "convert-pdf-to-images", + name: "Convert PDF to images", + editorCallback: async (editor: Editor, view: MarkdownView) => { + const selectedText = editor.getSelection(); + const pdfFile = this.fetchFileFromMdPath(selectedText); + if (!pdfFile) { + return; + } + const pngFiles = await this.parsePDF(pdfFile, { scale: 2 }); + editor.replaceSelection( + pngFiles.map((f) => `![[${f}]]`).join("\n") + ); + }, }); // This adds a complex command that can check whether the current state of the app allows execution of the command this.addCommand({ - id: 'open-sample-modal-complex', - name: 'Open sample modal (complex)', + id: "open-sample-modal-complex", + name: "Open sample modal (complex)", checkCallback: (checking: boolean) => { // Conditions to check - const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView); + const markdownView = + this.app.workspace.getActiveViewOfType(MarkdownView); if (markdownView) { // If checking is true, we're simply "checking" if the command can be run. // If checking is false, then we want to actually perform the operation. @@ -62,7 +87,7 @@ export default class MyPlugin extends Plugin { // This command will only show up in Command Palette when the check function returns true return true; } - } + }, }); // This adds a settings tab so the user can configure various aspects of the plugin @@ -70,25 +95,96 @@ export default class MyPlugin extends Plugin { // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin) // Using this function will automatically remove the event listener when this plugin is disabled. - this.registerDomEvent(document, 'click', (evt: MouseEvent) => { - console.log('click', evt); + this.registerDomEvent(document, "click", (evt: MouseEvent) => { + console.log("click", evt); }); // When registering intervals, this function will automatically clear the interval when the plugin is disabled. - this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000)); + this.registerInterval( + window.setInterval(() => console.log("setInterval"), 5 * 60 * 1000) + ); } onunload() { - + console.log("unloading plugin"); } async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + this.settings = Object.assign( + {}, + DEFAULT_SETTINGS, + await this.loadData() + ); } async saveSettings() { await this.saveData(this.settings); } + + private checkPathInput(input: string): string | null { + const matches = input.match(/!\[\[([^\]]+)\]\]/); // match ![[something]], -> something (capture group 1) + if (matches && matches[1]) { + return matches[1]; + } + return null; + } + + private fetchVaultFile(path: string): TFile | null { + const vaultFiles = this.app.vault.getFiles(); + const file = + vaultFiles.find((f) => f.path === path) ?? + vaultFiles.find((f) => f.path.contains(path)); // we attempt to find the file by path, if that fails we try to find it by substring + if (!file) { + new Notice(`File not found: ${path}.`); + return null; + } + return file; + } + + /** + * attempts to parse a string as a file path + * @param input the string to parse + * @returns the file if it is a valid file path, null otherwise + */ + private fetchFileFromMdPath(input: string): TFile | null { + const path = input.trim(); + if (!path) { + new Notice("Please select a valid PDF file link."); + return null; + } + const filePath = this.checkPathInput(path); + if (!filePath) { + new Notice( + `Invalid file path: ${path}. Please highlight a valid PDF file ![[link.pdf]].` + ); + return null; + } + const file = this.fetchVaultFile(filePath); + if (!file) { + new Notice(`File not found: ${filePath}.`); + return null; + } + if (file.extension !== "pdf") { + new Notice(`File is not a PDF: ${filePath}.`); + return null; + } + return file; + } + + async parsePDF(file: TFile, options: Options): Promise { + const document = await pdf(file.path, options); + let i = 1; + let fileNames: string[] = []; + for await (const image of document) { + const newFile = await this.app.vault.createBinary( + file.basename + `2img-${i}.png`, + image + ); + i++; + fileNames.push(newFile.path); + } + return fileNames; + } } class SampleModal extends Modal { @@ -97,12 +193,12 @@ class SampleModal extends Modal { } onOpen() { - const {contentEl} = this; - contentEl.setText('Woah!'); + const { contentEl } = this; + contentEl.setText("Woah!"); } onClose() { - const {contentEl} = this; + const { contentEl } = this; contentEl.empty(); } } @@ -116,19 +212,21 @@ class SampleSettingTab extends PluginSettingTab { } display(): void { - const {containerEl} = this; + const { containerEl } = this; containerEl.empty(); new Setting(containerEl) - .setName('Setting #1') - .setDesc('It\'s a secret') - .addText(text => text - .setPlaceholder('Enter your secret') - .setValue(this.plugin.settings.mySetting) - .onChange(async (value) => { - this.plugin.settings.mySetting = value; - await this.plugin.saveSettings(); - })); + .setName("Setting #1") + .setDesc("It's a secret") + .addText((text) => + text + .setPlaceholder("Enter your secret") + .setValue(this.plugin.settings.mySetting) + .onChange(async (value) => { + this.plugin.settings.mySetting = value; + await this.plugin.saveSettings(); + }) + ); } } diff --git a/manifest.json b/manifest.json index dfa940e..efb0bd8 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,11 @@ { - "id": "sample-plugin", - "name": "Sample Plugin", - "version": "1.0.0", + "id": "pdf-annotator", + "name": "PDF Annotator", + "version": "0.0.1", "minAppVersion": "0.15.0", - "description": "Demonstrates some of the capabilities of the Obsidian API.", + "description": "A tool that allows greater annotation of PDFs.", "author": "Obsidian", - "authorUrl": "https://obsidian.md", - "fundingUrl": "https://obsidian.md/pricing", - "isDesktopOnly": false + "authorUrl": "https://github.com/cubexy", + "fundingUrl": "https://www.tierschutzbund.de/helfen/spenden/jetzt-spenden/", + "isDesktopOnly": true }