Skip to content

Commit fb83818

Browse files
CopilotSIkebeCopilot
authored
Convert JavaScript to TypeScript for improved type safety and maintainability (#4)
* Initial plan * Convert JavaScript to TypeScript with build setup Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Complete TypeScript conversion with cleanup and documentation Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Replace magic numbers with named constants for default window dimensions Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Add GitHub Copilot setup workflow for development environment Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Enhance GitHub Copilot setup workflow with comprehensive validation and better structure Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Improve Copilot setup workflow with better error handling and validation Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Fix copilot-setup-steps.yml to match official documentation specification Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Add Devcontainer support for development environment Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Update Node.js to v22 LTS and add Japanese comments to tsconfig.json Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Remove Japanese comments from tsconfig.json for cleaner JSON format Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> * Update src/background.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactor: Move type definitions to a new types.ts file and update imports in background.ts and popup.ts * Cleanup: Remove unnecessary comments from copilot-setup-steps.yml for clarity * Cleanup: Remove npm cache configuration from Copilot setup steps * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix: Update import path for types and correct return type in getDisplayInfo function * Refactor: Consolidate type definitions into shared-types.ts and update references in background.ts and popup.ts * Enhance: Update watch script in package.json to preserve output and add watchOptions in tsconfig.json * Enhance: Add GitHub Actions extension to VSCode customizations in devcontainer.json --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SIkebe <17608272+SIkebe@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: SIkebe <SIkebe@users.noreply.github.com>
1 parent 8428499 commit fb83818

File tree

11 files changed

+232
-50
lines changed

11 files changed

+232
-50
lines changed

.devcontainer/devcontainer.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "Split Translator Development",
3+
"image": "mcr.microsoft.com/devcontainers/javascript-node:22-bookworm",
4+
"features": {
5+
"ghcr.io/devcontainers/features/github-cli:1": {}
6+
},
7+
"postCreateCommand": "npm install",
8+
"customizations": {
9+
"vscode": {
10+
"extensions": [
11+
"github.vscode-github-actions",
12+
"ms-vscode.vscode-typescript-next"
13+
]
14+
}
15+
},
16+
"remoteUser": "node"
17+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: "Copilot Setup Steps"
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
paths:
7+
- .github/workflows/copilot-setup-steps.yml
8+
pull_request:
9+
paths:
10+
- .github/workflows/copilot-setup-steps.yml
11+
12+
jobs:
13+
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
14+
copilot-setup-steps:
15+
runs-on: ubuntu-latest
16+
17+
permissions:
18+
contents: read
19+
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v4
23+
24+
- name: Set up Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: "22"
28+
29+
- name: Install JavaScript dependencies
30+
run: npm install
31+
32+
- name: Build TypeScript project
33+
run: npm run build

README.md

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,21 +110,57 @@ split-translator/
110110
| **Screen Resolution** | Minimum width of 800px is recommended |
111111
| **Browser Version** | Chrome 88+, Edge 88+ (Manifest V3 required) |
112112

113-
## 🛠️ Development & Customization
113+
## 🛠️ Development & Building
114114

115-
### Adjusting Window Overlap
116-
```javascript
117-
const OVERLAP_PIXELS = 8; // Adjustable in background.js
115+
This extension is built with **TypeScript** for enhanced type safety and maintainability.
116+
117+
### Prerequisites
118+
- Node.js (v22 or higher)
119+
- npm
120+
121+
### Building from Source
122+
```bash
123+
# Install dependencies
124+
npm install
125+
126+
# Build TypeScript to JavaScript
127+
npm run build
128+
129+
# Clean build directory
130+
npm run clean
131+
132+
# Watch for changes during development
133+
npm run watch
134+
135+
# Build and package for distribution
136+
npm run package
137+
```
138+
139+
### Project Structure
140+
```
141+
src/
142+
├── background.ts # Service worker (TypeScript)
143+
├── popup.ts # Popup script (TypeScript)
144+
dist/
145+
├── background.js # Compiled service worker
146+
├── popup.js # Compiled popup script
147+
```
148+
149+
### Development & Customization
150+
151+
#### Adjusting Window Overlap
152+
```typescript
153+
const OVERLAP_PIXELS = 8; // Adjustable in src/background.ts
118154
```
119155

120-
### Adding a Language
156+
#### Adding a Language
121157
```html
122158
<!-- Add to the <select> element in popup.html -->
123159
<option value="new_language_code">🏁 New Language Name</option>
124160
```
125161

126-
### Timeout Setting
127-
```javascript
162+
#### Timeout Setting
163+
```typescript
128164
await waitForTabReady(tabId, 3000); // 3 seconds timeout
129165
```
130166

build-package.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ if exist "dist\split-translator.zip" del "dist\split-translator.zip"
1313
echo [*] Creating package...
1414

1515
REM Create ZIP file using PowerShell
16-
powershell -Command "& { Add-Type -AssemblyName System.IO.Compression.FileSystem; $zip = [System.IO.Compression.ZipFile]::Open('dist\split-translator.zip', 'Create'); $files = @('manifest.json', 'popup.html', 'popup.js', 'background.js', 'LICENSE', 'PRIVACY_POLICY.md'); foreach ($file in $files) { if (Test-Path $file) { [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $file, $file) | Out-Null; Write-Host \"+ $file\" } }; if (Test-Path 'icons') { Get-ChildItem 'icons\*.png' | ForEach-Object { [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $_.FullName, 'icons\' + $_.Name) | Out-Null; Write-Host \"+ icons\$($_.Name)\" } }; $zip.Dispose() }"
16+
powershell -Command "& { Add-Type -AssemblyName System.IO.Compression.FileSystem; $zip = [System.IO.Compression.ZipFile]::Open('dist\split-translator.zip', 'Create'); $files = @('manifest.json', 'popup.html', 'dist\popup.js', 'dist\background.js', 'LICENSE', 'PRIVACY_POLICY.md'); foreach ($file in $files) { if (Test-Path $file) { [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $file, $file) | Out-Null; Write-Host \"+ $file\" } }; if (Test-Path 'icons') { Get-ChildItem 'icons\*.png' | ForEach-Object { [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $_.FullName, 'icons\' + $_.Name) | Out-Null; Write-Host \"+ icons\$($_.Name)\" } }; $zip.Dispose() }"
1717

1818
echo.
1919
echo [OK] Package created successfully: dist\split-translator.zip

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
}
2323
},
2424
"background": {
25-
"service_worker": "background.js"
25+
"service_worker": "dist/background.js"
2626
},
2727
"icons": {
2828
"16": "icons/icon-16.png",

package.json

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
"url": "https://github.com/SIkebe/split-translator/issues"
1313
},
1414
"scripts": {
15-
"package": "build-package.bat"
15+
"build": "tsc",
16+
"watch": "tsc --watch --preserveWatchOutput",
17+
"package": "npm run build && build-package.bat",
18+
"clean": "rimraf dist",
19+
"dev": "npm run clean && npm run build && echo 'Development build complete. Load dist/ folder in Chrome Extensions.'"
1620
},
1721
"keywords": [
1822
"browser-extension",
@@ -31,13 +35,15 @@
3135
},
3236
"license": "MIT",
3337
"devDependencies": {
34-
"@types/chrome": "0.0.329"
38+
"@types/chrome": "0.0.329",
39+
"rimraf": "6.0.1",
40+
"typescript": "5.8.3"
3541
},
3642
"files": [
3743
"manifest.json",
3844
"popup.html",
39-
"popup.js",
40-
"background.js",
45+
"dist/popup.js",
46+
"dist/background.js",
4147
"icons/",
4248
"LICENSE",
4349
"PRIVACY_POLICY.md",

popup.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,6 @@ <h1>Split Translator</h1>
228228
</div>
229229
</main>
230230

231-
<script src="popup.js"></script>
231+
<script src="dist/popup.js"></script>
232232
</body>
233233
</html>

background.js renamed to src/background.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
// Background script (Service Worker)
22

3+
/// <reference path="shared-types.ts" />
4+
35
// Constants
46
const OVERLAP_PIXELS = 8; // Compensate for window frame gaps
57
const MIN_WINDOW_WIDTH = 400; // Minimum window width in pixels
68
const MIN_WINDOW_HEIGHT = 300; // Minimum window height in pixels
9+
const DEFAULT_WINDOW_WIDTH = 800; // Default window width when current window width is unavailable
10+
const DEFAULT_WINDOW_HEIGHT = 600; // Default window height when current window height is unavailable
711

812
// Common error handler
9-
function handleError(error, context) {
13+
function handleError(error: Error, context: string): { success: false; error: string } {
1014
console.error(`${context}:`, error);
1115
return {
1216
success: false,
@@ -15,7 +19,7 @@ function handleError(error, context) {
1519
}
1620

1721
// Message listener
18-
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
22+
chrome.runtime.onMessage.addListener((request: SplitAndTranslateMessage, sender, sendResponse) => {
1923
if (request.action === 'splitAndTranslate') {
2024
handleSplitAndTranslate(request.currentTab, request.targetLanguage)
2125
.then(result => sendResponse(result))
@@ -25,7 +29,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2529
});
2630

2731
// Split view handling
28-
async function handleSplitView(currentTab, targetLanguage) {
32+
async function handleSplitView(currentTab: chrome.tabs.Tab, targetLanguage: string): Promise<{ success: true }> {
2933
try {
3034
console.log('Starting split view:', currentTab);
3135

@@ -44,8 +48,8 @@ async function handleSplitView(currentTab, targetLanguage) {
4448
'file://',
4549
];
4650

47-
if (UNSUPPORTED_PREFIXES.some(prefix => currentTab.url.startsWith(prefix)) ||
48-
currentTab.url.includes('translate.goog')) {
51+
if (UNSUPPORTED_PREFIXES.some(prefix => currentTab.url!.startsWith(prefix)) ||
52+
currentTab.url!.includes('translate.goog')) {
4953
throw new Error('This page type cannot be translated. Please try on a regular website.');
5054
}
5155

@@ -71,14 +75,14 @@ async function handleSplitView(currentTab, targetLanguage) {
7175
const rightWidth = halfWidth + OVERLAP_PIXELS;
7276

7377
// Calculate positions (use entire display)
74-
const leftPosition = {
78+
const leftPosition: WindowPosition = {
7579
left: displayLeft,
7680
top: displayTop,
7781
width: leftWidth,
7882
height: displayHeight
7983
};
8084

81-
const rightPosition = {
85+
const rightPosition: WindowPosition = {
8286
left: displayLeft + halfWidth - OVERLAP_PIXELS,
8387
top: displayTop,
8488
width: rightWidth,
@@ -96,15 +100,19 @@ async function handleSplitView(currentTab, targetLanguage) {
96100
state: 'normal'
97101
});
98102

103+
if (!rightWindow || !rightWindow.tabs || !rightWindow.tabs[0]?.id || !rightWindow.id) {
104+
throw new Error('Failed to create right window');
105+
}
106+
99107
// Resize left window
100108
await chrome.windows.update(currentTab.windowId, {
101109
...leftPosition,
102110
state: 'normal'
103111
});
104112

105113
// Save data
106-
const splitViewData = {
107-
originalTabId: currentTab.id,
114+
const splitViewData: SplitViewData = {
115+
originalTabId: currentTab.id!,
108116
duplicatedTabId: rightWindow.tabs[0].id,
109117
targetLanguage: targetLanguage,
110118
originalWindowId: currentTab.windowId,
@@ -122,12 +130,12 @@ async function handleSplitView(currentTab, targetLanguage) {
122130
}
123131

124132
// Execute split view and translation at once
125-
async function handleSplitAndTranslate(currentTab, targetLanguage) {
133+
async function handleSplitAndTranslate(currentTab: chrome.tabs.Tab, targetLanguage: string): Promise<{ success: true }> {
126134
try {
127135
console.log('Starting split view + translation:', currentTab);
128136

129137
// Prepare translation URL in advance (for parallel processing)
130-
const translateUrl = `https://translate.google.com/translate?sl=auto&tl=${targetLanguage}&u=${encodeURIComponent(currentTab.url)}`;
138+
const translateUrl = `https://translate.google.com/translate?sl=auto&tl=${targetLanguage}&u=${encodeURIComponent(currentTab.url!)}`;
131139

132140
// 1. Execute split view
133141
await handleSplitView(currentTab, targetLanguage);
@@ -148,7 +156,7 @@ async function handleSplitAndTranslate(currentTab, targetLanguage) {
148156
const currentUrl = rightTab.url;
149157

150158
// Do nothing if URL is already Google Translate page
151-
if (currentUrl.includes('translate.google.com')) {
159+
if (currentUrl && currentUrl.includes('translate.google.com')) {
152160
console.log('Right tab is already a Google Translate page');
153161
return { success: true };
154162
}
@@ -176,7 +184,7 @@ async function handleSplitAndTranslate(currentTab, targetLanguage) {
176184
}
177185

178186
// Get display information (helper function)
179-
async function getDisplayInfo() {
187+
async function getDisplayInfo(): Promise<chrome.system.display.DisplayUnitInfo[]> {
180188
if (!chrome.system?.display) return [];
181189

182190
return new Promise((resolve) => {
@@ -192,7 +200,7 @@ async function getDisplayInfo() {
192200
}
193201

194202
// Wait for tab to finish loading (helper function)
195-
async function waitForTabReady(tabId, maxWaitTime = 3000) {
203+
async function waitForTabReady(tabId: number, maxWaitTime: number = 3000): Promise<void> {
196204
const startTime = Date.now();
197205
while (Date.now() - startTime < maxWaitTime) {
198206
try {
@@ -206,7 +214,7 @@ async function waitForTabReady(tabId, maxWaitTime = 3000) {
206214
}
207215

208216
// Helper function to enforce minimum dimensions
209-
function enforceMinimumDimensions(bounds) {
217+
function enforceMinimumDimensions(bounds: DisplayBounds): DisplayBounds {
210218
return {
211219
...bounds,
212220
width: Math.max(bounds.width, MIN_WINDOW_WIDTH),
@@ -215,22 +223,22 @@ function enforceMinimumDimensions(bounds) {
215223
}
216224

217225
// Get display bounds (helper function)
218-
function getDisplayBounds(displays, currentWindow) {
219-
let bounds;
226+
function getDisplayBounds(displays: any[], currentWindow: chrome.windows.Window): DisplayBounds {
227+
let bounds: DisplayBounds;
220228

221229
if (!displays || !displays.length) {
222230
console.warn('No display information available, using current window bounds');
223231
bounds = {
224-
left: currentWindow.left, // Preserve current window position
225-
top: currentWindow.top, // Preserve current window position
226-
width: currentWindow.width,
227-
height: currentWindow.height
232+
left: currentWindow.left ?? 0, // Preserve current window position
233+
top: currentWindow.top ?? 0, // Preserve current window position
234+
width: currentWindow.width ?? DEFAULT_WINDOW_WIDTH,
235+
height: currentWindow.height ?? DEFAULT_WINDOW_HEIGHT
228236
};
229237
} else {
230238
// Find the display to which the current window belongs
231239
// Use window center point for accurate detection
232-
const windowCenterX = currentWindow.left + (currentWindow.width / 2);
233-
const windowCenterY = currentWindow.top + (currentWindow.height / 2);
240+
const windowCenterX = (currentWindow.left ?? 0) + ((currentWindow.width ?? DEFAULT_WINDOW_WIDTH) / 2);
241+
const windowCenterY = (currentWindow.top ?? 0) + ((currentWindow.height ?? DEFAULT_WINDOW_HEIGHT) / 2);
234242

235243
const display = displays.find(d =>
236244
windowCenterX >= d.workArea.left &&

0 commit comments

Comments
 (0)