mirror of
https://github.com/devon22/obsidian-mediaviewer.git
synced 2026-07-22 05:35:13 +00:00
2.1.1
This commit is contained in:
parent
40305ceb70
commit
aaccfc7b16
13 changed files with 6170 additions and 2747 deletions
|
|
@ -1,3 +0,0 @@
|
|||
node_modules/
|
||||
|
||||
main.js
|
||||
|
|
@ -1,49 +1,49 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
`;
|
||||
|
||||
const prod = (process.argv[2] === "production");
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["src/main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
minify: prod,
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild();
|
||||
process.exit(0);
|
||||
} else {
|
||||
await context.watch();
|
||||
}
|
||||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
`;
|
||||
|
||||
const prod = (process.argv[2] === "production");
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["src/main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins],
|
||||
format: "cjs",
|
||||
target: "es2020",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
minify: prod,
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild();
|
||||
process.exit(0);
|
||||
} else {
|
||||
await context.watch();
|
||||
}
|
||||
|
|
|
|||
35
eslint.config.mjs
Normal file
35
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import tseslint from 'typescript-eslint';
|
||||
import obsidianmd from "eslint-plugin-obsidianmd";
|
||||
import globals from "globals";
|
||||
import { globalIgnores } from "eslint/config";
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
parserOptions: {
|
||||
projectService: {
|
||||
allowDefaultProject: [
|
||||
'eslint.config.mjs',
|
||||
'manifest.json'
|
||||
]
|
||||
},
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
extraFileExtensions: ['.json']
|
||||
},
|
||||
},
|
||||
},
|
||||
...obsidianmd.configs.recommended,
|
||||
globalIgnores([
|
||||
"node_modules",
|
||||
"dist",
|
||||
"esbuild.config.mjs",
|
||||
"eslint.config.mjs",
|
||||
"version-bump.mjs",
|
||||
"versions.json",
|
||||
"main.js",
|
||||
"package.json"
|
||||
]),
|
||||
);
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "mediaviewer",
|
||||
"name": "Media Viewer",
|
||||
"version": "2.1.0",
|
||||
"minAppVersion": "1.1.0",
|
||||
"version": "2.1.1",
|
||||
"minAppVersion": "1.8.7",
|
||||
"description": "View and manage media files within your notes.",
|
||||
"author": "Devon22",
|
||||
"authorUrl": "https://github.com/Devon22",
|
||||
|
|
|
|||
7992
package-lock.json
generated
7992
package-lock.json
generated
File diff suppressed because it is too large
Load diff
60
package.json
60
package.json
|
|
@ -1,28 +1,32 @@
|
|||
{
|
||||
"name": "mediaviewer",
|
||||
"version": "2.1.0",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/linux-x64": "^0.17.3",
|
||||
"@esbuild/win32-x64": "^0.17.3"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "mediaviewer",
|
||||
"version": "2.1.1",
|
||||
"description": "View and manage media files within your notes.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
||||
"@typescript-eslint/parser": "^8.59.3",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"eslint": "^10.3.0",
|
||||
"eslint-plugin-obsidianmd": "^0.3.0",
|
||||
"globals": "^17.6.0",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.59.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/linux-x64": "^0.17.3",
|
||||
"@esbuild/win32-x64": "^0.17.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -261,11 +261,11 @@ export class FullScreenModal extends Modal {
|
|||
if (!Platform.isAndroidApp) {
|
||||
video.src = media.url;
|
||||
const videoIcon = container.createDiv('mv-video-indicator');
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
const svg = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
const path = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('fill', 'currentColor');
|
||||
path.setAttribute('d', 'M8 5v14l11-7z');
|
||||
svg.appendChild(path);
|
||||
|
|
@ -276,11 +276,11 @@ export class FullScreenModal extends Modal {
|
|||
audio.src = media.url;
|
||||
container.onclick = () => this.showMedia(index);
|
||||
const audioIcon = container.createDiv('mv-audio-indicator');
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
const svg = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
const path = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('fill', 'currentColor');
|
||||
path.setAttribute('d', 'M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z');
|
||||
svg.appendChild(path);
|
||||
|
|
@ -318,13 +318,13 @@ export class FullScreenModal extends Modal {
|
|||
prevArea.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
const prevIndex = (this.currentIndex - 1 + this.mediaUrls.length) % this.mediaUrls.length;
|
||||
this.showMedia(prevIndex);
|
||||
void this.showMedia(prevIndex);
|
||||
};
|
||||
|
||||
nextArea.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
const nextIndex = (this.currentIndex + 1) % this.mediaUrls.length;
|
||||
this.showMedia(nextIndex);
|
||||
void this.showMedia(nextIndex);
|
||||
};
|
||||
|
||||
this.fullImage = this.fullMediaView.createEl('img', { cls: 'mv-full-image' });
|
||||
|
|
@ -349,7 +349,7 @@ export class FullScreenModal extends Modal {
|
|||
// 根據類型決定是否自動顯示一張圖片
|
||||
if (this.openType === 'command') {
|
||||
if (this.plugin.settings.autoOpenFirstImage) {
|
||||
this.showMedia(0);
|
||||
void this.showMedia(0);
|
||||
}
|
||||
} else if (this.openType === 'thumbnail') {
|
||||
// 從縮圖開啟時,不需要做任何特殊處理,
|
||||
|
|
@ -401,12 +401,12 @@ export class FullScreenModal extends Modal {
|
|||
this.fullVideo.style.display = 'block';
|
||||
this.fullImage.style.display = 'none';
|
||||
this.fullVideo.loop = true;
|
||||
this.fullVideo.play();
|
||||
void this.fullVideo.play();
|
||||
}
|
||||
|
||||
// 顯示圖片資訊
|
||||
if (this.plugin.settings.showImageInfo || this.plugin.settings.allowMediaDeletion || this.plugin.settings.autoPlayInterval > 0) {
|
||||
this.showImageInfo(media);
|
||||
void this.showImageInfo(media);
|
||||
}
|
||||
|
||||
// 如果之前是自動播放狀態,則繼續自動播放
|
||||
|
|
@ -490,7 +490,7 @@ export class FullScreenModal extends Modal {
|
|||
this.fullMediaView.appendChild(infoPanel);
|
||||
|
||||
// 設定三秒後淡出
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
infoPanel.classList.add('fade');
|
||||
}, 3000);
|
||||
}
|
||||
|
|
@ -647,7 +647,7 @@ export class FullScreenModal extends Modal {
|
|||
this.fullImage.style.width = `${newWidth}px`;
|
||||
this.updatePinchZoomMargin(newWidth);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
const maxScrollLeft = this.fullMediaView.scrollWidth - this.fullMediaView.clientWidth;
|
||||
const maxScrollTop = this.fullMediaView.scrollHeight - this.fullMediaView.clientHeight;
|
||||
const nextScrollLeft = this.fullImage.offsetLeft + (this.pinchStartImageX * widthRatio) - this.pinchStartCenterX;
|
||||
|
|
@ -657,6 +657,81 @@ export class FullScreenModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
private toggleImageZoom(event: MouseEvent) {
|
||||
if (this.suppressNextClick) {
|
||||
this.suppressNextClick = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.plugin.settings.displayOriginalSize &&
|
||||
this.fullImage.naturalWidth < this.fullMediaView.clientWidth &&
|
||||
this.fullImage.naturalHeight < this.fullMediaView.clientHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target !== this.fullImage) return;
|
||||
|
||||
if (!this.isZoomed) { // 縮放
|
||||
// 計算點擊位置相對於圖片的百分比
|
||||
const rect = this.fullImage.getBoundingClientRect();
|
||||
const clickX = event.clientX - rect.left;
|
||||
const clickY = event.clientY - rect.top;
|
||||
const clickXPercent = clickX / rect.width;
|
||||
const clickYPercent = clickY / rect.height;
|
||||
|
||||
// 根據圖片與視窗的長寬比來決定放大模式
|
||||
const imageAspect = this.fullImage.naturalWidth / this.fullImage.naturalHeight;
|
||||
const screenAspect = this.fullMediaView.clientWidth / this.fullMediaView.clientHeight;
|
||||
|
||||
// 如果圖片比視窗更"細長" (Aspect Ratio 較小),則寬度填滿 (Fit Width),垂直捲動
|
||||
// 如果圖片比視窗更"扁平" (Aspect Ratio 較大),則高度填滿 (Fit Height),水平捲動
|
||||
if (imageAspect < screenAspect) {
|
||||
this.fullImage.style.width = '100vw';
|
||||
this.fullImage.style.height = 'auto';
|
||||
this.fullMediaView.style.overflowX = 'hidden';
|
||||
this.fullMediaView.style.overflowY = 'scroll';
|
||||
} else {
|
||||
this.fullImage.style.width = 'auto';
|
||||
this.fullImage.style.height = '100vh';
|
||||
this.fullMediaView.style.overflowX = 'scroll';
|
||||
this.fullMediaView.style.overflowY = 'hidden';
|
||||
|
||||
// 將事件處理程序存儲在類別屬性中
|
||||
this.handleWheel = (e) => {
|
||||
e.preventDefault();
|
||||
this.fullMediaView.scrollLeft += e.deltaY;
|
||||
};
|
||||
this.fullMediaView.addEventListener('wheel', this.handleWheel);
|
||||
}
|
||||
|
||||
this.fullImage.style.maxWidth = 'none';
|
||||
this.fullImage.style.maxHeight = 'none';
|
||||
this.fullImage.style.position = 'relative';
|
||||
this.fullImage.style.left = '0';
|
||||
this.fullImage.style.top = '0';
|
||||
this.fullImage.style.margin = 'auto';
|
||||
this.fullImage.style.transform = 'none';
|
||||
this.fullImage.style.cursor = 'zoom-out';
|
||||
this.isZoomed = true;
|
||||
|
||||
// 在下一幀執行滾動,確保圖片已經放大
|
||||
window.requestAnimationFrame(() => {
|
||||
if (this.fullMediaView.scrollWidth > this.fullMediaView.clientWidth) {
|
||||
// 水平滾動到點擊位置
|
||||
const scrollX = (this.fullImage.scrollWidth * clickXPercent) - (this.fullMediaView.clientWidth / 2);
|
||||
this.fullMediaView.scrollLeft = Math.max(0, Math.min(scrollX, this.fullImage.scrollWidth - this.fullMediaView.clientWidth));
|
||||
}
|
||||
if (this.fullMediaView.scrollHeight > this.fullMediaView.clientHeight) {
|
||||
// 垂直滾動到點擊位置
|
||||
const scrollY = (this.fullImage.scrollHeight * clickYPercent) - (this.fullMediaView.clientHeight / 2);
|
||||
this.fullMediaView.scrollTop = Math.max(0, Math.min(scrollY, this.fullImage.scrollHeight - this.fullMediaView.clientHeight));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.resetImageStyles();
|
||||
}
|
||||
}
|
||||
|
||||
registerMediaEvents() {
|
||||
// 點擊預覽區域背景時關閉
|
||||
this.fullMediaView.onclick = (event) => {
|
||||
|
|
@ -669,83 +744,17 @@ export class FullScreenModal extends Modal {
|
|||
}
|
||||
};
|
||||
|
||||
// 圖片點擊事件(放大)
|
||||
// 圖片點擊事件(桌面點一下縮放,行動裝置雙擊縮放)
|
||||
this.fullImage.onclick = (event) => {
|
||||
// 阻止事件冒泡,避免觸發外層的點擊事件
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.suppressNextClick) {
|
||||
this.suppressNextClick = false;
|
||||
return;
|
||||
}
|
||||
if (!Platform.isMobileApp) this.toggleImageZoom(event);
|
||||
};
|
||||
|
||||
if (this.plugin.settings.displayOriginalSize &&
|
||||
this.fullImage.naturalWidth < this.fullMediaView.clientWidth &&
|
||||
this.fullImage.naturalHeight < this.fullMediaView.clientHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target === this.fullImage) {
|
||||
if (!this.isZoomed) { // 縮放
|
||||
// 計算點擊位置相對於圖片的百分比
|
||||
const rect = this.fullImage.getBoundingClientRect();
|
||||
const clickX = event.clientX - rect.left;
|
||||
const clickY = event.clientY - rect.top;
|
||||
const clickXPercent = clickX / rect.width;
|
||||
const clickYPercent = clickY / rect.height;
|
||||
|
||||
// 根據圖片與視窗的長寬比來決定放大模式
|
||||
const imageAspect = this.fullImage.naturalWidth / this.fullImage.naturalHeight;
|
||||
const screenAspect = this.fullMediaView.clientWidth / this.fullMediaView.clientHeight;
|
||||
|
||||
// 如果圖片比視窗更"細長" (Aspect Ratio 較小),則寬度填滿 (Fit Width),垂直捲動
|
||||
// 如果圖片比視窗更"扁平" (Aspect Ratio 較大),則高度填滿 (Fit Height),水平捲動
|
||||
if (imageAspect < screenAspect) {
|
||||
this.fullImage.style.width = '100vw';
|
||||
this.fullImage.style.height = 'auto';
|
||||
this.fullMediaView.style.overflowX = 'hidden';
|
||||
this.fullMediaView.style.overflowY = 'scroll';
|
||||
} else {
|
||||
this.fullImage.style.width = 'auto';
|
||||
this.fullImage.style.height = '100vh';
|
||||
this.fullMediaView.style.overflowX = 'scroll';
|
||||
this.fullMediaView.style.overflowY = 'hidden';
|
||||
|
||||
// 將事件處理程序存儲在類別屬性中
|
||||
this.handleWheel = (e) => {
|
||||
e.preventDefault();
|
||||
this.fullMediaView.scrollLeft += e.deltaY;
|
||||
};
|
||||
this.fullMediaView.addEventListener('wheel', this.handleWheel);
|
||||
}
|
||||
|
||||
this.fullImage.style.maxWidth = 'none';
|
||||
this.fullImage.style.maxHeight = 'none';
|
||||
this.fullImage.style.position = 'relative';
|
||||
this.fullImage.style.left = '0';
|
||||
this.fullImage.style.top = '0';
|
||||
this.fullImage.style.margin = 'auto';
|
||||
this.fullImage.style.transform = 'none';
|
||||
this.fullImage.style.cursor = 'zoom-out';
|
||||
this.isZoomed = true;
|
||||
|
||||
// 在下一幀執行滾動,確保圖片已經放大
|
||||
requestAnimationFrame(() => {
|
||||
if (this.fullMediaView.scrollWidth > this.fullMediaView.clientWidth) {
|
||||
// 水平滾動到點擊位置
|
||||
const scrollX = (this.fullImage.scrollWidth * clickXPercent) - (this.fullMediaView.clientWidth / 2);
|
||||
this.fullMediaView.scrollLeft = Math.max(0, Math.min(scrollX, this.fullImage.scrollWidth - this.fullMediaView.clientWidth));
|
||||
}
|
||||
if (this.fullMediaView.scrollHeight > this.fullMediaView.clientHeight) {
|
||||
// 垂直滾動到點擊位置
|
||||
const scrollY = (this.fullImage.scrollHeight * clickYPercent) - (this.fullMediaView.clientHeight / 2);
|
||||
this.fullMediaView.scrollTop = Math.max(0, Math.min(scrollY, this.fullImage.scrollHeight - this.fullMediaView.clientHeight));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.resetImageStyles();
|
||||
}
|
||||
}
|
||||
this.fullImage.ondblclick = (event) => {
|
||||
event.stopPropagation();
|
||||
if (Platform.isMobileApp) this.toggleImageZoom(event);
|
||||
};
|
||||
|
||||
// 滾輪事件
|
||||
|
|
@ -816,13 +825,13 @@ export class FullScreenModal extends Modal {
|
|||
// 顯示上一個媒體
|
||||
showPrevMedia() {
|
||||
this.currentIndex = (this.currentIndex - 1 + this.mediaUrls.length) % this.mediaUrls.length;
|
||||
this.showMedia(this.currentIndex);
|
||||
void this.showMedia(this.currentIndex);
|
||||
}
|
||||
|
||||
// 顯示下一個媒體
|
||||
showNextMedia() {
|
||||
this.currentIndex = (this.currentIndex + 1) % this.mediaUrls.length;
|
||||
this.showMedia(this.currentIndex);
|
||||
void this.showMedia(this.currentIndex);
|
||||
}
|
||||
|
||||
// 註冊觸控事件處理器(行動裝置拖曳翻頁)
|
||||
|
|
@ -1041,11 +1050,11 @@ export class FullScreenModal extends Modal {
|
|||
if (!Platform.isAndroidApp) {
|
||||
video.src = media.url;
|
||||
const videoIcon = container.createDiv('mv-video-indicator');
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
const svg = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
const path = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('d', 'M8 5v14l11-7z');
|
||||
svg.appendChild(path);
|
||||
videoIcon.appendChild(svg);
|
||||
|
|
@ -1055,11 +1064,11 @@ export class FullScreenModal extends Modal {
|
|||
audio.src = media.url;
|
||||
container.onclick = () => this.showMedia(idx);
|
||||
const audioIcon = container.createDiv('mv-audio-indicator');
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
const svg = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
const path = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('fill', 'currentColor');
|
||||
path.setAttribute('d', 'M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z');
|
||||
svg.appendChild(path);
|
||||
|
|
@ -1096,13 +1105,13 @@ export class FullScreenModal extends Modal {
|
|||
prevArea.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
const prevIndex = (this.currentIndex - 1 + this.mediaUrls.length) % this.mediaUrls.length;
|
||||
this.showMedia(prevIndex);
|
||||
void this.showMedia(prevIndex);
|
||||
};
|
||||
|
||||
nextArea.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
const nextIndex = (this.currentIndex + 1) % this.mediaUrls.length;
|
||||
this.showMedia(nextIndex);
|
||||
void this.showMedia(nextIndex);
|
||||
};
|
||||
|
||||
this.fullImage = this.fullMediaView.createEl('img', { cls: 'mv-full-image' });
|
||||
|
|
@ -1124,7 +1133,7 @@ export class FullScreenModal extends Modal {
|
|||
// 自動顯示下一張圖片(如果還有的話)
|
||||
if (this.mediaUrls.length > 0) {
|
||||
const nextIndex = Math.min(index, this.mediaUrls.length - 1);
|
||||
this.showMedia(nextIndex);
|
||||
void this.showMedia(nextIndex);
|
||||
}
|
||||
|
||||
new Notice(t('media_deleted'));
|
||||
|
|
|
|||
|
|
@ -653,8 +653,8 @@ export class GalleryBlock {
|
|||
|
||||
createGalleryElement(mediaUrlsData: MediaUrlsData) {
|
||||
const { items, containerInfo, galleryId, sourcePath } = mediaUrlsData;
|
||||
const titleDiv = document.createElement('div');
|
||||
const galleryDiv = document.createElement('div');
|
||||
const titleDiv = activeDocument.createElement('div');
|
||||
const galleryDiv = activeDocument.createElement('div');
|
||||
galleryDiv.className = 'mvgb-media-gallery-grid';
|
||||
|
||||
// 使用從 mediaUrlsData 中取得的 galleryId
|
||||
|
|
@ -690,7 +690,7 @@ export class GalleryBlock {
|
|||
link.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const leaf = this.app.workspace.getLeaf('tab');
|
||||
leaf.openFile(file);
|
||||
void leaf.openFile(file);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
|
|
@ -708,7 +708,7 @@ export class GalleryBlock {
|
|||
galleryDiv.style.borderRadius = '0 8px 8px 8px';
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
const container = activeDocument.createElement('div');
|
||||
container.className = 'mvgb-media-container';
|
||||
container.appendChild(titleDiv);
|
||||
container.appendChild(galleryDiv);
|
||||
|
|
@ -731,21 +731,21 @@ export class GalleryBlock {
|
|||
|
||||
// 不使用分頁時,如果允許新增圖片,則顯示傳統的新增按鈕
|
||||
if (containerInfo.addButtonEnabled || items.length === 0) {
|
||||
const addContainer = document.createElement('div');
|
||||
const addContainer = activeDocument.createElement('div');
|
||||
addContainer.className = 'mv-media-thumbnail-container mvgb-add-media-button';
|
||||
|
||||
const addIcon = addContainer.createDiv('mvgb-add-media-icon');
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
const svg = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
const path = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('fill', 'currentColor');
|
||||
path.setAttribute('d', 'M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z');
|
||||
svg.appendChild(path);
|
||||
addIcon.appendChild(svg);
|
||||
|
||||
const addIconText = document.createElement('div');
|
||||
const addIconText = activeDocument.createElement('div');
|
||||
addIconText.className = 'mvgb-add-media-text';
|
||||
addIconText.textContent = t('add_image');
|
||||
addIcon.appendChild(addIconText);
|
||||
|
|
@ -771,10 +771,10 @@ export class GalleryBlock {
|
|||
GalleryBlock.paginationState.delete(galleryId);
|
||||
}
|
||||
|
||||
const controlsDiv = document.createElement('div');
|
||||
const controlsDiv = activeDocument.createElement('div');
|
||||
controlsDiv.className = 'mvgb-gallery-controls';
|
||||
|
||||
const paginationDiv = document.createElement('div');
|
||||
const paginationDiv = activeDocument.createElement('div');
|
||||
paginationDiv.className = 'mvgb-pagination';
|
||||
|
||||
const prevPageButton = paginationDiv.createEl('button', {
|
||||
|
|
@ -881,75 +881,77 @@ export class GalleryBlock {
|
|||
this.clearReorderClasses(galleryDiv);
|
||||
});
|
||||
|
||||
galleryDiv.addEventListener('drop', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
galleryDiv.removeClass('drag-over');
|
||||
galleryDiv.removeClass('drag-left');
|
||||
galleryDiv.removeClass('drag-right');
|
||||
galleryDiv.addEventListener('drop', (e) => {
|
||||
void (async () => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
galleryDiv.removeClass('drag-over');
|
||||
galleryDiv.removeClass('drag-left');
|
||||
galleryDiv.removeClass('drag-right');
|
||||
|
||||
const dragEvent = e as DragEvent;
|
||||
if (this.isGalleryReorderDrag(dragEvent)) {
|
||||
try {
|
||||
const payload = JSON.parse(dragEvent.dataTransfer?.getData(GALLERY_REORDER_MIME) || '') as GalleryReorderPayload;
|
||||
const reorderTarget = this.getReorderTarget(dragEvent, galleryDiv);
|
||||
this.clearReorderClasses(galleryDiv);
|
||||
const dragEvent = e as DragEvent;
|
||||
if (this.isGalleryReorderDrag(dragEvent)) {
|
||||
try {
|
||||
const payload = JSON.parse(dragEvent.dataTransfer?.getData(GALLERY_REORDER_MIME) || '') as GalleryReorderPayload;
|
||||
const reorderTarget = this.getReorderTarget(dragEvent, galleryDiv);
|
||||
this.clearReorderClasses(galleryDiv);
|
||||
|
||||
if (!reorderTarget || payload.galleryId !== galleryId || payload.index === reorderTarget.index) {
|
||||
return;
|
||||
if (!reorderTarget || payload.galleryId !== galleryId || payload.index === reorderTarget.index) {
|
||||
return;
|
||||
}
|
||||
|
||||
const draggedItem = items[payload.index];
|
||||
const targetItem = items[reorderTarget.index];
|
||||
if (!draggedItem || !targetItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.moveGalleryItem(galleryId, draggedItem, targetItem, reorderTarget.position, sourcePath, this.getCurrentGalleryPage(galleryDiv));
|
||||
} catch (error) {
|
||||
console.error('Error reordering gallery item:', error);
|
||||
}
|
||||
|
||||
const draggedItem = items[payload.index];
|
||||
const targetItem = items[reorderTarget.index];
|
||||
if (!draggedItem || !targetItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.moveGalleryItem(galleryId, draggedItem, targetItem, reorderTarget.position, sourcePath, this.getCurrentGalleryPage(galleryDiv));
|
||||
} catch (error) {
|
||||
console.error('Error reordering gallery item:', error);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 計算是否落在右側(右側代表加到最後,左側代表加到最前)
|
||||
const rect = galleryDiv.getBoundingClientRect();
|
||||
const midX = rect.left + rect.width / 2;
|
||||
const clientX = (e as DragEvent).clientX;
|
||||
const insertAtEnd = clientX >= midX;
|
||||
// 計算是否落在右側(右側代表加到最後,左側代表加到最前)
|
||||
const rect = galleryDiv.getBoundingClientRect();
|
||||
const midX = rect.left + rect.width / 2;
|
||||
const clientX = (e as DragEvent).clientX;
|
||||
const insertAtEnd = clientX >= midX;
|
||||
|
||||
const files = [];
|
||||
const files = [];
|
||||
|
||||
// 處理所有拖放的項目
|
||||
for (const item of (e.dataTransfer as any).items) {
|
||||
if (item.kind === 'string') {
|
||||
if (item.type === 'text/uri-list') {
|
||||
// 處理 URI 列表
|
||||
const uriPromise = new Promise(resolve => {
|
||||
item.getAsString((string: string) => {
|
||||
resolve({ type: 'uri', getData: () => string });
|
||||
// 處理所有拖放的項目
|
||||
for (const item of (e.dataTransfer as any).items) {
|
||||
if (item.kind === 'string') {
|
||||
if (item.type === 'text/uri-list') {
|
||||
// 處理 URI 列表
|
||||
const uriPromise = new Promise(resolve => {
|
||||
item.getAsString((string: string) => {
|
||||
resolve({ type: 'uri', getData: () => string });
|
||||
});
|
||||
});
|
||||
});
|
||||
files.push(uriPromise);
|
||||
} else if (item.type === 'text/plain') {
|
||||
const textPromise = new Promise(resolve => {
|
||||
item.getAsString((string: string) => {
|
||||
resolve({ type: 'text', getData: () => string });
|
||||
files.push(uriPromise);
|
||||
} else if (item.type === 'text/plain') {
|
||||
const textPromise = new Promise(resolve => {
|
||||
item.getAsString((string: string) => {
|
||||
resolve({ type: 'text', getData: () => string });
|
||||
});
|
||||
});
|
||||
});
|
||||
files.push(textPromise);
|
||||
files.push(textPromise);
|
||||
}
|
||||
} else if (item.kind === 'file') {
|
||||
// 處理一般檔案
|
||||
files.push(item.getAsFile());
|
||||
}
|
||||
} else if (item.kind === 'file') {
|
||||
// 處理一般檔案
|
||||
files.push(item.getAsFile());
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedFiles = await Promise.all(files);
|
||||
const modal = new ImageUploadModal(this.app, this.plugin, galleryDiv, sourcePath);
|
||||
// 依左右區域設定插入位置
|
||||
modal.insertAtEnd = insertAtEnd;
|
||||
await modal.handleFiles(resolvedFiles);
|
||||
const resolvedFiles = await Promise.all(files);
|
||||
const modal = new ImageUploadModal(this.app, this.plugin, galleryDiv, sourcePath);
|
||||
// 依左右區域設定插入位置
|
||||
modal.insertAtEnd = insertAtEnd;
|
||||
await modal.handleFiles(resolvedFiles);
|
||||
})();
|
||||
});
|
||||
|
||||
// 右鍵選單
|
||||
|
|
@ -994,9 +996,11 @@ export class GalleryBlock {
|
|||
}
|
||||
}
|
||||
|
||||
// 讀取文件內容
|
||||
const targetFile = activeFile;
|
||||
this.app.vault.read(targetFile).then((content) => {
|
||||
void (async () => {
|
||||
// 讀取文件內容
|
||||
const targetFile = activeFile;
|
||||
const content = await this.app.vault.read(targetFile);
|
||||
|
||||
// 尋找包含當前 gallery ID 的 gallery 區塊
|
||||
const galleryBlockRegex = /```gallery\n([\s\S]*?)```/g;
|
||||
let match;
|
||||
|
|
@ -1024,14 +1028,20 @@ export class GalleryBlock {
|
|||
|
||||
// 設置確認後的回調函數,使用 vault.process 修改文件
|
||||
modal.onConfirm = (newGalleryBlock: string) => {
|
||||
this.app.vault.process(targetFile, (fileContent) => {
|
||||
void this.app.vault.process(targetFile, (fileContent) => {
|
||||
return fileContent.substring(0, matchPosition.start) +
|
||||
newGalleryBlock +
|
||||
fileContent.substring(matchPosition.end);
|
||||
}).catch((error) => {
|
||||
console.error('Error updating gallery block:', error);
|
||||
new Notice(t('gallery_not_found'));
|
||||
});
|
||||
};
|
||||
|
||||
modal.open();
|
||||
})().catch((error) => {
|
||||
console.error('Error opening gallery settings:', error);
|
||||
new Notice(t('gallery_not_found'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1061,9 +1071,11 @@ export class GalleryBlock {
|
|||
}
|
||||
}
|
||||
|
||||
// 讀取文件內容
|
||||
const targetFile = activeFile;
|
||||
this.app.vault.read(targetFile).then((content) => {
|
||||
void (async () => {
|
||||
// 讀取文件內容
|
||||
const targetFile = activeFile;
|
||||
const content = await this.app.vault.read(targetFile);
|
||||
|
||||
// 尋找包含當前 gallery ID 的 gallery 區塊
|
||||
const galleryBlockRegex = /```gallery\n([\s\S]*?)```/g;
|
||||
let match;
|
||||
|
|
@ -1079,7 +1091,7 @@ export class GalleryBlock {
|
|||
}
|
||||
}
|
||||
|
||||
if (!matchPosition.start) {
|
||||
if (!match || match.length === 0) {
|
||||
new Notice(t('gallery_not_found'));
|
||||
return;
|
||||
}
|
||||
|
|
@ -1088,11 +1100,14 @@ export class GalleryBlock {
|
|||
const newContent = content.substring(matchPosition.start + '```gallery\n'.length, matchPosition.end - '```'.length);
|
||||
|
||||
// 使用 vault.process 修改文件
|
||||
this.app.vault.process(targetFile, (fileContent) => {
|
||||
await this.app.vault.process(targetFile, (fileContent) => {
|
||||
return fileContent.substring(0, matchPosition.start) +
|
||||
newContent +
|
||||
fileContent.substring(matchPosition.end);
|
||||
});
|
||||
})().catch((error) => {
|
||||
console.error('Error ungenerating gallery block:', error);
|
||||
new Notice(t('gallery_not_found'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1104,42 +1119,42 @@ export class GalleryBlock {
|
|||
}
|
||||
|
||||
createNoteContainer(item: GalleryItem, index: number, allItems: GalleryItem[], galleryId: string, sourcePath?: string, isFiltered?: boolean) {
|
||||
const container = document.createElement('div');
|
||||
const container = activeDocument.createElement('div');
|
||||
container.className = 'mv-media-thumbnail-container mvgb-note-thumbnail';
|
||||
container.dataset.galleryId = galleryId;
|
||||
container.dataset.galleryIndex = index.toString();
|
||||
|
||||
const notePreview = document.createElement('div');
|
||||
const notePreview = activeDocument.createElement('div');
|
||||
notePreview.className = 'mvgb-note-preview';
|
||||
|
||||
// 如果有縮圖,使用縮圖
|
||||
if (item.thumbnail) {
|
||||
const img = document.createElement('img');
|
||||
const img = activeDocument.createElement('img');
|
||||
img.src = item.thumbnail;
|
||||
img.className = 'mvgb-note-thumbnail-image';
|
||||
notePreview.appendChild(img);
|
||||
} else {
|
||||
// 否則使用預設圖示
|
||||
const noteIcon = document.createElement('div');
|
||||
const noteIcon = activeDocument.createElement('div');
|
||||
noteIcon.className = 'mvgb-note-icon';
|
||||
|
||||
if (item.isExternalLink) {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
const svg = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
const path = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('fill', 'currentColor');
|
||||
path.setAttribute('d', 'M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z');
|
||||
svg.appendChild(path);
|
||||
noteIcon.appendChild(svg);
|
||||
noteIcon.classList.add('external-link');
|
||||
} else {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
const svg = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
const path = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('fill', 'currentColor');
|
||||
path.setAttribute('d', 'M14,17H7V15H14V17M17,13H7V11H17V13M17,9H7V7H17V9M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z');
|
||||
svg.appendChild(path);
|
||||
|
|
@ -1167,7 +1182,7 @@ export class GalleryBlock {
|
|||
} else if (item.isInternalLink && item.file) {
|
||||
// 內部連結:在新分頁開啟筆記
|
||||
const leaf = this.app.workspace.getLeaf('tab');
|
||||
leaf.openFile(item.file);
|
||||
void leaf.openFile(item.file);
|
||||
} else {
|
||||
// 其他連結:嘗試在新分頁開啟
|
||||
window.open(item.path, '_blank', 'noopener,noreferrer');
|
||||
|
|
@ -1242,7 +1257,7 @@ export class GalleryBlock {
|
|||
currentPage = parseInt(paginationDiv.dataset.currentPage);
|
||||
}
|
||||
}
|
||||
this.renameGalleryItem(galleryId, item, sourcePath, currentPage);
|
||||
void this.renameGalleryItem(galleryId, item, sourcePath, currentPage);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -1261,7 +1276,7 @@ export class GalleryBlock {
|
|||
currentPage = parseInt(paginationDiv.dataset.currentPage);
|
||||
}
|
||||
}
|
||||
this.deleteGalleryItem(galleryId, item, sourcePath, currentPage);
|
||||
void this.deleteGalleryItem(galleryId, item, sourcePath, currentPage);
|
||||
});
|
||||
});
|
||||
menu.showAtMouseEvent(e);
|
||||
|
|
@ -1300,7 +1315,7 @@ export class GalleryBlock {
|
|||
if (page === totalPages && currentPageItems.length < itemsPerPage) {
|
||||
const placeholdersNeeded = itemsPerPage - currentPageItems.length;
|
||||
for (let i = 0; i < placeholdersNeeded; i++) {
|
||||
const placeholder = document.createElement('div');
|
||||
const placeholder = activeDocument.createElement('div');
|
||||
placeholder.className = 'mv-media-thumbnail-container placeholder';
|
||||
placeholder.style.visibility = 'hidden'; // 隱藏但保持佔位
|
||||
galleryDiv.appendChild(placeholder);
|
||||
|
|
@ -1320,13 +1335,13 @@ export class GalleryBlock {
|
|||
}
|
||||
|
||||
createMediaContainer(media: GalleryItem, index: number, allItems: GalleryItem[], galleryId: string, sourcePath?: string, isFiltered?: boolean) {
|
||||
const container = document.createElement('div');
|
||||
const container = activeDocument.createElement('div');
|
||||
container.className = 'mv-media-thumbnail-container';
|
||||
container.dataset.galleryId = galleryId;
|
||||
container.dataset.galleryIndex = index.toString();
|
||||
|
||||
if (media.type === 'image') {
|
||||
const img = document.createElement('img') as HTMLImageElement;
|
||||
const img = activeDocument.createElement('img') as HTMLImageElement;
|
||||
if (media.url) {
|
||||
img.src = media.url;
|
||||
img.alt = media.path || '';
|
||||
|
|
@ -1339,13 +1354,13 @@ export class GalleryBlock {
|
|||
if (media.path.toLowerCase().match(/\.(mp4|mkv|mov|webm)$/)) {
|
||||
// 如果有自訂縮圖,使用縮圖顯示
|
||||
if (media.thumbnail) {
|
||||
const img = document.createElement('img') as HTMLImageElement;
|
||||
const img = activeDocument.createElement('img') as HTMLImageElement;
|
||||
img.src = media.thumbnail;
|
||||
img.alt = media.path || '';
|
||||
img.className = 'mvgb-video-thumbnail';
|
||||
container.appendChild(img);
|
||||
} else {
|
||||
const video = document.createElement('video') as HTMLVideoElement;
|
||||
const video = activeDocument.createElement('video') as HTMLVideoElement;
|
||||
if (!Platform.isAndroidApp) {
|
||||
video.src = media.url;
|
||||
}
|
||||
|
|
@ -1354,13 +1369,13 @@ export class GalleryBlock {
|
|||
}
|
||||
|
||||
if (!Platform.isAndroidApp || media.thumbnail) {
|
||||
const videoIcon = document.createElement('div');
|
||||
const videoIcon = activeDocument.createElement('div');
|
||||
videoIcon.className = 'mv-video-indicator';
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
const svg = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
const path = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('d', 'M8 5v14l11-7z');
|
||||
svg.appendChild(path);
|
||||
videoIcon.appendChild(svg);
|
||||
|
|
@ -1370,26 +1385,26 @@ export class GalleryBlock {
|
|||
// 處理音樂檔案
|
||||
// 如果有自訂縮圖,使用縮圖顯示
|
||||
if (media.thumbnail) {
|
||||
const img = document.createElement('img') as HTMLImageElement;
|
||||
const img = activeDocument.createElement('img') as HTMLImageElement;
|
||||
img.src = media.thumbnail;
|
||||
img.alt = media.path || '';
|
||||
img.className = 'mvgb-video-thumbnail';
|
||||
container.appendChild(img);
|
||||
} else {
|
||||
const audio = document.createElement('audio') as HTMLAudioElement;
|
||||
const audio = activeDocument.createElement('audio') as HTMLAudioElement;
|
||||
audio.src = media.url;
|
||||
audio.style.pointerEvents = 'none';
|
||||
container.appendChild(audio);
|
||||
}
|
||||
|
||||
if (!Platform.isAndroidApp || media.thumbnail) {
|
||||
const audioIcon = document.createElement('div');
|
||||
const audioIcon = activeDocument.createElement('div');
|
||||
audioIcon.className = 'mv-audio-indicator';
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
const svg = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('width', '24');
|
||||
svg.setAttribute('height', '24');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
const path = activeDocument.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('fill', 'currentColor');
|
||||
path.setAttribute('d', 'M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z');
|
||||
svg.appendChild(path);
|
||||
|
|
@ -1398,7 +1413,7 @@ export class GalleryBlock {
|
|||
}
|
||||
|
||||
// 添加檔案名稱顯示
|
||||
const filenameDiv = document.createElement('div');
|
||||
const filenameDiv = activeDocument.createElement('div');
|
||||
filenameDiv.className = 'mv-audio-filename';
|
||||
let filename = '';
|
||||
if (media.path) {
|
||||
|
|
@ -1413,18 +1428,18 @@ export class GalleryBlock {
|
|||
}
|
||||
|
||||
if (typeof media.title === 'object' && media.title !== null) {
|
||||
const linkArea = document.createElement('div');
|
||||
const linkArea = activeDocument.createElement('div');
|
||||
linkArea.className = 'mvgb-media-link-area';
|
||||
|
||||
if (media.title.type === 'text') {
|
||||
// 純文字
|
||||
const textSpan = document.createElement('span');
|
||||
const textSpan = activeDocument.createElement('span');
|
||||
textSpan.textContent = media.title.text;
|
||||
textSpan.style.color = 'white';
|
||||
linkArea.appendChild(textSpan);
|
||||
} else {
|
||||
// 內部或外部連結
|
||||
const link = document.createElement('a');
|
||||
const link = activeDocument.createElement('a');
|
||||
link.textContent = media.title.text;
|
||||
|
||||
if (media.title.url) {
|
||||
|
|
@ -1436,7 +1451,7 @@ export class GalleryBlock {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const leaf = this.app.workspace.getLeaf('tab');
|
||||
leaf.openFile(file);
|
||||
void leaf.openFile(file);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1485,11 +1500,11 @@ export class GalleryBlock {
|
|||
container.onclick = () => {
|
||||
const modal = new FullScreenModal(this.app, this.plugin, 'thumbnail', sourcePath);
|
||||
modal.open();
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
const allUrls = modal.mediaUrls;
|
||||
const targetIndex = allUrls.findIndex(m => m.url === media.url);
|
||||
if (targetIndex !== -1) {
|
||||
modal.showMedia(targetIndex);
|
||||
void modal.showMedia(targetIndex);
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
|
@ -1561,7 +1576,7 @@ export class GalleryBlock {
|
|||
currentPage = parseInt(paginationDiv.dataset.currentPage);
|
||||
}
|
||||
}
|
||||
this.renameGalleryItem(galleryId, media, sourcePath, currentPage);
|
||||
void this.renameGalleryItem(galleryId, media, sourcePath, currentPage);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -1580,7 +1595,7 @@ export class GalleryBlock {
|
|||
currentPage = parseInt(paginationDiv.dataset.currentPage);
|
||||
}
|
||||
}
|
||||
this.deleteGalleryItem(galleryId, media, sourcePath, currentPage);
|
||||
void this.deleteGalleryItem(galleryId, media, sourcePath, currentPage);
|
||||
});
|
||||
});
|
||||
menu.showAtMouseEvent(e);
|
||||
|
|
@ -1611,11 +1626,11 @@ export class GalleryBlock {
|
|||
}
|
||||
|
||||
createHoverControls(item: GalleryItem, index: number, allItems: GalleryItem[], galleryId: string, sourcePath?: string) {
|
||||
const controls = document.createElement('div');
|
||||
const controls = activeDocument.createElement('div');
|
||||
controls.className = 'mvgb-hover-controls';
|
||||
|
||||
if (item.loc) {
|
||||
const dragHandle = document.createElement('button');
|
||||
const dragHandle = activeDocument.createElement('button');
|
||||
dragHandle.className = 'mvgb-hover-btn mvgb-btn-drag';
|
||||
dragHandle.type = 'button';
|
||||
dragHandle.title = t('drag_reorder');
|
||||
|
|
@ -1871,75 +1886,77 @@ export class GalleryBlock {
|
|||
}
|
||||
|
||||
const currentName = file.basename;
|
||||
const modal = new RenameModal(this.app, currentName, async (newName) => {
|
||||
if (!newName || newName === currentName) return;
|
||||
const modal = new RenameModal(this.app, currentName, (newName) => {
|
||||
void (async () => {
|
||||
if (!newName || newName === currentName) return;
|
||||
|
||||
try {
|
||||
const parentPath = file.parent ? file.parent.path : "";
|
||||
const newPath = parentPath === "/" || parentPath === ""
|
||||
? `${newName}.${file.extension}`
|
||||
: `${parentPath}/${newName}.${file.extension}`;
|
||||
await this.app.fileManager.renameFile(file, newPath);
|
||||
try {
|
||||
const parentPath = file.parent ? file.parent.path : "";
|
||||
const newPath = parentPath === "/" || parentPath === ""
|
||||
? `${newName}.${file.extension}`
|
||||
: `${parentPath}/${newName}.${file.extension}`;
|
||||
await this.app.fileManager.renameFile(file, newPath);
|
||||
|
||||
// 更新 Gallery 區塊中的檔案名稱連結
|
||||
let activeFile: TFile | null = null;
|
||||
if (sourcePath) {
|
||||
activeFile = this.app.vault.getAbstractFileByPath(sourcePath) as TFile | null;
|
||||
}
|
||||
// 更新 Gallery 區塊中的檔案名稱連結
|
||||
let activeFile: TFile | null = null;
|
||||
if (sourcePath) {
|
||||
activeFile = this.app.vault.getAbstractFileByPath(sourcePath) as TFile | null;
|
||||
}
|
||||
|
||||
if (!activeFile) {
|
||||
activeFile = this.app.workspace.getActiveFile();
|
||||
}
|
||||
if (!activeFile) {
|
||||
activeFile = this.app.workspace.getActiveFile();
|
||||
}
|
||||
|
||||
if (activeFile) {
|
||||
const content = await this.app.vault.read(activeFile);
|
||||
const galleryBlockRegex = /```gallery\n([\s\S]*?)```/g;
|
||||
let match;
|
||||
if (activeFile) {
|
||||
const content = await this.app.vault.read(activeFile);
|
||||
const galleryBlockRegex = /```gallery\n([\s\S]*?)```/g;
|
||||
let match;
|
||||
|
||||
while ((match = galleryBlockRegex.exec(content)) !== null) {
|
||||
const blockContent = match[1];
|
||||
const blockId = 'gallery-' + this.hashString(blockContent.trim());
|
||||
while ((match = galleryBlockRegex.exec(content)) !== null) {
|
||||
const blockContent = match[1];
|
||||
const blockId = 'gallery-' + this.hashString(blockContent.trim());
|
||||
|
||||
if (blockId === galleryId) {
|
||||
if (item.loc) {
|
||||
const lines = blockContent.split('\n');
|
||||
const start = item.loc.start;
|
||||
const end = item.loc.end;
|
||||
if (blockId === galleryId) {
|
||||
if (item.loc) {
|
||||
const lines = blockContent.split('\n');
|
||||
const start = item.loc.start;
|
||||
const end = item.loc.end;
|
||||
|
||||
// 將選定範圍內的舊檔名替換為新檔名
|
||||
for (let i = start; i <= end; i++) {
|
||||
lines[i] = lines[i].replace(currentName, newName);
|
||||
// 將選定範圍內的舊檔名替換為新檔名
|
||||
for (let i = start; i <= end; i++) {
|
||||
lines[i] = lines[i].replace(currentName, newName);
|
||||
}
|
||||
|
||||
const newBlockContent = lines.join('\n');
|
||||
const newId = 'gallery-' + this.hashString(newBlockContent.trim());
|
||||
|
||||
if (currentPage) {
|
||||
GalleryBlock.paginationState.set(newId, currentPage);
|
||||
}
|
||||
|
||||
const matchStart = match.index;
|
||||
const matchEnd = match.index + match[0].length;
|
||||
const newBlock = '```gallery\n' + newBlockContent + '```';
|
||||
|
||||
const restoreScroll = captureScrollRestore(this.app, galleryId);
|
||||
await this.app.vault.process(activeFile, (fileContent) => {
|
||||
return fileContent.substring(0, matchStart) +
|
||||
newBlock +
|
||||
fileContent.substring(matchEnd);
|
||||
});
|
||||
restoreScroll(newId);
|
||||
}
|
||||
|
||||
const newBlockContent = lines.join('\n');
|
||||
const newId = 'gallery-' + this.hashString(newBlockContent.trim());
|
||||
|
||||
if (currentPage) {
|
||||
GalleryBlock.paginationState.set(newId, currentPage);
|
||||
}
|
||||
|
||||
const matchStart = match.index;
|
||||
const matchEnd = match.index + match[0].length;
|
||||
const newBlock = '```gallery\n' + newBlockContent + '```';
|
||||
|
||||
const restoreScroll = captureScrollRestore(this.app, galleryId);
|
||||
await this.app.vault.process(activeFile, (fileContent) => {
|
||||
return fileContent.substring(0, matchStart) +
|
||||
newBlock +
|
||||
fileContent.substring(matchEnd);
|
||||
});
|
||||
restoreScroll(newId);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Notice(t('file_renamed') || 'File renamed');
|
||||
} catch (error) {
|
||||
console.error('Error renaming file:', error);
|
||||
new Notice(t('error_renaming_file') || 'Error renaming file');
|
||||
}
|
||||
new Notice(t('file_renamed') || 'File renamed');
|
||||
} catch (error) {
|
||||
console.error('Error renaming file:', error);
|
||||
new Notice(t('error_renaming_file') || 'Error renaming file');
|
||||
}
|
||||
})();
|
||||
});
|
||||
modal.open();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,71 +44,73 @@ export class ImageUploadModal extends Modal {
|
|||
cls: 'mvgb-paste-button',
|
||||
});
|
||||
|
||||
pasteButton.addEventListener('click', async () => {
|
||||
try {
|
||||
// 先嘗試讀取剪貼簿中的文字內容
|
||||
const text = await navigator.clipboard.readText();
|
||||
if (text) {
|
||||
// 使用正則表達式找出所有網址
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/gi;
|
||||
const urls = text.match(urlRegex);
|
||||
pasteButton.addEventListener('click', () => {
|
||||
void (async () => {
|
||||
try {
|
||||
// 先嘗試讀取剪貼簿中的文字內容
|
||||
const text = await navigator.clipboard.readText();
|
||||
if (text) {
|
||||
// 使用正則表達式找出所有網址
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/gi;
|
||||
const urls = text.match(urlRegex);
|
||||
|
||||
if (urls && urls.length > 0) {
|
||||
// 將每個網址轉換成 Markdown 圖片格式
|
||||
const markdownLinks = urls.map(url => ``);
|
||||
await this.handleLinks(markdownLinks);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是網址或檔案路徑,或是讀取失敗,則嘗試讀取剪貼簿中的圖片
|
||||
const items = await navigator.clipboard.read();
|
||||
for (const item of items) {
|
||||
const imageTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
|
||||
const imageType = item.types.find(type => imageTypes.includes(type));
|
||||
|
||||
if (imageType) {
|
||||
const blob = await item.getType(imageType);
|
||||
// 嘗試取得目前的 Markdown 檔案
|
||||
// 優先使用來源路徑,若無效則使用當前開啟的檔案
|
||||
let activeFile: TFile | null = null;
|
||||
if (this.sourcePath) {
|
||||
activeFile = this.app.vault.getAbstractFileByPath(this.sourcePath) as TFile | null;
|
||||
if (urls && urls.length > 0) {
|
||||
// 將每個網址轉換成 Markdown 圖片格式
|
||||
const markdownLinks = urls.map(url => ``);
|
||||
await this.handleLinks(markdownLinks);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是網址或檔案路徑,或是讀取失敗,則嘗試讀取剪貼簿中的圖片
|
||||
const items = await navigator.clipboard.read();
|
||||
for (const item of items) {
|
||||
const imageTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
|
||||
const imageType = item.types.find(type => imageTypes.includes(type));
|
||||
|
||||
if (imageType) {
|
||||
const blob = await item.getType(imageType);
|
||||
// 嘗試取得目前的 Markdown 檔案
|
||||
// 優先使用來源路徑,若無效則使用當前開啟的檔案
|
||||
let activeFile: TFile | null = null;
|
||||
if (this.sourcePath) {
|
||||
activeFile = this.app.vault.getAbstractFileByPath(this.sourcePath) as TFile | null;
|
||||
}
|
||||
|
||||
if (!activeFile) {
|
||||
activeFile = this.app.workspace.getActiveFile();
|
||||
if (!activeFile) {
|
||||
new Notice(t('please_open_note'));
|
||||
return;
|
||||
activeFile = this.app.workspace.getActiveFile();
|
||||
if (!activeFile) {
|
||||
new Notice(t('please_open_note'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 取得附件資料夾路徑
|
||||
const attachmentFolderPath = this.getAttachmentFolderPath(activeFile);
|
||||
// 確保附件資料夾存在
|
||||
await this.ensureFolderExists(attachmentFolderPath);
|
||||
let safeName = this.getSafeFileName(`pasted_image.${imageType.split('/')[1]}`);
|
||||
let timestamp = new Date().toISOString().replace(/[-:.TZ]/g, "");
|
||||
let fileNameWithoutExt = safeName.substring(0, safeName.lastIndexOf('.'));
|
||||
let extension = safeName.substring(safeName.lastIndexOf('.'));
|
||||
safeName = `${fileNameWithoutExt}_${timestamp}${extension}`;
|
||||
let newFilePath = `${attachmentFolderPath}/${safeName}`;
|
||||
if (await this.app.vault.adapter.exists(newFilePath)) {
|
||||
let counter = 1;
|
||||
while (await this.app.vault.adapter.exists(newFilePath)) {
|
||||
safeName = `${fileNameWithoutExt}_${timestamp}_${counter}${extension}`;
|
||||
newFilePath = `${attachmentFolderPath}/${safeName}`;
|
||||
counter++;
|
||||
// 取得附件資料夾路徑
|
||||
const attachmentFolderPath = this.getAttachmentFolderPath(activeFile);
|
||||
// 確保附件資料夾存在
|
||||
await this.ensureFolderExists(attachmentFolderPath);
|
||||
let safeName = this.getSafeFileName(`pasted_image.${imageType.split('/')[1]}`);
|
||||
let timestamp = new Date().toISOString().replace(/[-:.TZ]/g, "");
|
||||
let fileNameWithoutExt = safeName.substring(0, safeName.lastIndexOf('.'));
|
||||
let extension = safeName.substring(safeName.lastIndexOf('.'));
|
||||
safeName = `${fileNameWithoutExt}_${timestamp}${extension}`;
|
||||
let newFilePath = `${attachmentFolderPath}/${safeName}`;
|
||||
if (await this.app.vault.adapter.exists(newFilePath)) {
|
||||
let counter = 1;
|
||||
while (await this.app.vault.adapter.exists(newFilePath)) {
|
||||
safeName = `${fileNameWithoutExt}_${timestamp}_${counter}${extension}`;
|
||||
newFilePath = `${attachmentFolderPath}/${safeName}`;
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
const newFile = new File([blob], safeName, { type: imageType });
|
||||
await this.handleFiles([newFile]);
|
||||
}
|
||||
const newFile = new File([blob], safeName, { type: imageType });
|
||||
await this.handleFiles([newFile]);
|
||||
}
|
||||
} catch (err) {
|
||||
new Notice(`${t('clipboard_error')}: ${err instanceof Error ? err.message : String(err)}`);
|
||||
console.error('剪貼簿讀取錯誤:', err);
|
||||
}
|
||||
} catch (err) {
|
||||
new Notice(t('clipboard_error' + err));
|
||||
console.error('剪貼簿讀取錯誤:', err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 新增插入位置選項
|
||||
|
|
@ -172,21 +174,23 @@ export class ImageUploadModal extends Modal {
|
|||
dropZone.removeClass('drag-right');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', async (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.removeClass('drag-over');
|
||||
dropZone.removeClass('drag-left');
|
||||
dropZone.removeClass('drag-right');
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
void (async () => {
|
||||
e.preventDefault();
|
||||
dropZone.removeClass('drag-over');
|
||||
dropZone.removeClass('drag-left');
|
||||
dropZone.removeClass('drag-right');
|
||||
|
||||
if (e.dataTransfer === null) return;
|
||||
// 依左右區域設定插入位置
|
||||
const rect = dropZone.getBoundingClientRect();
|
||||
const midX = rect.left + rect.width / 2;
|
||||
const clientX = (e as DragEvent).clientX;
|
||||
this.insertAtEnd = clientX >= midX;
|
||||
if (e.dataTransfer === null) return;
|
||||
// 依左右區域設定插入位置
|
||||
const rect = dropZone.getBoundingClientRect();
|
||||
const midX = rect.left + rect.width / 2;
|
||||
const clientX = (e as DragEvent).clientX;
|
||||
this.insertAtEnd = clientX >= midX;
|
||||
|
||||
const files = (e.dataTransfer.files as any);
|
||||
await this.handleFiles(files);
|
||||
const files = (e.dataTransfer.files as any);
|
||||
await this.handleFiles(files);
|
||||
})();
|
||||
});
|
||||
|
||||
const fileInput = contentEl.createEl('input', {
|
||||
|
|
@ -198,10 +202,12 @@ export class ImageUploadModal extends Modal {
|
|||
}
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', async () => {
|
||||
if (fileInput.files && fileInput.files.length > 0) {
|
||||
await this.handleFiles(Array.from(fileInput.files));
|
||||
}
|
||||
fileInput.addEventListener('change', () => {
|
||||
void (async () => {
|
||||
if (fileInput.files && fileInput.files.length > 0) {
|
||||
await this.handleFiles(Array.from(fileInput.files));
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
dropZone.addEventListener('click', () => {
|
||||
|
|
@ -359,7 +365,7 @@ export class ImageUploadModal extends Modal {
|
|||
const newGalleryId = 'gallery-' + this.hashString(newBlockContent.trim());
|
||||
|
||||
// 使用 setTimeout 確保在 DOM 更新後執行
|
||||
if (restoreScroll) setTimeout(() => restoreScroll(newGalleryId), 0);
|
||||
if (restoreScroll) window.setTimeout(() => restoreScroll(newGalleryId), 0);
|
||||
|
||||
// 更新整個文件內容
|
||||
return (
|
||||
|
|
@ -436,7 +442,7 @@ export class ImageUploadModal extends Modal {
|
|||
const newGalleryId = 'gallery-' + this.hashString(newBlockContent.trim());
|
||||
|
||||
// 使用 setTimeout 確保在 DOM 更新後執行
|
||||
if (restoreScroll) setTimeout(() => restoreScroll(newGalleryId), 0);
|
||||
if (restoreScroll) window.setTimeout(() => restoreScroll(newGalleryId), 0);
|
||||
|
||||
// 更新整個文件內容
|
||||
return (
|
||||
|
|
@ -483,7 +489,6 @@ export class ImageUploadModal extends Modal {
|
|||
// 取得 vault 的附件設定
|
||||
const basePath = (this.app.vault as any).config.attachmentFolderPath
|
||||
|
||||
|
||||
if (basePath.startsWith('./')) {
|
||||
// 如果是相對路徑,則使用筆記所在資料夾
|
||||
if (!activeFile.parent) {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export default class MediaViewPlugin extends Plugin {
|
|||
|
||||
if (this.settings.openMediaBrowserOnClick) {
|
||||
// 添加點擊圖片的事件監聽
|
||||
this.registerDomEvent(document, 'click', (evt) => {
|
||||
this.registerDomEvent(activeDocument, 'click', (evt) => {
|
||||
const target = (evt.target) as HTMLImageElement;
|
||||
if (!target) return;
|
||||
|
||||
|
|
@ -91,12 +91,12 @@ export default class MediaViewPlugin extends Plugin {
|
|||
modal.open();
|
||||
|
||||
// 等待 modal 載入完成後顯示對應圖片
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
const allUrls = modal.mediaUrls;
|
||||
const targetUrl = target.src;
|
||||
const targetIndex = allUrls.findIndex(m => m.url === targetUrl);
|
||||
if (targetIndex !== -1) {
|
||||
modal.showMedia(targetIndex);
|
||||
void modal.showMedia(targetIndex);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,13 +46,13 @@ export function findScrollableContainer(app: App, startEl: HTMLElement | null):
|
|||
* @returns 還原捲動位置的函數
|
||||
*/
|
||||
export function captureScrollRestore(app: App, galleryId: string): ((newGalleryId?: string) => void) {
|
||||
const galleryEl = document.querySelector(`.mvgb-media-gallery-grid[data-gallery-id="${CSS.escape(galleryId)}"]`) as HTMLElement | null;
|
||||
const galleryEl = activeDocument.querySelector(`.mvgb-media-gallery-grid[data-gallery-id="${CSS.escape(galleryId)}"]`) as HTMLElement | null;
|
||||
const container = findScrollableContainer(app, galleryEl);
|
||||
const top = container ? container.scrollTop : 0;
|
||||
|
||||
return (newGalleryId?: string) => {
|
||||
const targetId = newGalleryId || galleryId;
|
||||
const galleryElAfter = document.querySelector(
|
||||
const galleryElAfter = activeDocument.querySelector(
|
||||
`.mvgb-media-gallery-grid[data-gallery-id="${CSS.escape(targetId)}"]`
|
||||
) as HTMLElement | null;
|
||||
const containerAfter = findScrollableContainer(app, galleryElAfter);
|
||||
|
|
@ -65,8 +65,8 @@ export function captureScrollRestore(app: App, galleryId: string): ((newGalleryI
|
|||
}
|
||||
};
|
||||
|
||||
requestAnimationFrame(doRestore);
|
||||
setTimeout(doRestore, 50);
|
||||
setTimeout(doRestore, 250);
|
||||
window.requestAnimationFrame(doRestore);
|
||||
window.setTimeout(doRestore, 50);
|
||||
window.setTimeout(doRestore, 250);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ type LanguageKey = keyof Translations;
|
|||
|
||||
// 全域翻譯函式
|
||||
export function t(key: string): string {
|
||||
const lang = window.localStorage.getItem('language') as LanguageKey;
|
||||
//const lang: LanguageKey = getLanguage() as LanguageKey;
|
||||
const lang: LanguageKey = getLanguage() as LanguageKey;
|
||||
const translations = TRANSLATIONS[lang] || TRANSLATIONS['en'];
|
||||
return translations[key] || key;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"2.1.0": "1.1.0"
|
||||
"2.1.0": "1.1.0",
|
||||
"2.1.1": "1.8.7"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue