This commit is contained in:
Devon22 2025-03-29 00:35:47 +08:00
parent 36ef0bf990
commit 572ea5a2f1
12 changed files with 365 additions and 53 deletions

View file

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

View file

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

4
package-lock.json generated
View file

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

View file

@ -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": {

View file

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

View file

@ -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);
}
// 保存視圖狀態

View file

@ -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);
}
}
// 顯示下一個媒體檔案

47
src/fileUtils.ts Normal file
View file

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

View file

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

View file

@ -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': 'フォルダを選択',

View file

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

View file

@ -1,3 +1,3 @@
{
"1.9.2": "1.1.0"
"1.9.3": "1.1.0"
}