diff --git a/index.html b/index.html index 8e465dc..6cc6ea4 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,6 @@ + @@ -7,9 +8,12 @@ - + +
@@ -32,7 +36,9 @@
@@ -41,14 +47,18 @@
@@ -66,21 +76,24 @@ Size - -
- -
- -
+ + - + - + - + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 728d32b..3b1d863 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,10 @@ "marked": "^15.0.12" }, "devDependencies": { - "electron": "^39.2.6" + "@types/marked": "^5.0.2", + "@types/node": "^25.0.1", + "electron": "^39.2.6", + "typescript": "^5.9.3" } }, "node_modules/@electron/get": { @@ -93,14 +96,21 @@ "@types/node": "*" } }, + "node_modules/@types/marked": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz", + "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { - "version": "22.19.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", - "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.1.tgz", + "integrity": "sha512-czWPzKIAXucn9PtsttxmumiQ9N0ok9FrBwgRWrwmVLlp86BrMExzvXRLFYRJ+Ex3g6yqj+KuaxfX1JTgV2lpfg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/responselike": { @@ -307,6 +317,23 @@ "node": ">= 12.20.55" } }, + "node_modules/electron/node_modules/@types/node": { + "version": "22.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", + "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -848,10 +875,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index c7f581b..e073441 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,22 @@ { "name": "teleprompter", "version": "1.0.0", - "main": "main.js", + "main": "dist/main.js", "scripts": { - "start": "electron ." + "build": "tsc", + "start": "npm run build && electron ." }, "keywords": [], "author": "", "license": "ISC", "description": "", "devDependencies": { - "electron": "^39.2.6" + "@types/marked": "^5.0.2", + "@types/node": "^25.0.1", + "electron": "^39.2.6", + "typescript": "^5.9.3" }, "dependencies": { "marked": "^15.0.12" } -} +} \ No newline at end of file diff --git a/preload.js b/preload.js deleted file mode 100644 index e25bb2a..0000000 --- a/preload.js +++ /dev/null @@ -1,6 +0,0 @@ -const { contextBridge, ipcRenderer } = require('electron'); - -contextBridge.exposeInMainWorld('electronAPI', { - openFileDialog: () => ipcRenderer.invoke('open-file-dialog'), - readFile: (filePath) => ipcRenderer.invoke('read-file', filePath) -}); diff --git a/main.js b/src/main.ts similarity index 80% rename from main.js rename to src/main.ts index 71df66c..6e9bd7c 100644 --- a/main.js +++ b/src/main.ts @@ -1,6 +1,6 @@ -const { app, BrowserWindow, ipcMain, dialog } = require('electron'); -const path = require('path'); -const fs = require('fs'); +import { app, BrowserWindow, ipcMain, dialog } from 'electron'; +import * as path from 'path'; +import * as fs from 'fs'; function createWindow() { const win = new BrowserWindow({ @@ -17,7 +17,8 @@ function createWindow() { titleBarStyle: 'hiddenInset' // Looks cleaner on macOS, normal on others }); - win.loadFile('index.html'); + win.loadFile(path.join(__dirname, '../index.html')); + // Note: index.html is in the root, dist/main.js is in dist/. So we need to go up one level. } app.whenReady().then(() => { @@ -52,7 +53,7 @@ ipcMain.handle('open-file-dialog', async () => { } }); -ipcMain.handle('read-file', async (event, filePath) => { +ipcMain.handle('read-file', async (event, filePath: string) => { try { const content = fs.readFileSync(filePath, 'utf-8'); return content; diff --git a/src/preload.ts b/src/preload.ts new file mode 100644 index 0000000..4c2ecf3 --- /dev/null +++ b/src/preload.ts @@ -0,0 +1,6 @@ +import { contextBridge, ipcRenderer } from 'electron'; + +contextBridge.exposeInMainWorld('electronAPI', { + openFileDialog: () => ipcRenderer.invoke('open-file-dialog'), + readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath) +}); diff --git a/renderer.js b/src/renderer.ts similarity index 60% rename from renderer.js rename to src/renderer.ts index 899bf5e..c2f6c22 100644 --- a/renderer.js +++ b/src/renderer.ts @@ -1,18 +1,31 @@ -const btnHeroOpen = document.getElementById('btn-hero-open'); -const btnOpen = document.getElementById('btn-open'); -const btnPlay = document.getElementById('btn-play'); -const btnReset = document.getElementById('btn-reset'); -const inputSpeed = document.getElementById('input-speed'); -const inputSize = document.getElementById('input-size'); -const markdownOutput = document.getElementById('markdown-output'); -const placeholder = document.getElementById('placeholder'); -const scrollContainer = document.getElementById('scroll-container'); -const iconPlay = document.getElementById('icon-play'); -const iconPause = document.getElementById('icon-pause'); +interface ElectronAPI { + openFileDialog: () => Promise; + readFile: (path: string) => Promise; +} + +interface Window { + electronAPI: ElectronAPI; +} + +declare const marked: { + parse: (content: string) => string; +}; + +const btnHeroOpen = document.getElementById('btn-hero-open') as HTMLButtonElement; +const btnOpen = document.getElementById('btn-open') as HTMLButtonElement; +const btnPlay = document.getElementById('btn-play') as HTMLButtonElement; +const btnReset = document.getElementById('btn-reset') as HTMLButtonElement; +const inputSpeed = document.getElementById('input-speed') as HTMLInputElement; +const inputSize = document.getElementById('input-size') as HTMLInputElement; +const markdownOutput = document.getElementById('markdown-output') as HTMLDivElement; +const placeholder = document.getElementById('placeholder') as HTMLDivElement; +const scrollContainer = document.getElementById('scroll-container') as HTMLDivElement; +const iconPlay = document.getElementById('icon-play') as HTMLElement; +const iconPause = document.getElementById('icon-pause') as HTMLElement; let isScrolling = false; let scrollAccumulator = 0; -let animationId; +let animationId: number; let scrollSpeed = parseInt(inputSpeed.value, 10); // Initialize @@ -28,7 +41,7 @@ function setupEventListeners() { btnPlay.addEventListener('click', toggleScrolling); // Spacebar to toggle - document.addEventListener('keydown', (e) => { + document.addEventListener('keydown', (e: KeyboardEvent) => { if (e.code === 'Space') { e.preventDefault(); // Prevent page down scroll toggleScrolling(); @@ -40,27 +53,31 @@ function setupEventListeners() { scrollContainer.scrollTo({ top: 0, behavior: 'smooth' }); }); - inputSpeed.addEventListener('input', (e) => { - scrollSpeed = parseInt(e.target.value, 10); + inputSpeed.addEventListener('input', (e: Event) => { + const target = e.target as HTMLInputElement; + scrollSpeed = parseInt(target.value, 10); }); - inputSize.addEventListener('input', (e) => { - updateFontSize(e.target.value); + inputSize.addEventListener('input', (e: Event) => { + const target = e.target as HTMLInputElement; + updateFontSize(target.value); }); } async function openFile() { - const filePath = await window.electronAPI.openFileDialog(); + // Explicitly cast window if interface merging fails in some environments + const api = (window as unknown as Window).electronAPI; + const filePath = await api.openFileDialog(); if (filePath) { - const content = await window.electronAPI.readFile(filePath); + const content = await api.readFile(filePath); renderMarkdown(content); } } -function renderMarkdown(content) { +function renderMarkdown(content: string) { if (!content) return; - // Using marked from global scope (loaded via script tag) + // Using marked from global scope const html = marked.parse(content); markdownOutput.innerHTML = html; @@ -72,7 +89,7 @@ function renderMarkdown(content) { scrollContainer.scrollTop = 0; } -function updateFontSize(size) { +function updateFontSize(size: string) { markdownOutput.style.fontSize = `${size}px`; } @@ -106,31 +123,17 @@ function stopScrolling() { function step() { if (!isScrolling) return; - // Calculate scroll amount based on speed - // Map 1-100 to sensible range. - // Let's say max speed (100) is ~300 pixels per second? ~5px/frame at 60fps - // Min speed (1) is ~10 pixels per second? ~0.16px/frame - - // Formula: (speed / 100) * maxPixelsPerFrame - // Adding a base speed so 1 isn't 0. - const maxSpeedValues = 5.0; const minSpeedValues = 0.2; - // Exponential curve for better control at lower speeds - // Normalized speed 0-1 const normalizedSpeed = (scrollSpeed - 1) / 99; - // Linear interpolation? Or quadratic? Quadratic feels "acceleration" - // Let's stick to linear for predictability first, user says "simple" const pixelsPerFrame = minSpeedValues + (normalizedSpeed * (maxSpeedValues - minSpeedValues)); scrollAccumulator += pixelsPerFrame; const pixelsToScroll = Math.floor(scrollAccumulator); if (pixelsToScroll >= 1) { - // Stop if at bottom - // Use a small buffer (1px) for float inconsistencies if (Math.ceil(scrollContainer.scrollTop + scrollContainer.clientHeight) >= scrollContainer.scrollHeight) { stopScrolling(); return; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..946759c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file