This commit is contained in:
Devon22 2026-05-13 22:44:09 +08:00
parent 40305ceb70
commit aaccfc7b16
13 changed files with 6170 additions and 2747 deletions

View file

@ -1,3 +0,0 @@
node_modules/
main.js

View file

@ -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
View 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"
]),
);

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"
}
}

View file

@ -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'));

View file

@ -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();
}

View file

@ -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 => `![](${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 => `![](${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) {

View file

@ -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);
}

View file

@ -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);
};
}

View file

@ -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;
}

View file

@ -1,3 +1,4 @@
{
"2.1.0": "1.1.0"
"2.1.0": "1.1.0",
"2.1.1": "1.8.7"
}