In meinem Artikel Eine Textauswahl im Browser in Obsidian speichern habe ich detailliert erklärt, wie ich auf der macOS-Plattform eine Text-Markierung in einem beliebigen Browser als Snippet in eine Obsidian-Notiz einfüge. Diese Lösung nutzt die Alfred App, das eine JavaScript-Skript sowie ein Python-Skript verwendet und mit allen Browsern funktioniert.
Für iOS und iPadOS musste ich jedoch eine abgespeckte Version des Workflows erstellen, die ausschließlich im Safari-Browser läuft. Dies liegt daran, JavaScript-Skripte via Kurzbefehle nur im Safari-Browser ausgeführt werden kann. Neben Kurzbefehle-App verwende ich zusätzlich die Erweiterung Actions for Obsidian von Carlo Zottmann. Diese Erweiterung kostet zwar ein paar Euro, die sich aber lohnen, wenn man mit den Kurzbefehlen Abläufe in Obsidian automatisieren möchte. Zum Testen reicht aber auch der kostenlose Testzeitraum.
Weitere Details zum Aufbau und zur Funktionsweise der Skripte finden sich im ersten Teil des vorherigen Artikels.
Der Kurzbefehl: Capture Browser Selection
Der Kurzbefehl einfacher aufgebaut als die Alfred Lösung für macOS, da die Erweiterung von “Actions for Obsidian” den Python Teil durch einen einfachen Aufruf einer Aktion ersetzt.
Als Eingabe wird nur das Share-Sheet ausgewählt und dort nur die Optionen für den Safari-Browser. Damit ist der Kontext für die nächste Aktion JavaScript im aktiven Safari-Tab ausführen
gesetzt. Das JavaScript, dass hier eingesetzt wird ist im Prinzip das gleiche, wie in der macOS Lösung, mit dem einem Unterschied, dass in der letzten Zeile statt main();
nun completion(main());
steht. Die Funktion completion()
ist notwendig, damit das Ergebnis an die nächste Aktion übergeben wird.
/** * Extracts the currently selected HTML from the browser window. * @returns {string} The HTML string of the selected content. */ function getSelectionHtml() { let html = ""; if (window.getSelection) { const sel = window.getSelection(); if (sel.rangeCount) { const container = document.createElement("div"); for (let i = 0, len = sel.rangeCount; i < len; ++i) { container.appendChild(sel.getRangeAt(i).cloneContents()); } html = container.innerHTML; } else { } } else { } return html; } /** * Converts HTML content into Markdown. * @param {string} html - HTML content to be converted. * @returns {string} The converted Markdown. */ function convertHtmlToMarkdown(html) { const doc = new DOMParser().parseFromString(html, 'text/html'); let markdown = convertNode(doc.body); return markdown.trim(); } /** * Converts an HTML node to its Markdown representation. * @param {Node} node - The HTML node to convert. * @returns {string} Markdown representation of the node. */ function convertNode(node) { let markdown = ''; if (node.nodeType === Node.ELEMENT_NODE) { switch (node.tagName) { case 'H1': case 'H2': case 'H3': case 'H4': case 'H5': case 'H6': markdown += `${'#'.repeat(parseInt(node.tagName[1]))} ${node.textContent.trim()}\n\n`; break; case 'P': markdown += `${node.textContent.trim()}\n\n`; break; case 'A': markdown += `[${node.textContent}](${node.href})`; break; case 'UL': case 'OL': markdown += convertList(node); break; case 'IMG': const alt = node.alt || 'Image'; const src = node.src || ''; const title = node.title ? ` "${node.title}"` : ''; markdown += `![${alt}](${src}${title})\n`; break; case 'PRE': case 'CODE': markdown += `\`\`\`\n${node.textContent}\n\`\`\`\n\n`; break; case 'TABLE': markdown += convertTableToMarkdown(node); break; default: node.childNodes.forEach(child => { markdown += convertNode(child); }); break; } } else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) { markdown += node.textContent.trim() + ' '; } return markdown; } /** * Converts HTML lists to Markdown. * @param {HTMLElement} list - The list element. * @returns {string} Markdown formatted list. */ function convertList(list) { let markdown = ''; const items = list.children; for (let item of items) { if (item.tagName === 'LI') { const prefix = list.tagName === 'OL' ? (Array.from(list.children).indexOf(item) + 1) + '.' : '-'; markdown += `${prefix} ${item.textContent.trim()}\n`; } } return markdown + '\n'; } /** * Converts HTML tables to Markdown. * @param {HTMLTableElement} table - The table element. * @returns {string} Markdown formatted table. */ function convertTableToMarkdown(table) { let markdown = ''; const rows = table.querySelectorAll('tr'); rows.forEach((row, index) => { let rowMarkdown = '|'; row.querySelectorAll('th, td').forEach(cell => { rowMarkdown += ` ${cell.textContent.trim()} |`; }); markdown += rowMarkdown + '\n'; // Add header separator for the first row if it contains headers if (index === 0 && row.querySelector('th')) { markdown += '|' + Array.from(row.children).map(() => ' --- ').join('|') + '|\n'; } }); return markdown + '\n'; } /** * Main function that orchestrates the conversion from HTML to Markdown. * @returns {void} */ function main() { const html = getSelectionHtml(); if (!html) { return; } let markdown = convertHtmlToMarkdown(html); const datum = new Date().toLocaleDateString(); const title = document.title; const url = window.location.href; const header = `\n\n>[\!snippets] ${datum} [${title}](${url})\n`; markdown = markdown.split('\n').map(line => '> ' + line).join('\n'); markdown = header + markdown; return (markdown); } completion(main());
Das Ergebnis wird nun in der Aktion Prepend
aus der Sammlung “Actions for Obsidian” übergeben, die dann auch noch mit dem Pfad zu der Notiz (bei mir 98 Adminstration/Bookmarks/Snippets), dem Namen der Vault, sowie dem Ort und dem Marker versehen wird. Als Pfad wird der interne Obsidian Pfad verwendet, daher muss hier nicht die Extension .md
mit angegeben werden.
Wenn der Kurzbefehl ausgeführt wird, wird neben Obsidian auch die App “Actions for Obsidian” aktiv, was wohl technische Gründe hat. Ich preferiere zur Kontrolle auf den Fokus auf der “Snippet” Datei zu haben, daher schliesse ich mit einer weiteren Aktion Open note
ab. Eigentlich findet sich unter den erweiterten Optionen der Prepend
-Aktion eine Option, die das automatisch erledigen sollte, die aber hier keine Auswirkung hat.
Wenn der Kurzbefehl definiert ist, kann er über das Share-Sheet im Safari-Browser aufgerufen werden. Komischerweise aber nur da und nicht über das Share-Sheet das an der Auswahl angezeigt wird.
Wie schon erwähnt, funktioniert der Kurzbefehl auch auf dem Mac, wobei es dort reicht den Kurzbefehl in der Menüleiste “anzupinnen” und darüber aufzurufen.
Schreibe einen Kommentar