Skip to content

dodie/link-to-text-fragment-generator-bookmarklet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

dabbb4c · Nov 11, 2024

History

4 Commits
Nov 10, 2024
Nov 10, 2024
Nov 11, 2024

Repository files navigation

Link with Text Fragment generator bookmarklet

Text Fragments allow linking directly to a specific portion of text in a web document, without requiring the author to annotate it with an ID. This bookmarklet generates such URLs based on the selected text.

How to use

Add the following link as bookmark to your bookmarks bar. Use the bookmark on a page to create links with text fragments.

javascript:(function()%7B(function()%7B%0A%09const%20existingPanel%20%3D%20document.querySelector(%22%23bookmarkletUrlScrollToTextFragment%22)%3B%0A%09if%20(existingPanel)%20%7B%0A%09%09existingPanel.parentNode.removeChild(existingPanel)%3B%0A%09%09return%20false%3B%0A%09%7D%0A%0A%09function%20getSelectedWords()%20%7B%0A%09%09const%20selection%20%3D%20window.getSelection()%3B%0A%09%09if%20(selection.rangeCount%20%3D%3D%3D%200)%20return%20null%3B%0A%0A%09%09const%20range%20%3D%20selection.getRangeAt(0)%3B%0A%09%09let%20selectedText%20%3D%20selection.toString()%3B%0A%09%09%0A%09%09if%20(selectedText.trim()%20%3D%3D%3D%20%22%22)%20return%20null%3B%0A%09%09%0A%09%09%2F%2F%20Clone%20and%20expand%20the%20range%20to%20capture%20the%20word%20before%20the%20selection%0A%09%09const%20startRange%20%3D%20range.cloneRange()%3B%0A%09%09startRange.collapse(true)%3B%0A%09%09startRange.setStart(startRange.startContainer%2C%200)%3B%0A%09%09%0A%09%09const%20completeTextBefore%20%3D%20startRange.toString()%3B%0A%09%09let%20beforeText%20%3D%20undefined%3B%0A%09%09if%20(completeTextBefore%5BcompleteTextBefore.length%20-%201%5D%20!%3D%3D%20%22%20%22%20%26%26%20selectedText%5B0%5D%20!%3D%3D%20%22%20%22)%20%7B%0A%09%09%09const%20wordsBefore%20%3D%20completeTextBefore.split(%2F%5Cs%2B%2F)%3B%0A%09%09%09selectedText%20%3D%20wordsBefore%5BwordsBefore.length%20-%201%5D%20%2B%20selectedText%3B%0A%09%09%09beforeText%20%3D%20wordsBefore.slice(0%2C%20wordsBefore.length%20-%201).join(%22%20%22)%3B%0A%09%09%7D%20else%20%7B%0A%09%09%09beforeText%20%3D%20completeTextBefore%3B%0A%09%09%7D%0A%09%09%0A%09%09%2F%2F%20Clone%20and%20expand%20the%20range%20to%20capture%20the%20word%20after%20the%20selection%0A%09%09const%20endRange%20%3D%20range.cloneRange()%3B%0A%09%09endRange.collapse(false)%3B%0A%09%09endRange.setEnd(endRange.endContainer%2C%20endRange.endContainer.length)%3B%0A%09%09%0A%09%09const%20completeTextAfter%20%3D%20endRange.toString()%3B%0A%09%09let%20afterText%20%3D%20undefined%3B%0A%09%09if%20(completeTextAfter%5B0%5D%20!%3D%3D%20%22%20%22%20%26%26%20selectedText%5BselectedText.length%20-%201%5D%20!%3D%3D%20%22%20%22)%20%7B%0A%09%09%09wordsAfter%20%3D%20completeTextAfter.split(%2F%5Cs%2B%2F)%3B%0A%09%09%09selectedText%20%3D%20selectedText%20%2B%20wordsAfter%5B0%5D%3B%0A%09%09%09afterText%20%3D%20wordsAfter.slice(1%2C%20wordsAfter.length).join(%22%20%22)%3B%0A%09%09%7D%20else%20%7B%0A%09%09%09afterText%20%3D%20completeTextAfter%3B%0A%09%09%7D%0A%09%09%0A%09%09return%20%7B%0A%09%09%09beforeWords%3A%20beforeText%2C%0A%09%09%09selectedWords%3A%20selectedText.trim()%2C%0A%09%09%09afterWords%3A%20afterText%0A%09%09%7D%3B%0A%09%7D%0A%09%0A%09function%20addSelection(textStart%2C%20textEnd%2C%20prefix%2C%20suffix)%20%7B%0A%09%09function%20addProperty(config%2C%20labelText%2C%20required%2C%20inputValue)%20%7B%0A%09%09%09const%20propertyContainer%20%3D%20document.createElement(%22div%22)%3B%0A%09%09%09config.appendChild(propertyContainer)%3B%0A%09%09%0A%09%09%09const%20label%20%3D%20document.createElement(%22label%22)%3B%0A%09%09%09label.innerHTML%20%3D%20labelText%3B%0A%09%09%09label.style.display%20%3D%20%22inline-block%22%3B%0A%09%09%09label.style.width%20%3D%20%2215%25%22%3B%0A%09%09%09propertyContainer.appendChild(label)%3B%0A%09%09%09%0A%09%09%09const%20input%20%3D%20document.createElement(%22input%22)%3B%0A%09%09%09input.style.width%20%3D%20%2280%25%22%3B%0A%09%09%09if%20(inputValue)%20%7B%0A%09%09%09%09input.value%20%3D%20inputValue%3B%0A%09%09%09%7D%0A%09%09%09propertyContainer.appendChild(input)%3B%0A%09%09%09input.addEventListener(%22keyup%22%2C%20(event)%20%3D%3E%20%7B%0A%09%09%09%09if%20(required)%20%7B%0A%09%09%09%09%09if%20(input.value.trim()%20%3D%3D%3D%20%22%22)%20%7B%0A%09%09%09%09%09%09input.style.background%20%3D%20%22%23ff8c8c%22%3B%0A%09%09%09%09%09%7D%20else%20%7B%0A%09%09%09%09%09%09input.style.background%20%3D%20%22%22%3B%0A%09%09%09%09%09%7D%0A%09%09%09%09%7D%0A%09%09%09%09renderLink()%3B%0A%09%09%09%7D)%3B%0A%09%09%7D%0A%09%09%0A%09%09const%20config%20%3D%20document.createElement(%22div%22)%3B%0A%09%09addProperty(config%2C%20%22textStart*%3A%22%2C%20true%2C%20textStart)%3B%0A%09%09addProperty(config%2C%20%22textEnd%3A%22%2C%20false%2C%20textEnd)%3B%0A%09%09addProperty(config%2C%20%22prefix%3A%22%2C%20false%2C%20prefix)%3B%0A%09%09addProperty(config%2C%20%22suffix%3A%22%2C%20false%2C%20suffix)%3B%0A%09%09%0A%09%09config.style.borderBottom%20%3D%20%221px%20solid%20%23DADADA%22%3B%0A%09%09config.style.paddingBottom%20%3D%20%223px%22%3B%0A%09%09config.style.marginBottom%20%3D%20%223px%22%3B%0A%09%09%0A%09%09configs.appendChild(config)%3B%0A%09%7D%0A%09%0A%09function%20createTextFragment(textStart%2C%20textEnd%2C%20prefix%2C%20suffix)%20%7B%0A%09%09let%20result%20%3D%20%22text%3D%22%3B%0A%09%09%0A%09%09if%20(prefix%20!%3D%3D%20%22%22)%20%7B%0A%09%09%09result%20%3D%20result%20%2B%20encodeURIComponent(prefix)%20%2B%20%22-%2C%22%3B%0A%09%09%7D%0A%09%09%0A%09%09if%20(textStart%20!%3D%3D%20%22%22)%20%7B%0A%09%09%09result%20%3D%20result%20%2B%20encodeURIComponent(textStart)%3B%0A%09%09%7D%0A%09%09else%7B%0A%09%09%09throw%20new%20Error('textStart%20is%20mandatory%20for%20a%20text%20fragment')%3B%0A%09%09%7D%0A%09%09%0A%09%09if%20(textEnd%20!%3D%3D%20%22%22)%20%7B%0A%09%09%09result%20%3D%20result%20%2B%20%22%2C%22%20%2B%20encodeURIComponent(textEnd)%3B%0A%09%09%7D%0A%09%09%0A%09%09if%20(suffix%20!%3D%3D%20%22%22)%20%7B%0A%09%09%09result%20%3D%20result%20%2B%20%22%2C-%22%20%2B%20encodeURIComponent(suffix)%3B%0A%09%09%7D%0A%09%09%0A%09%09%0A%09%09return%20result%3B%0A%09%7D%0A%09%0A%09function%20renderLink()%20%7B%0A%09%09const%20textFragments%20%3D%20%5B%5D%0A%09%09configs.childNodes.forEach((config)%20%3D%3E%20%7B%0A%09%09%09const%20textStartElement%20%3D%20config.childNodes%5B0%5D.querySelector(%22input%22)%3B%0A%09%09%09const%20textEndElement%20%3D%20config.childNodes%5B1%5D.querySelector(%22input%22)%3B%0A%09%09%09const%20prefixElement%20%3D%20config.childNodes%5B2%5D.querySelector(%22input%22)%3B%0A%09%09%09const%20suffixElement%20%3D%20config.childNodes%5B3%5D.querySelector(%22input%22)%3B%0A%09%09%09%0A%09%09%09const%20textStart%20%3D%20textStartElement.value.trim()%3B%0A%09%09%09if%20(textStart%20%3D%3D%3D%20%22%22)%20%7B%0A%09%09%09%09return%3B%0A%09%09%09%7D%0A%09%09%09%0A%09%09%09const%20textEnd%20%3D%20textEndElement.disabled%20%3F%20%22%22%20%3A%20textEndElement.value.trim()%3B%0A%09%09%09const%20prefix%20%3D%20prefixElement.disabled%20%3F%20%22%22%20%3A%20prefixElement.value.trim()%3B%0A%09%09%09const%20suffix%20%3D%20suffixElement.disabled%20%3F%20%22%22%20%3A%20suffixElement.value.trim()%3B%0A%09%09%09textFragments.push(createTextFragment(textStart%2C%20textEnd%2C%20prefix%2C%20suffix))%3B%0A%09%09%7D)%3B%0A%09%09%0A%09%09if%20(textFragments.length%20%3D%3D%3D%200)%20%7B%0A%09%09%09resultLink.innerHTML%20%3D%20%22%3Ci%3ESelect%20some%20text%20on%20the%20page%20and%20click%20%3Cb%3Eadd%3C%2Fb%3E.%3C%2Fi%3E%22%3B%0A%09%09%09return%3B%0A%09%09%7D%0A%09%09url%20%3D%20window.location.href%20%2B%20%22%23%3A~%3A%22%20%2B%20textFragments.join(%22%26%22)%3B%0A%09%09resultLink.innerHTML%20%3D%20%22URL%20with%20%3Cb%3EText%20Fragment%3A%3C%2Fb%3E%3Cbr%2F%3E%20%3Ca%20target%3D'_blank'%20href%3D'%22%2Burl%2B%22'%3E%22%20%2B%20url%20%2B%20%22%3C%2Fa%3E%3Cbr%2F%3E%22%3B%0A%09%7D%0A%09%0A%09%2F%2F%20Init%20UI%0A%09const%20container%20%3D%20document.createElement(%22div%22)%3B%0A%09container.id%20%3D%20%22bookmarkletUrlScrollToTextFragment%22%3B%0A%09container.style.position%20%3D%20'fixed'%3B%0A%09container.style.right%20%3D%20'5px'%3B%0A%09container.style.bottom%20%3D%20'5px'%3B%0A%09container.style.padding%20%3D%20'15px'%3B%0A%09container.style.border%20%3D%20'1px%20solid%20rgb(150%2C%20150%2C%20150)'%3B%0A%09container.style.zIndex%20%3D%20999999999999999%3B%0A%09container.style.backgroundColor%20%3D%20'white'%3B%0A%09container.style.width%20%3D%20'500px'%3B%0A%09%0A%09const%20resultLink%20%3D%20document.createElement(%22div%22)%3B%0A%09resultLink.style.marginBottom%20%3D%20'10px'%3B%0A%09container.appendChild(resultLink)%3B%0A%09%0A%09%0A%09const%20configs%20%3D%20document.createElement(%22div%22)%3B%0A%09container.appendChild(configs)%3B%0A%09%0A%09const%20addButton%20%3D%20document.createElement(%22input%22)%3B%0A%09addButton.type%20%3D%20%22button%22%3B%0A%09addButton.value%20%3D%20%22add%22%3B%0A%09addButton.addEventListener('click'%2C%20()%20%3D%3E%20%7B%0A%09%09const%20selectedWords%20%3D%20getSelectedWords()%3B%0A%09%09if%20(!selectedWords)%20%7B%0A%09%09%09alertMessage.innerHTML%20%3D%20%22%3Cbr%3ESelect%20something%20on%20the%20page%20to%20enable%20adding%20more%20fragments%22%3B%0A%09%09%09return%3B%0A%09%09%7D%0A%09%09alertMessage.innerHTML%20%3D%20%22%22%3B%0A%09%09if%20(addWithContextCheckbox.checked)%20%7B%0A%09%09%09addSelection(selectedWords%5B%22selectedWords%22%5D%2C%20%22%22%2C%20selectedWords%5B%22beforeWords%22%5D%2C%20%20selectedWords%5B%22afterWords%22%5D)%3B%0A%09%09%7D%20else%20%7B%0A%09%09%09addSelection(selectedWords%5B%22selectedWords%22%5D%2C%20%22%22%2C%20%22%22%2C%20%22%22)%3B%0A%09%09%7D%0A%09%09renderLink()%3B%0A%09%7D)%3B%0A%09addButton.style.marginTop%20%3D%20'10px'%3B%0A%09container.appendChild(addButton)%3B%0A%09%0A%09const%20addWithContextCheckbox%20%3D%20document.createElement(%22input%22)%3B%0A%09addWithContextCheckbox.type%20%3D%20%22checkbox%22%3B%0A%09container.appendChild(addWithContextCheckbox)%3B%0A%09%0A%09const%20addWithContextLabel%20%3D%20document.createElement(%22label%22)%3B%0A%09addWithContextLabel.innerHTML%20%3D%20%22Add%20prefix%20and%20suffix%20based%20on%20context%20(longer%20link%2C%20more%20precise)%22%0A%09container.appendChild(addWithContextLabel)%3B%0A%09%0A%09const%20alertMessage%20%3D%20document.createElement(%22span%22)%3B%0A%09alertMessage.style.color%20%3D%20%22red%22%3B%0A%09container.appendChild(alertMessage)%3B%0A%09%0A%09document.body.appendChild(container)%3B%0A%0A%09%2F%2F%20Init%20state%0A%09const%20textFragments%20%3D%20%5B%5D%0A%09%0A%09const%20selectedWords%20%3D%20getSelectedWords()%3B%0A%09%0A%09if%20(selectedWords)%20%7B%0A%09%09addSelection(selectedWords%5B%22selectedWords%22%5D%2C%20%22%22%2C%20%22%22%2C%20%22%22)%3B%0A%09%7D%0A%09renderLink()%3B%0A%7D)()%3B%7D)()%3B

image

Or, just create your own bookmarklet with a bookmarklet maker such as https://caiorss.github.io/bookmarklet-maker/ based on src/link-with-text-fragment-generator-bookmarklet.

Alternatives

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published