Skip to content

Instantly share code, notes, and snippets.

@barneycarroll
Last active September 13, 2020 04:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barneycarroll/70107eaa98378aab0da1d99998782854 to your computer and use it in GitHub Desktop.
Save barneycarroll/70107eaa98378aab0da1d99998782854 to your computer and use it in GitHub Desktop.
A browser plugin script to identify previous / next pagination links in forums and expose them to generic keyboard navigation
const options = {
duration: 600,
easing: 'cubic-bezier(0, 0.55, 0.45, 1)',
fill: 'both',
}
function feedback(message = 'error'){
const $circle = render(circle)
$circle.animate({
transform: ['scale(0%)', 'scale(120%)'],
}, options)
.onfinish = () => {
$circle.animate({
opacity: [1, 0],
}, options)
}
const $symbol = render(
message === 'error' ? x : arrow
)
if(message === 'next')
$symbol.firstElementChild.style.transform = 'rotate(.5turns)'
$symbol.animate({
transform: ['scale(200%)', 'scale(90%)'],
opacity: [0, 1],
}, {
...options,
duration: 200,
})
.onfinish = () => {
$symbol.animate({
opacity: [1, 0]
}, options)
}
}
function render(source){
const $ = document.createElement('div')
$.innerHTML = `
<div style="
transform-origin: 50% 50%;
position: fixed;
height: 100vh;
width: 100vw;
left: 0;
top: 0;
">
<object
type=image/svg+xml
data="data:image/svg+xml;base64,${ btoa(source) }"
style="object-fit: contain">
</object>
</div>
`
return document.body.appendChild(
$.firstElementChild
)
}
const arrow = `\
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 640 640">
<defs/>
<defs>
<path id="a" d="M311 0L0 320l311 320V437h329V202H311V0z"/>
</defs>
<use fill="#492d67" xlink:href="#a"/>
</svg>
`
const circle = `\
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 640 640">
<defs/>
<defs>
<path id="a" d="M640 320a320 320 0 11-640 0 320 320 0 01640 0z"/>
</defs>
<use fill="#f22ce6" xlink:href="#a"/>
</svg>
`
const x = `\
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 640 640">
<defs/>
<defs>
<path id="a" d="M435 320l205 205-115 115-205-205-205 205L0 525l205-205L0 115 115 0l205 205L525 0l115 115-205 205z"/>
</defs>
<use fill="#492d67" xlink:href="#a"/>
</svg>
`
{
"manifest_version": 2,
"name": "Traverse",
"version": "1.0",
"description": "Forum navigation assistance script. Finds common previous / next page link patterns and binds their actions to Ctrl + ⟵ / Ctrl + ⟶ keyboard commands.",
"icons": {
"64": "icons/traverse.svg"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["DOM.js"],
"run_at": "document_end"
}
],
"commands": {
"previous": {
"suggested_key": {
"default": "Ctrl+Left",
"mac": "MacCtrl+Left"
},
"description": "Navigate to previous page in thread"
},
"next": {
"suggested_key": {
"default": "Ctrl+Right",
"mac": "MacCtrl+Right"
},
"description": "Navigate to next page in thread"
}
}
}
// Commands interface
browser.commands.onCommand.addListener(direction =>
act(controls[direction])
)
// Controls are searched for on request...
const controls = new Proxy({}, {
get: (_, command) => _[command] || findControls()[command]
})
// ...up until the document is ready, at which point they're fixed
document.addEventListener('DOMContentLoaded', () => {
Object.assign(controls, findControls())
})
// Text patterns used by pagination controls,
// in order of most to least likely / ambiguous
const patterns = [
['previous', 'next'],
['<', '>' ],
['prev', 'next'],
['‹', '›' ],
['«', '»' ],
['<<', '>>' ],
].map(pair =>
pair.map(string =>
new RegExp('\\s*' + string + '\\s*', 'i')
)
)
// Element properties to test those patterns on
const properties = ['textContent', 'rel', 'title']
function findControls() {
const controls = {}
const $all = Array.from(document.all)
// The 2 commands, whose index maps to that of the pattern pair (0 , 1)
void ['previous', 'next'].forEach((command, index) =>
// Iterate through the patterns
patterns.find(pair =>
// And for every element on the page
$all.find($ =>
// If any of the relevant element properties match the pattern...
properties.some(property =>
pair[index].test($[property])
)
&&
// The containing actionable element is the pertinent control
(controls[command] = $.closest('a,button,input'))
)
)
)
return controls
}
// Activate pagination controls
function act($) {
if ($.onclick)
$.onclick()
if ($.matches('a'))
window.location.assign($.href)
else if ($.matches('input[type=submit]'))
$.form.submit()
}
Display the source blob
Display the rendered blob
Raw
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64" viewBox="0 0 640 640">
<defs>
<path id="a" d="M155 300a45 45 0 1190 0 45 45 0 01-90 0z"/>
<path id="b" d="M400 370a45 45 0 1190 0 45 45 0 01-90 0z"/>
<path id="c" d="M234 368a45 45 0 1190 0 45 45 0 01-90 0z"/>
<path id="h" d="M268 368h-66v-67h66v67z"/>
<path id="i" d="M460 335a90 90 0 11180 0 90 90 0 01-180 0z"/>
<path id="d" d="M318 300a45 45 0 1190 0 45 45 0 01-90 0z"/>
<path id="k" d="M460 335a90 90 0 10180 0 90 90 0 00-180 0z"/>
<path id="l" d="M0 335a90 90 0 11180 0 90 90 0 01-180 0z"/>
<path id="m" d="M353 367h-66v-66h66v66z"/>
<path id="n" d="M87 245L0 335l87 90v-57h93v-66H87v-57z"/>
<path id="o" d="M460 335a90 90 0 10180 0 90 90 0 00-180 0z"/>
<path id="p" d="M440 368h-66v-66h66v66z"/>
<path id="q" d="M460 335a90 90 0 11180 0 90 90 0 01-180 0z"/>
<path id="r" d="M553 245l87 90-87 90v-57h-93v-66h93v-57z"/>
<clipPath id="e">
<use xlink:href="#a"/>
</clipPath>
<clipPath id="f">
<use xlink:href="#b"/>
</clipPath>
<clipPath id="g">
<use xlink:href="#c"/>
</clipPath>
<clipPath id="j">
<use xlink:href="#d"/>
</clipPath>
</defs>
<use fill="#f22ce6" fill-opacity="0" xlink:href="#a"/>
<g clip-path="url(#e)">
<use fill-opacity="0" stroke="#f22ce6" stroke-width="40" xlink:href="#a"/>
</g>
<use fill="#f22ce6" fill-opacity="0" xlink:href="#b"/>
<g clip-path="url(#f)">
<use fill-opacity="0" stroke="#f22ce6" stroke-width="40" xlink:href="#b"/>
</g>
<use fill="#f22ce6" fill-opacity="0" xlink:href="#c"/>
<g clip-path="url(#g)">
<use fill-opacity="0" stroke="#f22ce6" stroke-width="40" xlink:href="#c"/>
</g>
<use fill="#7bc979" xlink:href="#h"/>
<use fill="#f22ce6" xlink:href="#i"/>
<use fill="#f22ce6" fill-opacity="0" xlink:href="#d"/>
<g clip-path="url(#j)">
<use fill-opacity="0" stroke="#f22ce6" stroke-width="40" xlink:href="#d"/>
</g>
<use fill="#f22ce6" xlink:href="#k"/>
<use fill="#f22ce6" xlink:href="#l"/>
<use fill="#7bc979" xlink:href="#m"/>
<use fill="#492d67" xlink:href="#n"/>
<g>
<use fill="#f22ce6" xlink:href="#o"/>
</g>
<g>
<use fill="#7bc979" xlink:href="#p"/>
</g>
<g>
<use fill="#f22ce6" xlink:href="#q"/>
</g>
<g>
<use fill="#492d67" xlink:href="#r"/>
</g>
</svg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment