@@ -10,5 +10,13 @@ | |||||
"appDescription": { | "appDescription": { | ||||
"message": "Browser extension to bookmark fragments of audio files", | "message": "Browser extension to bookmark fragments of audio files", | ||||
"description": "The description of the application" | "description": "The description of the application" | ||||
}, | |||||
"bookmarkSelectionContextMenuItemForFragment": { | |||||
"message": "Bookmark selected fragment", | |||||
"description": "The entry in the context ('right-click') menu when selecting an audio fragment" | |||||
}, | |||||
"bookmarkSelectionContextMenuItemForSingleMoment": { | |||||
"message": "Bookmark selected moment", | |||||
"description": "The entry in the context ('right-click') menu when selecting a single moment" | |||||
} | } | ||||
} | } |
@@ -61,7 +61,7 @@ async function init() { | |||||
// Bind window hash change to update player | // Bind window hash change to update player | ||||
window.addEventListener('hashchange', async function() { | |||||
window.addEventListener('hashchange', async function() { | |||||
if(playlist.isPlaying()) { | if(playlist.isPlaying()) { | ||||
eventEmitter.emit('stop') | eventEmitter.emit('stop') | ||||
// pause needs a small delay, coz the lib doesn't | // pause needs a small delay, coz the lib doesn't | ||||
@@ -72,7 +72,6 @@ async function init() { | |||||
eventEmitter.emit('play'); | eventEmitter.emit('play'); | ||||
}); | }); | ||||
// Load Playlist | // Load Playlist | ||||
await playlist.load([{ | await playlist.load([{ | ||||
src: document.URL | src: document.URL | ||||
@@ -83,6 +82,11 @@ async function init() { | |||||
// Start playing. A tiny delay seems needed in Firefox to show the cursor at the right place. | // Start playing. A tiny delay seems needed in Firefox to show the cursor at the right place. | ||||
requestAnimationFrame(() => eventEmitter.emit('play')); | requestAnimationFrame(() => eventEmitter.emit('play')); | ||||
return playlist; | |||||
} | } | ||||
init(); | |||||
init().then(playlist => { | |||||
// Store playlist as a shared global variable. | |||||
self.playlist = playlist; | |||||
}); |
@@ -0,0 +1,30 @@ | |||||
import { makeRemotelyCallable, remoteFunction } from 'webextension-rpc'; | |||||
function createPreciseUrl(url, selector) { | |||||
const properUrl = url.split('#')[0]; | |||||
let fragmentIdentifier; | |||||
if (selector.type === 'FragmentSelector') { | |||||
fragmentIdentifier = selector.value; | |||||
} else { | |||||
throw new Error('Unsupported selector type'); | |||||
} | |||||
return properUrl + '#' + fragmentIdentifier; | |||||
} | |||||
async function createBookmark({ tab }) { | |||||
const selector = await remoteFunction('describeSelection', { tabId: tab.id })(); | |||||
const bookmarkUrl = createPreciseUrl(tab.url, selector); | |||||
const filenameMatch = tab.url.match(/.*\/([^\/#?]+)(?:\?.*)?(?:#.*)?/); | |||||
const filename = filenameMatch ? filenameMatch[1] : 'Audio fragment'; | |||||
const bookmarkTitle = `${filename} ${start}–${end}`; | |||||
await browser.bookmarks.create({ | |||||
title: bookmarkTitle, | |||||
url: bookmarkUrl, | |||||
}); | |||||
} | |||||
makeRemotelyCallable({ createBookmark }, { insertExtraArg: true }); |
@@ -0,0 +1,76 @@ | |||||
import delay from 'delay'; | |||||
import { makeRemotelyCallable, remoteFunction } from 'webextension-rpc'; | |||||
function describeSelection() { | |||||
const playlist = self.playlist; | |||||
if (!playlist) { | |||||
throw new Error('Player has not been initialised.'); | |||||
} | |||||
let { start, end } = playlist.getTimeSelection(); | |||||
// Waveform Playlist tells us end=start when the selection is just a single line. | |||||
if (end === start) end = undefined; | |||||
// Round the numbers to two decimals. | |||||
if (start !== undefined) start = Math.round(start * 100) / 100; | |||||
if (end !== undefined) end = Math.round(end * 100) / 100; | |||||
const fragmentIdentifier = `t=${start || ''}` + (end ? `,${end}` : ''); | |||||
const selector = { | |||||
type: 'FragmentSelector', | |||||
conformsTo: 'http://www.w3.org/TR/media-frags/', | |||||
value: fragmentIdentifier, | |||||
}; | |||||
return selector; | |||||
} | |||||
makeRemotelyCallable({ | |||||
describeSelection, | |||||
}); | |||||
async function init() { | |||||
let menuEl; | |||||
function cleanupMenu() { | |||||
if (!menuEl) return; | |||||
menuEl.parentNode.removeChild(menuEl); | |||||
menuEl = undefined; | |||||
} | |||||
function onContextMenu(event) { | |||||
event.preventDefault(); | |||||
const left = event.pageX; | |||||
const top = event.pageY; | |||||
const playlist = self.playlist; | |||||
if (!playlist) { | |||||
throw new Error('Player has not been initialised.'); | |||||
} | |||||
let { start, end } = playlist.getTimeSelection(); | |||||
const fragmentIsSelected = start !== end; | |||||
cleanupMenu(); | |||||
menuEl = document.createElement('ul'); | |||||
const liEl = document.createElement('li'); | |||||
menuEl.appendChild(liEl); | |||||
menuEl.setAttribute('style', `list-style: none; margin: 0; padding: 0; position: absolute; top: ${top}; left: ${left}; z-index: 9999999; background: #eee; border: 1px solid black;`); | |||||
const buttonEl = document.createElement('button'); | |||||
buttonEl.innerText = browser.i18n.getMessage(fragmentIsSelected | |||||
? 'bookmarkSelectionContextMenuItemForFragment' | |||||
: 'bookmarkSelectionContextMenuItemForSingleMoment' | |||||
); | |||||
buttonEl.addEventListener('click', remoteFunction('createBookmark')); | |||||
liEl.appendChild(buttonEl); | |||||
document.body.appendChild(menuEl); | |||||
} | |||||
document.addEventListener('contextmenu', onContextMenu, false); | |||||
document.addEventListener('click', event => { | |||||
cleanupMenu(); | |||||
}) | |||||
} | |||||
init(); |
@@ -10,6 +10,8 @@ | |||||
}, | }, | ||||
"permissions": [ | "permissions": [ | ||||
"<all_urls>", | "<all_urls>", | ||||
"bookmarks", | |||||
"tabs", | |||||
"webRequest" | "webRequest" | ||||
] | ] | ||||
} | } |
@@ -1 +1,2 @@ | |||||
import '../audio-player/background.js' | import '../audio-player/background.js' | ||||
import '../create-bookmarks/background.js' |
@@ -1 +1,2 @@ | |||||
import '../audio-player/contentscript.js' | import '../audio-player/contentscript.js' | ||||
import '../create-bookmarks/contentscript.js' |
@@ -5524,6 +5524,29 @@ | |||||
"integrity": "sha512-ISB42vlgMyM7xE1u6pREeCqmmXjLsYu/nqAR8Dl/gIAnylb+KpRpvKbVkUYNFePhhXn0Obkkc3jasOII9ztUtg==", | "integrity": "sha512-ISB42vlgMyM7xE1u6pREeCqmmXjLsYu/nqAR8Dl/gIAnylb+KpRpvKbVkUYNFePhhXn0Obkkc3jasOII9ztUtg==", | ||||
"dev": true | "dev": true | ||||
}, | }, | ||||
"webextension-rpc": { | |||||
"version": "0.1.0", | |||||
"resolved": "https://registry.npmjs.org/webextension-rpc/-/webextension-rpc-0.1.0.tgz", | |||||
"integrity": "sha512-9JCBkXmeP7ossYauXgfvxqQurO5E0VK7uQnlIyirj1zzhGzV92TvOC4FhhqhYBphV9d0doUg+pCQtvRkGsk8+A==", | |||||
"requires": { | |||||
"@babel/runtime": "^7.6.2" | |||||
}, | |||||
"dependencies": { | |||||
"@babel/runtime": { | |||||
"version": "7.6.3", | |||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz", | |||||
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==", | |||||
"requires": { | |||||
"regenerator-runtime": "^0.13.2" | |||||
} | |||||
}, | |||||
"regenerator-runtime": { | |||||
"version": "0.13.3", | |||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", | |||||
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" | |||||
} | |||||
} | |||||
}, | |||||
"webextension-toolbox": { | "webextension-toolbox": { | ||||
"version": "3.0.0", | "version": "3.0.0", | ||||
"resolved": "https://registry.npmjs.org/webextension-toolbox/-/webextension-toolbox-3.0.0.tgz", | "resolved": "https://registry.npmjs.org/webextension-toolbox/-/webextension-toolbox-3.0.0.tgz", | ||||
@@ -16,6 +16,7 @@ | |||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"delay": "^4.3.0", | "delay": "^4.3.0", | ||||
"waveform-playlist": "^3.0.4" | |||||
"waveform-playlist": "^3.0.4", | |||||
"webextension-rpc": "^0.1.0" | |||||
} | } | ||||
} | } |