fix: 修复异常

This commit is contained in:
Lemon695 2026-02-21 00:35:40 +08:00
parent ef92618025
commit 632fce6133
9 changed files with 389 additions and 424 deletions

View file

@ -217,7 +217,7 @@ Contributions are welcome! Here's how you can help:
## 📝 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
---

View file

@ -219,7 +219,7 @@ obsidian-note-image-gallery/
## 📝 许可证
本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。
本项目采用 GNU 通用公共许可证 v3.0 - 详见 [LICENSE](LICENSE) 文件。
---

View file

@ -14,7 +14,7 @@ export const translations = {
saveCacheIndexFailed: 'Failed to save cache index:',
// Settings
imageGallerySettings: 'Image Gallery Settings',
imageGallerySettings: 'Image Gallery settings',
enableCache: 'Enable image cache',
enableCacheDesc: 'Cache remote images to speed up loading',
cacheValidPeriod: 'Cache valid period',
@ -146,8 +146,10 @@ export const translations = {
type Locale = keyof typeof translations;
export function getLocale(): Locale {
const lang = window.localStorage.getItem('language') || 'en-GB';
return (lang in translations ? lang : 'en-GB') as Locale;
// Use Obsidian's official i18n API instead of reading from localStorage directly
const lang: string = (window as unknown as { i18next?: { language?: string } }).i18next?.language ?? 'en-GB';
if (lang && lang.startsWith('zh')) return 'zh';
return 'en-GB';
}
export function t(key: keyof typeof translations["en-GB"], params?: Record<string, string>): string {

View file

@ -23,7 +23,7 @@ export default class NoteImageGalleryPlugin extends Plugin {
log.setDebugMode(this.settings.debugMode);
this.imageExtractorService = new ImageExtractorService();
this.imageCacheService = new ImageCacheService(this.app);
this.imageCacheService = new ImageCacheService(this.app, this);
this.imageLoader = new ObsidianImageLoader(this.app, this);
await this.imageCacheService.initCache();
@ -101,7 +101,7 @@ export default class NoteImageGalleryPlugin extends Plugin {
if (!(file instanceof TFile) || file.extension !== 'md') {
return null;
}
return await this.app.vault.read(file);
return await this.app.vault.cachedRead(file);
}
onunload() {

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
import {App} from 'obsidian';
import {App, Plugin} from 'obsidian';
import {log} from "../utils/log-utils";
interface CachedImage {
@ -21,6 +21,7 @@ interface CacheIndex {
export class ImageCacheService {
private app: App;
private pluginDir: string;
private maxCacheAge = 7 * 24 * 60 * 60 * 1000; // 7天缓存过期时间
private maxCacheSize = 100 * 1024 * 1024; // 100MB最大缓存大小
private cacheIndex: CacheIndex = {};
@ -30,15 +31,16 @@ export class ImageCacheService {
private debounceSaveTimeout: number | null = null;
private get cacheDir(): string {
return `${this.app.vault.configDir}/plugins/note-image-gallery/cache`;
return `${this.pluginDir}/cache`;
}
private get indexFile(): string {
return `${this.app.vault.configDir}/plugins/note-image-gallery/cache/index.json`;
return `${this.pluginDir}/cache/index.json`;
}
constructor(app: App) {
constructor(app: App, plugin: Plugin) {
this.app = app;
this.pluginDir = plugin.manifest.dir!;
void this.loadCacheIndex();
}
@ -49,28 +51,21 @@ export class ImageCacheService {
try {
const adapter = this.app.vault.adapter;
// 检查并创建插件目录
const pluginDir = `${this.app.vault.configDir}/plugins/note-image-gallery`;
if (!(await adapter.exists(pluginDir))) {
log.debug(() => `创建插件目录: ${pluginDir}`);
await adapter.mkdir(pluginDir);
}
// 检查并创建缓存目录
if (!(await adapter.exists(this.cacheDir))) {
log.debug(() => `创建缓存目录: ${this.cacheDir}`);
log.debug(() => `Creating cache directory: ${this.cacheDir}`);
await adapter.mkdir(this.cacheDir);
}
// 验证目录是否已创建成功
if (!(await adapter.exists(this.cacheDir))) {
throw new Error(`无法验证缓存目录是否创建成功: ${this.cacheDir}`);
throw new Error(`Failed to verify cache directory was created: ${this.cacheDir}`);
}
log.debug(() => `缓存目录已确认: ${this.cacheDir}`);
log.debug(() => `Cache directory confirmed: ${this.cacheDir}`);
return true;
} catch (error) {
log.error(() => `确保缓存目录存在时出错:`, error instanceof Error ? error : undefined);
log.error(() => `Error ensuring cache directory exists:`, error instanceof Error ? error : undefined);
throw error; // 重新抛出错误以便调用者知道操作失败
}
}
@ -80,9 +75,9 @@ export class ImageCacheService {
await this.ensureCacheDir();
await this.loadCacheIndex();
await this.cleanupOrphanedFiles();
log.debug(() => `缓存服务初始化完成,总大小: ${Math.round(this.totalCacheSize / 1024 / 1024)}MB`);
log.debug(() => `Cache service initialized, total size: ${Math.round(this.totalCacheSize / 1024 / 1024)}MB`);
} catch (error) {
log.error(() => `缓存服务初始化失败:`, error instanceof Error ? error : undefined);
log.error(() => `Cache service initialization failed:`, error instanceof Error ? error : undefined);
// 确保基本的缓存功能可用
this.cacheIndex = {};
this.totalCacheSize = 0;
@ -109,7 +104,7 @@ export class ImageCacheService {
this.totalCacheSize += entry.size;
});
log.debug(() => `已加载缓存索引,共 ${Object.keys(this.cacheIndex).length} 项,总大小 ${Math.round(this.totalCacheSize / 1024 / 1024)}MB`);
log.debug(() => `Cache index loaded, ${Object.keys(this.cacheIndex).length} entries, total size ${Math.round(this.totalCacheSize / 1024 / 1024)}MB`);
// 检查缓存文件是否实际存在
await this.validateCacheFiles();
@ -117,7 +112,7 @@ export class ImageCacheService {
// 加载后清理过期缓存
await this.cleanCache();
} catch (e) {
log.error(() => `加载缓存索引失败:`, e instanceof Error ? e : undefined);
log.error(() => `Failed to load cache index:`, e instanceof Error ? e : undefined);
this.cacheIndex = {};
this.totalCacheSize = 0;
}
@ -143,7 +138,7 @@ export class ImageCacheService {
if (!exists) {
invalidUrls.push(url);
this.totalCacheSize -= entry.size;
log.debug(() => `缓存文件不存在,从索引中移除: ${url}`);
log.debug(() => `Cache file missing, removing from index: ${url}`);
}
}
@ -153,7 +148,7 @@ export class ImageCacheService {
});
if (invalidUrls.length > 0) {
log.debug(() => `移除了 ${invalidUrls.length} 个无效的缓存项`);
log.debug(() => `Removed ${invalidUrls.length} invalid cache entries`);
await this.saveCacheIndex();
}
}
@ -163,7 +158,7 @@ export class ImageCacheService {
*/
public async saveCacheIndex() {
if (this._isSaving) {
log.debug(() => `缓存索引正在保存中,跳过重复调用`);
log.debug(() => `Cache index save already in progress, skipping`);
return;
}
@ -171,7 +166,7 @@ export class ImageCacheService {
let retries = 0;
const maxRetries = 3;
log.debug(() => `开始保存缓存索引,条目数: ${Object.keys(this.cacheIndex).length}`);
log.debug(() => `Saving cache index, entries: ${Object.keys(this.cacheIndex).length}`);
while (retries < maxRetries) {
try {
@ -179,27 +174,27 @@ export class ImageCacheService {
await this.ensureCacheDir();
// 准备JSON数据
const jsonData = JSON.stringify(this.cacheIndex, null, 2); // 使用格式化的JSON便于检查
const jsonData = JSON.stringify(this.cacheIndex);
// 写入文件
const adapter = this.app.vault.adapter;
await adapter.write(this.indexFile, jsonData);
log.debug(() => `缓存索引保存成功,大小: ${Math.round(jsonData.length / 1024)}KB`);
log.debug(() => `Cache index saved, size: ${Math.round(jsonData.length / 1024)}KB`);
break;
} catch (e) {
retries++;
log.error(() => `保存缓存索引失败 (尝试 ${retries}/${maxRetries}):`, e instanceof Error ? e : undefined);
log.error(() => `Failed to save cache index (attempt ${retries}/${maxRetries}):`, e instanceof Error ? e : undefined);
if (retries >= maxRetries) {
log.error(() => `达到最大重试次数,无法保存缓存索引`);
log.error(() => `Max retries reached, unable to save cache index`);
break;
}
// 等待短暂时间后重试
const delay = 500 * retries;
log.debug(() => `${delay}ms后重试保存缓存索引`);
await new Promise(resolve => setTimeout(resolve, delay));
log.debug(() => `Retrying cache index save in ${delay}ms`);
await new Promise(resolve => window.setTimeout(resolve, delay));
}
}
@ -210,23 +205,23 @@ export class ImageCacheService {
*
*/
async cacheImage(url: string, data: ArrayBuffer, etag?: string, mimeType?: string): Promise<string> {
log.debug(() => `请求缓存图片: ${url}, 大小: ${Math.round(data.byteLength / 1024)}KB, 类型: ${mimeType || '未知'}`);
log.debug(() => `Caching image: ${url}, size: ${Math.round(data.byteLength / 1024)}KB, type: ${mimeType || 'unknown'}`);
if (!data || data.byteLength === 0) {
log.error(() => `跳过缓存图片 ${url}: 数据无效或为空`);
log.error(() => `Skipping cache for ${url}: invalid or empty data`);
return this.arrayBufferToBase64(data);
}
// 检查是否应该缓存此图片
if (!this.shouldCacheImage(url, mimeType)) {
log.debug(() => `跳过缓存图片 ${url}: 缓存条件不满足`);
log.debug(() => `Skipping cache for ${url}: cache conditions not met`);
return await this.arrayBufferToBase64(data);
}
// 检查数据大小,过大的图片不缓存
const MAX_SINGLE_IMAGE_SIZE = 20 * 1024 * 1024; // 20MB单图片上限
if (data.byteLength > MAX_SINGLE_IMAGE_SIZE) {
log.debug(() => `跳过缓存图片 ${url}: 图片过大 (${Math.round(data.byteLength / 1024)}KB)`);
log.debug(() => `Skipping cache for ${url}: image too large (${Math.round(data.byteLength / 1024)}KB)`);
return await this.arrayBufferToBase64(data);
}
@ -237,14 +232,14 @@ export class ImageCacheService {
// 确保缓存目录存在
const dirExists = await this.ensureCacheDir();
if (!dirExists) {
throw new Error("无法确保缓存目录存在");
throw new Error("Failed to ensure cache directory exists");
}
// 生成文件名
const filename = this.generateFilename(url);
const filePath = `${this.cacheDir}/${filename}`;
log.debug(() => `写入缓存文件: ${filePath}, 大小: ${Math.round(data.byteLength / 1024)}KB`);
log.debug(() => `Writing cache file: ${filePath}, size: ${Math.round(data.byteLength / 1024)}KB`);
const dataArray = new Uint8Array(data);
@ -253,7 +248,7 @@ export class ImageCacheService {
const fileExists = await this.app.vault.adapter.exists(filePath);
if (!fileExists) {
throw new Error(`缓存文件写入后验证失败: ${filePath}`);
throw new Error(`Cache file verification failed after write: ${filePath}`);
}
// 文件写入成功后再更新索引
@ -274,9 +269,9 @@ export class ImageCacheService {
// 保存索引
this.debouncedSaveCacheIndex(5000); // 使用已有的防抖方法5秒后保存
log.debug(() => `成功缓存图片: ${url}, 总缓存大小: ${Math.round(this.totalCacheSize / 1024 / 1024)}MB`);
log.debug(() => `Image cached: ${url}, total cache size: ${Math.round(this.totalCacheSize / 1024 / 1024)}MB`);
} catch (error) {
log.error(() => `缓存图片 ${url} 失败:`, error instanceof Error ? error : undefined);
log.error(() => `Failed to cache image ${url}:`, error instanceof Error ? error : undefined);
}
return base64Data;
@ -286,43 +281,43 @@ export class ImageCacheService {
*
*/
async getCachedImage(url: string): Promise<CachedImage | null> {
log.debug(() => `检查缓存: ${url}`);
log.debug(() => `Checking cache: ${url}`);
// 首先检查缓存是否启用
if (!this.shouldUseCache()) {
log.debug(() => `缓存未启用,跳过检查: ${url}`);
log.debug(() => `Cache disabled, skipping check: ${url}`);
return null;
}
const cacheEntry = this.cacheIndex[url];
if (!cacheEntry) {
log.debug(() => `缓存未命中: ${url}`);
log.debug(() => `Cache miss: ${url}`);
return null;
}
// 检查缓存是否过期
if (Date.now() - cacheEntry.timestamp > this.maxCacheAge) {
log.debug(() => `缓存已过期: ${url}, 时间: ${new Date(cacheEntry.timestamp).toLocaleString()}`);
log.debug(() => `Cache expired: ${url}, time: ${new Date(cacheEntry.timestamp).toLocaleString()}`);
await this.removeCacheEntry(url);
return null;
}
try {
const filePath = `${this.cacheDir}/${cacheEntry.filename}`;
log.debug(() => `读取缓存文件: ${filePath}`);
log.debug(() => `Reading cache file: ${filePath}`);
// 检查文件是否存在
const exists = await this.app.vault.adapter.exists(filePath);
if (!exists) {
log.warn(() => `缓存索引存在但文件不存在: ${filePath}`);
log.warn(() => `Cache index entry exists but file missing: ${filePath}`);
await this.removeCacheEntry(url);
return null;
}
// 读取缓存文件
const arrayBuffer = await this.app.vault.adapter.readBinary(filePath);
log.debug(() => `读取缓存成功: ${url}, 大小: ${Math.round(arrayBuffer.byteLength / 1024)}KB`);
log.debug(() => `Cache read success: ${url}, size: ${Math.round(arrayBuffer.byteLength / 1024)}KB`);
const base64Data = await this.arrayBufferToBase64(arrayBuffer);
@ -339,7 +334,7 @@ export class ImageCacheService {
etag: cacheEntry.etag
};
} catch (e) {
log.error(() => `读取缓存图片 ${url} 失败:`, e instanceof Error ? e : undefined);
log.error(() => `Failed to read cached image ${url}:`, e instanceof Error ? e : undefined);
await this.removeCacheEntry(url);
return null;
}
@ -348,12 +343,12 @@ export class ImageCacheService {
// 添加防抖保存索引方法
private debouncedSaveCacheIndex(delay = 2000): void {
if (this.debounceSaveTimeout) {
clearTimeout(this.debounceSaveTimeout);
window.clearTimeout(this.debounceSaveTimeout);
}
this.debounceSaveTimeout = window.setTimeout(() => {
this.saveCacheIndex().catch((e: unknown) => {
log.error(() => `延迟保存缓存索引失败:`, e instanceof Error ? e : undefined);
log.error(() => `Deferred cache index save failed:`, e instanceof Error ? e : undefined);
});
this.debounceSaveTimeout = null;
}, delay);
@ -364,7 +359,7 @@ export class ImageCacheService {
*/
cleanup(): void {
if (this.debounceSaveTimeout) {
clearTimeout(this.debounceSaveTimeout);
window.clearTimeout(this.debounceSaveTimeout);
this.debounceSaveTimeout = null;
}
}
@ -427,13 +422,13 @@ export class ImageCacheService {
// 计算需要释放的空间
let spaceToFree = this.totalCacheSize - (this.maxCacheSize * 0.8); // 释放到80%
log.debug(() => `缓存超过限制,需要释放 ${Math.round(spaceToFree / 1024 / 1024)}MB 空间, 根据优先级`);
log.debug(() => `Cache over limit, need to free ${Math.round(spaceToFree / 1024 / 1024)}MB based on priority`);
// 从低分开始删除,直到释放足够空间
for (const entry of validEntries) {
if (spaceToFree <= 0) break;
log.debug(() => `删除低优先级缓存: ${entry.url}, 分数: ${entry['score']}, 大小: ${Math.round(entry.size / 1024)}KB`);
log.debug(() => `Removing low-priority cache: ${entry.url}, score: ${entry['score']}, size: ${Math.round(entry.size / 1024)}KB`);
await this.removeCacheEntry(entry.url);
freedSpace += entry.size;
@ -443,7 +438,7 @@ export class ImageCacheService {
}
if (removedCount > 0) {
log.debug(() => `缓存清理完成: 删除了 ${removedCount} 项,释放了 ${Math.round(freedSpace / 1024 / 1024)}MB 空间`);
log.debug(() => `Cache cleanup done: removed ${removedCount} entries, freed ${Math.round(freedSpace / 1024 / 1024)}MB`);
await this.saveCacheIndex();
}
}
@ -470,7 +465,7 @@ export class ImageCacheService {
// 从索引中删除
delete this.cacheIndex[url];
} catch (e) {
log.error(() => `移除缓存项 ${url} 失败:`, e instanceof Error ? e : undefined);
log.error(() => `Failed to remove cache entry ${url}:`, e instanceof Error ? e : undefined);
}
}
@ -492,13 +487,13 @@ export class ImageCacheService {
*
*/
private hashString(str: string): string {
let hash = 0;
// FNV-1a 32-bit: better distribution than djb2, fewer collisions for URL-like strings
let hash = 2166136261;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 转换为32位整数
hash ^= str.charCodeAt(i);
hash = (hash * 16777619) >>> 0; // keep 32-bit unsigned
}
return Math.abs(hash).toString(16);
return hash.toString(16);
}
/**
@ -606,11 +601,11 @@ export class ImageCacheService {
if (this.shouldUseCacheCallback) {
const enabled = this.shouldUseCacheCallback();
if (!enabled) {
log.debug(() => `缓存已禁用(通过回调函数)`);
log.debug(() => `Cache disabled (via callback)`);
}
return enabled;
}
log.debug(() => `缓存已启用(默认值)`);
log.debug(() => `Cache enabled (default)`);
return true; // 默认启用缓存
}
@ -663,9 +658,9 @@ export class ImageCacheService {
// 保存空索引
await this.saveCacheIndex();
log.debug(() => `所有缓存已清除`);
log.debug(() => `All cache cleared`);
} catch (e) {
log.error(() => `清除所有缓存失败:`, e instanceof Error ? e : undefined);
log.error(() => `Failed to clear all cache:`, e instanceof Error ? e : undefined);
}
}
@ -702,17 +697,17 @@ export class ImageCacheService {
const filePath = `${this.cacheDir}/${filename}`;
try {
await adapter.remove(filePath);
log.debug(() => `已删除孤立的缓存文件: ${filename}`);
log.debug(() => `Deleted orphaned cache file: ${filename}`);
} catch (e) {
log.error(() => `删除孤立的缓存文件失败: ${filename}`, e instanceof Error ? e : undefined);
log.error(() => `Failed to delete orphaned cache file: ${filename}`, e instanceof Error ? e : undefined);
}
}
if (orphanedFiles.length > 0) {
log.debug(() => `共删除了 ${orphanedFiles.length} 个孤立的缓存文件`);
log.debug(() => `Deleted ${orphanedFiles.length} orphaned cache files`);
}
} catch (e) {
log.error(() => `清理孤立文件失败:`, e instanceof Error ? e : undefined);
log.error(() => `Failed to clean up orphaned files:`, e instanceof Error ? e : undefined);
}
}
@ -740,7 +735,7 @@ export class ImageCacheService {
return cacheFiles.map(file => file.name);
} catch (e) {
log.error(() => `列出缓存目录文件失败:`, e instanceof Error ? e : undefined);
log.error(() => `Failed to list cache directory files:`, e instanceof Error ? e : undefined);
return [];
}
}

View file

@ -53,7 +53,7 @@ export class ObsidianImageLoader {
return false;
} catch (error) {
log.error(() => `通过Obsidian API加载本地图片失败: ${imagePath}`, error instanceof Error ? error : undefined);
log.error(() => `Failed to load local image via Obsidian API: ${imagePath}`, error instanceof Error ? error : undefined);
return false;
}
}
@ -103,8 +103,8 @@ export class ObsidianImageLoader {
return [...new Set(result)];
} catch (error) {
log.error(() => `生成替代路径时出错:`, error instanceof Error ? error : undefined);
return [originalPath]; // 出错时返回原始路径
log.error(() => `Error generating alternative paths:`, error instanceof Error ? error : undefined);
return [originalPath]; // return original path on error
}
}
@ -158,7 +158,7 @@ export class ObsidianImageLoader {
img.src = objectUrl;
try {
log.debug(() => `尝试缓存网络图片: ${imageUrl}, 内容类型: ${contentType}`);
log.debug(() => `Caching network image: ${imageUrl}, content-type: ${contentType}`);
await this.plugin.imageCacheService.cacheImage(
imageUrl,
@ -167,12 +167,12 @@ export class ObsidianImageLoader {
contentType
);
} catch (error) {
log.error(() => `缓存图片失败: ${imageUrl}`, error instanceof Error ? error : undefined);
log.error(() => `Failed to cache image: ${imageUrl}`, error instanceof Error ? error : undefined);
}
return true;
} catch (error) {
log.error(() => `通过Obsidian API加载网络图片失败: ${imageUrl}`, error instanceof Error ? error : undefined);
log.error(() => `Failed to load network image via Obsidian API: ${imageUrl}`, error instanceof Error ? error : undefined);
return false;
}
}

View file

@ -1,7 +1,7 @@
import {log} from "./log-utils";
/**
*
* Retry handler
*/
export class RetryHandler {
constructor(
@ -24,12 +24,12 @@ export class RetryHandler {
lastError = error as Error;
if (attempt < this.maxRetries) {
log.debug(() => `${context || '操作'} 失败,重试 ${attempt + 1}/${this.maxRetries}`);
log.debug(() => `${context || 'Operation'} failed, retrying ${attempt + 1}/${this.maxRetries}`);
this.onRetry?.(attempt + 1);
// 添加指数退避延迟,避免立即重试
await new Promise(resolve => setTimeout(resolve, Math.min(1000 * Math.pow(2, attempt), 5000)));
// exponential backoff to avoid immediate retry
await new Promise(resolve => window.setTimeout(resolve, Math.min(1000 * Math.pow(2, attempt), 5000)));
} else {
log.error(() => `${context || '操作'} 达到最大重试次数`, error as Error);
log.error(() => `${context || 'Operation'} reached max retries`, error as Error);
this.onFinalFailure?.(error as Error);
}
}

View file

@ -1,37 +1,44 @@
.modal:has(.current-note-image-gallery) {
width: 90vw !important;
height: 90vh !important;
max-width: 90vw !important;
max-height: 90vh !important;
/* Plugin: note-image-gallery — all classes prefixed with nig- */
.modal:has(.nig-gallery) {
--dialog-width: 90vw;
--dialog-max-width: 90vw;
--dialog-max-height: 90vh;
width: 90vw;
height: 90vh;
max-width: 90vw;
max-height: 90vh;
overflow: hidden;
padding: 0;
}
.modal:has(.current-note-image-gallery) > .modal-close-button {
top: 12px !important;
right: 12px !important;
.modal:has(.nig-gallery) > .modal-close-button {
top: 12px;
right: 12px;
z-index: 10;
}
.modal-content.current-note-image-gallery {
width: 100% !important;
height: 100% !important;
display: flex !important;
flex-direction: column !important;
padding: 0 !important;
.modal-content.nig-gallery {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 0;
}
.current-note-image-gallery {
width: 90vw !important;
height: 90vh !important;
max-width: 90vw !important;
max-height: 90vh !important;
.nig-gallery {
width: 90vw;
height: 90vh;
max-width: 90vw;
max-height: 90vh;
border-radius: 12px;
overflow: hidden;
display: flex !important;
flex-direction: column !important;
padding: 0 !important;
display: flex;
flex-direction: column;
padding: 0;
}
.modal-toolbar {
.nig-toolbar {
display: flex;
justify-content: flex-start;
align-items: center;
@ -41,13 +48,13 @@
position: relative;
}
.modal-title {
.nig-title {
font-size: 16px;
font-weight: 500;
margin-right: 15px;
}
.image-wall-container {
.nig-image-wall-container {
flex: 1;
overflow-y: auto;
width: 100%;
@ -55,16 +62,16 @@
}
/* 优化瀑布流列数 */
.image-wall.waterfall {
.nig-image-wall.nig-waterfall {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)) !important;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-auto-rows: 20px;
gap: 16px;
padding: 16px;
}
/* 图片项样式 */
.image-item {
.nig-image-item {
position: relative;
width: 100%;
cursor: pointer;
@ -72,21 +79,21 @@
background: var(--background-secondary);
border-radius: 8px;
overflow: hidden;
display: flex; /* 改用 flex 布局使内容居中 */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.image-item:hover {
.nig-image-item:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1;
}
.image-item img {
.nig-image-item img {
width: 100%;
height: auto; /* 让高度自适应,不要强制 100% */
height: auto;
object-fit: cover;
border-radius: 8px;
display: block;
@ -95,7 +102,7 @@
}
/* 大图查看遮罩 */
.lightbox-overlay {
.nig-lightbox-overlay {
position: fixed;
top: 0;
left: 0;
@ -108,13 +115,13 @@
z-index: 1000;
}
.lightbox-overlay img {
.nig-lightbox-overlay img {
max-width: 90%;
max-height: 90vh;
object-fit: contain;
}
.lightbox-close {
.nig-lightbox-close {
position: fixed;
top: 20px;
right: 20px;
@ -132,7 +139,7 @@
}
/* 加载状态 */
.loading-text {
.nig-loading-text {
position: absolute;
top: 50%;
left: 50%;
@ -144,7 +151,7 @@
}
/* 错误状态 */
.image-item.error {
.nig-image-item.nig-error {
padding: 20px;
text-align: center;
color: var(--text-error);
@ -157,45 +164,45 @@
/* 响应式布局 */
@media (max-width: 768px) {
.image-wall.waterfall {
.nig-image-wall.nig-waterfall {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-auto-rows: 15px;
gap: 8px;
}
.current-note-image-gallery {
width: 95vw !important;
height: 95vh !important;
.nig-gallery {
width: 95vw;
height: 95vh;
}
.filter-toolbar {
.nig-filter-toolbar {
padding-right: 40px;
}
.filter-container, .sort-container {
.nig-filter-container, .nig-sort-container {
gap: 4px;
}
.filter-btn, .sort-select {
.nig-filter-btn, .nig-sort-select {
padding: 2px 6px;
font-size: 11px;
}
}
@media (max-width: 600px) {
.modal-toolbar {
.nig-toolbar {
flex-wrap: wrap;
padding: 8px 12px;
}
.filter-toolbar {
.nig-filter-toolbar {
margin-left: 0;
margin-top: 8px;
width: 100%;
}
}
.image-context-menu {
.nig-context-menu {
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -204,18 +211,18 @@
padding: 4px 0;
}
.image-context-menu .menu-item {
.nig-context-menu .nig-menu-item {
padding: 6px 12px;
cursor: pointer;
color: var(--text-normal);
}
.image-context-menu .menu-item:hover {
.nig-context-menu .nig-menu-item:hover {
background-color: var(--background-modifier-hover);
}
/* 图片容器样式 */
.lightbox-image-container {
.nig-lightbox-image-container {
position: relative;
max-width: 90%;
max-height: 90vh;
@ -224,14 +231,14 @@
align-items: center;
}
.lightbox-image-container img {
.nig-lightbox-image-container img {
max-width: 100%;
max-height: 90vh;
object-fit: contain;
}
/* 导航按钮样式 */
.lightbox-nav {
.nig-lightbox-nav {
position: fixed;
top: 50%;
transform: translateY(-50%);
@ -248,20 +255,20 @@
z-index: 1001;
}
.lightbox-nav:hover {
.nig-lightbox-nav:hover {
background: rgba(0, 0, 0, 0.8);
}
.lightbox-nav.prev {
.nig-lightbox-nav.prev {
left: 20px;
}
.lightbox-nav.next {
.nig-lightbox-nav.next {
right: 20px;
}
/* 图片计数器样式 */
.lightbox-counter {
.nig-lightbox-counter {
position: fixed;
bottom: 20px;
left: 50%;
@ -275,60 +282,60 @@
}
/* 进度条样式 */
.progress-container {
.nig-progress-container {
flex: 0 1 300px;
margin: 0 20px 0 0;
display: flex;
align-items: center;
overflow: hidden; /* 防止过渡期间内容溢出 */
overflow: hidden;
transition: opacity 0.5s ease, height 0.4s ease, margin 0.4s ease;
height: 20px; /* 固定一致的高度 */
height: 20px;
}
.progress-container.complete {
.nig-progress-container.nig-complete {
opacity: 0;
height: 0;
margin: 0;
pointer-events: none;
}
.progress-container progress {
.nig-progress-container progress {
width: 100%;
height: 4px; /* 更细的进度条 */
height: 4px;
border-radius: 2px;
overflow: hidden;
appearance: none; /* 去除默认样式 */
appearance: none;
border: none;
background-color: rgba(0, 0, 0, 0.1); /* 浅色背景 */
background-color: rgba(0, 0, 0, 0.1);
}
/* 进度条填充样式 */
.progress-container progress::-webkit-progress-value {
background-color: var(--interactive-accent, #1a73e8); /* 谷歌蓝色 */
transition: width 0.3s ease; /* 平滑过渡 */
.nig-progress-container progress::-webkit-progress-value {
background-color: var(--interactive-accent, #1a73e8);
transition: width 0.3s ease;
}
.progress-container progress::-moz-progress-bar {
.nig-progress-container progress::-moz-progress-bar {
background-color: var(--interactive-accent, #1a73e8);
transition: width 0.3s ease;
}
/* 进度指示器文本 */
.progress-text {
.nig-progress-text {
font-size: 12px;
color: var(--text-muted);
margin-left: 8px;
min-width: 50px; /* 固定宽度避免跳动 */
min-width: 50px;
text-align: right;
}
/* 在暗色主题中适应 */
.theme-dark .progress-container progress {
.theme-dark .nig-progress-container progress {
background-color: rgba(255, 255, 255, 0.1);
}
/* 图片占位符样式 */
.image-placeholder {
.nig-image-placeholder {
position: absolute;
top: 0;
left: 0;
@ -338,17 +345,18 @@
align-items: center;
justify-content: center;
background: var(--background-secondary);
z-index: 1; /* 确保在图片加载前显示在上层 */
z-index: 1;
}
/* 确保加载完成的图片显示在最上层 */
.image-item img[style*="opacity: 1"] {
.nig-image-item img.nig-loaded {
opacity: 1;
z-index: 2;
position: relative;
}
/* 缩放控制样式 */
.lightbox-controls {
.nig-lightbox-controls {
position: fixed;
bottom: 60px;
left: 50%;
@ -360,7 +368,7 @@
gap: 12px;
}
.zoom-button {
.nig-zoom-button {
width: 32px;
height: 32px;
display: flex;
@ -373,26 +381,26 @@
font-size: 18px;
}
.zoom-button:hover {
.nig-zoom-button:hover {
background: rgba(255, 255, 255, 0.3);
}
/* 筛选和排序工具栏 */
.filter-toolbar {
.nig-filter-toolbar {
display: flex;
align-items: center;
gap: 16px;
margin-left: auto; /* 推到右侧 */
padding-right: 50px; /* 为默认关闭按钮留出空间 */
margin-left: auto;
padding-right: 50px;
}
.filter-container, .sort-container {
.nig-filter-container, .nig-sort-container {
display: flex;
align-items: center;
gap: 8px;
}
.filter-btn {
.nig-filter-btn {
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -402,17 +410,17 @@
transition: all 0.2s ease;
}
.filter-btn:hover {
.nig-filter-btn:hover {
background: var(--background-secondary);
}
.filter-btn.active {
.nig-filter-btn.nig-active {
background: var(--interactive-accent);
color: var(--text-on-accent);
border-color: var(--interactive-accent);
}
.sort-select {
.nig-sort-select {
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -420,18 +428,3 @@
font-size: 12px;
cursor: pointer;
}
.image-item img[style*="opacity: 1"] {
opacity: 1 !important;
z-index: 2;
position: relative;
}
/* 确保已加载图片保持可见状态 */
.image-item img[style*="opacity: 1"],
.image-item img.loaded,
.image-item img[complete="true"] {
opacity: 1 !important;
z-index: 2 !important;
position: relative !important;
}