Skip to content
Closed
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:
- "*"
17 changes: 9 additions & 8 deletions .github/workflows/test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ on:
types: [opened, synchronize, reopened]
workflow_dispatch:

permissions:
contents: read
pull-requests: read

jobs:
build:
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 +30,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/
5 changes: 5 additions & 0 deletions extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ async function runSpeedReader(): Promise<void> {
...(settings["speed-reader-settings"] || {}),
};

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

await browser.scripting.executeScript({
target: { tabId: tab.id },
func: (settings: Settings) => {
Expand Down
31 changes: 20 additions & 11 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,27 @@
"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"
},
"resolutions": {
"serialize-javascript": "^7.0.5",
"diff": "^8.0.3"
}
}
}
145 changes: 33 additions & 112 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 './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,28 @@ export class Renderer {
navigateWord: () => void): void {
this.removeUI();

const style = document.createElement('style');
style.textContent = styles(settings);
document.head.append(style);

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}px`);

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 +58,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 +141,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 @@ -140,103 +161,3 @@ export class Renderer {
this.container?.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;
}
65 changes: 65 additions & 0 deletions src/main/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#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;
position: relative;
background: var(--bg-color);
}

#speed-reader-container .speed-reader-word-container {
display: flex;
align-items: center;
margin: 20px 0px;
white-space: pre-wrap;
}

#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;
}
Loading