pake-cli 3.2.14 → 3.2.16
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/README.md +321 -307
- package/dist/cli.js +200 -21
- package/package.json +1 -1
- package/src-tauri/capabilities/default.json +2 -1
- package/src-tauri/src/app/invoke.rs +27 -7
- package/src-tauri/src/inject/event.js +524 -20
- package/src-tauri/src/util.rs +35 -18
- package/src-tauri/tauri.macos.conf.json +0 -1
|
@@ -36,22 +36,155 @@ function handleShortcut(event) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
39
|
+
// Configuration constants
|
|
40
|
+
const DOWNLOADABLE_FILE_EXTENSIONS = {
|
|
41
|
+
documents: [
|
|
42
|
+
"pdf",
|
|
43
|
+
"doc",
|
|
44
|
+
"docx",
|
|
45
|
+
"xls",
|
|
46
|
+
"xlsx",
|
|
47
|
+
"ppt",
|
|
48
|
+
"pptx",
|
|
49
|
+
"txt",
|
|
50
|
+
"rtf",
|
|
51
|
+
"odt",
|
|
52
|
+
"ods",
|
|
53
|
+
"odp",
|
|
54
|
+
"pages",
|
|
55
|
+
"numbers",
|
|
56
|
+
"key",
|
|
57
|
+
"epub",
|
|
58
|
+
"mobi",
|
|
59
|
+
],
|
|
60
|
+
archives: [
|
|
61
|
+
"zip",
|
|
62
|
+
"rar",
|
|
63
|
+
"7z",
|
|
64
|
+
"tar",
|
|
65
|
+
"gz",
|
|
66
|
+
"gzip",
|
|
67
|
+
"bz2",
|
|
68
|
+
"xz",
|
|
69
|
+
"lzma",
|
|
70
|
+
"deb",
|
|
71
|
+
"rpm",
|
|
72
|
+
"pkg",
|
|
73
|
+
"msi",
|
|
74
|
+
"exe",
|
|
75
|
+
"dmg",
|
|
76
|
+
"apk",
|
|
77
|
+
"ipa",
|
|
78
|
+
],
|
|
79
|
+
data: [
|
|
80
|
+
"json",
|
|
81
|
+
"xml",
|
|
82
|
+
"csv",
|
|
83
|
+
"sql",
|
|
84
|
+
"db",
|
|
85
|
+
"sqlite",
|
|
86
|
+
"yaml",
|
|
87
|
+
"yml",
|
|
88
|
+
"toml",
|
|
89
|
+
"ini",
|
|
90
|
+
"cfg",
|
|
91
|
+
"conf",
|
|
92
|
+
"log",
|
|
93
|
+
],
|
|
94
|
+
code: [
|
|
95
|
+
"js",
|
|
96
|
+
"ts",
|
|
97
|
+
"jsx",
|
|
98
|
+
"tsx",
|
|
99
|
+
"css",
|
|
100
|
+
"scss",
|
|
101
|
+
"sass",
|
|
102
|
+
"less",
|
|
103
|
+
"html",
|
|
104
|
+
"htm",
|
|
105
|
+
"php",
|
|
106
|
+
"py",
|
|
107
|
+
"java",
|
|
108
|
+
"cpp",
|
|
109
|
+
"c",
|
|
110
|
+
"h",
|
|
111
|
+
"cs",
|
|
112
|
+
"rb",
|
|
113
|
+
"go",
|
|
114
|
+
"rs",
|
|
115
|
+
"swift",
|
|
116
|
+
"kt",
|
|
117
|
+
"scala",
|
|
118
|
+
"sh",
|
|
119
|
+
"bat",
|
|
120
|
+
"ps1",
|
|
121
|
+
],
|
|
122
|
+
fonts: ["ttf", "otf", "woff", "woff2", "eot"],
|
|
123
|
+
design: ["ai", "psd", "sketch", "fig", "xd"],
|
|
124
|
+
system: [
|
|
125
|
+
"iso",
|
|
126
|
+
"img",
|
|
127
|
+
"bin",
|
|
128
|
+
"torrent",
|
|
129
|
+
"jar",
|
|
130
|
+
"war",
|
|
131
|
+
"indd",
|
|
132
|
+
"fla",
|
|
133
|
+
"swf",
|
|
134
|
+
"raw",
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const ALL_DOWNLOADABLE_EXTENSIONS = Object.values(
|
|
139
|
+
DOWNLOADABLE_FILE_EXTENSIONS,
|
|
140
|
+
).flat();
|
|
141
|
+
|
|
142
|
+
const DOWNLOAD_PATH_PATTERNS = [
|
|
143
|
+
"/download/",
|
|
144
|
+
"/files/",
|
|
145
|
+
"/attachments/",
|
|
146
|
+
"/assets/",
|
|
147
|
+
"/releases/",
|
|
148
|
+
"/dist/",
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
// Language detection utilities
|
|
152
|
+
function getUserLanguage() {
|
|
153
|
+
return navigator.language || navigator.userLanguage;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isChineseLanguage(language = getUserLanguage()) {
|
|
157
|
+
return (
|
|
158
|
+
language &&
|
|
159
|
+
(language.startsWith("zh") ||
|
|
160
|
+
language.includes("CN") ||
|
|
161
|
+
language.includes("TW") ||
|
|
162
|
+
language.includes("HK"))
|
|
53
163
|
);
|
|
54
|
-
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Unified file detection - replaces both isDownloadLink and isFileLink
|
|
167
|
+
function isDownloadableFile(url) {
|
|
168
|
+
try {
|
|
169
|
+
const urlObj = new URL(url);
|
|
170
|
+
const pathname = urlObj.pathname.toLowerCase();
|
|
171
|
+
|
|
172
|
+
// Get file extension
|
|
173
|
+
const extension = pathname.substring(pathname.lastIndexOf(".") + 1);
|
|
174
|
+
|
|
175
|
+
const fileExtensions = ALL_DOWNLOADABLE_EXTENSIONS;
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
fileExtensions.includes(extension) ||
|
|
179
|
+
// Check for download hints
|
|
180
|
+
urlObj.searchParams.has("download") ||
|
|
181
|
+
urlObj.searchParams.has("attachment") ||
|
|
182
|
+
// Check for common download paths
|
|
183
|
+
DOWNLOAD_PATH_PATTERNS.some((pattern) => pathname.includes(pattern))
|
|
184
|
+
);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
55
188
|
}
|
|
56
189
|
|
|
57
190
|
document.addEventListener("DOMContentLoaded", () => {
|
|
@@ -132,20 +265,24 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
132
265
|
}
|
|
133
266
|
|
|
134
267
|
// write the ArrayBuffer to a binary, and you're done
|
|
268
|
+
const userLanguage = navigator.language || navigator.userLanguage;
|
|
135
269
|
invoke("download_file_by_binary", {
|
|
136
270
|
params: {
|
|
137
271
|
filename,
|
|
138
272
|
binary: Array.from(binary),
|
|
273
|
+
language: userLanguage,
|
|
139
274
|
},
|
|
140
275
|
});
|
|
141
276
|
}
|
|
142
277
|
|
|
143
278
|
function downloadFromBlobUrl(blobUrl, filename) {
|
|
144
279
|
convertBlobUrlToBinary(blobUrl).then((binary) => {
|
|
280
|
+
const userLanguage = navigator.language || navigator.userLanguage;
|
|
145
281
|
invoke("download_file_by_binary", {
|
|
146
282
|
params: {
|
|
147
283
|
filename,
|
|
148
284
|
binary,
|
|
285
|
+
language: userLanguage,
|
|
149
286
|
},
|
|
150
287
|
});
|
|
151
288
|
});
|
|
@@ -183,7 +320,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
183
320
|
["blob", "data"].some((protocol) => url.startsWith(protocol));
|
|
184
321
|
|
|
185
322
|
const isDownloadRequired = (url, anchorElement, e) =>
|
|
186
|
-
anchorElement.download || e.metaKey || e.ctrlKey ||
|
|
323
|
+
anchorElement.download || e.metaKey || e.ctrlKey || isDownloadableFile(url);
|
|
187
324
|
|
|
188
325
|
const handleExternalLink = (url) => {
|
|
189
326
|
invoke("plugin:shell|open", {
|
|
@@ -254,7 +391,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
254
391
|
) {
|
|
255
392
|
e.preventDefault();
|
|
256
393
|
e.stopImmediatePropagation();
|
|
257
|
-
|
|
394
|
+
const userLanguage = getUserLanguage();
|
|
395
|
+
invoke("download_file", {
|
|
396
|
+
params: { url: absoluteUrl, filename, language: userLanguage },
|
|
397
|
+
});
|
|
258
398
|
return;
|
|
259
399
|
}
|
|
260
400
|
|
|
@@ -330,10 +470,340 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
330
470
|
document.addEventListener(
|
|
331
471
|
"keydown",
|
|
332
472
|
(e) => {
|
|
333
|
-
if (e.
|
|
473
|
+
if (e.key === "Process") e.stopPropagation();
|
|
334
474
|
},
|
|
335
475
|
true,
|
|
336
476
|
);
|
|
477
|
+
|
|
478
|
+
// Language detection and texts
|
|
479
|
+
const isChinese = isChineseLanguage();
|
|
480
|
+
|
|
481
|
+
const menuTexts = {
|
|
482
|
+
// Media operations
|
|
483
|
+
downloadImage: isChinese ? "下载图片" : "Download Image",
|
|
484
|
+
downloadVideo: isChinese ? "下载视频" : "Download Video",
|
|
485
|
+
downloadFile: isChinese ? "下载文件" : "Download File",
|
|
486
|
+
copyAddress: isChinese ? "复制地址" : "Copy Address",
|
|
487
|
+
openInBrowser: isChinese ? "浏览器打开" : "Open in Browser",
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// Menu theme configuration
|
|
491
|
+
const MENU_THEMES = {
|
|
492
|
+
dark: {
|
|
493
|
+
menu: {
|
|
494
|
+
background: "#2d2d2d",
|
|
495
|
+
border: "1px solid #404040",
|
|
496
|
+
color: "#ffffff",
|
|
497
|
+
shadow: "0 4px 16px rgba(0, 0, 0, 0.4)",
|
|
498
|
+
},
|
|
499
|
+
item: {
|
|
500
|
+
divider: "#404040",
|
|
501
|
+
hoverBg: "#404040",
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
light: {
|
|
505
|
+
menu: {
|
|
506
|
+
background: "#ffffff",
|
|
507
|
+
border: "1px solid #e0e0e0",
|
|
508
|
+
color: "#333333",
|
|
509
|
+
shadow: "0 4px 16px rgba(0, 0, 0, 0.15)",
|
|
510
|
+
},
|
|
511
|
+
item: {
|
|
512
|
+
divider: "#f0f0f0",
|
|
513
|
+
hoverBg: "#d0d0d0",
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// Theme detection and menu styles
|
|
519
|
+
function getTheme() {
|
|
520
|
+
const prefersDark = window.matchMedia(
|
|
521
|
+
"(prefers-color-scheme: dark)",
|
|
522
|
+
).matches;
|
|
523
|
+
return prefersDark ? "dark" : "light";
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function getMenuStyles(theme = getTheme()) {
|
|
527
|
+
return MENU_THEMES[theme] || MENU_THEMES.light;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Menu configuration constants
|
|
531
|
+
const MENU_CONFIG = {
|
|
532
|
+
id: "pake-context-menu",
|
|
533
|
+
minWidth: "120px", // Compact width for better UX
|
|
534
|
+
borderRadius: "6px", // Slightly more rounded for modern look
|
|
535
|
+
fontSize: "13px",
|
|
536
|
+
zIndex: "999999",
|
|
537
|
+
fontFamily:
|
|
538
|
+
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
539
|
+
// Menu item dimensions
|
|
540
|
+
itemPadding: "8px 16px", // Increased vertical padding for better comfort
|
|
541
|
+
itemLineHeight: "1.2",
|
|
542
|
+
itemBorderRadius: "3px", // Subtle rounded corners for menu items
|
|
543
|
+
itemTransition: "background-color 0.1s ease",
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// Create custom context menu
|
|
547
|
+
function createContextMenu() {
|
|
548
|
+
const contextMenu = document.createElement("div");
|
|
549
|
+
contextMenu.id = MENU_CONFIG.id;
|
|
550
|
+
const styles = getMenuStyles();
|
|
551
|
+
|
|
552
|
+
contextMenu.style.cssText = `
|
|
553
|
+
position: fixed;
|
|
554
|
+
background: ${styles.menu.background};
|
|
555
|
+
border: ${styles.menu.border};
|
|
556
|
+
border-radius: ${MENU_CONFIG.borderRadius};
|
|
557
|
+
box-shadow: ${styles.menu.shadow};
|
|
558
|
+
padding: 4px 0;
|
|
559
|
+
min-width: ${MENU_CONFIG.minWidth};
|
|
560
|
+
font-family: ${MENU_CONFIG.fontFamily};
|
|
561
|
+
font-size: ${MENU_CONFIG.fontSize};
|
|
562
|
+
color: ${styles.menu.color};
|
|
563
|
+
z-index: ${MENU_CONFIG.zIndex};
|
|
564
|
+
display: none;
|
|
565
|
+
user-select: none;
|
|
566
|
+
`;
|
|
567
|
+
document.body.appendChild(contextMenu);
|
|
568
|
+
return contextMenu;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function createMenuItem(text, onClick, divider = false) {
|
|
572
|
+
const item = document.createElement("div");
|
|
573
|
+
const styles = getMenuStyles();
|
|
574
|
+
|
|
575
|
+
item.style.cssText = `
|
|
576
|
+
padding: ${MENU_CONFIG.itemPadding};
|
|
577
|
+
cursor: pointer;
|
|
578
|
+
user-select: none;
|
|
579
|
+
font-weight: 400;
|
|
580
|
+
line-height: ${MENU_CONFIG.itemLineHeight};
|
|
581
|
+
transition: ${MENU_CONFIG.itemTransition};
|
|
582
|
+
white-space: nowrap;
|
|
583
|
+
border-radius: ${MENU_CONFIG.itemBorderRadius};
|
|
584
|
+
margin: 2px 4px;
|
|
585
|
+
border-bottom: ${divider ? `1px solid ${styles.item.divider}` : "none"};
|
|
586
|
+
`;
|
|
587
|
+
item.textContent = text;
|
|
588
|
+
|
|
589
|
+
item.addEventListener("mouseenter", () => {
|
|
590
|
+
item.style.backgroundColor = styles.item.hoverBg;
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
item.addEventListener("mouseleave", () => {
|
|
594
|
+
item.style.backgroundColor = "transparent";
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
item.addEventListener("click", (e) => {
|
|
598
|
+
e.preventDefault();
|
|
599
|
+
e.stopPropagation();
|
|
600
|
+
onClick();
|
|
601
|
+
hideContextMenu();
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
return item;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function showContextMenu(x, y, items) {
|
|
608
|
+
let contextMenu = document.getElementById(MENU_CONFIG.id);
|
|
609
|
+
|
|
610
|
+
// Always recreate menu to ensure theme is up-to-date
|
|
611
|
+
if (contextMenu) {
|
|
612
|
+
contextMenu.remove();
|
|
613
|
+
}
|
|
614
|
+
contextMenu = createContextMenu();
|
|
615
|
+
|
|
616
|
+
items.forEach((item) => {
|
|
617
|
+
contextMenu.appendChild(item);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
contextMenu.style.left = x + "px";
|
|
621
|
+
contextMenu.style.top = y + "px";
|
|
622
|
+
contextMenu.style.display = "block";
|
|
623
|
+
|
|
624
|
+
// Adjust position if menu goes off screen
|
|
625
|
+
const rect = contextMenu.getBoundingClientRect();
|
|
626
|
+
if (rect.right > window.innerWidth) {
|
|
627
|
+
contextMenu.style.left = x - rect.width + "px";
|
|
628
|
+
}
|
|
629
|
+
if (rect.bottom > window.innerHeight) {
|
|
630
|
+
contextMenu.style.top = y - rect.height + "px";
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function hideContextMenu() {
|
|
635
|
+
const contextMenu = document.getElementById(MENU_CONFIG.id);
|
|
636
|
+
if (contextMenu) {
|
|
637
|
+
contextMenu.style.display = "none";
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function downloadImage(imageUrl) {
|
|
642
|
+
// Convert relative URLs to absolute
|
|
643
|
+
if (imageUrl.startsWith("/")) {
|
|
644
|
+
imageUrl = window.location.origin + imageUrl;
|
|
645
|
+
} else if (imageUrl.startsWith("./")) {
|
|
646
|
+
imageUrl = new URL(imageUrl, window.location.href).href;
|
|
647
|
+
} else if (
|
|
648
|
+
!imageUrl.startsWith("http") &&
|
|
649
|
+
!imageUrl.startsWith("data:") &&
|
|
650
|
+
!imageUrl.startsWith("blob:")
|
|
651
|
+
) {
|
|
652
|
+
imageUrl = new URL(imageUrl, window.location.href).href;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Generate filename from URL
|
|
656
|
+
const filename = getFilenameFromUrl(imageUrl) || "image";
|
|
657
|
+
|
|
658
|
+
// Handle different URL types
|
|
659
|
+
if (imageUrl.startsWith("data:")) {
|
|
660
|
+
downloadFromDataUri(imageUrl, filename);
|
|
661
|
+
} else if (imageUrl.startsWith("blob:")) {
|
|
662
|
+
if (window.blobToUrlCaches && window.blobToUrlCaches.has(imageUrl)) {
|
|
663
|
+
downloadFromBlobUrl(imageUrl, filename);
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
// Regular HTTP(S) image
|
|
667
|
+
const userLanguage = navigator.language || navigator.userLanguage;
|
|
668
|
+
invoke("download_file", {
|
|
669
|
+
params: {
|
|
670
|
+
url: imageUrl,
|
|
671
|
+
filename: filename,
|
|
672
|
+
language: userLanguage,
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Check if element is media (image or video)
|
|
679
|
+
function getMediaInfo(target) {
|
|
680
|
+
// Check for img tags
|
|
681
|
+
if (target.tagName.toLowerCase() === "img") {
|
|
682
|
+
return { isMedia: true, url: target.src, type: "image" };
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Check for video tags
|
|
686
|
+
if (target.tagName.toLowerCase() === "video") {
|
|
687
|
+
return {
|
|
688
|
+
isMedia: true,
|
|
689
|
+
url: target.src || target.currentSrc,
|
|
690
|
+
type: "video",
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Check for elements with background images
|
|
695
|
+
if (target.style && target.style.backgroundImage) {
|
|
696
|
+
const bgImage = target.style.backgroundImage;
|
|
697
|
+
const urlMatch = bgImage.match(/url\(["']?([^"')]+)["']?\)/);
|
|
698
|
+
if (urlMatch) {
|
|
699
|
+
return { isMedia: true, url: urlMatch[1], type: "image" };
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Check for parent elements with background images
|
|
704
|
+
const parentWithBg = target.closest('[style*="background-image"]');
|
|
705
|
+
if (parentWithBg) {
|
|
706
|
+
const bgImage = parentWithBg.style.backgroundImage;
|
|
707
|
+
const urlMatch = bgImage.match(/url\(["']?([^"')]+)["']?\)/);
|
|
708
|
+
if (urlMatch) {
|
|
709
|
+
return { isMedia: true, url: urlMatch[1], type: "image" };
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return { isMedia: false, url: "", type: "" };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Simplified menu builder
|
|
717
|
+
function buildMenuItems(type, data) {
|
|
718
|
+
const userLanguage = navigator.language || navigator.userLanguage;
|
|
719
|
+
const items = [];
|
|
720
|
+
|
|
721
|
+
switch (type) {
|
|
722
|
+
case "media":
|
|
723
|
+
const downloadText =
|
|
724
|
+
data.type === "image"
|
|
725
|
+
? menuTexts.downloadImage
|
|
726
|
+
: menuTexts.downloadVideo;
|
|
727
|
+
items.push(
|
|
728
|
+
createMenuItem(downloadText, () => downloadImage(data.url)),
|
|
729
|
+
createMenuItem(menuTexts.copyAddress, () =>
|
|
730
|
+
navigator.clipboard.writeText(data.url),
|
|
731
|
+
),
|
|
732
|
+
createMenuItem(menuTexts.openInBrowser, () =>
|
|
733
|
+
invoke("plugin:shell|open", { path: data.url }),
|
|
734
|
+
),
|
|
735
|
+
);
|
|
736
|
+
break;
|
|
737
|
+
|
|
738
|
+
case "link":
|
|
739
|
+
if (data.isFile) {
|
|
740
|
+
items.push(
|
|
741
|
+
createMenuItem(menuTexts.downloadFile, () => {
|
|
742
|
+
const filename = getFilenameFromUrl(data.url);
|
|
743
|
+
invoke("download_file", {
|
|
744
|
+
params: { url: data.url, filename, language: userLanguage },
|
|
745
|
+
});
|
|
746
|
+
}),
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
items.push(
|
|
750
|
+
createMenuItem(menuTexts.copyAddress, () =>
|
|
751
|
+
navigator.clipboard.writeText(data.url),
|
|
752
|
+
),
|
|
753
|
+
createMenuItem(menuTexts.openInBrowser, () =>
|
|
754
|
+
invoke("plugin:shell|open", { path: data.url }),
|
|
755
|
+
),
|
|
756
|
+
);
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return items;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Handle right-click context menu
|
|
764
|
+
document.addEventListener(
|
|
765
|
+
"contextmenu",
|
|
766
|
+
function (event) {
|
|
767
|
+
const target = event.target;
|
|
768
|
+
|
|
769
|
+
// Check for media elements (images/videos)
|
|
770
|
+
const mediaInfo = getMediaInfo(target);
|
|
771
|
+
|
|
772
|
+
// Check for links (but not if it's media)
|
|
773
|
+
const linkElement = target.closest("a");
|
|
774
|
+
const isLink = linkElement && linkElement.href && !mediaInfo.isMedia;
|
|
775
|
+
|
|
776
|
+
// Only show custom menu for media or links
|
|
777
|
+
if (mediaInfo.isMedia || isLink) {
|
|
778
|
+
event.preventDefault();
|
|
779
|
+
event.stopPropagation();
|
|
780
|
+
|
|
781
|
+
let menuItems = [];
|
|
782
|
+
|
|
783
|
+
if (mediaInfo.isMedia) {
|
|
784
|
+
menuItems = buildMenuItems("media", mediaInfo);
|
|
785
|
+
} else if (isLink) {
|
|
786
|
+
const linkUrl = linkElement.href;
|
|
787
|
+
menuItems = buildMenuItems("link", {
|
|
788
|
+
url: linkUrl,
|
|
789
|
+
isFile: isDownloadableFile(linkUrl),
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
showContextMenu(event.clientX, event.clientY, menuItems);
|
|
794
|
+
}
|
|
795
|
+
// For all other elements, let browser's default context menu handle it
|
|
796
|
+
},
|
|
797
|
+
true,
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
// Hide context menu when clicking elsewhere
|
|
801
|
+
document.addEventListener("click", hideContextMenu);
|
|
802
|
+
document.addEventListener("keydown", (e) => {
|
|
803
|
+
if (e.key === "Escape") {
|
|
804
|
+
hideContextMenu();
|
|
805
|
+
}
|
|
806
|
+
});
|
|
337
807
|
});
|
|
338
808
|
|
|
339
809
|
document.addEventListener("DOMContentLoaded", function () {
|
|
@@ -376,6 +846,40 @@ function setDefaultZoom() {
|
|
|
376
846
|
}
|
|
377
847
|
|
|
378
848
|
function getFilenameFromUrl(url) {
|
|
379
|
-
|
|
380
|
-
|
|
849
|
+
try {
|
|
850
|
+
const urlPath = new URL(url).pathname;
|
|
851
|
+
let filename = urlPath.substring(urlPath.lastIndexOf("/") + 1);
|
|
852
|
+
|
|
853
|
+
// If no filename or no extension, generate one
|
|
854
|
+
if (!filename || !filename.includes(".")) {
|
|
855
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
856
|
+
|
|
857
|
+
// Detect image type from URL or data URI
|
|
858
|
+
if (url.startsWith("data:image/")) {
|
|
859
|
+
const mimeType = url.substring(11, url.indexOf(";"));
|
|
860
|
+
filename = `image-${timestamp}.${mimeType}`;
|
|
861
|
+
} else {
|
|
862
|
+
// Default to common image extensions based on common patterns
|
|
863
|
+
if (url.includes("jpg") || url.includes("jpeg")) {
|
|
864
|
+
filename = `image-${timestamp}.jpg`;
|
|
865
|
+
} else if (url.includes("png")) {
|
|
866
|
+
filename = `image-${timestamp}.png`;
|
|
867
|
+
} else if (url.includes("gif")) {
|
|
868
|
+
filename = `image-${timestamp}.gif`;
|
|
869
|
+
} else if (url.includes("webp")) {
|
|
870
|
+
filename = `image-${timestamp}.webp`;
|
|
871
|
+
} else if (url.includes("svg")) {
|
|
872
|
+
filename = `image-${timestamp}.svg`;
|
|
873
|
+
} else {
|
|
874
|
+
filename = `image-${timestamp}.png`; // default
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return filename;
|
|
880
|
+
} catch (e) {
|
|
881
|
+
// Fallback for invalid URLs
|
|
882
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
883
|
+
return `image-${timestamp}.png`;
|
|
884
|
+
}
|
|
381
885
|
}
|
package/src-tauri/src/util.rs
CHANGED
|
@@ -50,7 +50,10 @@ pub enum MessageType {
|
|
|
50
50
|
Failure,
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
pub fn
|
|
53
|
+
pub fn get_download_message_with_lang(
|
|
54
|
+
message_type: MessageType,
|
|
55
|
+
language: Option<String>,
|
|
56
|
+
) -> String {
|
|
54
57
|
let default_start_message = "Start downloading~";
|
|
55
58
|
let chinese_start_message = "开始下载中~";
|
|
56
59
|
|
|
@@ -60,28 +63,42 @@ pub fn get_download_message(message_type: MessageType) -> String {
|
|
|
60
63
|
let default_failure_message = "Download failed, please check your network connection~";
|
|
61
64
|
let chinese_failure_message = "下载失败,请检查你的网络连接~";
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
let is_chinese = language
|
|
67
|
+
.as_ref()
|
|
64
68
|
.map(|lang| {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
MessageType::Failure => chinese_failure_message,
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
match message_type {
|
|
73
|
-
MessageType::Start => default_start_message,
|
|
74
|
-
MessageType::Success => default_success_message,
|
|
75
|
-
MessageType::Failure => default_failure_message,
|
|
76
|
-
}
|
|
77
|
-
}
|
|
69
|
+
lang.starts_with("zh")
|
|
70
|
+
|| lang.contains("CN")
|
|
71
|
+
|| lang.contains("TW")
|
|
72
|
+
|| lang.contains("HK")
|
|
78
73
|
})
|
|
79
|
-
.unwrap_or_else(
|
|
74
|
+
.unwrap_or_else(|| {
|
|
75
|
+
// Try multiple environment variables for better system detection
|
|
76
|
+
["LANG", "LC_ALL", "LC_MESSAGES", "LANGUAGE"]
|
|
77
|
+
.iter()
|
|
78
|
+
.find_map(|var| env::var(var).ok())
|
|
79
|
+
.map(|lang| {
|
|
80
|
+
lang.starts_with("zh")
|
|
81
|
+
|| lang.contains("CN")
|
|
82
|
+
|| lang.contains("TW")
|
|
83
|
+
|| lang.contains("HK")
|
|
84
|
+
})
|
|
85
|
+
.unwrap_or(false)
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if is_chinese {
|
|
89
|
+
match message_type {
|
|
90
|
+
MessageType::Start => chinese_start_message,
|
|
91
|
+
MessageType::Success => chinese_success_message,
|
|
92
|
+
MessageType::Failure => chinese_failure_message,
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
match message_type {
|
|
80
96
|
MessageType::Start => default_start_message,
|
|
81
97
|
MessageType::Success => default_success_message,
|
|
82
98
|
MessageType::Failure => default_failure_message,
|
|
83
|
-
}
|
|
84
|
-
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
.to_string()
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
// Check if the file exists, if it exists, add a number to file name
|