Skip to content

Commit

Permalink
Improve Chrome text fragment handling & add docs
Browse files Browse the repository at this point in the history
closes #1
  • Loading branch information
wheelercj committed Mar 11, 2024
1 parent 530d764 commit e4a816f
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 37 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A browser extension that copies to the clipboard a markdown link to the current page.

![demo](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMDJwYWdiZnF2MTQ4eGRib2J3c3RhaXF1cHJhc3RzeG80cjNuZDJzcyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/L8eCYjgv1nzCCjq6v1/giphy.gif)
![demo](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbjZvZHNnZ2t2aXJhaGJydHMyZWN6cHliM3R5YmVjYjRncTNmNjB0NCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/7CtyDRiPZJm2HLB2IS/giphy.gif)

* [Add to Firefox](https://addons.mozilla.org/en-US/firefox/addon/stardown/)
* [Add to Chrome](https://chrome.google.com/webstore/detail/clicknohlhfdlfjfkaeongkbdgbmkbhb)
Expand All @@ -12,11 +12,10 @@ After installing, you can copy a markdown link by:
* clicking the extension's icon
* pressing `Ctrl/Cmd+Shift+U` (this can be customized in your browser's settings)
* right-clicking the page and choosing "Copy markdown link to here"
* selecting text, right-clicking it, and choosing the same option ("Copy markdown link to here")

In Chrome, selecting text before right-clicking it gives the benefit of adding a [text fragment](https://web.dev/articles/text-fragments) to the markdown link.
In Chrome, selecting text before right-clicking it and choosing "Copy markdown link to here" gives the benefit of adding a [text fragment](https://web.dev/articles/text-fragments) to the markdown link.

In all browsers, Stardown's right-click copy option will attempt to make a link that directs to the part of the page you right-clicked (it works if the HTML element you right-clicked has an `id` attribute).
In all browsers, Stardown's "Copy markdown link to here" context menu option attempts to make a link that directs to the specific part of the page you right-clicked. This uses a text fragment or any `id` attribute of the HTML element you right-clicked, or both.

## development

Expand Down
28 changes: 12 additions & 16 deletions chrome/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
const browser = chrome || browser;

browser.action.onClicked.addListener(async (tab) => {
await writeLinkToClipboard(tab, '', '');
await writeLinkToClipboard(tab, '');
await brieflyShowCheckmark();
});

Expand Down Expand Up @@ -47,43 +47,39 @@ function sendCopyMessage(info, tab) {
function (data) {
if (data) {
const id = data.clickedElementId;
writeLinkToClipboard(tab, id, info.selectionText);
writeLinkToClipboard(tab, id);
brieflyShowCheckmark();
}
},
);
}

/**
* writeLinkToClipboard copies a markdown link to the clipboard. If both an ID and a
* text fragment are provided, both are included in the link. Browsers that support text
* fragments will try to use them first, and use the ID as a fallback if necessary.
* writeLinkToClipboard copies a markdown link to the clipboard. The link may contain an
* HTML element ID, a text fragment, or both. Browsers that support text fragments will
* try to use them first, and use the ID as a fallback if necessary.
* @param {any} tab - The tab to copy the link from.
* @param {string|undefined} id - The ID of the HTML element to link to. If falsy, no ID
* is included in the link.
* @param {string|undefined} selectedText - The selected text to create a text fragment
* from to include in the link. If falsy, no text fragment is included in the link.
*/
async function writeLinkToClipboard(tab, id, selectedText) {
async function writeLinkToClipboard(tab, id) {
if (!id) {
id = '';
}
if (!selectedText) {
selectedText = '';
}

browser.scripting.executeScript({
target: { tabId: tab.id },
args: [id, selectedText],
function: (id, selectedText) => {
args: [id],
function: (id) => {
const title = document.title;
const url = location.href;
const selection = window.getSelection();

let link = `[${title}](${url}`;
if (id || selectedText) {
const arg = createTextFragmentArg(selection);
if (id || arg) {
link += `#${id}`;
if (selectedText) {
const arg = createTextFragmentArg(selectedText);
if (arg) {
link += `:~:text=${arg}`;
}
}
Expand Down
55 changes: 39 additions & 16 deletions chrome/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,48 @@ window.onload = function () {
}

/**
* createTextFragmentArg creates a text fragment argument for a markdown link. If the
* text is more than 8 words long, only the first 4 and last 4 words are used. If the
* text is empty, an empty string is returned.
* @param {string} text - The text to create a text fragment for.
* @returns {string} - The text fragment argument for a markdown link, or an empty
* string if the given text is empty.
* createTextFragmentArg creates for a markdown link a text fragment argument (the part
* after the `#:~:text=`). Only selection objects with type 'Range' are used; all other
* selections result in an empty string because this extension needs to also allow
* creating links that do not include text fragments.
* @param {Selection} selection - A Selection object; the result of window.getSelection.
* @returns {string}
*/
function createTextFragmentArg(text) {
if (!text) {
function createTextFragmentArg(selection) {
if (selection.type !== 'Range') {
return '';
}

// if text is more than 8 words long, use only the first 4 and last 4 words
const words = text.split(' ');
if (words.length > 8) {
const first4 = encodeURIComponent(words.slice(0, 4).join(' '));
const last4 = encodeURIComponent(words.slice(-4).join(' '));
return `${first4},${last4}`;
} else {
return encodeURIComponent(text);
// https://web.dev/articles/text-fragments#programmatic_text_fragment_link_generation
const result = window.generateFragment(selection);
switch (result.status) {
case 1:
console.error('generateFragment: the selection provided could not be used');
return '';
case 2:
console.error('generateFragment: no unique fragment could be identified for this selection');
return '';
case 3:
console.error('generateFragment: computation could not complete in time');
return '';
case 4:
console.error('generateFragment: an exception was raised during generation');
return '';
}

const fragment = result.fragment;

let arg = '';
if (fragment.prefix) {
arg += encodeURIComponent(fragment.prefix) + '-,';
}
arg += encodeURIComponent(fragment.textStart);
if (fragment.textEnd) {
arg += ',' + encodeURIComponent(fragment.textEnd);
}
if (fragment.suffix) {
arg += ',-' + encodeURIComponent(fragment.suffix);
}

return arg;
}
30 changes: 30 additions & 0 deletions chrome/fragment-generation-utils.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"<all_urls>"
],
"js": [
"content.js"
"content.js",
"fragment-generation-utils.js"
]
}
],
Expand Down

0 comments on commit e4a816f

Please sign in to comment.