For book­mark­ing web­sites dur­ing my infor­ma­tion research, I increas­ing­ly move from tools like Instapa­per and Omni­vore to Obsid­i­an as the place to save and orga­nize my book­marks.

In this arti­cle, I want to show a few solu­tions using Apple Short­cut togeth­er with the Short­cut exten­sion “Actions for Obsid­i­an” from Car­lo Zottman. These range from a sim­ple one-line book­marks to slight­ly more com­plex meth­ods that also save a selec­tion from with­in a brows­er win­dow. Let’s start with the sim­ple one.

1. Simple Bookmark

The first short­cut just adds a line which con­sist only of the cre­ation date and the click­able title plus a emp­ty line at the top of a ded­i­cat­ed Obsid­i­an note, in my case “98 Administration/Bookmarked/Bookmarks”.

This image depicts a screenshot of a user interface for creating a shortcut on a Mac computer. The purpose of the shortcut is to bookmark information in Obsidian. More in the text below

The short­cut work­flow includes sev­er­al actions:

  1. Receive URLs and Arti­cles input from “What’s On Screen”.
    • If no con­tent is found, the short­cut will Stop and Respond with “No Con­tent to cap­ture”.
  2. Get Arti­cle from Short­cut Input: It cap­tures an arti­cle from the pro­vid­ed input.
  3. Set vari­able url to URL: This action assigns the URL of the arti­cle in the vari­able url.
  4. Set vari­able title to Name: This stores the arti­cle’s name in the vari­able title.
  5. Cur­rent Date: Cap­tures the cur­rent date.
  6. Text: Con­structs a text string that includes the date, title, and URL.
  7. Prepend Text to note: Adds this con­struct­ed text to a spe­cif­ic note titled “98 Administration/Bookmarked/Bookmarks” in the “Obsid­i­an­Notes” vault after the head­line “#### Book­marks”, ensur­ing it starts on a new line and focus­es on the Book­marks at the end, which makes Obsid­i­an active. This last action is part of the “Actions for Obsid­i­an” exten­sion, which inte­grates seam­less­ly with the short­cut to acti­vate Obsid­i­an.

The Book­marks file needs to have the head­line “#### Book­marks” as a mark­er, so the actions find the place to include the book­mark just cap­tured. You can use any head­line and size you pre­fer. Ide­al­ly, I would pre­fer an invis­i­ble mark­er, which is achiev­able in Mark­down using syn­tax like %%text%%; how­ev­er, the “Actions for Obsid­i­an” exten­sion sup­ports only head­lines for this pur­pose.

2. Bookmark with Preview Image and Excerpt

In a lot of case, the first book­mark­ing style might be enough, but I also love to have some more con­text about the web pages, that I had book­marked lat­er on. There­fore, I devel­oped a sec­ond style that cap­tures both an image and the excerpt. This approach is par­tic­u­lar­ly use­ful for visu­al ref­er­enc­ing and quick con­tent review, enabling me to recall the essence of the web page with­out need­ing to revis­it it imme­di­ate­ly.

To achieve such a lay­out, the use of a table is obvi­ous. Unfor­tu­nate­ly, Mark­down does not offer the pos­si­bil­i­ty to cre­ate the required struc­ture at this point. For­tu­nate­ly, you can also sim­ply use an HTMl table direct­ly. With one restric­tion, which I will explain below.

Next code shows the HTML-Tem­plate:

<table style="border: none; border-collapse: collapse;">
    <tr>
        <td style="font-size: 1.2em; font-weight: bold; padding-left: 10px; padding-right: 10px;">
            [DATUM]
        </td>
        <td>
            <a href="URL">
                "[TITLE]"
            </a>
        </td>
    </tr>
    <tr>
        <td>
            <img src="[IMG]"
                 width="200px"
                 style="padding: 10px;">
        </td>
        <td style="padding-right: 10px;">
            <p>
                [EXCERPT]
            </p>
        </td>
    </tr>
</table>

It would also be pos­si­ble to add a back­ground col­or, but this would have side effects when using the dark mode.

The work­flow for this short­cut is as fol­lows:

  1. Receive URLs and Arti­cles from “What’s On Screen”. If noth­ing is found, the short­cut will Stop and Respond with “No Con­tent to cap­ture”.
  2. Get arti­cle from Short­cut Input: Retrieves an arti­cle from the input pro­vid­ed.
  3. Set vari­able url to URL: Stores the URL of the arti­cle.
  4. Set vari­able title to Name: Stores the title of the arti­cle.
  5. Set vari­able img to Main Image URL: Stores the main image URL of the arti­cle.
  6. Set vari­able excerpt to Excerpt: Stores a text excerpt from the arti­cle.
  7. Cur­rent Date: Retrieves the cur­rent date.
  8. Text: Cre­ates an HTML snip­pet that for­mats the date, title, URL, image, and excerpt into a table lay­out for inser­tion into Obsid­i­an.
  9. Uses the same call of the action “Prepend text to note as above”

I have been using this approach for sev­er­al months, ini­tial­ly not as an Apple Short­cut, but as an Alfred Work­flow with Python script­ing. This method pro­vides bet­ter cov­er­age for the pre­view image and excerpt than the Short­cut’s “Arti­cle” object. How­ev­er, using Python in a short­cut on iOS/iPadOS is not so easy, there­fore this method is ben­e­fi­cial as it works across all Apple plat­forms.

