From e7946725b545a4932f60e7fbdbbaa78c72fa6380 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 07:53:10 +0000
Subject: [PATCH 1/3] Initial plan
From 0b6f3ab63789e72a64af087f9512ed743d8fe202 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 07:56:47 +0000
Subject: [PATCH 2/3] Add drag and drop support for patch files and upgrade to
v1.0.1
Co-authored-by: unknowIfGuestInDream <57802425+unknowIfGuestInDream@users.noreply.github.com>
---
package-lock.json | 7 ++-
package.json | 2 +-
src/chrome/manifest.json | 2 +-
src/common/_locales/en/messages.json | 5 +-
src/common/_locales/ja/messages.json | 5 +-
src/common/_locales/zh/messages.json | 5 +-
src/common/diff-viewer.html | 2 +-
src/common/diff-viewer.js | 88 ++++++++++++++++++++++++++++
src/common/styles.css | 7 +++
src/edge/manifest.json | 2 +-
10 files changed, 116 insertions(+), 9 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 2851112..ebd3454 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,18 @@
{
"name": "patchreader-extension",
- "version": "1.0.0",
+ "version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "patchreader-extension",
- "version": "1.0.0",
+ "version": "1.0.1",
"license": "MIT",
"devDependencies": {
"diff2html": "^3.4.55"
+ },
+ "engines": {
+ "node": ">=22.0.0"
}
},
"node_modules/@profoundlogic/hogan": {
diff --git a/package.json b/package.json
index c77f738..66f5796 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "patchreader-extension",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Browser extension to read and render patch/diff files using diff2html",
"main": "index.js",
"engines": {
diff --git a/src/chrome/manifest.json b/src/chrome/manifest.json
index 0dd091f..b5a01f5 100644
--- a/src/chrome/manifest.json
+++ b/src/chrome/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Patch Reader",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Read and render patch/diff files with diff2html",
"default_locale": "en",
"icons": {
diff --git a/src/common/_locales/en/messages.json b/src/common/_locales/en/messages.json
index ce6313f..958e73b 100644
--- a/src/common/_locales/en/messages.json
+++ b/src/common/_locales/en/messages.json
@@ -30,7 +30,7 @@
"message": "After entering or uploading patch/diff content, the rendered result will be displayed here"
},
"inputPlaceholder": {
- "message": "Paste patch/diff content here, or click the upload button to upload .patch or .diff files..."
+ "message": "Paste patch/diff content here, drag and drop files here, or click the upload button to upload .patch or .diff files..."
},
"fileReadFailed": {
"message": "File read failed"
@@ -40,5 +40,8 @@
},
"viewed": {
"message": "Viewed"
+ },
+ "invalidFileType": {
+ "message": "Please drop .patch, .diff, or .txt files"
}
}
diff --git a/src/common/_locales/ja/messages.json b/src/common/_locales/ja/messages.json
index ecff61f..549055e 100644
--- a/src/common/_locales/ja/messages.json
+++ b/src/common/_locales/ja/messages.json
@@ -30,7 +30,7 @@
"message": "patch/diff 内容を入力またはアップロードすると、レンダリング結果がここに表示されます"
},
"inputPlaceholder": {
- "message": "ここに patch/diff 内容を貼り付けるか、アップロードボタンをクリックして .patch または .diff ファイルをアップロードしてください..."
+ "message": "ここに patch/diff 内容を貼り付けるか、ファイルをここにドラッグアンドドロップするか、アップロードボタンをクリックして .patch または .diff ファイルをアップロードしてください..."
},
"fileReadFailed": {
"message": "ファイルの読み取りに失敗しました"
@@ -40,5 +40,8 @@
},
"viewed": {
"message": "確認済み"
+ },
+ "invalidFileType": {
+ "message": ".patch、.diff、または .txt ファイルをドロップしてください"
}
}
diff --git a/src/common/_locales/zh/messages.json b/src/common/_locales/zh/messages.json
index fb9e27b..15027e0 100644
--- a/src/common/_locales/zh/messages.json
+++ b/src/common/_locales/zh/messages.json
@@ -30,7 +30,7 @@
"message": "输入或上传 patch/diff 内容后,渲染结果将显示在这里"
},
"inputPlaceholder": {
- "message": "在此粘贴 patch/diff 内容,或点击上传文件按钮上传 .patch 或 .diff 文件..."
+ "message": "在此粘贴 patch/diff 内容,拖拽文件到此处,或点击上传文件按钮上传 .patch 或 .diff 文件..."
},
"fileReadFailed": {
"message": "文件读取失败"
@@ -40,5 +40,8 @@
},
"viewed": {
"message": "已查看"
+ },
+ "invalidFileType": {
+ "message": "请拖拽 .patch、.diff 或 .txt 文件"
}
}
diff --git a/src/common/diff-viewer.html b/src/common/diff-viewer.html
index 2fb911c..05e9985 100644
--- a/src/common/diff-viewer.html
+++ b/src/common/diff-viewer.html
@@ -30,7 +30,7 @@
Input Patch/Diff Content
-
+
diff --git a/src/common/diff-viewer.js b/src/common/diff-viewer.js
index c1bbfce..18cb0ca 100644
--- a/src/common/diff-viewer.js
+++ b/src/common/diff-viewer.js
@@ -53,6 +53,94 @@
// Delegate click events for viewed checkboxes
diffOutput.addEventListener('change', handleViewedCheckboxChange);
+
+ // Drag and drop event listeners for the textarea
+ diffInput.addEventListener('dragover', handleDragOver);
+ diffInput.addEventListener('dragenter', handleDragEnter);
+ diffInput.addEventListener('dragleave', handleDragLeave);
+ diffInput.addEventListener('drop', handleDrop);
+ }
+
+ // Handle drag over event
+ function handleDragOver(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ // Handle drag enter event
+ function handleDragEnter(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ diffInput.classList.add('drag-over');
+ }
+
+ // Handle drag leave event
+ function handleDragLeave(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ diffInput.classList.remove('drag-over');
+ }
+
+ // Handle drop event
+ function handleDrop(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ diffInput.classList.remove('drag-over');
+
+ const files = event.dataTransfer.files;
+ if (!files || files.length === 0) return;
+
+ // Filter for valid file types
+ const validFiles = Array.from(files).filter(file => {
+ const ext = file.name.toLowerCase();
+ return ext.endsWith('.patch') || ext.endsWith('.diff') || ext.endsWith('.txt');
+ });
+
+ if (validFiles.length === 0) {
+ const errorMsg = window.i18n ? window.i18n.getMessage('invalidFileType') : 'Please drop .patch, .diff, or .txt files';
+ showError(errorMsg);
+ return;
+ }
+
+ processDroppedFiles(validFiles);
+ }
+
+ // Process dropped files
+ function processDroppedFiles(files) {
+ const readPromises = files.map(file => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = (e) => resolve({
+ name: file.name,
+ content: e.target.result
+ });
+ reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`));
+ reader.readAsText(file);
+ });
+ });
+
+ Promise.all(readPromises)
+ .then(results => {
+ // Combine all file contents
+ const combinedContent = results.map(r => {
+ // Add file header comment if multiple files
+ if (results.length > 1) {
+ return `# File: ${r.name}\n${r.content}`;
+ }
+ return r.content;
+ }).join('\n\n');
+
+ diffInput.value = combinedContent;
+ // Clear viewed files when new content is loaded
+ viewedFiles.clear();
+ saveViewedFiles();
+ renderDiff();
+ saveState();
+ })
+ .catch(error => {
+ const errorMsg = window.i18n ? window.i18n.getMessage('fileReadFailed') : 'File read failed';
+ showError(`${errorMsg}: ${error.message}`);
+ });
}
// Handle viewed checkbox change
diff --git a/src/common/styles.css b/src/common/styles.css
index 4084d1d..75f155a 100644
--- a/src/common/styles.css
+++ b/src/common/styles.css
@@ -173,6 +173,13 @@ body {
color: #6a737d;
}
+/* Drag and drop state */
+#diff-input.drag-over {
+ background-color: #dafbe1;
+ border: 2px dashed #238636;
+ outline: none;
+}
+
/* Output Section */
.output-section {
flex: 1;
diff --git a/src/edge/manifest.json b/src/edge/manifest.json
index 0dd091f..b5a01f5 100644
--- a/src/edge/manifest.json
+++ b/src/edge/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Patch Reader",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Read and render patch/diff files with diff2html",
"default_locale": "en",
"icons": {
From ede899e320a1866d280bb1f215a970ac9f9ab33c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 07:59:55 +0000
Subject: [PATCH 3/3] Refactor drag and drop code to address code review
feedback
Co-authored-by: unknowIfGuestInDream <57802425+unknowIfGuestInDream@users.noreply.github.com>
---
src/common/diff-viewer.js | 57 +++++++++++----------------------------
1 file changed, 16 insertions(+), 41 deletions(-)
diff --git a/src/common/diff-viewer.js b/src/common/diff-viewer.js
index 18cb0ca..4397879 100644
--- a/src/common/diff-viewer.js
+++ b/src/common/diff-viewer.js
@@ -61,6 +61,9 @@
diffInput.addEventListener('drop', handleDrop);
}
+ // Track drag enter/leave to handle nested elements
+ let dragCounter = 0;
+
// Handle drag over event
function handleDragOver(event) {
event.preventDefault();
@@ -71,6 +74,7 @@
function handleDragEnter(event) {
event.preventDefault();
event.stopPropagation();
+ dragCounter++;
diffInput.classList.add('drag-over');
}
@@ -78,13 +82,17 @@
function handleDragLeave(event) {
event.preventDefault();
event.stopPropagation();
- diffInput.classList.remove('drag-over');
+ dragCounter--;
+ if (dragCounter === 0) {
+ diffInput.classList.remove('drag-over');
+ }
}
// Handle drop event
function handleDrop(event) {
event.preventDefault();
event.stopPropagation();
+ dragCounter = 0;
diffInput.classList.remove('drag-over');
const files = event.dataTransfer.files;
@@ -92,8 +100,8 @@
// Filter for valid file types
const validFiles = Array.from(files).filter(file => {
- const ext = file.name.toLowerCase();
- return ext.endsWith('.patch') || ext.endsWith('.diff') || ext.endsWith('.txt');
+ const fileName = file.name.toLowerCase();
+ return fileName.endsWith('.patch') || fileName.endsWith('.diff') || fileName.endsWith('.txt');
});
if (validFiles.length === 0) {
@@ -102,12 +110,12 @@
return;
}
- processDroppedFiles(validFiles);
+ processFiles(validFiles);
}
- // Process dropped files
- function processDroppedFiles(files) {
- const readPromises = files.map(file => {
+ // Process files (shared by drag-drop and file upload)
+ function processFiles(files) {
+ const readPromises = Array.from(files).map(file => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve({
@@ -235,40 +243,7 @@
const files = event.target.files;
if (!files || files.length === 0) return;
- const readPromises = Array.from(files).map(file => {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = (e) => resolve({
- name: file.name,
- content: e.target.result
- });
- reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`));
- reader.readAsText(file);
- });
- });
-
- Promise.all(readPromises)
- .then(results => {
- // Combine all file contents
- const combinedContent = results.map(r => {
- // Add file header comment if multiple files
- if (results.length > 1) {
- return `# File: ${r.name}\n${r.content}`;
- }
- return r.content;
- }).join('\n\n');
-
- diffInput.value = combinedContent;
- // Clear viewed files when new content is loaded
- viewedFiles.clear();
- saveViewedFiles();
- renderDiff();
- saveState();
- })
- .catch(error => {
- const errorMsg = window.i18n ? window.i18n.getMessage('fileReadFailed') : 'File read failed';
- showError(`${errorMsg}: ${error.message}`);
- });
+ processFiles(files);
// Reset file input
fileInput.value = '';