Skip to content
Open
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
21 changes: 21 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "monthly"
groups:
npm:
patterns:
- "*"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
github-actions:
patterns:
- "*"
20 changes: 11 additions & 9 deletions .github/workflows/test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ on:
branches: master
pull_request:
branches: "*"
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:

permissions:
contents: read
pull-requests: read

jobs:
build:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

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

Expand All @@ -26,16 +31,13 @@ jobs:
yarn --version

- name: Run install
run: yarn install

- name: Run tests
run: yarn test
run: yarn install --frozen-lockfile

- name: Build
run: yarn build

- name: Upload build artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: speed-reader
path: ${{github.workspace}}/build/
25 changes: 21 additions & 4 deletions extension.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import browser from "webextension-polyfill";
import { defaultSettings, Settings } from "./src/main/Settings";

declare global {
Expand All @@ -7,6 +6,8 @@ declare global {
}
}

const browser = (globalThis as any).browser || (globalThis as any).chrome;

browser.runtime.onInstalled.addListener(() => {
browser.contextMenus.create({
id: "speed-reader",
Expand All @@ -25,6 +26,7 @@ async function runSpeedReader(): Promise<void> {
...(settings["speed-reader-settings"] || {}),
};

// Inject settings into the page
await browser.scripting.executeScript({
target: { tabId: tab.id },
func: (settings: Settings) => {
Expand All @@ -33,15 +35,30 @@ async function runSpeedReader(): Promise<void> {
args: [finalSettings],
});

await browser.scripting.executeScript({
// Check if the script is already loaded on this page
const results = await browser.scripting.executeScript({
target: { tabId: tab.id },
files: ["/build/speed-reader.js"],
func: () => {
if (typeof (window as any).startSpeedReader === 'function') {
(window as any).startSpeedReader();
return true;
}
return false;
},
});

// First run on this tab — inject the script file (which auto-calls startSpeedReader)
if (!results[0]?.result) {
await browser.scripting.executeScript({
target: { tabId: tab.id },
files: ["/build/speed-reader.js"],
});
}
}

browser.action.onClicked.addListener(runSpeedReader);

browser.contextMenus.onClicked.addListener((info) => {
browser.contextMenus.onClicked.addListener((info: any) => {
if (info.menuItemId == "speed-reader") {
runSpeedReader();
}
Expand Down
31 changes: 19 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
"name": "speed-reader",
"version": "1.6",
"license": "MIT",
"engines": {
"node": ">=24.0.0",
"yarn": ">=1.22.22"
},
"scripts": {
"start": "parcel src/test/test.html",
"start-settings": "parcel src/settings/settings.html",
Expand All @@ -13,22 +17,25 @@
"package": "rm -rf build && yarn build && zip -r extension.zip build icons manifest.json"
},
"devDependencies": {
"@parcel/core": "^2.14.4",
"@parcel/transformer-less": "2.14.4",
"@types/node": "^22.12.0",
"@parcel/core": "^2.16.4",
"@parcel/transformer-inline-string": "^2.16.4",
"@parcel/transformer-less": "^2.16.4",
"@types/chai": "^5.2.3",
"@types/mocha": "^10.0.10",
"@types/node": "^24.0.0",
"@types/webextension-polyfill": "^0.12.3",
"@types/mocha": "^7.0.2",
"@types/chai": "^4.2.11",
"chai": "^4.2.0",
"mocha": "^7.1.1",
"ts-node": "^8.8.1",
"jsdom": "^26.0.0",
"chai": "^6.2.2",
"jsdom": "^29.1.0",
"jsdom-global": "^3.0.2",
"less": "^4.2.2",
"mocha": "11.7.5",
"parcel": "^2.14.4",
"typescript": "^5.7.3"
"ts-node": "^10.9.2",
"typescript": "^6.0.3"
},
"dependencies": {
"webextension-polyfill": "^0.12.0"
"dependencies": {},
"resolutions": {
"serialize-javascript": "^7.0.5",
"diff": "^8.0.3"
}
}
149 changes: 38 additions & 111 deletions src/main/Renderer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { Iterator } from './Iterator';
import { Settings } from './Settings';
import { remainingTime } from './words';
import templateStr from 'bundle-text:./template.html';
import * as styles from 'bundle-text:./styles.css';

export class Renderer {
private container!: HTMLDivElement;
private wordStartEl!: HTMLDivElement;
private wordMiddleEl!: HTMLDivElement;
private wordEndEl!: HTMLDivElement;
private speedCurrentEl!: HTMLSpanElement;
private timeEl!: HTMLDivElement;

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

Expand All @@ -14,18 +21,33 @@ export class Renderer {
navigateWord: () => void): void {
this.removeUI();

const style = document.createElement('style');
style.textContent = styles(settings);
document.head.append(style);
const styleEl = document.createElement('style');
styleEl.id = 'speed-reader-style';
styleEl.textContent = (styles as any).default;
document.head.append(styleEl);

document.body.innerHTML += `
<div id="speed-reader-container">
${template('&nbsp;', '', '', 0, '')}
</div>
`;
document.body.insertAdjacentHTML('beforeend', templateStr);

this.container = document.querySelector('#speed-reader-container')!;

this.container.style.setProperty('--bg-color', settings.backgroundColor);
this.container.style.setProperty('--text-color', settings.textColor);
this.container.style.setProperty('--middle-letter-color', settings.middleLetterColor);
this.container.style.setProperty('--font-family', settings.fontFamily);
this.container.style.setProperty('--font-size', settings.fontSize);

const wrapper = this.container.querySelector('.speed-reader-wrapper') as HTMLElement;
wrapper.style.width = settings.fullScreen ? '100%' : settings.width;
wrapper.style.height = settings.fullScreen ? '100%' : settings.height;

const wordContainer = this.container.querySelector('.speed-reader-word-container') as HTMLElement;
wordContainer.style.height = settings.fullScreen ? '90%' : 'auto';
this.wordStartEl = this.container.querySelector('.speed-reader-word-start')!;
this.wordMiddleEl = this.container.querySelector('.speed-reader-word-middle')!;
this.wordEndEl = this.container.querySelector('.speed-reader-word-end')!;
this.speedCurrentEl = this.container.querySelector('.speed-reader-speed-current')!;
this.timeEl = this.container.querySelector('.speed-reader-time')!;

this.bindEvents(
settings,
togglePause,
Expand All @@ -41,7 +63,11 @@ export class Renderer {
const time = this.renderTime(interval);
const [start, middle, end] = this.renderWords(word);

this.container.innerHTML = template(start, middle, end, wpm, time);
this.wordStartEl.textContent = start;
this.wordMiddleEl.textContent = middle;
this.wordEndEl.textContent = end;
this.speedCurrentEl.textContent = wpm.toString();
this.timeEl.textContent = time;
}

private bindEvents(
Expand Down Expand Up @@ -120,9 +146,9 @@ export class Renderer {
if (word.charAt(middleIndex) === ' ') middleIndex--;

return [
word.substring(0, middleIndex).replace(/\s/g, '&nbsp'),
word.substring(0, middleIndex),
word.charAt(middleIndex),
word.substring(middleIndex + 1).replace(/\s/g, '&nbsp'),
word.substring(middleIndex + 1),
];
}

Expand All @@ -138,105 +164,6 @@ export class Renderer {

private removeUI(): void {
this.container?.remove();
document.getElementById('speed-reader-style')?.remove();
}
}

const template = (
start: string,
middle: string,
end: string,
wpm: number,
time: string,) =>
`<div class="speed-reader-wrapper">
<div class="speed-reader-word-container">
<div class="speed-reader-word-start">${start}</div>
<div class="speed-reader-word-middle">${middle}</div>
<div class="speed-reader-word-end">${end}</div>
</div>
<div class="speed-reader-controls">
<div class="speed-reader-speed">
<span class="speed-reader-speed-minus">-</span>
<span class="speed-reader-speed-current">${wpm}</span>
<span class="speed-reader-speed-plus">+</span>
</div>
<div class="speed-reader-time">${time}</div>
</div>
</div>`;

const styles = (settings: Settings) => `
#speed-reader-container {
--bg-color: ${settings.backgroundColor};
--text-color: ${settings.textColor};
--middle-letter-color: ${settings.middleLetterColor};
--font-family: ${settings.fontFamily};
--font-size: ${settings.fontSize};
}

#speed-reader-container {
position: fixed;
z-index: 9999;
top: 0;
left: 0;
height: 100%;
width: 100%;

display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;

background: rgba(128, 128, 128, .50);

color: var(--text-color);
font-family: var(--font-family);
font-size: var(--font-size);
}

#speed-reader-container .speed-reader-wrapper {
padding: 10px;
width: ${settings.fullScreen ? '100%' : settings.width};
height: ${settings.fullScreen ? '100%' : settings.height};
position: relative;
background: var(--bg-color);
}

#speed-reader-container .speed-reader-word-container {
display: flex;
align-items: center;
margin: 20px 0px;
height: ${settings.fullScreen ? '90%' : 'auto'};
}

#speed-reader-container .speed-reader-word-start {
flex: 1;
text-align: right;
}

#speed-reader-container .speed-reader-word-end {
flex: 1;
text-align: left;
}

#speed-reader-container .speed-reader-word-middle {
flex: 0;
color: var(--middle-letter-color);
}

#speed-reader-container .speed-reader-controls {
font-size: 12px;
display: flex;
}

#speed-reader-container .speed-reader-speed {
flex: 1;
}

#speed-reader-container .speed-reader-speed-plus,
#speed-reader-container .speed-reader-speed-minus {
cursor: pointer;
user-select: none;
display: inline-block;
width: 15px;
text-align: center;
}
`;
4 changes: 4 additions & 0 deletions src/main/parcel.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module 'bundle-text:*' {
const value: string;
export default value;
}
Loading