A Javascript bookmarklet for automatic Markdown Quote Formatting

What it does

  • this fast.

What to expect:

> Because we’re parsing a web page, and not reading information from a database, the retrieval process is certainly not perfect, but I believe it’s good enough to often be useful, saving the time and potential inaccuracies associated with manually typing in a particular quotation.

{:quote-from:notenik.app||article|Importing a Quotation from WikiQuote|https://notenik.app|https://notenik.app/adventures/importing-a-quotation-from-wikiquote.html}

The logic:

> $SELECTION

{:quote-from:$AUTHOR_OR_WEBSITE_OR_NIL|$YEAR_OR_NIL|article|$WEB_PAGE_TITLE|$ROOT_URL|$PAGE_URL}
  • Where if the value is NIL it returns an empty string.
  • I chose article as a placeholder but you can change that to whatever you want

The code

javascript:(function(){function escapeMarkdown(text){return text.replace(/^/gm,'> ').replace(/`/g,'\\`')}function extractAuthor(){const authorMeta=document.querySelector('meta[name="author"], meta[property="article:author"], meta[name="twitter:creator"], meta[property="og:article:author"]');if(authorMeta&&authorMeta.content){return authorMeta.content.trim()}const authorSelectors=['.author-name','.by-author','.author','.byline','[rel="author"]','[itemprop="author"]','.post-author','.entry-author','.article-author','.content-author'];for(const selector of authorSelectors){const element=document.querySelector(selector);if(element){const text=element.textContent.trim();return text.replace(/^(by|written by|author:)\s*/i,'').trim()}}return ''}function extractYear(){const dateMeta=document.querySelector('meta[property="article:published_time"], meta[name="publish_date"], meta[name="publication_date"], meta[property="og:article:published_time"], time[datetime], [itemprop="datePublished"]');if(dateMeta){const dateContent=dateMeta.content||dateMeta.getAttribute('datetime')||dateMeta.textContent;if(dateContent){const yearMatch=dateContent.match(/(\d{4})/);if(yearMatch){return yearMatch[1]}}}const urlYearMatch=location.href.match(/\/(\d{4})\//);if(urlYearMatch){const year=parseInt(urlYearMatch[1]);if(year>=1990&&year<=new Date().getFullYear()){return urlYearMatch[1]}}const dateSelectors=['.post-date','.entry-date','.publish-date','.article-date','.date'];for(const selector of dateSelectors){const element=document.querySelector(selector);if(element){const yearMatch=element.textContent.match(/(\d{4})/);if(yearMatch){const year=parseInt(yearMatch[1]);if(year>=1990&&year<=new Date().getFullYear()){return yearMatch[1]}}}}return ''}function getWebsiteName(){const ogSiteName=document.querySelector('meta[property="og:site_name"]');if(ogSiteName&&ogSiteName.content){return ogSiteName.content.trim()}const appName=document.querySelector('meta[name="application-name"]');if(appName&&appName.content){return appName.content.trim()}return location.hostname.replace(/^www\./,'')}function getRootUrl(){return location.protocol+'//'+location.hostname}function cleanTitle(title){return title.replace(/\s*[\|–—-]\s*.*$/,'').trim()}const selection=window.getSelection().toString().trim();if(!selection){alert('Please select some text first.');return}const author=extractAuthor();const year=extractYear();const websiteName=author||getWebsiteName();const pageTitle=cleanTitle(document.title);const rootUrl=getRootUrl();const pageUrl=location.href;const citation=`{:quote-from:${ websiteName }|${ year }|article|${ pageTitle }|${ rootUrl }|${ pageUrl }}`;const md=`${escapeMarkdown(selection)}\n\n${ citation }`;if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(md).then(()=>{const toast=document.createElement('div');toast.textContent='Markdown quote copied!';toast.style.cssText=`
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    background: #4CAF50;
                    color: white;
                    padding: 12px 24px;
                    border-radius: 4px;
                    z-index: 999999;
                    font-family: system-ui, -apple-system, sans-serif;
                    font-size: 14px;
                    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
                `;document.body.appendChild(toast);setTimeout(()=>document.body.removeChild(toast),2000)},(err)=>{console.error('Copy failed',err);prompt('Copy the Markdown quote manually:',md)})}else{const textarea=document.createElement('textarea');textarea.value=md;textarea.style.cssText='position:absolute;left:-9999px';document.body.appendChild(textarea);textarea.select();try{const successful=document.execCommand('copy');if(successful){alert('Markdown quote copied to clipboard!')}else{throw new Error('Copy command failed')}}catch(err){console.error('Copy failed',err);prompt('Copy the Markdown quote manually:',md)}document.body.removeChild(textarea)}})();
  • Full disclosure: I used Claude to generate this Javascript.

You should check out the un-minified code here: Notenik Quote-From Bookmarklet · GitHub.

In practice

  1. Add to Bookmarks in your Browser
    • Or use whatever fancy automated method of your liking
  2. Select text, click the Bookmark
  3. You should see a green confirmation window that indicates the selection was successfully copied
    • Or you have to copy it manually from a text window

Moving forward

Craftier minds may figure out how to enhance this code to query for indicators to anticipate the Work Type according to semantic hints.

I wonder if there are any other ways we can automate or manipulate the rest of Notenik’s exotic formatting commands.

1 Like