markpdfdown 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { app, ipcMain, dialog, shell, safeStorage, protocol,
|
|
1
|
+
import { app, ipcMain, dialog, clipboard, nativeImage, shell, safeStorage, protocol, BrowserWindow } from "electron";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs, { promises } from "fs";
|
|
4
4
|
import isDev from "electron-is-dev";
|
|
@@ -2635,6 +2635,7 @@ const IPC_CHANNELS = {
|
|
|
2635
2635
|
FILE: {
|
|
2636
2636
|
GET_IMAGE_PATH: "file:getImagePath",
|
|
2637
2637
|
DOWNLOAD_MARKDOWN: "file:downloadMarkdown",
|
|
2638
|
+
COPY_IMAGE_TO_CLIPBOARD: "file:copyImageToClipboard",
|
|
2638
2639
|
SELECT_DIALOG: "file:selectDialog",
|
|
2639
2640
|
UPLOAD: "file:upload",
|
|
2640
2641
|
UPLOAD_FILE_CONTENT: "file:uploadFileContent"
|
|
@@ -3444,6 +3445,20 @@ function registerTaskDetailHandlers() {
|
|
|
3444
3445
|
);
|
|
3445
3446
|
console.log("[IPC] TaskDetail handlers registered");
|
|
3446
3447
|
}
|
|
3448
|
+
async function createImageFromSource(imageSource) {
|
|
3449
|
+
const normalizedSource = imageSource.trim();
|
|
3450
|
+
const lowerSource = normalizedSource.toLowerCase();
|
|
3451
|
+
if (lowerSource.startsWith("data:image/")) {
|
|
3452
|
+
return nativeImage.createFromDataURL(normalizedSource);
|
|
3453
|
+
}
|
|
3454
|
+
if (lowerSource.startsWith("http://") || lowerSource.startsWith("https://")) {
|
|
3455
|
+
throw new Error("Remote image URLs are not allowed");
|
|
3456
|
+
}
|
|
3457
|
+
if (lowerSource.startsWith("file://")) {
|
|
3458
|
+
return nativeImage.createFromPath(fileURLToPath(normalizedSource));
|
|
3459
|
+
}
|
|
3460
|
+
return nativeImage.createFromPath(normalizedSource);
|
|
3461
|
+
}
|
|
3447
3462
|
function registerFileHandlers() {
|
|
3448
3463
|
ipcMain.handle(
|
|
3449
3464
|
IPC_CHANNELS.FILE.GET_IMAGE_PATH,
|
|
@@ -3506,6 +3521,25 @@ function registerFileHandlers() {
|
|
|
3506
3521
|
}
|
|
3507
3522
|
}
|
|
3508
3523
|
);
|
|
3524
|
+
ipcMain.handle(
|
|
3525
|
+
IPC_CHANNELS.FILE.COPY_IMAGE_TO_CLIPBOARD,
|
|
3526
|
+
async (_, imageSource) => {
|
|
3527
|
+
try {
|
|
3528
|
+
if (!imageSource) {
|
|
3529
|
+
return { success: false, error: "Image source is required" };
|
|
3530
|
+
}
|
|
3531
|
+
const image = await createImageFromSource(imageSource);
|
|
3532
|
+
if (image.isEmpty()) {
|
|
3533
|
+
return { success: false, error: "Image data is empty or invalid" };
|
|
3534
|
+
}
|
|
3535
|
+
clipboard.writeImage(image);
|
|
3536
|
+
return { success: true, data: { copied: true } };
|
|
3537
|
+
} catch (error) {
|
|
3538
|
+
console.error("[IPC] file:copyImageToClipboard error:", error);
|
|
3539
|
+
return { success: false, error: error.message || "Failed to copy image" };
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
);
|
|
3509
3543
|
ipcMain.handle(IPC_CHANNELS.FILE.SELECT_DIALOG, async (_, allowOffice) => {
|
|
3510
3544
|
try {
|
|
3511
3545
|
const pdfAndImageExtensions = ["pdf", "jpg", "jpeg", "png", "bmp", "gif"];
|
|
@@ -4373,6 +4407,88 @@ class CloudService {
|
|
|
4373
4407
|
static instance;
|
|
4374
4408
|
constructor() {
|
|
4375
4409
|
}
|
|
4410
|
+
extractDownloadFileName(contentDisposition, fallback) {
|
|
4411
|
+
const rfc5987Name = this.parseRFC5987Filename(contentDisposition);
|
|
4412
|
+
if (rfc5987Name) {
|
|
4413
|
+
return this.sanitizeDownloadFileName(rfc5987Name, fallback);
|
|
4414
|
+
}
|
|
4415
|
+
const plainName = this.parsePlainFilename(contentDisposition);
|
|
4416
|
+
if (!plainName) {
|
|
4417
|
+
return this.sanitizeDownloadFileName(fallback, fallback);
|
|
4418
|
+
}
|
|
4419
|
+
const repairedName = this.tryRepairUtf8Mojibake(plainName);
|
|
4420
|
+
return this.sanitizeDownloadFileName(repairedName || plainName, fallback);
|
|
4421
|
+
}
|
|
4422
|
+
parseRFC5987Filename(contentDisposition) {
|
|
4423
|
+
const match = contentDisposition.match(/filename\*\s*=\s*([^;]+)/i);
|
|
4424
|
+
if (!match) return null;
|
|
4425
|
+
const rawValue = match[1]?.trim();
|
|
4426
|
+
if (!rawValue) return null;
|
|
4427
|
+
const unquoted = rawValue.replace(/^"(.*)"$/, "$1");
|
|
4428
|
+
const parts = unquoted.match(/^([^']*)'[^']*'(.*)$/);
|
|
4429
|
+
if (!parts) return null;
|
|
4430
|
+
const charset = (parts[1] || "utf-8").trim().toLowerCase();
|
|
4431
|
+
const encodedValue = parts[2] || "";
|
|
4432
|
+
try {
|
|
4433
|
+
if (charset === "utf-8" || charset === "utf8") {
|
|
4434
|
+
return decodeURIComponent(encodedValue);
|
|
4435
|
+
}
|
|
4436
|
+
const bytes = this.percentDecodeToBytes(encodedValue);
|
|
4437
|
+
if (charset === "iso-8859-1" || charset === "latin1") {
|
|
4438
|
+
return Buffer.from(bytes).toString("latin1");
|
|
4439
|
+
}
|
|
4440
|
+
return Buffer.from(bytes).toString("utf8");
|
|
4441
|
+
} catch {
|
|
4442
|
+
return null;
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
parsePlainFilename(contentDisposition) {
|
|
4446
|
+
const match = contentDisposition.match(/filename\s*=\s*("(?:\\.|[^"])*"|[^;]+)/i);
|
|
4447
|
+
if (!match) return null;
|
|
4448
|
+
let value = match[1]?.trim();
|
|
4449
|
+
if (!value) return null;
|
|
4450
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
4451
|
+
value = value.slice(1, -1).replace(/\\"/g, '"');
|
|
4452
|
+
}
|
|
4453
|
+
return value;
|
|
4454
|
+
}
|
|
4455
|
+
percentDecodeToBytes(input) {
|
|
4456
|
+
const bytes = [];
|
|
4457
|
+
for (let i = 0; i < input.length; i++) {
|
|
4458
|
+
const ch = input[i];
|
|
4459
|
+
if (ch === "%" && i + 2 < input.length) {
|
|
4460
|
+
const hex = input.slice(i + 1, i + 3);
|
|
4461
|
+
const parsed = Number.parseInt(hex, 16);
|
|
4462
|
+
if (!Number.isNaN(parsed)) {
|
|
4463
|
+
bytes.push(parsed);
|
|
4464
|
+
i += 2;
|
|
4465
|
+
continue;
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
bytes.push(input.charCodeAt(i));
|
|
4469
|
+
}
|
|
4470
|
+
return bytes;
|
|
4471
|
+
}
|
|
4472
|
+
tryRepairUtf8Mojibake(input) {
|
|
4473
|
+
const hasCjk = /[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/.test(input);
|
|
4474
|
+
if (hasCjk) return null;
|
|
4475
|
+
const latinSupplementCount = Array.from(input).filter((ch) => {
|
|
4476
|
+
const code = ch.charCodeAt(0);
|
|
4477
|
+
return code >= 192 && code <= 255;
|
|
4478
|
+
}).length;
|
|
4479
|
+
if (latinSupplementCount < 2) return null;
|
|
4480
|
+
const repaired = Buffer.from(input, "latin1").toString("utf8");
|
|
4481
|
+
if (!repaired) return null;
|
|
4482
|
+
const repairedHasCjk = /[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/.test(repaired);
|
|
4483
|
+
const roundTrip = Buffer.from(repaired, "utf8").toString("latin1") === input;
|
|
4484
|
+
if (repairedHasCjk && roundTrip) {
|
|
4485
|
+
return repaired;
|
|
4486
|
+
}
|
|
4487
|
+
return null;
|
|
4488
|
+
}
|
|
4489
|
+
sanitizeDownloadFileName(input, fallback) {
|
|
4490
|
+
return path.basename(input).replace(/[\u0000-\u001f<>:"|?*]/g, "_") || fallback;
|
|
4491
|
+
}
|
|
4376
4492
|
normalizeCheckoutStatus(data) {
|
|
4377
4493
|
if (!data || typeof data !== "object") {
|
|
4378
4494
|
return null;
|
|
@@ -4694,9 +4810,8 @@ class CloudService {
|
|
|
4694
4810
|
};
|
|
4695
4811
|
}
|
|
4696
4812
|
const contentDisposition = res.headers.get("Content-Disposition") || "";
|
|
4697
|
-
const
|
|
4698
|
-
const
|
|
4699
|
-
const fileName = path.basename(rawName).replace(/[\u0000-\u001f<>:"|?*]/g, "_") || `task-${id}.pdf`;
|
|
4813
|
+
const fallbackName = `task-${id}.pdf`;
|
|
4814
|
+
const fileName = this.extractDownloadFileName(contentDisposition, fallbackName);
|
|
4700
4815
|
const buffer = await res.arrayBuffer();
|
|
4701
4816
|
return { success: true, data: { buffer, fileName } };
|
|
4702
4817
|
} catch (error) {
|
package/dist/preload/index.js
CHANGED
|
@@ -41,7 +41,8 @@ electron.contextBridge.exposeInMainWorld("api", {
|
|
|
41
41
|
upload: (taskId, filePath) => electron.ipcRenderer.invoke("file:upload", taskId, filePath),
|
|
42
42
|
uploadFileContent: (taskId, fileName, fileBuffer) => electron.ipcRenderer.invoke("file:uploadFileContent", taskId, fileName, fileBuffer),
|
|
43
43
|
getImagePath: (taskId, page) => electron.ipcRenderer.invoke("file:getImagePath", taskId, page),
|
|
44
|
-
downloadMarkdown: (taskId) => electron.ipcRenderer.invoke("file:downloadMarkdown", taskId)
|
|
44
|
+
downloadMarkdown: (taskId) => electron.ipcRenderer.invoke("file:downloadMarkdown", taskId),
|
|
45
|
+
copyImageToClipboard: (imageSource) => electron.ipcRenderer.invoke("file:copyImageToClipboard", imageSource)
|
|
45
46
|
},
|
|
46
47
|
// ==================== Completion APIs ====================
|
|
47
48
|
completion: {
|