feat: Migrate project to TypeScript by adding configuration, dependencies, and converting preload script.
This commit is contained in:
+22
-9
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -7,9 +8,12 @@
|
||||
<!-- Premium Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;700&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;700&family=JetBrains+Mono:wght@400&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<!-- Main Scroll Area -->
|
||||
@@ -32,7 +36,9 @@
|
||||
<button id="btn-open" class="icon-btn" title="Open File">
|
||||
<!-- Folder Icon -->
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon">
|
||||
<path d="M4 6C4 4.89543 4.89543 4 6 4H10L12 6H18C19.1046 6 20 6.89543 20 8V18C20 19.1046 19.1046 20 18 20H6C4.89543 20 4 19.1046 4 18V6Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path
|
||||
d="M4 6C4 4.89543 4.89543 4 6 4H10L12 6H18C19.1046 6 20 6.89543 20 8V18C20 19.1046 19.1046 20 18 20H6C4.89543 20 4 19.1046 4 18V6Z"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -43,12 +49,16 @@
|
||||
<button id="btn-play" class="action-btn" title="Start/Stop Scrolling">
|
||||
<!-- Play Icon -->
|
||||
<svg id="icon-play" width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon">
|
||||
<path d="M5 3L19 12L5 21V3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="currentColor"/>
|
||||
<path d="M5 3L19 12L5 21V3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" fill="currentColor" />
|
||||
</svg>
|
||||
<!-- Pause Icon (Initially hidden) -->
|
||||
<svg id="icon-pause" width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon" style="display: none;">
|
||||
<path d="M10 4H6V20H10V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="currentColor"/>
|
||||
<path d="M18 4H14V20H18V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="currentColor"/>
|
||||
<svg id="icon-pause" width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon"
|
||||
style="display: none;">
|
||||
<path d="M10 4H6V20H10V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" fill="currentColor" />
|
||||
<path d="M18 4H14V20H18V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" fill="currentColor" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -72,8 +82,10 @@
|
||||
<div class="control-group">
|
||||
<button id="btn-reset" class="icon-btn" title="Reset Position">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon">
|
||||
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 3v5h5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M3 3v5h5" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -81,6 +93,7 @@
|
||||
</div>
|
||||
|
||||
<script src="./node_modules/marked/marked.min.js"></script>
|
||||
<script src="renderer.js"></script>
|
||||
<script src="dist/renderer.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Generated
+49
-8
@@ -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"
|
||||
},
|
||||
|
||||
+7
-3
@@ -1,16 +1,20 @@
|
||||
{
|
||||
"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"
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
});
|
||||
@@ -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<string | null>;
|
||||
readFile: (path: string) => Promise<string>;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user