The restric­tion I men­tioned above is some­thing I did­n’t rec­og­nize until I wrote this arti­cle and test­ed the scripts again. There is a dif­fer­ence in how HTML tables are ren­dered, depend­ing on the modes and themes that are used. I always work in the “Edit­ing View” com­bined with “Live Pre­view,” so I had­n’t men­tioned it before. The results appear as expect­ed in the Stan­dard Theme when using “Edit­ing View” with the “Live Pre­view” set­ting:

But, when switch­ing to the “Read­ing View,” the ren­der­ing becomes total­ly dif­fer­ent.

So far, I haven’t been able to find the right solu­tion to solve this issue. I’ve tried hard and did a lot of research but with­out a con­vinc­ing result. If any­one has a tip, I’d real­ly appre­ci­ate it. The ren­der­ing looks a bit bet­ter when using the “Min­i­mal” theme, which shows that dif­fer­ent themes can affect how tables in HTML appear. Check how this solu­tion works with your mode and theme. set­tings. I also looked at the exam­ples from the link shown in the pic­ture; they weren’t very help­ful in this case, but the arti­cle is still worth read­ing: Cus­tom CSS for tables — 5 New styles, ready to use in your notes

3. Capture a Selection with the bookmark

The third solu­tion cur­rent­ly works only in Safari because there is no way to run JavaScript from a short­cut in any oth­er brows­er. How­ev­er, I had the oppor­tu­ni­ty to test a very ear­ly ver­sion of anoth­er promis­ing short­cut exten­sion from Car­lo Zottmann called Brows­er Action which will even­tu­al­ly enable JavaScript exe­cu­tion in almost every brows­er on a Mac. Thus, in the future, this solu­tion can be eas­i­ly extend­ed to all browsers. I also imple­ment­ed a ver­sion with the Alfred app that sup­ports all browsers and I dis­cussed this solu­tion on my Ger­man-speak­ing blog, titled “Eine Tex­tauswahl im Brows­er in Obsid­i­an spe­ich­ern”. I haven’t trans­lat­ed it yet, so if you are inter­est­ed, please use a trans­la­tion app.

To cap­ture a sec­tion in a brows­er win­dow the nat­ur­al method is to use JavaScript. Because I’m not a JavaScript expert I cre­at­ed that part with the Help of ChatGPT.I explain the script exten­sive­ly in my Ger­man arti­cle about the Alfred app solu­tion. Here, I will only present the essen­tial parts of the scripts and the short­cut itself.

This is the JavaScript code:

/**
 * 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());

This script fetch­es the selec­tion from the brows­er and process­es the HTML tags to con­vert them into the cor­re­spond­ing Mark­down. The script con­verts only the fol­low­ing HTML tags, and from what I see, it does so almost with­out errors:

  • <H1> bis <H6>
  • <p>
  • <a>
  • <img>
  • <pre> und <code>
  • <ul> und <ol>
  • <table>

The JavaScript also cre­ates the Mark­down that I will use for my spe­cial Obsid­i­an note for these snip­pets:

>[\!snippets] 02.02.24: [example website](https://example.com)
> This is the first line of the selection
> A second line

The result on the page looks like this:

snippet example for the markdown code above

I use a self-defined call­out [!snippets]. If you are not famil­iar with this, you can opt for one of the pre­de­fined call­outs, such as [!info]. Nev­er­the­less, regard­less of which snip­pet type you use, the back­slash \ before the ! in the code is nec­es­sary because using ! alone with­out the escape sym­bol pro­duced errors. The two \n\n in the code before [\!snippets] serve to visu­al­ly sep­a­rate the blocks in the Obsid­i­an note.

This image shows a screenshot of a Mac interface for a shortcut designed to capture selections from a web browser and save them to a specific note in Obsidian. The steps in the shortcut workflow are as follows: The steps are explained in the text below
  1. Com­ment:Just an expla­na­tion that the next action sets the Obsid­i­an path to the snip­pet note.
  2. Text: Inputs the text “98 Administration/Bookmarked/Snippets” to spec­i­fy the note where the snip­pet will be includ­ed.
  3. Set vari­able note to path of the note: Assigns the pre­vi­ous­ly spec­i­fied path to a vari­able named “note”. This can also be done direct in the prepend call, I use it here just for trans­paren­cy.
  4. Run JavaScript on Active Safari Tab: Exe­cutes a JavaScript snip­pet in the active Safari tab to cap­ture the HTML of the select­ed text. The script checks if there is a selec­tion and logs details about it, includ­ing cloning the con­tents to ensure it cap­tures a stand­alone copy.
  5. Set vari­able High­light to JavaScript Result: Saves the result of the JavaScript exe­cu­tion to a vari­able named “High­light”.
  6. Prepend High­light to note: Adds the cap­tured HTML as mark­down con­tent to the spec­i­fied Obsid­i­an note at the begin­ning of the sec­tion under the head­line “###### Snip­pets”.

That’s all

I cap­tured the exam­ple screen­shots using the Short­cut app on my Mac. How­ev­er, it’s impor­tant to note that these short­cuts works seam­less­ly on iPhone and iPads as well. They may work on the Apple Vision as well, but until Tim Cook sends me a test device, that will remain uncon­firmed!

Leave a Reply

Your email address will not be published. Required fields are marked *