From 64cc7a6ed904fd2adc21c45b3860fd705e5c51d5 Mon Sep 17 00:00:00 2001 From: Bauke Date: Wed, 1 Sep 2021 12:46:29 +0200 Subject: [PATCH] Big and ugly commit to clean a bunch of stuff up. --- README.md | 12 +- docs/scripts/clone-and-install.sh | 36 - package.json | 39 +- .../queue-version-0-1-2.png | Bin .../queue-version-0-1-5.png | Bin source/background.ts | 77 +- source/content-scripts.ts | 6 +- source/index.html | 3 +- source/index.ts | 12 - source/scss/modern-normalize.scss | 1 + source/settings-page.ts | 35 +- source/types.d.ts | 29 + source/utilities/browser.ts | 38 + source/utilities/components/confirm-button.ts | 58 + source/utilities/components/index.ts | 76 - source/utilities/components/link.ts | 20 + source/utilities/components/page-footer.ts | 8 +- source/utilities/components/page-header.ts | 5 +- source/utilities/components/page-main.ts | 154 +- source/utilities/index.ts | 86 - source/utilities/log.ts | 15 + source/utilities/migrations/2020-11-26.ts | 9 +- source/utilities/migrations/index.ts | 16 +- source/utilities/settings.ts | 186 +- source/utilities/version.ts | 4 + tsconfig.json | 7 +- yarn.lock | 5463 ++++++++--------- 27 files changed, 2991 insertions(+), 3404 deletions(-) delete mode 100755 docs/scripts/clone-and-install.sh rename {docs/screenshots => screenshots}/queue-version-0-1-2.png (100%) rename {docs/screenshots => screenshots}/queue-version-0-1-5.png (100%) delete mode 100644 source/index.ts create mode 100644 source/scss/modern-normalize.scss create mode 100644 source/utilities/browser.ts create mode 100644 source/utilities/components/confirm-button.ts create mode 100644 source/utilities/components/link.ts delete mode 100644 source/utilities/index.ts create mode 100644 source/utilities/log.ts create mode 100644 source/utilities/version.ts diff --git a/README.md b/README.md index aa6c9ff..efc17a2 100644 --- a/README.md +++ b/README.md @@ -4,22 +4,16 @@ [![Queue on AMO](https://img.shields.io/amo/v/holllo-queue)](https://addons.mozilla.org/firefox/addon/holllo-queue) -![Queue 0.1.5](./docs/screenshots/queue-version-0-1-5.png) +![Queue 0.1.5](./screenshots/queue-version-0-1-5.png) ## Installation -* Queue is available [through AMO](https://addons.mozilla.org/firefox/addon/holllo-queue/). +* Queue is available [through Mozilla Addons](https://addons.mozilla.org/firefox/addon/holllo-queue/). * Or via manual installation by either building from source yourself or using a prebuilt version available in the [Releases page](https://github.com/Holllo/queue/releases). ## Development -[Node.js LTS](https://nodejs.org) and [Yarn](https://yarnpkg.com/) are required to build and develop the extension. As well as a relatively recent version of [Firefox](https://www.mozilla.org/firefox/). - -To get started, [a script](https://github.com/Holllo/queue/blob/main/docs/scripts/clone-and-install.sh) to clone the repository and install the dependencies is available. You can download and execute the script in one go with the following command. - -```sh -bash -c "$(curl -fsSL https://raw.githubusercontent.com/Holllo/queue/main/docs/scripts/clone-and-install.sh)" -``` +[NodeJS](https://nodejs.org) and [Yarn](https://yarnpkg.com) are required to build and develop the extension. As well as a relatively recent version of [Firefox](https://www.mozilla.org/firefox/). To test the extension, run `yarn start:firefox`. diff --git a/docs/scripts/clone-and-install.sh b/docs/scripts/clone-and-install.sh deleted file mode 100755 index 32a8b4a..0000000 --- a/docs/scripts/clone-and-install.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash - -set -e - -required_commands=( - "git" - "yarn" -) - -for cmd in ${required_commands[*]}; do - if ! command -v "$cmd" &> /dev/null; then - echo "Command \`$cmd\` could not be found and is required for this script to function." - exit - fi -done - -echo "Cloning git repository" -echo "$ git clone 'https://github.com/Holllo/queue'" -git clone 'https://github.com/Holllo/queue' -echo - -echo "Changing directory to the git repository" -echo "$ cd 'queue'" -cd 'queue' -echo - -echo "Installing the dependencies" -echo "$ yarn --silent" -echo -yarn --silent -echo - -echo "Building the extension" -echo "$ yarn build" -yarn build -echo diff --git a/package.json b/package.json index 07de4b1..c6fbee4 100644 --- a/package.json +++ b/package.json @@ -13,28 +13,26 @@ "clean": "trash '.cache/' 'build/' 'web-ext-artifacts/'", "build": "yarn clean && parcel build 'source/manifest.json' -d 'build/' && web-ext build --source-dir 'build/' && yarn zip-source", "zip-source": "zip -r 'web-ext-artifacts/queue-source.zip' 'README.md' 'yarn.lock' 'tsconfig.json' 'package.json' '.gitignore' 'LICENSE' 'source/'", - "test": "xo && stylelint 'source/scss/**/*.scss' && tsc --noEmit" + "test": "xo && stylelint 'source/scss/**/*.scss' && tsc" }, "dependencies": { - "htm": "^3.0.4", - "modern-normalize": "^1.0.0", - "preact": "^10.5.7", - "webextension-polyfill-ts": "^0.22.0" + "htm": "^3.1.0", + "modern-normalize": "^1.1.0", + "preact": "^10.5.14", + "webextension-polyfill": "^0.8.0" }, "devDependencies": { - "eslint-config-xo-typescript": "^0.35.0", - "husky": "^4.3.0", - "parcel-bundler": "^1.12.4", + "@types/webextension-polyfill": "^0.8.0", + "parcel-bundler": "^1.12.5", "parcel-plugin-web-extension": "^1.6.1", - "sass": "^1.29.0", - "stylelint": "^13.8.0", + "sass": "^1.38.2", + "stylelint": "^13.13.1", "stylelint-config-xo-scss": "^0.14.0", "stylelint-config-xo-space": "^0.15.1", - "trash-cli": "^3.1.0", - "typescript": "^4.1.2", - "web-ext": "^5.3.0", - "web-ext-types": "^3.2.1", - "xo": "^0.34.2" + "trash-cli": "^4.0.0", + "typescript": "^4.4.2", + "web-ext": "^6.3.0", + "xo": "^0.44.0" }, "stylelint": { "extends": [ @@ -61,17 +59,12 @@ "prettier": true, "rules": { "@typescript-eslint/no-implicit-any-catch": "off", - "@typescript-eslint/no-loop-func": "off" + "@typescript-eslint/no-loop-func": "off", + "no-await-in-loop": "off" }, "space": true }, "browserslist": [ "last 2 Chrome versions" - ], - "husky": { - "hooks": { - "pre-commit": "yarn test", - "pre-push": "yarn test" - } - } + ] } diff --git a/docs/screenshots/queue-version-0-1-2.png b/screenshots/queue-version-0-1-2.png similarity index 100% rename from docs/screenshots/queue-version-0-1-2.png rename to screenshots/queue-version-0-1-2.png diff --git a/docs/screenshots/queue-version-0-1-5.png b/screenshots/queue-version-0-1-5.png similarity index 100% rename from docs/screenshots/queue-version-0-1-5.png rename to screenshots/queue-version-0-1-5.png diff --git a/source/background.ts b/source/background.ts index 8429440..7c29645 100644 --- a/source/background.ts +++ b/source/background.ts @@ -1,41 +1,36 @@ -import {browser, Menus} from 'webextension-polyfill-ts'; -import { - error, - getManifest, - getNextQItem, - getSettings, - migrate, - newQItemID, - QItem, - QMessage, - removeQItem, - saveSettings, - updateBadge, - versionAsNumber -} from '.'; +import browser, {Menus} from 'webextension-polyfill'; + +import {error} from './utilities/log'; +import {getManifest, updateBadge} from './utilities/browser'; +import {versionAsNumber} from './utilities/version'; +import Settings from './utilities/settings'; +import {migrate} from './utilities/migrations'; +import {Queue} from './types.d'; let timeoutID: number | null = null; browser.browserAction.onClicked.addListener(async () => { + const settings = await Settings.get(); + // When the extension icon is initially clicked, create a timeout for 500ms // that will open the next queue item when it expires. // If the icon is clicked again in those 500ms, open the options page instead. if (timeoutID === null) { timeoutID = window.setTimeout(async () => { timeoutID = null; - const nextItem = await getNextQItem(); + const nextItem = settings.nextItem(); if (nextItem === undefined) { await openOptionsPage(); } else { const tabs = await browser.tabs.query({ currentWindow: true, - active: true + active: true, }); - const message: QMessage = { + const message: Queue.Message = { action: 'queue open url', - data: nextItem + data: nextItem, }; try { @@ -44,7 +39,7 @@ browser.browserAction.onClicked.addListener(async () => { await browser.tabs.create({active: true, url: nextItem.url}); } - await removeQItem(nextItem.id); + await settings.removeItem(nextItem.id); } }, 500); } else { @@ -54,15 +49,17 @@ browser.browserAction.onClicked.addListener(async () => { } }); -browser.runtime.onMessage.addListener(async (request: QMessage) => { - if (request.action === 'queue update badge') { - await updateBadge(); - } -}); +browser.runtime.onMessage.addListener( + async (request: Queue.Message) => { + if (request.action === 'queue update badge') { + await updateBadge(await Settings.get()); + } + }, +); browser.runtime.onInstalled.addListener(async () => { const manifest = getManifest(); - const settings = await getSettings(); + const settings = await Settings.get(); const versionGotUpdated = versionAsNumber(manifest.version) > versionAsNumber(settings.latestVersion); @@ -93,9 +90,7 @@ async function openOptionsPage() { return browser.runtime.openOptionsPage(); } -/** - * The callback function for custom context menu entries. - */ +/** The callback function for custom context menu entries. */ function contextCreated() { if ( browser.runtime.lastError !== null && @@ -109,27 +104,27 @@ const contextMenus: Menus.CreateCreatePropertiesType[] = [ { id: 'queue-add-new-link', title: 'Add to Queue', - contexts: ['link'] + contexts: ['link'], }, { id: 'queue-add-new-link-tab', title: 'Add to Queue', - contexts: ['tab'] + contexts: ['tab'], }, { id: 'queue-open-next-link-in-new-tab', title: 'Open next link in new tab', - contexts: ['browser_action'] + contexts: ['browser_action'], }, { id: 'queue-open-options-page', title: 'Open the extension page', - contexts: ['browser_action'] - } + contexts: ['browser_action'], + }, ]; const contextMenuIDs: Set = new Set( - contextMenus.map((value) => value.id!) + contextMenus.map((value) => value.id!), ); for (const menu of contextMenus) { @@ -142,7 +137,7 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => { return; } - const settings = await getSettings(); + const settings = await Settings.get(); if (id.includes('queue-add-new-link')) { let text: string | undefined; @@ -160,20 +155,20 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => { settings.queue.push({ added: new Date(), - id: newQItemID(settings.queue), + id: settings.newItemId(), text: text ?? url, - url + url, }); - await saveSettings(settings); + await settings.save(); await updateBadge(settings); } else if (id === 'queue-open-next-link-in-new-tab') { - const nextItem = await getNextQItem(settings); + const nextItem = settings.nextItem(); if (nextItem === undefined) { await openOptionsPage(); } else { await browser.tabs.create({active: true, url: nextItem.url}); - await removeQItem(nextItem.id); + await settings.removeItem(nextItem.id); } } else if (id === 'queue-open-options-page') { await openOptionsPage(); diff --git a/source/content-scripts.ts b/source/content-scripts.ts index 25af61e..651df72 100644 --- a/source/content-scripts.ts +++ b/source/content-scripts.ts @@ -1,8 +1,8 @@ -import {browser} from 'webextension-polyfill-ts'; -import {initializeBackgroundMessageHandler} from '.'; +import browser from 'webextension-polyfill'; -initializeBackgroundMessageHandler(); +import {backgroundHandler} from './utilities/browser'; (async () => { + backgroundHandler(); await browser.runtime.sendMessage({action: 'queue update badge'}); })(); diff --git a/source/index.html b/source/index.html index b68850d..3462642 100644 --- a/source/index.html +++ b/source/index.html @@ -6,8 +6,7 @@ Queue - + diff --git a/source/index.ts b/source/index.ts deleted file mode 100644 index d2edcb7..0000000 --- a/source/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {html} from 'htm/preact'; - -type QMessageAction = 'queue open url' | 'queue update badge'; - -export type QMessage = { - action: QMessageAction; - data: T; -}; - -export type QComponent = ReturnType; - -export * from './utilities'; diff --git a/source/scss/modern-normalize.scss b/source/scss/modern-normalize.scss new file mode 100644 index 0000000..d7870e5 --- /dev/null +++ b/source/scss/modern-normalize.scss @@ -0,0 +1 @@ +@import '../../node_modules/modern-normalize/modern-normalize.css'; diff --git a/source/settings-page.ts b/source/settings-page.ts index 555e8ce..c2851b0 100644 --- a/source/settings-page.ts +++ b/source/settings-page.ts @@ -1,34 +1,31 @@ import {html, render} from 'htm/preact'; -import { - initializeBackgroundMessageHandler, - log, - getManifest, - getSettings, - PageFooter, - PageHeader, - PageMain, - saveSettings -} from '.'; +import browser from 'webextension-polyfill'; -(async () => { - initializeBackgroundMessageHandler(); +import {backgroundHandler, getManifest} from './utilities/browser'; +import {debug} from './utilities/log'; +import {PageFooter, PageHeader, PageMain} from './utilities/components'; +import Settings from './utilities/settings'; +window.addEventListener('DOMContentLoaded', async () => { window.HollloQueue = { dumpBackup: async () => { - log(JSON.stringify(await browser.storage.local.get(), null, 2)); + debug(JSON.stringify(await browser.storage.local.get(), null, 2)); }, dumpSettings: async () => { - log(JSON.stringify(await getSettings(), null, 2)); - } + debug(JSON.stringify(await Settings.get(), null, 2)); + }, }; + backgroundHandler(); + await browser.runtime.sendMessage({action: 'queue update badge'}); + const manifest = getManifest(); - const settings = await getSettings(); + const settings = await Settings.get(); const showVersionUpdated = settings.versionGotUpdated; if (showVersionUpdated) { settings.versionGotUpdated = false; - await saveSettings(settings); + await settings.save(); } render( @@ -40,6 +37,6 @@ import { showVersionUpdated=${showVersionUpdated} /> `, - document.body + document.body, ); -})(); +}); diff --git a/source/types.d.ts b/source/types.d.ts index b2b5bd8..0ed775e 100644 --- a/source/types.d.ts +++ b/source/types.d.ts @@ -1,3 +1,6 @@ +import {html} from 'htm/preact'; +import browser from 'webextension-polyfill'; + // TypeScript fix to make it see this file as a module. export {}; @@ -11,3 +14,29 @@ declare global { HollloQueue: HollloQueue; } } + +export namespace Queue { + type Component = ReturnType; + + type Item = { + added: Date; + id: number; + text?: string; + url: string; + }; + + type Manifest = {nodeEnv?: string} & browser.Manifest.ManifestBase; + + type Message = { + action: MessageAction; + data: T; + }; + + type MessageAction = 'queue open url' | 'queue update badge'; + + type Migration = { + date: Date; + upgrade: (previous: Record) => Record; + version: string; + }; +} diff --git a/source/utilities/browser.ts b/source/utilities/browser.ts new file mode 100644 index 0000000..3bef652 --- /dev/null +++ b/source/utilities/browser.ts @@ -0,0 +1,38 @@ +import browser from 'webextension-polyfill'; + +import {Queue} from '../types.d'; +import Settings from './settings'; + +/** Initializes the background message handler. */ +export function backgroundHandler() { + browser.runtime.onMessage.addListener((request: Queue.Message) => { + if (request.action === 'queue open url') { + // @ts-expect-error Changing to . + const message: Queue.Message = request; + window.location.href = message.data.url; + } + }); +} + +/** Returns the WebExtension Manifest. */ +export function getManifest(): Queue.Manifest { + return browser.runtime.getManifest(); +} + +/** + * Updates the extension icon badge with the number of saved items. This can + * only be run from the background script. + */ +export async function updateBadge(settings: Settings): Promise { + await browser.browserAction.setBadgeBackgroundColor({ + color: '#2a2041', + }); + + await browser.browserAction.setBadgeText({ + text: settings.queue.length === 0 ? null : settings.queue.length.toString(), + }); + + browser.browserAction.setBadgeTextColor({ + color: '#f2efff', + }); +} diff --git a/source/utilities/components/confirm-button.ts b/source/utilities/components/confirm-button.ts new file mode 100644 index 0000000..c0b98cc --- /dev/null +++ b/source/utilities/components/confirm-button.ts @@ -0,0 +1,58 @@ +import {html} from 'htm/preact'; +import {useState} from 'preact/hooks'; + +import {Queue} from '../../types.d'; + +type ConfirmButtonProps = { + // Extra classes to add to the button. + class: string; + // The click handler to call when confirmed. + clickHandler: (event: MouseEvent) => void; + // The class to add when in the confirm state. + confirmClass: string; + // The text to use when in the confirm state. + confirmText: string; + // The text to use when in the default state. + text: string; + // The timeout for the confirm state to return back to default. + timeout: number; + // The title to add to the element. + title: string; +}; + +/** + * Creates a button that requires 2 clicks to trigger the main click handler. + * @param props The ConfirmButton properties. + */ +export function ConfirmButton(props: ConfirmButtonProps): Queue.Component { + let timeoutHandle: number | undefined; + + const [isConfirmed, setIsConfirmed] = useState(false); + const click = (event: MouseEvent) => { + if (isConfirmed) { + clearTimeout(timeoutHandle); + props.clickHandler(event); + timeoutHandle = undefined; + setIsConfirmed(false); + } else { + timeoutHandle = window.setTimeout(() => { + setIsConfirmed(false); + }, props.timeout); + setIsConfirmed(true); + } + }; + + const confirmedClass = isConfirmed ? props.confirmClass : ''; + const text = isConfirmed ? props.confirmText : props.text; + const title = isConfirmed ? `Confirm ${props.title}` : props.title; + + return html` + + `; +} diff --git a/source/utilities/components/index.ts b/source/utilities/components/index.ts index 7d539ac..aef368b 100644 --- a/source/utilities/components/index.ts +++ b/source/utilities/components/index.ts @@ -1,79 +1,3 @@ -import {html} from 'htm/preact'; -import {useState} from 'preact/hooks'; -import {QComponent} from '../..'; - -type LinkProps = { - class: string; - text: string; - url: string; -}; - -/** - * Creates a new element with target="_blank" and rel="noopener". - * @param props The Link properties. - */ -export function Link(props: LinkProps): QComponent { - return html` - - ${props.text} - - `; -} - -type ConfirmButtonProps = { - // Extra classes to add to the button. - class: string; - // The click handler to call when confirmed. - clickHandler: (event: MouseEvent) => void; - // The class to add when in the confirm state. - confirmClass: string; - // The text to use when in the confirm state. - confirmText: string; - // The text to use when in the default state. - text: string; - // The timeout for the confirm state to return back to default. - timeout: number; - // The title to add to the element. - title: string; -}; - -/** - * Creates a button that requires 2 clicks to trigger the main click handler. - * @param props The ConfirmButton properties. - */ -export function ConfirmButton(props: ConfirmButtonProps): QComponent { - let timeoutHandle: number | undefined; - - const [isConfirmed, setIsConfirmed] = useState(false); - const click = (event: MouseEvent) => { - if (isConfirmed) { - clearTimeout(timeoutHandle); - props.clickHandler(event); - timeoutHandle = undefined; - setIsConfirmed(false); - } else { - timeoutHandle = window.setTimeout(() => { - setIsConfirmed(false); - }, props.timeout); - setIsConfirmed(true); - } - }; - - const confirmedClass = isConfirmed ? props.confirmClass : ''; - const text = isConfirmed ? props.confirmText : props.text; - const title = isConfirmed ? `Confirm ${props.title}` : props.title; - - return html` - - `; -} - export * from './page-footer'; export * from './page-header'; export * from './page-main'; diff --git a/source/utilities/components/link.ts b/source/utilities/components/link.ts new file mode 100644 index 0000000..d45c4a0 --- /dev/null +++ b/source/utilities/components/link.ts @@ -0,0 +1,20 @@ +import {html} from 'htm/preact'; +import {Queue} from '../../types.d'; + +type LinkProps = { + class: string; + text: string; + url: string; +}; + +/** + * Creates a new element with target="_blank" and rel="noopener". + * @param props The Link properties. + */ +export function Link(props: LinkProps): Queue.Component { + return html` + + ${props.text} + + `; +} diff --git a/source/utilities/components/page-footer.ts b/source/utilities/components/page-footer.ts index 4ea70c7..60506b5 100644 --- a/source/utilities/components/page-footer.ts +++ b/source/utilities/components/page-footer.ts @@ -1,12 +1,14 @@ import {html} from 'htm/preact'; -import {Link, QComponent, QManifest, Settings} from '../..'; + +import {Queue} from '../../types.d'; +import {Link} from './link'; type FooterProps = { - manifest: QManifest; + manifest: Queue.Manifest; showVersionUpdated: boolean; }; -export function PageFooter(props: FooterProps): QComponent { +export function PageFooter(props: FooterProps): Queue.Component { const donateLink = html`<${Link} text="Donate" url="https://liberapay.com/Holllo" diff --git a/source/utilities/components/page-header.ts b/source/utilities/components/page-header.ts index 8f107f0..8d12e68 100644 --- a/source/utilities/components/page-header.ts +++ b/source/utilities/components/page-header.ts @@ -1,7 +1,8 @@ import {html} from 'htm/preact'; -import {QComponent} from '../..'; -export function PageHeader(): QComponent { +import {Queue} from '../../types.d'; + +export function PageHeader(): Queue.Component { return html`