Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Obsidian Bookmarklet to clip pages

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.

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: Promise.all([import('https://unpkg.com/turndown@6.0.0?module'), import('https://unpkg.com/@tehshrike/readability@0.2.0'), ]).then(async ([{
default: Turndown
}, {
default: Readability
}]) => {
/* Optional vault name */
const vault = "";
/* Optional folder name such as "Clippings/" */
const folder = "";
/* Optional tags */
const tags = "#clippings";
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 getFileName(fileName) {
var userAgent = window.navigator.userAgent,
platform = window.navigator.platform,
windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'];
if (windowsPlatforms.indexOf(platform) !== -1) {
fileName = fileName.replace(':', '').replace(/[/\\?%*|"<>]/g, '-');
} else {
fileName = fileName.replace(':', '').replace(/\//g, '-').replace(/\\/g, '-');
}
return fileName;
}
const fileName = getFileName(title);
if (selection) {
var markdownify = selection;
} else {
var markdownify = content;
}
if (vault) {
var vaultName = '&vault=' + encodeURIComponent(`${vault}`);
} else {
var vaultName = '';
}
const markdownBody = new Turndown({
headingStyle: 'atx',
hr: '---',
bulletListMarker: '-',
codeBlockStyle: 'fenced',
emDelimiter: '*',
}).turndown(markdownify);
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);
const fileContent =
"author:: " + byline + "\n"
+ "source:: [" + title + "](" + document.URL + ")\n"
+ "clipped:: [[" + today + "]]\n"
+ "published:: \n\n"
+ tags + "\n\n"
+ markdownBody ;
document.location.href = "obsidian://new?"
+ "file=" + encodeURIComponent(folder + fileName)
+ "&content=" + encodeURIComponent(fileContent)
+ vaultName ;
})
javascript:(function()%7Bjavascript%3A%20Promise.all(%5Bimport('https%3A%2F%2Funpkg.com%2Fturndown%406.0.0%3Fmodule')%2C%20import('https%3A%2F%2Funpkg.com%2F%40tehshrike%2Freadability%400.2.0')%2C%20%5D).then(async%20(%5B%7B%0A%20%20%20%20default%3A%20Turndown%0A%7D%2C%20%7B%0A%20%20%20%20default%3A%20Readability%0A%7D%5D)%20%3D%3E%20%7B%0A%0A%20%20%2F*%20Optional%20vault%20name%20*%2F%0A%20%20const%20vault%20%3D%20%22%22%3B%0A%0A%20%20%2F*%20Optional%20folder%20name%20such%20as%20%22Clippings%2F%22%20*%2F%0A%20%20const%20folder%20%3D%20%22%22%3B%0A%0A%20%20%2F*%20Optional%20tags%20%20*%2F%0A%20%20const%20tags%20%3D%20%22%23clippings%22%3B%0A%0A%20%20function%20getSelectionHtml()%20%7B%0A%20%20%20%20var%20html%20%3D%20%22%22%3B%0A%20%20%20%20if%20(typeof%20window.getSelection%20!%3D%20%22undefined%22)%20%7B%0A%20%20%20%20%20%20%20%20var%20sel%20%3D%20window.getSelection()%3B%0A%20%20%20%20%20%20%20%20if%20(sel.rangeCount)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20container%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(var%20i%20%3D%200%2C%20len%20%3D%20sel.rangeCount%3B%20i%20%3C%20len%3B%20%2B%2Bi)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20container.appendChild(sel.getRangeAt(i).cloneContents())%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20html%20%3D%20container.innerHTML%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%20else%20if%20(typeof%20document.selection%20!%3D%20%22undefined%22)%20%7B%0A%20%20%20%20%20%20%20%20if%20(document.selection.type%20%3D%3D%20%22Text%22)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20html%20%3D%20document.selection.createRange().htmlText%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%20%20return%20html%3B%0A%20%20%7D%0A%0A%20%20const%20selection%20%3D%20getSelectionHtml()%3B%0A%0A%20%20const%20%7B%0A%20%20%20%20%20%20title%2C%0A%20%20%20%20%20%20byline%2C%0A%20%20%20%20%20%20content%0A%20%20%7D%20%3D%20new%20Readability(document.cloneNode(true)).parse()%3B%0A%0A%20%20function%20getFileName(fileName)%20%7B%0A%20%20%20%20var%20userAgent%20%3D%20window.navigator.userAgent%2C%0A%20%20%20%20%20%20%20%20platform%20%3D%20window.navigator.platform%2C%0A%20%20%20%20%20%20%20%20windowsPlatforms%20%3D%20%5B'Win32'%2C%20'Win64'%2C%20'Windows'%2C%20'WinCE'%5D%3B%0A%0A%20%20%20%20if%20(windowsPlatforms.indexOf(platform)%20!%3D%3D%20-1)%20%7B%0A%20%20%20%20%20%20fileName%20%3D%20fileName.replace('%3A'%2C%20'').replace(%2F%5B%2F%5C%5C%3F%25*%7C%22%3C%3E%5D%2Fg%2C%20'-')%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20fileName%20%3D%20fileName.replace('%3A'%2C%20'').replace(%2F%5C%2F%2Fg%2C%20'-').replace(%2F%5C%5C%2Fg%2C%20'-')%3B%0A%20%20%20%20%7D%0A%20%20%20%20return%20fileName%3B%0A%20%20%7D%0A%20%20const%20fileName%20%3D%20getFileName(title)%3B%0A%0A%20%20if%20(selection)%20%7B%0A%20%20%20%20%20%20var%20markdownify%20%3D%20selection%3B%0A%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20var%20markdownify%20%3D%20content%3B%0A%20%20%7D%0A%0A%20%20if%20(vault)%20%7B%0A%20%20%20%20%20%20var%20vaultName%20%3D%20'%26vault%3D'%20%2B%20encodeURIComponent(%60%24%7Bvault%7D%60)%3B%0A%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20var%20vaultName%20%3D%20''%3B%0A%20%20%7D%0A%0A%20%20const%20markdownBody%20%3D%20new%20Turndown(%7B%0A%20%20%20%20%20%20headingStyle%3A%20'atx'%2C%0A%20%20%20%20%20%20hr%3A%20'---'%2C%0A%20%20%20%20%20%20bulletListMarker%3A%20'-'%2C%0A%20%20%20%20%20%20codeBlockStyle%3A%20'fenced'%2C%0A%20%20%20%20%20%20emDelimiter%3A%20'*'%2C%0A%20%20%7D).turndown(markdownify)%3B%0A%0A%20%20var%20date%20%3D%20new%20Date()%3B%0A%0A%20%20function%20convertDate(date)%20%7B%0A%20%20%20%20var%20yyyy%20%3D%20date.getFullYear().toString()%3B%0A%20%20%20%20var%20mm%20%3D%20(date.getMonth()%2B1).toString()%3B%0A%20%20%20%20var%20dd%20%20%3D%20date.getDate().toString()%3B%0A%20%20%20%20var%20mmChars%20%3D%20mm.split('')%3B%0A%20%20%20%20var%20ddChars%20%3D%20dd.split('')%3B%0A%20%20%20%20return%20yyyy%20%2B%20'-'%20%2B%20(mmChars%5B1%5D%3Fmm%3A%220%22%2BmmChars%5B0%5D)%20%2B%20'-'%20%2B%20(ddChars%5B1%5D%3Fdd%3A%220%22%2BddChars%5B0%5D)%3B%0A%20%20%7D%0A%0A%20%20const%20today%20%3D%20convertDate(date)%3B%0A%0A%20%20const%20fileContent%20%3D%20%0A%20%20%20%20%20%20%22author%3A%3A%20%22%20%2B%20byline%20%2B%20%22%5Cn%22%0A%20%20%20%20%20%20%2B%20%22source%3A%3A%20%5B%22%20%2B%20title%20%2B%20%22%5D(%22%20%2B%20document.URL%20%2B%20%22)%5Cn%22%0A%20%20%20%20%20%20%2B%20%22clipped%3A%3A%20%5B%5B%22%20%2B%20today%20%2B%20%22%5D%5D%5Cn%22%0A%20%20%20%20%20%20%2B%20%22published%3A%3A%20%5Cn%5Cn%22%20%0A%20%20%20%20%20%20%2B%20tags%20%2B%20%22%5Cn%5Cn%22%0A%20%20%20%20%20%20%2B%20markdownBody%20%3B%0A%20%20%0A%20%20document.location.href%20%3D%20%22obsidian%3A%2F%2Fnew%3F%22%0A%20%20%20%20%2B%20%22file%3D%22%20%2B%20encodeURIComponent(folder%20%2B%20fileName)%0A%20%20%20%20%2B%20%22%26content%3D%22%20%2B%20encodeURIComponent(fileContent)%0A%20%20%20%20%2B%20vaultName%20%3B%0A%7D)%7D)()%3B
@gkzsolt

This comment has been minimized.

Copy link

@gkzsolt gkzsolt commented Aug 7, 2021

Hi
This would be very nice, but I get "Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: https://unpkg.com/turndown@6.0.0?module" when clicking the bookmarklet.

@andrezgz

This comment has been minimized.

Copy link

@andrezgz andrezgz commented Aug 7, 2021

Hi
This would be very nice, but I get "Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: https://unpkg.com/turndown@6.0.0?module" when clicking the bookmarklet.

This issue seems to be triggered when using the bookmarklet inside GitHub:

Refused to load the script 'https://unpkg.com/turndown@6.0.0?module' because it violates the following Content Security Policy directive: "script-src github.githubassets.com". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

I've tested it in another page, like https://obsidianroundup.org/ and it's working.

@Moonbase59

This comment has been minimized.

Copy link

@Moonbase59 Moonbase59 commented Aug 7, 2021

@kepano: When constructing the obsidian:// link, don’t you think it’d be better to also URI-encode folder & name? People can have odd file & folder names …

@Moonbase59

This comment has been minimized.

Copy link

@Moonbase59 Moonbase59 commented Aug 7, 2021

@kepano: Here’s my slightly modified version for us YAML aficionados, would be glad if you’d include that in your collection:

javascript: Promise.all([import('https://unpkg.com/turndown@6.0.0?module'), import('https://unpkg.com/@tehshrike/readability@0.2.0'), ]).then(async ([{
    default: Turndown
}, {
    default: Readability
}]) => {

  /* Optional vault name */
  const vault = "";

  /* Optional folder name such as "Clippings/" */
  const folder = "";

  /* Optional tags  */
  const tags = "clippings";

  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 getFileName(fileName) {
    var userAgent = window.navigator.userAgent,
        platform = window.navigator.platform,
        windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'];

    if (windowsPlatforms.indexOf(platform) !== -1) {
      fileName = fileName.replace(':', '').replace(/[/\\?%*|"<>]/g, '-');
    } else {
      fileName = fileName.replace(':', '').replace(/\//g, '-').replace(/\\/g, '-');
    }
    return fileName;
  }
  const fileName = getFileName(title);

  if (selection) {
      var markdownify = selection;
  } else {
      var markdownify = content;
  }

  if (vault) {
      var vaultName = '&vault=' + encodeURIComponent(`${vault}`);
  } else {
      var vaultName = '';
  }

  const markdownBody = new Turndown({
      headingStyle: 'atx',
      hr: '---',
      bulletListMarker: '-',
      codeBlockStyle: 'fenced',
      emDelimiter: '*',
  }).turndown(markdownify);

  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);

  const fileContent = "---\n"
      + "date: " + today + "\n"
      + "tags: [" + tags + "]\n"
      + "source: " + document.URL + "\n"
      + "author: " + byline + "\n"
      + "---\n\n"
      + markdownBody;

  document.location.href = "obsidian://new?"
    + "name=" + encodeURIComponent(folder + fileName)
    + "&content=" + encodeURIComponent(fileContent)
    + vaultName;
})
@kepano

This comment has been minimized.

Copy link
Owner Author

@kepano kepano commented Aug 7, 2021

@kepano: When constructing the obsidian:// link, don’t you think it’d be better to also URI-encode folder & name? People can have odd file & folder names …

You're right! Updated the code with your suggestions.

@psychsatani

This comment has been minimized.

Copy link

@psychsatani psychsatani commented Aug 8, 2021

Doesn't work on safari on macOS Monterey. Does it work on public versions of safari? Works on Firefox well enough. Thanks a lot for this!

@kepano

This comment has been minimized.

Copy link
Owner Author

@kepano kepano commented Aug 8, 2021

Doesn't work on safari on macOS Monterey. Does it work on public versions of safari? Works on Firefox well enough. Thanks a lot for this!

I think Safari prefers if the JS has been minified and URI encoded. I have a minified version in the Gist, but if you choose to edit the variables or template you'll need to do that yourself. You can run the code through Bookmarket Maker to minify + create the bookmarklet for Safari.

@psychsatani

This comment has been minimized.

Copy link

@psychsatani psychsatani commented Aug 8, 2021

I have no idea on javascript. Will open the tab in Firefox and manage for now. Given how many centuries notion took to develop a clipper for safari, I have learnt to push it around.

@subsector

This comment has been minimized.

Copy link

@subsector subsector commented Aug 8, 2021

I have no idea on javascript. Will open the tab in Firefox and manage for now. Given how many centuries notion took to develop a clipper for safari, I have learnt to push it around.

This is working fine for me in Safari. I opened the minified js above as Raw and copied that.

@chrisgrieser

This comment has been minimized.

Copy link

@chrisgrieser chrisgrieser commented Aug 9, 2021

@kepano thanks for the really useful bookmarklet! (and @Moonbase59 for the yaml version)

Is it maybe possible to get the date of publication as well? That would be super useful when you want to clip newspaper articles.

@chrisgrieser

This comment has been minimized.

Copy link

@chrisgrieser chrisgrieser commented Aug 9, 2021

also, small improvement: for the URL-scheme, using name creates a problem because notes are placed in the default location for new notes.

When you have save in the same folder as the current file set as default location for new notes, and when you also set an optional folder at the top of the bookmarklet script, multiple consecutive clippings result in tons of unintended nested folders, as the "current file" (the clipping) will always be one level deeper than before. 👽

Use file instead of name in the url scheme and this is fixed.

@kepano

This comment has been minimized.

Copy link
Owner Author

@kepano kepano commented Aug 9, 2021

Use file instead of name in the url scheme and this is fixed.

Thanks, this change is made

Is it maybe possible to get the date of publication as well? That would be super useful when you want to clip newspaper articles.

I'm using Mozilla's readability library which unfortunately doesn't provide published date as an output. Would love to see this too, but don't have the time to explore adding this feature right now. Might be a good suggestion for the readability library overall as I could imagine other use cases for it.

@nimerino

This comment has been minimized.

Copy link

@nimerino nimerino commented Aug 15, 2021

@kepano: Here’s my slightly modified version for us YAML aficionados, would be glad if you’d include that in your collection:

Thank you so much @Moonbase59 for the YAML extension.

And @kepano, I take my hat off to you as always.

@bblais

This comment has been minimized.

Copy link

@bblais bblais commented Aug 18, 2021

How hard would it be to export/save all images as well, kind of like the "Save Entire Website" option in Chrome? I say this because, when I want to save a website in Obsidian I don't want to count on the website keeping all the images, and I want to be able to look at it offline. It's a pretty cool tool!

@kepano

This comment has been minimized.

Copy link
Owner Author

@kepano kepano commented Aug 19, 2021

How hard would it be to export/save all images as well, kind of like the "Save Entire Website" option in Chrome?

You can do this using the MarkDownload extension

@chrisgrieser

This comment has been minimized.

Copy link

@chrisgrieser chrisgrieser commented Aug 22, 2021

I'm using Mozilla's readability library which unfortunately doesn't provide published date as an output. Would love to see this too, but don't have the time to explore adding this feature right now.

Unfortunately, my knowledge of javascript is very basic, but I found a way via shell script to get the publication date, as it usually simply follows the datePublished in the source code of a webpage.

curl -s "https://www.wired.com/story/unbearable-cuteness-instagram-hedgehog-influencers/" | grep -Eo "datePublished[^,]*" | grep -Eo "[[:digit:]]{4}" | head -n 1

Does this maybe help you to quickly implement it via javascript?

@projectfaktory

This comment has been minimized.

Copy link

@projectfaktory projectfaktory commented Sep 10, 2021

Thank you for this!

@Keitaro-AH

This comment has been minimized.

Copy link

@Keitaro-AH Keitaro-AH commented Sep 15, 2021

Hi, to preface, i am pretty new to Obsidian and these types of codings.

I got the following problem. If i mark a small part of a website, the snippet works. If i use Ctrl+a or send the complete Site, it doesnt. It shows a obsidian:\new?.... link in the dev console, however nothing happens in Obsidian. (happens with your code and my changed code)

Any tips what could be happening here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment