Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/test_build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: "Test and build workflow"
on:
push:
branches: master
pull_request:
branches: "*"
types: [opened, synchronize, reopened]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
cache-dependency-path: yarn.lock

- name: Install Yarn
run: |
npm install -g yarn
yarn --version

- name: Run install
run: yarn install

- name: Run tests
run: yarn test

- name: Build
run: yarn build

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: speed-reader
path: ${{github.workspace}}/build/
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
.cache
.parcel-cache
dist
build
extension.zip
Expand Down
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@ There are other extensions that do the same, but none for Firefox that doesn't r

## Usage

Simply select the text you want to speed-read and click the extension button.

Simply select the text you want to speed-read and click the extension button,
right click the page and then the extension button, or by default
use CTRL+ALT+U, see [extension shortcuts](https://support.mozilla.org/en-US/kb/manage-extension-shortcuts-firefox) on how to change (thanks @sheldoncork).

### Hotkeys

|Button|Action|
|-|-|
|Spacebar|Toggle pause|
|Escape|Close it|
|Arrow Up|Speed Up|
|Arrow Down|Speed Down|
|Arrow Left|Previous word (useful when paused)|
|Arrow Right|Next word (useful when paused)|
| Button | Action |
| ----------- | ---------------------------------- |
| Spacebar | Toggle pause |
| Escape | Close it |
| Arrow Up | Speed Up |
| Arrow Down | Speed Down |
| Arrow Left | Previous word (useful when paused) |
| Arrow Right | Next word (useful when paused) |

You can also click anywhere on the background to close it.

Expand Down Expand Up @@ -53,4 +54,4 @@ yarn build

Creates a `build/speed-reader.js` file that's ready to be executed.

It is possible to simply add that file as a bookmarklet to achieve the same result as installing the extension.
It is possible to simply add that file as a [bookmarklet](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#installing) to achieve the same result as installing the extension.
28 changes: 0 additions & 28 deletions extension.js

This file was deleted.

48 changes: 48 additions & 0 deletions extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import browser from "webextension-polyfill";
import { defaultSettings, Settings } from "./src/main/Settings";

declare global {
interface Window {
speedReaderSettings: Settings;
}
}

browser.runtime.onInstalled.addListener(() => {
browser.contextMenus.create({
id: "speed-reader",
title: "Speed Reader",
contexts: ["selection"],
});
});

async function runSpeedReader(): Promise<void> {
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return;

const settings = await browser.storage.sync.get("speed-reader-settings");
const finalSettings: Settings = {
...defaultSettings,
...(settings["speed-reader-settings"] || {}),
};

await browser.scripting.executeScript({
target: { tabId: tab.id },
func: (settings: Settings) => {
window.speedReaderSettings = settings;
},
args: [finalSettings],
});

await browser.scripting.executeScript({
target: { tabId: tab.id },
files: ["/build/speed-reader.js"],
});
}

browser.action.onClicked.addListener(runSpeedReader);

browser.contextMenus.onClicked.addListener((info) => {
if (info.menuItemId == "speed-reader") {
runSpeedReader();
}
});
25 changes: 18 additions & 7 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "Speed Reader",
"version": "1.4",
"version": "1.6",
"description": "Speed-read the web",
"homepage_url": "https://github.com/filipesabella/speed-reader",
"browser_specific_settings": {
"gecko": {
"id": "{9ad66b79-fd5e-477a-8915-eea26c83ff42}",
"strict_min_version": "42.0"
"strict_min_version": "109.0"
}
},
"icons": {
"48": "icons/speed-reader-48.png",
"96": "icons/speed-reader-96.png"
},
"permissions": [
"contextMenus",
"activeTab",
"storage"
"storage",
"scripting"
],
"browser_action": {
"action": {
"default_icon": "icons/speed-reader-36.png",
"default_title": "Speed Reader"
},
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Alt+U"
},
"description": "Speed-read the selected text"
}
},
"background": {
"scripts": [
"extension.js"
]
"build/extension.js"
],
"service_worker": "build/extension.js"
},
"options_ui": {
"page": "build/settings.html"
Expand Down
38 changes: 22 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
{
"name": "speed-reader",
"version": "1.0.4",
"main": "index.js",
"version": "1.6",
"license": "MIT",
"scripts": {
"start": "parcel src/test/test.html",
"start-settings": "parcel src/settings/settings.html",
"test": "mocha src/test/**/*.test.ts -r jsdom-global/register -r ts-node/register",
"build-settings": "parcel build src/settings/settings.html --out-dir build/ --out-file settings --public-url ./ --no-source-maps --no-minify",
"build-main": "parcel build src/main/speed-reader.ts --out-dir build/ --out-file speed-reader.js --no-source-maps --no-minify",
"build": "yarn test && yarn build-settings && yarn build-main",
"package": "rm -rf build && yarn build && zip -r extension.zip build icons extension.js manifest.json"
"test": "mocha --require ts-node/register src/test/**/*.test.ts",
"build-settings": "parcel build src/settings/settings.html --dist-dir build/ --public-url ./ --no-source-maps",
"build-main": "parcel build src/main/speed-reader.ts --dist-dir build/ --no-source-maps",
"build-extension": "parcel build extension.ts --dist-dir build/ --no-source-maps",
"build": "yarn test && yarn build-settings && yarn build-main && yarn build-extension",
"package": "rm -rf build && yarn build && zip -r extension.zip build icons manifest.json"
},
"devDependencies": {
"@types/chai": "^4.2.14",
"@types/mocha": "^8.2.0",
"@types/node": "^14.14.14",
"@parcel/core": "^2.14.4",
"@parcel/transformer-less": "2.14.4",
"@types/node": "^22.12.0",
"@types/webextension-polyfill": "^0.12.3",
"@types/mocha": "^7.0.2",
"@types/chai": "^4.2.11",
"chai": "^4.2.0",
"jsdom": "^16.4.0",
"mocha": "^7.1.1",
"ts-node": "^8.8.1",
"jsdom": "^26.0.0",
"jsdom-global": "^3.0.2",
"less": "^4.1.2",
"mocha": "^8.2.1",
"parcel-bundler": "^1.12.4",
"ts-node": "^9.1.1",
"typescript": "^4.1.3"
"less": "^4.2.2",
"parcel": "^2.14.4",
"typescript": "^5.7.3"
},
"dependencies": {
"webextension-polyfill": "^0.12.0"
}
}
6 changes: 3 additions & 3 deletions src/main/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Settings } from './Settings';
import { remainingTime } from './words';

export class Renderer {
private container: HTMLDivElement;
private container!: HTMLDivElement;

constructor(private readonly words: Iterator<string>) { }

Expand Down Expand Up @@ -80,7 +80,7 @@ export class Renderer {

const handleEvent = (type: 'press' | 'down' | 'up') =>
(e: KeyboardEvent) => {
const handler = eventHandlers[type][e.code];
const handler = (eventHandlers[type] as { [key: string]: () => void })[e.code];
if (handler) {
e.preventDefault();
handler();
Expand Down Expand Up @@ -122,7 +122,7 @@ export class Renderer {
return [
word.substring(0, middleIndex).replace(/\s/g, '&nbsp'),
word.charAt(middleIndex),
word.substr(middleIndex + 1).replace(/\s/g, '&nbsp'),
word.substring(middleIndex + 1).replace(/\s/g, '&nbsp'),
];
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ export type Settings = {
height: string;
speedIncrement: number;
initialSpeed: number;
punctuationDelayMultiplier: number;
wordAmount: number;
};

// duplicated in extension.js. when in the mood, de-duplicate by building
// extension.js with parcel bundler to support importing from settings.ts
export const defaultSettings = {
fontFamily: 'monospace',
backgroundColor: 'hsl(0, 0%, 15%)',
Expand All @@ -28,6 +27,7 @@ export const defaultSettings = {
height: 'auto',
speedIncrement: 30,
initialSpeed: 400,
punctuationDelayMultiplier: 2,
wordAmount: 1,
};

Expand Down
4 changes: 3 additions & 1 deletion src/main/words.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Iterator } from './Iterator';
import { defaultSettings } from './Settings';

export function textToWords(text: string, wordAmount: number)
: Iterator<string> {
Expand Down Expand Up @@ -44,7 +45,8 @@ export function timeoutForWord(interval: number, word: string): number {
if (word.match(/\n$/)) {
intervalMultiplier = 3;
} else if (word.match(/[,\.\?\!\:]$/)) {
intervalMultiplier = 2;
intervalMultiplier = defaultSettings.punctuationDelayMultiplier < 1 ?
1 : defaultSettings.punctuationDelayMultiplier; // don't allow < 1 on the multiplier
}

return interval * intervalMultiplier;
Expand Down
6 changes: 5 additions & 1 deletion src/settings/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ <h1>Speed Reader Settings</h1>
<input name="initialSpeed"
type="number"
placeholder="Any number" />
<label>Punctuation Delay Multiplier</label>
<input name="punctuationDelayMultiplier"
type="number"
placeholder="Any number" />
<label>Speed increment</label>
<input name="speedIncrement"
type="number"
Expand All @@ -57,7 +61,7 @@ <h1>Speed Reader Settings</h1>
He<span class="middle">l</span>lo
</div>
</div>
<script src="settings.ts"></script>
<script src="settings.ts" type="module"></script>
</body>

</html>
16 changes: 8 additions & 8 deletions src/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
.querySelector<HTMLDivElement>('#speed-reader-settings form')!;
loadSettingsFromStorage().then(settings => {
container.querySelectorAll('input').forEach(e => {
const attribute = e.name;
const attribute = e.name as keyof Settings;
if (e.type === 'checkbox') {
e.checked = settings[attribute];
e.checked = settings[attribute] as boolean;
} else {
e.value = settings[attribute];
e.value = settings[attribute] as string;
}

e.onchange = e.onkeyup = () => {
Expand Down Expand Up @@ -67,16 +67,16 @@ import {
function readSettingsFromForm(): Settings {
const container = document
.querySelector<HTMLDivElement>('#speed-reader-settings form')!;
const settings = { ...defaultSettings };
const settings: Settings = { ...defaultSettings };
container.querySelectorAll('input').forEach(e => {
const attribute = e.name;
const attribute = e.name as keyof Settings;
if (e.type === 'checkbox') {
settings[attribute] = e.checked;
(settings as any)[attribute] = e.checked;
} else if (e.type === 'number') {
settings[attribute] =
(settings as any)[attribute] =
parseInt(e.value) || defaultSettings.speedIncrement;
} else {
settings[attribute] = e.value;
(settings as any)[attribute] = e.value;
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/test/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
</p>
</div>

<script src="../main/speed-reader.ts"></script>
<script src="../main/speed-reader.ts" type="module"></script>
<script>
window.onload = () => {
var range = document.createRange();
Expand Down
Loading