Skip to content

Instantly share code, notes, and snippets.

@jppaolim
Forked from kepano/obsidian-web-clipper.js
Last active October 21, 2023 01:00
Show Gist options
  • Save jppaolim/97552a7c424a7ae5d2c17ce846eadeee to your computer and use it in GitHub Desktop.
Save jppaolim/97552a7c424a7ae5d2c17ce846eadeee to your computer and use it in GitHub Desktop.
Obsidian Web Clipper Bookmarklet to save articles and pages from the web (for Safari, Chrome, Firefox, and mobile browsers)

By @kepano

🎉 Support my work at buymeacoffee.com/kepano

Demo

You can find a demo of this bookmarklet on YouTube

Installation

Create a new bookmark in your browser, then copy/paste the minified code below into the URL field.

You can customize the output using the optional variables at the top, and the template at the bottom. The default template is designed for use with the Dataview plugin. If you make changes I recommend using Bookmarklet Maker to minify and URI encode the bookmarklet.

Usage

By default, clicking the bookmarklet creates a new Obsidian file from the main body of the article (similar to Readability view). Alternatively you can choose to create a file from a selection, by either selecting all (CMD+A), or just a portion of the page.

Any images in the content will be embedded as external references. If you want to download images locally you can use the Local Images plugin which allows you to download images for a note.

Troubleshooting

This bookmarklet may not work on all websites. If you run into issues, you can also try the MarkDownload browser extension which provides similar functionality. You can troubleshoot issues by opening the Developer Console in your browser and checking if any errors appear when you click the bookmarklet. The most common error is that a website or the browser itself is blocking third party code execution. Unfortunately there is no good solve for that yet.

javascript: (async () => {
const [{ default: Turndown }, readabilityModule] = await Promise.all([
import('https://unpkg.com/turndown?module'),
import('https://cdn.skypack.dev/@mozilla/readability')
]);
const Readability = readabilityModule.Readability;
/* Optional vault name */
const vault = "Main";
/* Optional folder name such as "Clippings/" */
const folder = "Inbox/Capture/";
/* Optional tags */
var tagLines = ['tags:'];
tagLines.push(' - AI'); // The initial "AI" tag
/* Parse the site's meta keywords content into tags, if present --> from experience it's not so interesting so commenting*/
if (document.querySelector('meta[name="keywords" i]')) {
var keywords = document.querySelector('meta[name="keywords" i]').getAttribute('content').split(',');
keywords.forEach(function(keyword) {
let tag = keyword.trim();
//tagLines.push(' - ' + tag); // Add each keyword as a new list item uncomment if you want to capture them.
});
}
const tagsYAML = tagLines.join('\n'); // Join each line into a single string
function getSelectionHtml() {
var html = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
html = document.selection.createRange().htmlText;
}
}
return html;
}
const selection = getSelectionHtml();
const {
title,
byline,
content
} = new Readability(document.cloneNode(true)).parse();
function sanitizeYAMLstring(str) {
return str.replace(/["'“”‘’]/g, '');
}
const sanitizedTitle = sanitizeYAMLstring(title);
function getFileName(fileName) {
const invalidChars = /[:/\\?%*|"<>]/g;
fileName = fileName.replace(invalidChars, ' - ');
fileName = fileName.replace(/\s+/g, ' ').trim();
return fileName;
}
function fixMarkdownLinks(text) {
const regex = /\[\s*\n*!\[\](?:\()([^\)]+)(?:\))\s*\n*\]\(([^)]+)\)/g;
return text.replace(regex, "[![]($1)]($2)");
}
const fileName = getFileName(sanitizedTitle);
if (selection) {
var markdownify = selection;
} else {
var markdownify = content;
}
if (vault) {
var vaultName = '&vault=' + encodeURIComponent(`${vault}`);
} else {
var vaultName = '';
}
const markdownBodyTemp = new Turndown({
headingStyle: 'atx',
hr: '---',
bulletListMarker: '-',
codeBlockStyle: 'fenced',
emDelimiter: '*',
}).turndown(markdownify);
const markdownBody = fixMarkdownLinks(markdownBodyTemp);
var date = new Date();
function convertDate(date) {
var yyyy = date.getFullYear().toString();
var mm = (date.getMonth()+1).toString();
var dd = date.getDate().toString();
var mmChars = mm.split('');
var ddChars = dd.split('');
return yyyy + '-' + (mmChars[1]?mm:"0"+mmChars[0]) + '-' + (ddChars[1]?dd:"0"+ddChars[0]);
}
const today = convertDate(date);
// Utility function to get meta content by name or property
function getMetaContent(attr, value) {
var element = document.querySelector(`meta[${attr}='${value}']`);
return element ? element.getAttribute("content").trim() : "";
}
// Fetch byline, meta author, property author, or site name
var author = byline || getMetaContent("name", "author") || getMetaContent("property", "author") || getMetaContent("property", "og:site_name");
// Check if there's an author and add brackets
var authorBrackets = author ? `"[[${author}]]"` : "";
// Get descriptino
var desc = getMetaContent("name", "description") || getMetaContent("property", "description") || getMetaContent("property", "og:description"); ;
const sanitizedDesc = sanitizeYAMLstring(desc)
/* YAML front matter as tags render cleaner with special chars */
const fileContent =
'---\n'
+ 'category: "[[Clippings]]"\n'
+ 'author: ' + authorBrackets + '\n'
+ 'title: "' + sanitizedTitle + '"\n'
+ 'source: ' + document.URL + '\n'
+ 'clipped: ' + today + '\n'
+ 'description: "' + sanitizedDesc + '"\n'
+ 'summary: "' + '"\n'
+ tagsYAML + '\n' // Include the tags in the new format
+ "publish: false\n"
+ '---\n\n'
+ "# "+ fileName +'"\n'
+ markdownBody ;
document.location.href = "obsidian://new?"
+ "file=" + encodeURIComponent(folder + fileName)
+ "&content=" + encodeURIComponent(fileContent)
+ vaultName ;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment