diff --git a/main.ts b/main.ts index 30ba655..9e41f8a 100644 --- a/main.ts +++ b/main.ts @@ -3,6 +3,7 @@ import { GridView } from './src/GridView'; import { showFolderSelectionModal } from './src/FolderSelectionModal'; import { GallerySettings, DEFAULT_SETTINGS, GridExplorerSettingTab } from './src/settings'; import { t } from './src/translations'; +import { updateCustomDocumentExtensions } from './src/fileUtils'; // 插件類型定義 export default class GridExplorerPlugin extends Plugin { @@ -149,10 +150,12 @@ export default class GridExplorerPlugin extends Plugin { async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + updateCustomDocumentExtensions(this.settings); } async saveSettings() { await this.saveData(this.settings); + updateCustomDocumentExtensions(this.settings); // 當設定變更時,更新所有開啟的 GridView 實例 const leaves = this.app.workspace.getLeavesOfType('grid-view'); diff --git a/manifest.json b/manifest.json index 684329b..bc1015e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "gridexplorer", "name": "GridExplorer", - "version": "1.9.2", + "version": "1.9.3", "minAppVersion": "1.1.0", "description": "Browse note files in a grid view.", "author": "Devon22", diff --git a/package-lock.json b/package-lock.json index c857bc8..142f29f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gridexplorer", - "version": "1.9.2", + "version": "1.9.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gridexplorer", - "version": "1.9.2", + "version": "1.9.3", "license": "MIT", "devDependencies": { "@types/node": "^16.11.6", diff --git a/package.json b/package.json index ce16478..94158b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridexplorer", - "version": "1.9.2", + "version": "1.9.3", "description": "Browse note files in a grid view.", "main": "main.js", "scripts": { diff --git a/src/FileWatcher.ts b/src/FileWatcher.ts index ec066fd..6a19df6 100644 --- a/src/FileWatcher.ts +++ b/src/FileWatcher.ts @@ -24,6 +24,9 @@ export class FileWatcher { this.plugin.registerEvent( this.app.vault.on('create', (file) => { if (file instanceof TFile) { + if(this.gridView.sourceMode === 'random-note') { + return; + } if (this.gridView.sourceMode === 'folder' && this.gridView.sourcePath && this.gridView.searchQuery === '') { const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; if (fileDirPath === this.gridView.sourcePath) { @@ -40,6 +43,9 @@ export class FileWatcher { this.plugin.registerEvent( this.app.vault.on('delete', (file) => { if (file instanceof TFile) { + if(this.gridView.sourceMode === 'random-note') { + return; + } if (this.gridView.sourceMode === 'folder' && this.gridView.sourcePath && this.gridView.searchQuery === '') { const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; if (fileDirPath === this.gridView.sourcePath) { @@ -56,6 +62,9 @@ export class FileWatcher { this.plugin.registerEvent( this.app.vault.on('rename', (file, oldPath) => { if (file instanceof TFile) { + if(this.gridView.sourceMode === 'random-note') { + return; + } if (this.gridView.sourceMode === 'folder' && this.gridView.sourcePath && this.gridView.searchQuery === '') { const fileDirPath = file.path.split('/').slice(0, -1).join('/') || '/'; const oldDirPath = oldPath.split('/').slice(0, -1).join('/') || '/'; diff --git a/src/GridView.ts b/src/GridView.ts index 604627b..ce01b4c 100644 --- a/src/GridView.ts +++ b/src/GridView.ts @@ -7,6 +7,7 @@ import { showSearchModal } from './SearchModal'; import { FileWatcher } from './FileWatcher'; import { t } from './translations'; import GridExplorerPlugin from '../main'; +import { isDocumentFile, isMediaFile, isImageFile, isVideoFile, isAudioFile } from './fileUtils'; // 定義網格視圖 export class GridView extends ItemView { @@ -71,6 +72,8 @@ export class GridView extends ItemView { return t('backlinks_mode'); } else if (this.sourceMode === 'all-files') { return t('all_files_mode'); + } else if (this.sourceMode === 'random-note') { + return t('random_note_mode'); } else { return ''; } @@ -94,10 +97,10 @@ export class GridView extends ItemView { if (!(file instanceof TFile)) return false; // 如果是 Markdown 檔案,直接包含 - if (file.extension === 'md' || file.extension === 'pdf' || file.extension === 'canvas') return true; + if (isDocumentFile(file)) return true; // 如果是媒體檔案,根據設定決定是否包含 - if (this.plugin.settings.showMediaFiles && this.isMediaFile(file)) { + if (this.plugin.settings.showMediaFiles && isMediaFile(file)) { return true; } @@ -157,8 +160,8 @@ export class GridView extends ItemView { const file = this.app.vault.getAbstractFileByPath(item.path); if (file instanceof TFile) { // 根據設定決定是否包含媒體檔案 - if (file.extension === 'md' || file.extension === 'pdf' || file.extension === 'canvas' || - (this.plugin.settings.showMediaFiles && this.isMediaFile(file))) { + if (isDocumentFile(file) || + (this.plugin.settings.showMediaFiles && isMediaFile(file))) { bookmarkedFiles.add(file); } } @@ -176,10 +179,10 @@ export class GridView extends ItemView { // 根據設定過濾檔案 const filteredFiles = allFiles.filter(file => { // 如果是 Markdown 檔案,直接包含 - if (file.extension === 'md' || file.extension === 'pdf' || file.extension === 'canvas') return true; + if (isDocumentFile(file)) return true; // 如果是媒體檔案,根據設定決定是否包含 - if (this.plugin.settings.showMediaFiles && this.isMediaFile(file)) { + if (this.plugin.settings.showMediaFiles && isMediaFile(file)) { return true; } @@ -192,10 +195,10 @@ export class GridView extends ItemView { const allFiles = this.app.vault.getFiles(); const filteredFiles = allFiles.filter(file => { // 如果是 Markdown 檔案,直接包含 - if (file.extension === 'md' || file.extension === 'pdf' || file.extension === 'canvas') return true; + if (isDocumentFile(file)) return true; // 如果是媒體檔案,根據設定決定是否包含 - if (this.randomNoteIncludeMedia && this.isMediaFile(file)) { + if (this.randomNoteIncludeMedia && isMediaFile(file)) { return true; } @@ -269,12 +272,6 @@ export class GridView extends ItemView { }); } - // 判斷是否為媒體檔案 - isMediaFile(file: TFile): boolean { - const mediaExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4', 'webm', 'mov', 'avi', 'mkv']; - return mediaExtensions.includes(file.extension.toLowerCase()); - } - async render() { // 儲存當前捲動位置 const scrollContainer = this.containerEl.children[1] as HTMLElement; @@ -783,12 +780,12 @@ export class GridView extends ItemView { // 根據設定過濾檔案 const filteredFiles = allFiles.filter(file => { - // 非媒體檔案(Markdown、PDF、Canvas)始終包含 - if (['md', 'pdf', 'canvas'].includes(file.extension.toLowerCase())) { + // 文件檔案始終包含 + if (isDocumentFile(file)) { return true; } // 媒體檔案根據 searchMediaFiles 設定決定是否包含 - if (this.isMediaFile(file)) { + if (isMediaFile(file)) { return this.plugin.settings.searchMediaFiles; } return false; @@ -896,14 +893,12 @@ export class GridView extends ItemView { const imageArea = fileEl.querySelector('.ge-image-area'); if (imageArea && !imageArea.hasAttribute('data-loaded')) { // 根據檔案類型處理 - const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; - const videoExtensions = ['mp4', 'webm', 'mov', 'avi', 'mkv']; - if (imageExtensions.includes(file.extension.toLowerCase())) { + if (isImageFile(file)) { // 直接顯示圖片 const img = imageArea.createEl('img'); img.src = this.app.vault.getResourcePath(file); imageArea.setAttribute('data-loaded', 'true'); - } else if (videoExtensions.includes(file.extension.toLowerCase())) { + } else if (isVideoFile(file)) { // 根據設定決定是否顯示影片縮圖 if (this.plugin.settings.showVideoThumbnails) { // 顯示影片縮圖 @@ -952,26 +947,30 @@ export class GridView extends ItemView { // 創建標題容器 const titleContainer = contentArea.createDiv('ge-title-container'); + const extension = file.extension.toLowerCase(); // 添加檔案類型圖示 - const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; - const videoExtensions = ['mp4', 'webm', 'mov', 'avi', 'mkv']; - - if (imageExtensions.includes(file.extension.toLowerCase())) { + if (isImageFile(file)) { const iconContainer = titleContainer.createDiv('ge-icon-container ge-img'); setIcon(iconContainer, 'image'); - } else if (videoExtensions.includes(file.extension.toLowerCase())) { + } else if (isVideoFile(file)) { const iconContainer = titleContainer.createDiv('ge-icon-container ge-video'); setIcon(iconContainer, 'play-circle'); - } else if (file.extension === 'pdf') { + } else if (isAudioFile(file)) { + const iconContainer = titleContainer.createDiv('ge-icon-container ge-audio'); + setIcon(iconContainer, 'music'); + } else if (extension === 'pdf') { const iconContainer = titleContainer.createDiv('ge-icon-container ge-pdf'); setIcon(iconContainer, 'paperclip'); - } else if (file.extension === 'canvas') { + } else if (extension === 'canvas') { const iconContainer = titleContainer.createDiv('ge-icon-container ge-canvas'); setIcon(iconContainer, 'layout-dashboard'); - } else { + } else if (extension === 'md' || extension === 'txt') { const iconContainer = titleContainer.createDiv('ge-icon-container'); setIcon(iconContainer, 'file-text'); + } else { + const iconContainer = titleContainer.createDiv('ge-icon-container'); + setIcon(iconContainer, 'file'); } // 創建標題(立即載入) @@ -994,11 +993,15 @@ export class GridView extends ItemView { } // 根據檔案類型處理點擊事件 - if (this.isMediaFile(file)) { + if (isMediaFile(file)) { // 開啟媒體檔案 - this.openMediaFile(file, files); + if (isAudioFile(file)) { + this.openAudioFile(file); + } else { + this.openMediaFile(file, files); + } } else { - // 開啟 Markdown 檔案、PDF 檔案和 Canvas 檔案 + // 開啟文件檔案 if (event.ctrlKey) { this.app.workspace.getLeaf(true).openFile(file); } else { @@ -1017,7 +1020,7 @@ export class GridView extends ItemView { fileEl.addEventListener('mouseup', (event) => { if (event.button === 1) { event.preventDefault(); - if (!this.isMediaFile(file)) { + if (!isMediaFile(file)) { this.app.workspace.getLeaf(true).openFile(file); } } @@ -1027,7 +1030,7 @@ export class GridView extends ItemView { // 添加拖曳功能 fileEl.setAttribute('draggable', 'true'); fileEl.addEventListener('dragstart', (event) => { - const isMedia = this.isMediaFile(file); + const isMedia = isMediaFile(file); const mdLink = isMedia ? `![[${file.path}]]` // 媒體檔案使用 ![[]] 格式 : `[[${file.path}]]`; // 一般檔案使用 [[]] 格式 @@ -1291,23 +1294,133 @@ export class GridView extends ItemView { openMediaFile(file: TFile, mediaFiles?: TFile[]) { // 如果沒有傳入媒體檔案列表,則獲取 const getMediaFilesPromise = mediaFiles - ? Promise.resolve(mediaFiles.filter(f => this.isMediaFile(f))) - : this.getFiles().then(allFiles => allFiles.filter(f => this.isMediaFile(f))); + ? Promise.resolve(mediaFiles.filter(f => isMediaFile(f))) + : this.getFiles().then(allFiles => allFiles.filter(f => isMediaFile(f))); getMediaFilesPromise.then(filteredMediaFiles => { // 找到當前檔案在媒體檔案列表中的索引 const currentIndex = filteredMediaFiles.findIndex(f => f.path === file.path); if (currentIndex === -1) return; - + // 使用 MediaModal 開啟媒體檔案,並傳入 this 作為 gridView 參數 const mediaModal = new MediaModal(this.app, file, filteredMediaFiles, this); mediaModal.open(); }); } + + // 開啟音樂檔案 + openAudioFile(file: TFile) { + // 移除所有已經存在的音樂播放器 + const existingPlayers = document.querySelectorAll('.ge-floating-audio-player'); + existingPlayers.forEach(player => player.remove()); + + // 創建音樂播放器容器 + const audioPlayerContainer = document.createElement('div'); + audioPlayerContainer.className = 'ge-floating-audio-player'; + + // 創建音樂元素 + const audio = document.createElement('audio'); + audio.controls = true; + audio.src = this.app.vault.getResourcePath(file); + + // 創建標題元素 + const titleElement = document.createElement('div'); + titleElement.className = 'ge-audio-title'; + titleElement.textContent = file.basename; + + // 創建關閉按鈕 + const closeButton = document.createElement('div'); + closeButton.className = 'ge-audio-close-button'; + setIcon(closeButton, 'x'); + closeButton.addEventListener('click', () => { + // 移除音樂播放器 + audioPlayerContainer.remove(); + }); + + // 創建拖曳控制元素 + const handleElement = document.createElement('div'); + handleElement.className = 'ge-audio-handle'; + + // 將元素添加到容器中 + audioPlayerContainer.appendChild(handleElement); + audioPlayerContainer.appendChild(titleElement); + audioPlayerContainer.appendChild(audio); + audioPlayerContainer.appendChild(closeButton); + + // 設定拖曳事件 + let isDragging = false; + let offsetX = 0; + let offsetY = 0; + let isTouchEvent = false; + + handleElement.addEventListener('mousedown', (e) => { + if (isTouchEvent) return; // 如果是觸控事件觸發的,則忽略滑鼠事件 + isDragging = true; + offsetX = e.clientX - audioPlayerContainer.getBoundingClientRect().left; + offsetY = e.clientY - audioPlayerContainer.getBoundingClientRect().top; + audioPlayerContainer.classList.add('ge-audio-dragging'); + }); + + handleElement.addEventListener('touchstart', (e) => { + isTouchEvent = true; + isDragging = true; + const touch = e.touches[0]; + offsetX = touch.clientX - audioPlayerContainer.getBoundingClientRect().left; + offsetY = touch.clientY - audioPlayerContainer.getBoundingClientRect().top; + audioPlayerContainer.classList.add('ge-audio-dragging'); + }, { passive: true }); + + document.addEventListener('mousemove', (e) => { + if (!isDragging || isTouchEvent) return; + + const x = e.clientX - offsetX; + const y = e.clientY - offsetY; + + audioPlayerContainer.style.left = `${x}px`; + audioPlayerContainer.style.top = `${y}px`; + }); + + document.addEventListener('touchmove', (e) => { + if (!isDragging) return; + + const touch = e.touches[0]; + const x = touch.clientX - offsetX; + const y = touch.clientY - offsetY; + + audioPlayerContainer.style.left = `${x}px`; + audioPlayerContainer.style.top = `${y}px`; + + // 防止頁面滾動 + e.preventDefault(); + }, { passive: false }); + + document.addEventListener('mouseup', () => { + if (isTouchEvent) return; + isDragging = false; + audioPlayerContainer.classList.remove('ge-audio-dragging'); + }); + + document.addEventListener('touchend', () => { + isDragging = false; + isTouchEvent = false; + audioPlayerContainer.classList.remove('ge-audio-dragging'); + }); + + // 將音樂播放器添加到文檔中 + document.body.appendChild(audioPlayerContainer); + + // 設定初始位置 + const rect = audioPlayerContainer.getBoundingClientRect(); + audioPlayerContainer.style.left = `${window.innerWidth - rect.width - 20}px`; + audioPlayerContainer.style.top = `${window.innerHeight - rect.height - 20}px`; + + // 播放音樂 + audio.play(); + } // 顯示搜尋 modal showSearchModal(defaultQuery = '') { - showSearchModal(this.app,this, defaultQuery); + showSearchModal(this.app, this, defaultQuery); } // 保存視圖狀態 diff --git a/src/MediaModal.ts b/src/MediaModal.ts index adbd5b5..f46d40c 100644 --- a/src/MediaModal.ts +++ b/src/MediaModal.ts @@ -1,4 +1,5 @@ import { App, Modal, TFile, setIcon } from 'obsidian'; +import { isImageFile, isVideoFile, isAudioFile } from './fileUtils'; export class MediaModal extends Modal { private file: TFile; @@ -153,10 +154,10 @@ export class MediaModal extends Modal { this.isZoomed = false; const mediaFile = this.mediaFiles[index]; - const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; - const videoExtensions = ['mp4', 'webm', 'mov', 'avi', 'mkv']; - if (imageExtensions.includes(mediaFile.extension.toLowerCase())) { + + + if (isImageFile(mediaFile)) { // 創建圖片元素 const img = document.createElement('img'); img.className = 'ge-fullscreen-image'; @@ -184,8 +185,8 @@ export class MediaModal extends Modal { this.toggleImageZoom(img); }); - } else if (videoExtensions.includes(mediaFile.extension.toLowerCase())) { - // 對於影片,維持原有的處理方式 + } else if (isVideoFile(mediaFile) || isAudioFile(mediaFile)) { + // 對於影片和音樂,維持原有的處理方式 if (this.currentMediaElement) { this.currentMediaElement.remove(); } @@ -194,9 +195,24 @@ export class MediaModal extends Modal { video.controls = true; video.autoplay = true; video.src = this.app.vault.getResourcePath(mediaFile); + mediaContainer.appendChild(video); this.currentMediaElement = video; } + + const oldFileNameElement = mediaContainer.querySelector('.ge-fullscreen-file-name'); + if (oldFileNameElement) { + oldFileNameElement.remove(); + } + + if(isAudioFile(mediaFile)) { + //顯示檔案名稱 + const fileName = mediaFile.name; + const fileNameElement = document.createElement('div'); + fileNameElement.className = 'ge-fullscreen-file-name'; + fileNameElement.textContent = fileName; + mediaContainer.appendChild(fileNameElement); + } } // 顯示下一個媒體檔案 diff --git a/src/fileUtils.ts b/src/fileUtils.ts new file mode 100644 index 0000000..6895d6e --- /dev/null +++ b/src/fileUtils.ts @@ -0,0 +1,47 @@ +import { TFile } from 'obsidian'; +import { DEFAULT_SETTINGS, type GallerySettings } from './settings'; + +let customDocumentExtensions: string[] = []; + +// 更新自訂文件副檔名列表 +export function updateCustomDocumentExtensions(settings: GallerySettings) { + if (settings.customDocumentExtensions) { + customDocumentExtensions = settings.customDocumentExtensions + .split(',') + .map(ext => ext.trim().toLowerCase()) + .filter(ext => ext.length > 0); + } else { + customDocumentExtensions = []; + } +} + +// 檢查檔案是否為文件檔案 +export function isDocumentFile(file: TFile): boolean { + const defaultDocumentExtensions = ['md', 'pdf', 'canvas']; + const extension = file.extension.toLowerCase(); + return defaultDocumentExtensions.includes(extension) || + customDocumentExtensions.includes(extension); +} + +// 檢查檔案是否為圖片檔案 +export function isImageFile(file: TFile): boolean { + const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'bmp', 'svg']; + return imageExtensions.includes(file.extension.toLowerCase()); +} + +// 檢查檔案是否為影片檔案 +export function isVideoFile(file: TFile): boolean { + const videoExtensions = ['mp4', 'webm', 'mov', 'avi', 'mkv', 'ogv']; + return videoExtensions.includes(file.extension.toLowerCase()); +} + +// 檢查檔案是否為音樂檔案 +export function isAudioFile(file: TFile): boolean { + const audioExtensions = ['flac', 'm4a', 'mp3', 'ogg', 'wav', 'webm', '3gp']; + return audioExtensions.includes(file.extension.toLowerCase()); +} + +// 檢查檔案是否為媒體檔案 +export function isMediaFile(file: TFile): boolean { + return isImageFile(file) || isVideoFile(file) || isAudioFile(file); +} diff --git a/src/settings.ts b/src/settings.ts index 773a3e5..4e63d3a 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -24,6 +24,7 @@ export interface GallerySettings { showBacklinksMode: boolean; // 是否顯示反向連結模式 showAllFilesMode: boolean; // 是否顯示所有檔案模式 showRandomNoteMode: boolean; // 是否顯示隨機筆記模式 + customDocumentExtensions: string; // 自訂文件副檔名(用逗號分隔) } // 預設設定 @@ -42,13 +43,14 @@ export const DEFAULT_SETTINGS: GallerySettings = { searchMediaFiles: false, // 預設搜尋時也包含圖片和影片 showVideoThumbnails: false, // 預設不顯示影片縮圖 defaultOpenLocation: 'tab', // 預設開啟位置:新分頁 - showParentFolderItem: false, // 預設顯示"返回上级文件夹"選項 - reuseExistingLeaf: false, // 是否重用現有的網格視圖 + showParentFolderItem: false, // 預設不顯示"返回上级文件夹"選項 + reuseExistingLeaf: false, // 預設不重用現有的網格視圖 showBookmarksMode: true, // 預設顯示書籤模式 showSearchMode: true, // 預設顯示搜尋結果模式 showBacklinksMode: true, // 預設顯示反向連結模式 - showAllFilesMode: false, // 預設顯示所有檔案模式 - showRandomNoteMode: false, // 預設顯示隨機筆記模式 + showAllFilesMode: false, // 預設不顯示所有檔案模式 + showRandomNoteMode: false, // 預設不顯示隨機筆記模式 + customDocumentExtensions: '', // 自訂文件副檔名(用逗號分隔) }; // 設定頁面類別 @@ -254,6 +256,20 @@ export class GridExplorerSettingTab extends PluginSettingTab { }); }); + // 自訂文件副檔名設定 + new Setting(containerEl) + .setName(t('custom_document_extensions')) + .setDesc(t('custom_document_extensions_desc')) + .addText(text => { + text + .setPlaceholder(t('custom_document_extensions_placeholder')) + .setValue(this.plugin.settings.customDocumentExtensions) + .onChange(async (value) => { + this.plugin.settings.customDocumentExtensions = value; + await this.plugin.saveSettings(); + }); + }); + // 顯示"返回上级文件夹"選項設定 new Setting(containerEl) .setName(t('show_parent_folder_item')) diff --git a/src/translations.ts b/src/translations.ts index 975e6d3..6c5197b 100644 --- a/src/translations.ts +++ b/src/translations.ts @@ -118,6 +118,10 @@ export const TRANSLATIONS: Translations = { 'reuse_existing_leaf': '重複使用已開啟的視圖', 'reuse_existing_leaf_desc': '開啟網格視圖時,優先使用已開啟的視圖而非建立新視圖', + 'custom_document_extensions': '自訂文件檔案副檔名', + 'custom_document_extensions_desc': '額外的文件副檔名(用逗號分隔,不含點號)', + 'custom_document_extensions_placeholder': '例如:txt,doc,docx', + // 選擇資料夾對話框 'select_folders': '選擇資料夾', 'open_grid_view': '開啟網格視圖', @@ -231,6 +235,10 @@ export const TRANSLATIONS: Translations = { 'reuse_existing_leaf': 'Reuse existing view', 'reuse_existing_leaf_desc': 'When opening Grid View, prioritize using an existing view instead of creating a new one', + 'custom_document_extensions': 'Custom Document Extensions', + 'custom_document_extensions_desc': 'Additional document extensions (comma-separated, without dots)', + 'custom_document_extensions_placeholder': 'e.g., txt,doc,docx', + // Select Folder Dialog 'select_folders': 'Select folder', 'open_grid_view': 'Open grid view', @@ -344,6 +352,10 @@ export const TRANSLATIONS: Translations = { 'reuse_existing_leaf': '重复使用已打开的视图', 'reuse_existing_leaf_desc': '打开网格视图时,优先使用已打开的视图而非创建新视图', + 'custom_document_extensions': '自定义文件扩展名', + 'custom_document_extensions_desc': '额外的文件扩展名(用逗号分隔,不含点号)', + 'custom_document_extensions_placeholder': '例如:txt,doc,docx', + // 选择文件夹对话框 'select_folders': '选择文件夹', 'open_grid_view': '打开网格视图', @@ -455,7 +467,11 @@ export const TRANSLATIONS: Translations = { 'open_in_right_sidebar': '右サイドバーで開く', 'open_in_new_tab': '新しいタブで開く', 'reuse_existing_leaf': '既存のビューを再利用', - 'reuse_existing_leaf_desc': 'グリッドビューを開くとき、新しいビューを作成せずに既存のビューを優先的に使用', + 'reuse_existing_leaf_desc': 'グリッドビューを開くとき、新しいビューを作成せずに既存のビューを優先使用', + + 'custom_document_extensions': 'カスタム文書拡張子', + 'custom_document_extensions_desc': '追加の文書拡張子(カンマ区切り、ドット無し)', + 'custom_document_extensions_placeholder': '例:txt,doc,docx', // フォルダ選択ダイアログ 'select_folders': 'フォルダを選択', diff --git a/styles.css b/styles.css index ae7ac79..72ac057 100644 --- a/styles.css +++ b/styles.css @@ -85,6 +85,10 @@ color: var(--color-red); } +.ge-icon-container.ge-audio { + color: var(--color-purple); +} + .ge-icon-container.ge-pdf { color: var(--color-orange); } @@ -599,6 +603,20 @@ margin: 0 auto; } +.ge-fullscreen-file-name { + position: fixed; + top: 15px; + left: 50%; + transform: translateX(-50%); + background-color: rgba(0, 0, 0, 0.6); + color: white; + padding: 8px 16px; + border-radius: 4px; + font-size: 14px; + z-index: 1007; + white-space: nowrap; +} + .ge-media-close-button { position: fixed; top: 15px; @@ -706,3 +724,77 @@ border: 2px dashed var(--interactive-accent); transform: scale(1.05); } + +/* 音樂播放器 */ +.ge-floating-audio-player { + position: fixed; + width: 300px; + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + z-index: 1100; + padding: 10px; + display: flex; + flex-direction: column; + gap: 8px; + transition: box-shadow 0.3s ease; +} + +.ge-floating-audio-player:hover { + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); +} + +.ge-audio-title { + font-size: 14px; + font-weight: 500; + color: var(--text-normal); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 24px; + cursor: default; + margin: 2px; +} + +.ge-floating-audio-player audio { + width: 100%; + outline: none; +} + +.ge-audio-close-button { + position: absolute; + top: 8px; + right: 8px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + cursor: pointer; + border-radius: 50%; + transition: background-color 0.2s ease, color 0.2s ease; + z-index: 1102; +} + +.ge-audio-close-button:hover { + background-color: var(--background-modifier-error-hover); + color: white; +} + +.ge-audio-handle { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 30px; + cursor: move; + border-radius: 8px 8px 0 0; + z-index: 1101; +} + +.ge-audio-dragging { + opacity: 0.9; + user-select: none; +} diff --git a/versions.json b/versions.json index 9256ca2..ce9df6a 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.9.2": "1.1.0" + "1.9.3": "1.1.0" } \ No newline at end of file