diff --git a/manifest.json b/manifest.json index 42a2abf..422e5f7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "mediaviewer", "name": "Media Viewer", - "version": "2.1.13", + "version": "2.2.0", "minAppVersion": "1.8.7", "description": "Transform your notes with beautiful interactive media galleries. Seamlessly view, zoom, and manage images, videos, and audio files.", "author": "Devon22", diff --git a/package-lock.json b/package-lock.json index 3a9dcd1..3063ede 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mediaviewer", - "version": "2.1.13", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mediaviewer", - "version": "2.1.13", + "version": "2.2.0", "license": "MIT", "devDependencies": { "@types/node": "^16.11.6", diff --git a/package.json b/package.json index a29d1c4..1c64a75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mediaviewer", - "version": "2.1.13", + "version": "2.2.0", "description": "Transform your notes with beautiful interactive media galleries. Seamlessly view, zoom, and manage images, videos, and audio files.", "main": "main.js", "scripts": { diff --git a/src/galleryBlock.ts b/src/galleryBlock.ts index 1cf655e..9544aeb 100644 --- a/src/galleryBlock.ts +++ b/src/galleryBlock.ts @@ -24,17 +24,19 @@ class RenameModal extends Modal { const { contentEl } = this; contentEl.createEl('h2', { text: t('rename') }); - new Setting(contentEl) + const inputSetting = new Setting(contentEl) .setName(t('new_filename')) - .addText(text => text - .setValue(this.currentName) - .onChange(value => this.newName = value) - .inputEl.addEventListener('keydown', (e) => { + .addText(text => { + text.setValue(this.currentName) + .onChange(value => this.newName = value); + text.inputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') { this.onConfirm(this.newName); this.close(); } - })); + }); + }); + inputSetting.settingEl.addClass('mv-settings-input-flex'); new Setting(contentEl) .addButton(button => button @@ -55,6 +57,75 @@ class RenameModal extends Modal { } } +// 編輯 Alt 對話框 +class AltModal extends Modal { + altText: string; + onConfirm: (altText: string) => void; + onDelete?: () => void; + currentAlt: string; + isEdit: boolean; + + constructor(app: App, currentAlt: string, isEdit: boolean, onConfirm: (altText: string) => void, onDelete?: () => void) { + super(app); + this.currentAlt = currentAlt; + this.altText = currentAlt; + this.isEdit = isEdit; + this.onConfirm = onConfirm; + this.onDelete = onDelete; + } + + onOpen() { + const { contentEl } = this; + contentEl.createEl('h2', { text: this.isEdit ? t('edit_alt') || 'Edit alt' : t('add_alt') || 'Add alt' }); + + const inputSetting = new Setting(contentEl) + .setName('Alt') + .addText(text => { + text.setValue(this.currentAlt) + .onChange(value => this.altText = value); + text.inputEl.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + this.onConfirm(this.altText); + this.close(); + } + }); + }); + inputSetting.settingEl.addClass('mv-settings-input-flex'); + + const buttonsSetting = new Setting(contentEl); + + if (this.isEdit && this.onDelete) { + buttonsSetting.addButton(button => button + .setButtonText(t('delete') || 'Delete') + .setWarning() + .onClick(() => { + if (this.onDelete) { + this.onDelete(); + } + this.close(); + })); + } + + buttonsSetting.addButton(button => button + .setButtonText(t('confirm')) + .setCta() + .onClick(() => { + this.onConfirm(this.altText); + this.close(); + })); + + buttonsSetting.addButton(button => button + .setButtonText(t('cancel') || 'Cancel') + .onClick(() => this.close())); + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + } +} + + interface GalleryItem { type: string; url?: string; @@ -712,11 +783,11 @@ export class GalleryBlock { } const appWithPlugins = this.app as ObsidianAppWithPlugins; const zipPlugin = appWithPlugins.plugins?.plugins?.['gridexplorer']; - + if (!zipPlugin || typeof zipPlugin.getThumbnail !== 'function') { return null; } - + return await zipPlugin.getThumbnail(zipPath); } @@ -1240,6 +1311,7 @@ export class GalleryBlock { } this.addCopyAltMenuItem(menu, item); + this.addEditAltMenuItem(menu, item, galleryId, sourcePath, container); if (item.file) { menu.addItem((menuItem) => { @@ -1641,6 +1713,7 @@ export class GalleryBlock { } this.addCopyAltMenuItem(menu, media); + this.addEditAltMenuItem(menu, media, galleryId, sourcePath, container); if (media.file) { menu.addItem((menuItem) => { @@ -1712,6 +1785,43 @@ export class GalleryBlock { }); } + private addEditAltMenuItem(menu: Menu, item: GalleryItem, galleryId: string, sourcePath?: string, container?: HTMLElement) { + if (!item.loc) { + return; + } + + const hasAlt = !!(item.altText && item.altText.trim() !== ''); + menu.addItem((menuItem) => { + menuItem + .setTitle(hasAlt ? (t('edit_alt') || 'Edit alt') : (t('add_alt') || 'Add alt')) + .setIcon("pencil") + .onClick(() => { + let currentPage = 1; + if (container) { + const galleryDiv = container.closest('.mvgb-media-gallery-grid'); + if (galleryDiv) { + const paginationDiv = galleryDiv.parentElement?.querySelector('.mvgb-pagination'); + if (paginationDiv?.instanceOf(HTMLElement) && paginationDiv.dataset.currentPage) { + currentPage = parseInt(paginationDiv.dataset.currentPage); + } + } + } + const modal = new AltModal( + this.app, + item.altText || '', + hasAlt, + (newAlt) => { + void this.editGalleryItemAlt(galleryId, item, false, newAlt, sourcePath, currentPage); + }, + () => { + void this.editGalleryItemAlt(galleryId, item, true, '', sourcePath, currentPage); + } + ); + modal.open(); + }); + }); + } + private addGalleryBlockMenuItems(menu: Menu, galleryDiv: HTMLElement, galleryId: string, sourcePath?: string) { menu.addItem((item) => { item @@ -2188,4 +2298,87 @@ export class GalleryBlock { }); modal.open(); } + + async editGalleryItemAlt(galleryId: string, item: GalleryItem, isDelete: boolean, newAlt: string, sourcePath?: string, currentPage?: number) { + 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) { + new Notice(t('please_open_note') || 'Please open a note first'); + return; + } + + try { + const content = await this.app.vault.read(activeFile); + const galleryBlockRegex = /```gallery\r?\n([\s\S]*?)```/g; + let match; + + 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; + + let hasAltLine = false; + let altIndex = -1; + + for (let i = start; i <= end; i++) { + if (/^\s*alt:\s*(.*)$/i.test(lines[i])) { + hasAltLine = true; + altIndex = i; + break; + } + } + + if (isDelete) { + if (hasAltLine && altIndex !== -1) { + lines.splice(altIndex, 1); + } + } else { + if (hasAltLine && altIndex !== -1) { + lines[altIndex] = `alt: ${newAlt}`; + } else { + // 原本沒有 alt,在項目的最前面插入一行 alt: + lines.splice(start, 0, `alt: ${newAlt}`); + } + } + + 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); + new Notice(isDelete ? (t('alt_removed') || 'Alt text removed') : (t('alt_updated') || 'Alt text updated')); + } + break; + } + } + } catch (error) { + console.error('Error editing alt:', error); + new Notice(t('error_updating_alt') || 'Error updating alt text'); + } + } } diff --git a/src/translations.ts b/src/translations.ts index 7364a3b..73d117e 100644 --- a/src/translations.ts +++ b/src/translations.ts @@ -48,6 +48,11 @@ const TRANSLATIONS: Translations = { 'new_filename': '新的檔案名稱', 'copy_alt': '複製 alt 內容', 'alt_copied': '已複製 alt 內容', + 'add_alt': '新增 alt', + 'edit_alt': '編輯 alt', + 'alt_removed': '已移除 alt 內容', + 'alt_updated': '已更新 alt 內容', + 'error_updating_alt': '更新 alt 內容時發生錯誤', 'choose_note': '選擇筆記', 'choose_note_placeholder': '搜尋筆記名稱...', @@ -132,6 +137,11 @@ const TRANSLATIONS: Translations = { 'new_filename': 'New filename', 'copy_alt': 'Copy alt text', 'alt_copied': 'Alt text copied', + 'add_alt': 'Add alt', + 'edit_alt': 'Edit alt', + 'alt_removed': 'Alt text removed', + 'alt_updated': 'Alt text updated', + 'error_updating_alt': 'Error updating alt text', 'choose_note': 'Choose Note', 'choose_note_placeholder': 'Search note name...', @@ -216,6 +226,11 @@ const TRANSLATIONS: Translations = { 'new_filename': '新的文件名', 'copy_alt': '复制 alt 内容', 'alt_copied': '已复制 alt 内容', + 'add_alt': '新增 alt', + 'edit_alt': '编辑 alt', + 'alt_removed': '已移除 alt 内容', + 'alt_updated': '已更新 alt 内容', + 'error_updating_alt': '更新 alt 内容时发生错误', 'choose_note': '选择笔记', 'choose_note_placeholder': '搜索笔记名称...', @@ -298,6 +313,11 @@ const TRANSLATIONS: Translations = { 'new_filename': '新しいファイル名', 'copy_alt': 'alt テキストをコピー', 'alt_copied': 'alt テキストをコピーしました', + 'add_alt': 'alt 追加', + 'edit_alt': 'alt 編集', + 'alt_removed': 'alt テキストを削除しました', + 'alt_updated': 'alt テキストを更新しました', + 'error_updating_alt': 'alt テキストの更新中にエラーが発生しました', 'choose_note': 'ノートを選択', 'choose_note_placeholder': 'ノートの名前を検索...', diff --git a/styles.css b/styles.css index d96fa72..1be180e 100644 --- a/styles.css +++ b/styles.css @@ -1016,3 +1016,15 @@ width: 20px; height: 20px; } + +/* Alt & Rename Modals */ +.mv-settings-input-flex .setting-item-control { + flex-grow: 1; +} + +.mv-settings-input-flex .setting-item-control input[type="text"] { + width: 100% !important; + min-width: 180px; + max-width: 350px; +} +