mirror of
https://github.com/lemon695/obsidian-note-image-gallery.git
synced 2026-07-22 06:41:53 +00:00
Harden the release branch for Obsidian market submission
Constraint: Keep the plugin release-safe without introducing new dependencies Rejected: Commit the branch with mismatched version metadata and missing release verifier | that would leave the release flow broken Confidence: high Scope-risk: moderate Directive: Keep package, manifest, and versions metadata aligned before cutting future release tags Tested: npm run verify:release; npm run lint; npm run build Not-tested: Real Obsidian desktop and mobile smoke tests on this branch
This commit is contained in:
parent
85f17e8a64
commit
fe9dda8898
12 changed files with 166 additions and 331 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -25,3 +25,4 @@ data.json
|
|||
/build.sh
|
||||
/lint-result.txt
|
||||
/docs/
|
||||
/.omx/
|
||||
|
|
|
|||
19
README.md
19
README.md
|
|
@ -84,7 +84,7 @@
|
|||
|
||||
There are three ways to open the image gallery:
|
||||
|
||||
1. **Command Palette**: Press `Ctrl/Cmd + P` and search for "Open Current Note Image Gallery"
|
||||
1. **Command Palette**: Press `Ctrl/Cmd + P` and search for "Open gallery for current note"
|
||||
2. **Ribbon Icon**: Click the image gallery icon in the left sidebar
|
||||
3. **Hotkey**: Assign a custom hotkey in Settings → Hotkeys
|
||||
|
||||
|
|
@ -136,6 +136,16 @@ Access plugin settings via Settings → Note Image Gallery
|
|||
|
||||
---
|
||||
|
||||
## 🔒 Privacy and disclosures
|
||||
|
||||
- **No account required**: The plugin works without signing in to any service.
|
||||
- **No telemetry or ads**: The plugin doesn't collect analytics or show advertisements.
|
||||
- **Network use**: The plugin may send network requests only when your current note contains remote image URLs and you choose to open the gallery.
|
||||
- **External file access**: The plugin reads images referenced by the current note and stores remote-image cache files inside the plugin data directory.
|
||||
- **Open source**: The source code is available in this repository.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Performance Tips
|
||||
|
||||
1. **Enable Caching**: Keep image caching enabled for faster loading of remote images
|
||||
|
|
@ -156,6 +166,9 @@ npm install
|
|||
# Build for development (with watch mode)
|
||||
npm run dev
|
||||
|
||||
# Run lint
|
||||
npm run lint
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
|
|
@ -169,7 +182,9 @@ npm run build
|
|||
obsidian-note-image-gallery/
|
||||
├── src/
|
||||
│ ├── main.ts # Plugin entry point
|
||||
│ ├── settings.ts # Settings tab and configuration
|
||||
│ ├── settings.ts # Settings tab and configuration
|
||||
│ ├── i18n/
|
||||
│ │ └── locale.ts # Internationalization strings
|
||||
│ ├── service/
|
||||
│ │ ├── current-note-image-gallery-service.ts # Main gallery service
|
||||
│ │ ├── image-cache-service.ts # Cache management
|
||||
|
|
|
|||
17
README_zh.md
17
README_zh.md
|
|
@ -84,7 +84,7 @@
|
|||
|
||||
有三种方式打开图片墙:
|
||||
|
||||
1. **命令面板**:按 `Ctrl/Cmd + P` 并搜索"当前文件"或"Current file"
|
||||
1. **命令面板**:按 `Ctrl/Cmd + P` 并搜索“打开当前笔记图片墙”或 “Open gallery for current note”
|
||||
2. **侧边栏图标**:点击左侧边栏中的图片墙图标
|
||||
3. **快捷键**:在设置 → 快捷键中自定义快捷键
|
||||
|
||||
|
|
@ -136,6 +136,16 @@
|
|||
|
||||
---
|
||||
|
||||
## 🔒 隐私与披露
|
||||
|
||||
- **无需账号**:插件不需要登录任何服务即可使用。
|
||||
- **无遥测、无广告**:插件不会收集分析数据,也不会展示广告。
|
||||
- **网络访问**:仅当当前笔记包含远程图片 URL 且你主动打开图片墙时,插件才会请求这些远程图片。
|
||||
- **外部文件访问**:插件会读取当前笔记引用的图片,并把远程图片缓存写入插件数据目录。
|
||||
- **开源**:源码已在本仓库公开。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 性能优化建议
|
||||
|
||||
1. **启用缓存**:保持图片缓存开启以加快远程图片加载速度
|
||||
|
|
@ -156,6 +166,9 @@ npm install
|
|||
# 开发构建(带监听模式)
|
||||
npm run dev
|
||||
|
||||
# 运行 Lint
|
||||
npm run lint
|
||||
|
||||
# 生产构建
|
||||
npm run build
|
||||
|
||||
|
|
@ -169,7 +182,7 @@ npm run build
|
|||
obsidian-note-image-gallery/
|
||||
├── src/
|
||||
│ ├── main.ts # 插件入口
|
||||
│ ├── settings.ts # 设置选项卡和配置
|
||||
│ ├── settings.ts # 设置选项卡和配置
|
||||
│ ├── i18n/
|
||||
│ │ └── locale.ts # 国际化翻译
|
||||
│ ├── service/
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "note-image-gallery",
|
||||
"name": "Note Image Gallery",
|
||||
"version": "1.0.11",
|
||||
"version": "1.1.0",
|
||||
"minAppVersion": "1.9.14",
|
||||
"description": "Display all images from your note in a gallery view.",
|
||||
"author": "Lemon695",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"name": "obsidian-note-image-gallery",
|
||||
"version": "1.0.10",
|
||||
"version": "1.1.0",
|
||||
"description": "An Obsidian plugin to display note's images in a gallery view",
|
||||
"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",
|
||||
"verify:release": "node scripts/verify-release.mjs",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
|
|
@ -19,7 +20,7 @@
|
|||
"note"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"license": "GPL-3.0-only",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.39.1",
|
||||
|
|
|
|||
111
scripts/verify-release.mjs
Normal file
111
scripts/verify-release.mjs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/* eslint-env node */
|
||||
/* global console, process */
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
|
||||
function readJson(path) {
|
||||
return JSON.parse(readFileSync(path, "utf8"));
|
||||
}
|
||||
|
||||
function readText(path) {
|
||||
return readFileSync(path, "utf8");
|
||||
}
|
||||
|
||||
const packageJson = readJson("package.json");
|
||||
const manifest = readJson("manifest.json");
|
||||
const versions = readJson("versions.json");
|
||||
const readme = readText("README.md");
|
||||
const readmeZh = readText("README_zh.md");
|
||||
const license = readText("LICENSE");
|
||||
|
||||
const checks = [];
|
||||
|
||||
function check(ok, label, detail = "") {
|
||||
checks.push({ ok, label, detail });
|
||||
}
|
||||
|
||||
function includesAll(text, snippets) {
|
||||
return snippets.every((snippet) => text.includes(snippet));
|
||||
}
|
||||
|
||||
check(existsSync("README.md"), "README.md exists");
|
||||
check(existsSync("README_zh.md"), "README_zh.md exists");
|
||||
check(existsSync("LICENSE"), "LICENSE exists");
|
||||
check(existsSync("manifest.json"), "manifest.json exists");
|
||||
check(existsSync("package-lock.json"), "package-lock.json exists");
|
||||
|
||||
check(
|
||||
packageJson.version === manifest.version,
|
||||
"package.json version matches manifest.json",
|
||||
`${packageJson.version} vs ${manifest.version}`
|
||||
);
|
||||
|
||||
check(
|
||||
versions[manifest.version] === manifest.minAppVersion,
|
||||
"versions.json contains current release mapping",
|
||||
`${manifest.version} -> ${versions[manifest.version] ?? "missing"}`
|
||||
);
|
||||
|
||||
check(
|
||||
/^\d+\.\d+\.\d+$/.test(manifest.version),
|
||||
"manifest version uses x.y.z format",
|
||||
manifest.version
|
||||
);
|
||||
|
||||
check(
|
||||
/^[a-z-]+$/.test(manifest.id) && !manifest.id.endsWith("plugin") && !manifest.id.includes("obsidian"),
|
||||
"manifest id satisfies Obsidian rules",
|
||||
manifest.id
|
||||
);
|
||||
|
||||
check(
|
||||
packageJson.license === "GPL-3.0-only",
|
||||
"package.json license is aligned",
|
||||
packageJson.license
|
||||
);
|
||||
|
||||
check(
|
||||
license.includes("GNU GENERAL PUBLIC LICENSE"),
|
||||
"LICENSE content looks like GPL"
|
||||
);
|
||||
|
||||
check(
|
||||
includesAll(readme, [
|
||||
"## 🔒 Privacy and disclosures",
|
||||
"No account required",
|
||||
"No telemetry or ads",
|
||||
"Network use",
|
||||
"External file access"
|
||||
]),
|
||||
"README.md contains disclosures"
|
||||
);
|
||||
|
||||
check(
|
||||
includesAll(readmeZh, [
|
||||
"## 🔒 隐私与披露",
|
||||
"无需账号",
|
||||
"无遥测、无广告",
|
||||
"网络访问",
|
||||
"外部文件访问"
|
||||
]),
|
||||
"README_zh.md contains disclosures"
|
||||
);
|
||||
|
||||
check(
|
||||
!existsSync("main.js"),
|
||||
"main.js is not committed in repo root"
|
||||
);
|
||||
|
||||
const failed = checks.filter((item) => !item.ok);
|
||||
|
||||
for (const item of checks) {
|
||||
const icon = item.ok ? "✅" : "❌";
|
||||
const suffix = item.detail ? ` — ${item.detail}` : "";
|
||||
console.log(`${icon} ${item.label}${suffix}`);
|
||||
}
|
||||
|
||||
if (failed.length > 0) {
|
||||
console.error(`\nRelease verification failed: ${failed.length} issue(s) found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("\nRelease verification passed.");
|
||||
|
|
@ -3,12 +3,12 @@ import { getLanguage } from 'obsidian';
|
|||
export const translations = {
|
||||
'en-GB': {
|
||||
// Command names
|
||||
currentFile: 'Current file',
|
||||
currentFile: 'Open gallery for current note',
|
||||
clearImageCache: 'Clear image cache',
|
||||
|
||||
// Main plugin
|
||||
loadingPlugin: 'Loading plugin v',
|
||||
openGallery: 'Open gallery',
|
||||
openGallery: 'Open image gallery',
|
||||
imageCacheCleared: 'Image cache cleared',
|
||||
noImagesFound: 'No images found in current note',
|
||||
errorOpeningGallery: 'Error opening image gallery',
|
||||
|
|
@ -25,7 +25,7 @@ export const translations = {
|
|||
cacheSizeZeroOrInvalid: 'Cache size is 0 or invalid',
|
||||
getCacheSizeFailed: 'Failed to get cache size:',
|
||||
unableToGetCacheSize: 'Unable to get cache size, please try to reinitialize cache',
|
||||
cacheStatus: 'Cache Status',
|
||||
cacheStatus: 'Cache status',
|
||||
currentCacheSize: 'Current cache size: {size} MB / {maxSize} MB',
|
||||
maxCacheSize: 'Maximum cache size',
|
||||
maxCacheSizeDesc: 'Maximum size for image cache: {size} MB',
|
||||
|
|
@ -35,7 +35,7 @@ export const translations = {
|
|||
clearCache: 'Clear cache',
|
||||
clearCacheDesc: 'Delete all cached images',
|
||||
clearAllCache: 'Clear all cache',
|
||||
developer: 'Developer',
|
||||
developer: 'Developer tools',
|
||||
debugMode: 'Debug mode',
|
||||
debugModeDesc: 'Enable debug mode to log detailed information to the console.',
|
||||
debugModeStatus: 'Debug mode {status}',
|
||||
|
|
@ -73,12 +73,12 @@ export const translations = {
|
|||
},
|
||||
'zh': {
|
||||
// Command names
|
||||
currentFile: '当前文件',
|
||||
currentFile: '打开当前笔记图片墙',
|
||||
clearImageCache: '清除图片缓存',
|
||||
|
||||
// Main plugin
|
||||
loadingPlugin: '加载插件 v',
|
||||
openGallery: '打开图片墙',
|
||||
openGallery: '打开图片画廊',
|
||||
imageCacheCleared: '图片缓存已清除',
|
||||
noImagesFound: '当前笔记中未找到图片',
|
||||
errorOpeningGallery: '打开图片墙时出错',
|
||||
|
|
@ -105,7 +105,7 @@ export const translations = {
|
|||
clearCache: '清除缓存',
|
||||
clearCacheDesc: '删除所有缓存的图片',
|
||||
clearAllCache: '清除全部缓存',
|
||||
developer: '开发者',
|
||||
developer: '开发者工具',
|
||||
debugMode: '调试模式',
|
||||
debugModeDesc: '启用调试模式以在控制台中记录详细信息。',
|
||||
debugModeStatus: '调试模式已{status}',
|
||||
|
|
@ -162,15 +162,3 @@ export function t(key: keyof typeof translations["en-GB"], params?: Record<strin
|
|||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调试函数:打印当前语言设置
|
||||
*/
|
||||
export function debugLocale(): void {
|
||||
console.debug('=== Locale Debug Info ===');
|
||||
console.debug('Current locale:', getLocale());
|
||||
console.debug('localStorage language:', window.localStorage.getItem('language'));
|
||||
console.debug('navigator.language:', navigator.language);
|
||||
console.debug('moment locale:', (window as unknown as Record<string, unknown>).moment);
|
||||
console.debug('========================');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,6 @@ import {RetryHandler} from "../utils/retry-handler";
|
|||
import {ResourceManager} from "../utils/resource-manager";
|
||||
import {t} from "../i18n/locale";
|
||||
|
||||
// 定义Electron请求接口(用于向后兼容)
|
||||
interface ElectronRequest {
|
||||
abort(): void;
|
||||
}
|
||||
|
||||
interface ImageRequest {
|
||||
controller?: AbortController;
|
||||
electronRequest?: ElectronRequest;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface ImageData {
|
||||
path: string;
|
||||
element: HTMLElement;
|
||||
|
|
@ -40,7 +29,6 @@ export class CurrentNoteImageGalleryService extends Modal {
|
|||
private images: string[] = [];
|
||||
private loadedImages = 0;
|
||||
private totalImages = 0;
|
||||
private currentRequests: Map<string, ImageRequest> = new Map();
|
||||
private imageDataMap: Map<string, ImageData> = new Map();
|
||||
private queueImageLoad: (imagePath: string, isVisible?: boolean) => void = () => {
|
||||
};
|
||||
|
|
@ -69,7 +57,6 @@ export class CurrentNoteImageGalleryService extends Modal {
|
|||
|
||||
onOpen() {
|
||||
this.loadedImages = 0;
|
||||
this.currentRequests.clear();
|
||||
this.imageDataMap.clear();
|
||||
|
||||
const {contentEl} = this;
|
||||
|
|
@ -521,40 +508,6 @@ export class CurrentNoteImageGalleryService extends Modal {
|
|||
}
|
||||
}
|
||||
|
||||
private createImageElement(imagePath: string, imageWall: HTMLElement) {
|
||||
const imageDiv = imageWall.createDiv('nig-image-item');
|
||||
imageDiv.setAttribute('data-path', imagePath);
|
||||
|
||||
// 存储图片元素引用
|
||||
this.imageDataMap.set(imagePath, {
|
||||
path: imagePath,
|
||||
element: imageDiv,
|
||||
isLoading: false,
|
||||
hasError: false
|
||||
});
|
||||
|
||||
// 监听此元素以实现懒加载
|
||||
this.intersectionObserver?.observe(imageDiv);
|
||||
|
||||
// 添加点击事件用于查看大图
|
||||
imageDiv.addEventListener('click', (e) => {
|
||||
// 阻止事件冒泡,避免与其他插件(如 Image Toolkit)冲突
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const currentIndex = this.images.indexOf(imagePath);
|
||||
this.createLightboxWithNavigation(currentIndex);
|
||||
});
|
||||
|
||||
imageDiv.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
const img = imageDiv.querySelector('img');
|
||||
if (img) {
|
||||
this.createContextMenu(e, img);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分批渲染图片元素,避免阻塞主线程
|
||||
*/
|
||||
|
|
@ -1323,185 +1276,6 @@ export class CurrentNoteImageGalleryService extends Modal {
|
|||
}
|
||||
}
|
||||
|
||||
private async loadWeiboImage(
|
||||
imagePath: string,
|
||||
img: HTMLImageElement,
|
||||
imageDiv: HTMLElement,
|
||||
loadingText: HTMLElement,
|
||||
retryCount = 0
|
||||
): Promise<void> {
|
||||
try {
|
||||
// 异步获取缓存
|
||||
const cachedImage = await this.plugin.imageCacheService.getCachedImage(imagePath);
|
||||
|
||||
if (cachedImage) {
|
||||
log.debug(() => `Loading Weibo image from cache: ${imagePath}`);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
img.onload = () => {
|
||||
this.handleImageLoadSuccess(img, imageDiv, loadingText, imagePath);
|
||||
resolve();
|
||||
};
|
||||
|
||||
img.onerror = (e) => {
|
||||
const error = e instanceof Error ? e : new Error('Cached Weibo image load error');
|
||||
log.error(() => `Cached Weibo image load error`, error);
|
||||
reject(error);
|
||||
};
|
||||
|
||||
// 设置图片源为缓存的base64数据
|
||||
img.src = cachedImage.data;
|
||||
}).catch(async (e) => {
|
||||
await this.loadWeiboImageWithoutCache(imagePath, img, imageDiv, loadingText, retryCount, () => {}, () => {});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.loadWeiboImageWithoutCache(imagePath, img, imageDiv, loadingText, retryCount, () => {}, () => {});
|
||||
} catch (error) {
|
||||
const currentRequestId = imageDiv.getAttribute('data-request-id');
|
||||
this.handleError(error instanceof Error ? error : new Error(String(error)), imageDiv, currentRequestId || undefined, retryCount);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadWeiboImageWithoutCache(
|
||||
imagePath: string,
|
||||
img: HTMLImageElement,
|
||||
imageDiv: HTMLElement,
|
||||
loadingText: HTMLElement,
|
||||
retryCount: number,
|
||||
resolve: () => void,
|
||||
reject: (error: unknown) => void
|
||||
): Promise<void> {
|
||||
// 使用 Obsidian 的 requestUrl API 代替 electron.remote
|
||||
const MAX_RETRIES = 3;
|
||||
|
||||
try {
|
||||
log.debug(() => `Loading Weibo image via requestUrl (no cache): ${imagePath}`);
|
||||
|
||||
const response = await requestUrl({
|
||||
url: imagePath,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Referer': 'https://weibo.com/',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const arrayBuffer = response.arrayBuffer;
|
||||
const contentType = response.headers['content-type'] || 'image/jpeg';
|
||||
const blob = new Blob([arrayBuffer], {type: contentType});
|
||||
|
||||
// 使用 ResourceManager 管理 objectURL
|
||||
const objectUrl = this.resourceManager.createObjectURL(imagePath, blob);
|
||||
|
||||
img.onload = () => {
|
||||
if (loadingText && loadingText.parentNode) {
|
||||
loadingText.remove();
|
||||
}
|
||||
|
||||
// 移除占位符
|
||||
const placeholder = imageDiv.querySelector('.nig-image-placeholder');
|
||||
if (placeholder) {
|
||||
placeholder.remove();
|
||||
}
|
||||
|
||||
const ratio = img.naturalHeight / img.naturalWidth;
|
||||
const baseHeight = 10;
|
||||
const heightSpan = Math.min(Math.ceil(ratio * baseHeight), 30);
|
||||
|
||||
imageDiv.setCssStyles({ gridRowEnd: `span ${heightSpan}` });
|
||||
img.setCssStyles({ opacity: '1' });
|
||||
this.loadedImages++;
|
||||
this.updateProgressBar();
|
||||
|
||||
const imageData = this.imageDataMap.get(imagePath);
|
||||
if (imageData) {
|
||||
imageData.isLoading = false;
|
||||
}
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
img.onerror = async () => {
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
log.debug(() => `Retrying Weibo image load (${retryCount + 1}/${MAX_RETRIES}): ${imagePath}`);
|
||||
await this.loadWeiboImage(imagePath, img, imageDiv, loadingText, retryCount + 1);
|
||||
resolve();
|
||||
} else {
|
||||
this.handleImageLoadFailure(imageDiv, t('loadingFailed'));
|
||||
reject(new Error('Max retries reached'));
|
||||
}
|
||||
};
|
||||
|
||||
img.src = objectUrl;
|
||||
|
||||
const imageData = this.imageDataMap.get(imagePath);
|
||||
if (imageData) {
|
||||
imageData.objectUrl = objectUrl;
|
||||
}
|
||||
|
||||
// 尝试缓存图片
|
||||
try {
|
||||
await this.plugin.imageCacheService.cacheImage(
|
||||
imagePath,
|
||||
arrayBuffer,
|
||||
undefined,
|
||||
contentType
|
||||
);
|
||||
log.debug(() => `Weibo image cached: ${imagePath}`);
|
||||
} catch (cacheError) {
|
||||
log.error(() => `Failed to cache Weibo image: ${imagePath}`, cacheError instanceof Error ? cacheError : undefined);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(() => `requestUrl failed to load Weibo image: ${imagePath}`, error instanceof Error ? error : undefined);
|
||||
|
||||
const imageData = this.imageDataMap.get(imagePath);
|
||||
if (imageData) {
|
||||
imageData.isLoading = false;
|
||||
}
|
||||
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
log.debug(() => `Retrying after request failure (${retryCount + 1}/${MAX_RETRIES}): ${imagePath}`);
|
||||
window.setTimeout(() => {
|
||||
this.loadWeiboImage(imagePath, img, imageDiv, loadingText, retryCount + 1)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}, 1000 * (retryCount + 1));
|
||||
} else {
|
||||
this.handleImageLoadFailure(imageDiv, t('loadingFailed'));
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleError(error: Error, imageDiv: HTMLElement, requestId: string | undefined, retryCount: number) {
|
||||
const MAX_RETRIES = 3;
|
||||
|
||||
log.error(() => 'Error loading Weibo image:', error instanceof Error ? error : undefined);
|
||||
if (requestId) {
|
||||
this.currentRequests.delete(requestId);
|
||||
}
|
||||
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
log.debug(() => `Retrying after error (${retryCount + 1}/${MAX_RETRIES})`);
|
||||
window.setTimeout(() => {
|
||||
const img = imageDiv.querySelector('img');
|
||||
const loadingText = imageDiv.querySelector('.nig-loading-text');
|
||||
if (img && loadingText) {
|
||||
const imgSrc = img.src;
|
||||
void this.loadWeiboImage(imgSrc, img, imageDiv, loadingText as HTMLElement, retryCount + 1);
|
||||
}
|
||||
}, 1000 * (retryCount + 1));
|
||||
} else {
|
||||
this.handleImageLoadFailure(imageDiv, t('loadingFailed'));
|
||||
}
|
||||
}
|
||||
|
||||
private updateProgressBar() {
|
||||
const progressEl = this.contentEl.querySelector('progress');
|
||||
const progressText = this.contentEl.querySelector('.nig-progress-text');
|
||||
|
|
@ -1569,16 +1343,6 @@ export class CurrentNoteImageGalleryService extends Modal {
|
|||
return dest.path;
|
||||
}
|
||||
|
||||
// 如果是文件名(没有路径),尝试在库中查找
|
||||
if (!cleanLink.includes('/')) {
|
||||
const allFiles = this.app.vault.getFiles();
|
||||
const matchedFiles = allFiles.filter(f => f.name === cleanLink);
|
||||
if (matchedFiles.length > 0) {
|
||||
log.debug(() => `Found by filename: ${matchedFiles[0].path}`);
|
||||
return matchedFiles[0].path;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果都失败了,尝试添加_resources前缀
|
||||
if (!cleanLink.startsWith('_resources/')) {
|
||||
const resourcePath = `_resources/${cleanLink}`;
|
||||
|
|
@ -2048,19 +1812,6 @@ export class CurrentNoteImageGalleryService extends Modal {
|
|||
onClose() {
|
||||
this.isClosed = true;
|
||||
|
||||
this.currentRequests.forEach((request) => {
|
||||
try {
|
||||
if (request.controller) {
|
||||
request.controller.abort();
|
||||
} else if (request.electronRequest) {
|
||||
request.electronRequest.abort();
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(() => 'Error aborting request:', e instanceof Error ? e : undefined);
|
||||
}
|
||||
});
|
||||
this.currentRequests.clear();
|
||||
|
||||
this.resourceManager.revokeAll();
|
||||
this.imageDataMap.clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ export class ImageCacheService {
|
|||
constructor(app: App, plugin: Plugin) {
|
||||
this.app = app;
|
||||
this.pluginDir = plugin.manifest.dir!;
|
||||
void this.loadCacheIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -42,14 +42,18 @@ class HybridImageExtractor implements ImageExtractor {
|
|||
* 处理格式:![[图片文件名]]
|
||||
*/
|
||||
class WikiImageExtractor implements ImageExtractor {
|
||||
// 修改正则:不匹配后面紧跟 ]( 的情况(混合格式)
|
||||
private readonly regex = /!\[\[(.*?)]](?!\()/g;
|
||||
private readonly regex = /!\[\[(.*?)]]/g;
|
||||
|
||||
extract(content: string): string[] {
|
||||
const images: string[] = [];
|
||||
let match;
|
||||
|
||||
while ((match = this.regex.exec(content)) !== null) {
|
||||
const nextChar = content[match.index + match[0].length];
|
||||
if (nextChar === '(') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (match[1]) {
|
||||
const imagePath = this.cleanImagePath(match[1]);
|
||||
if (imagePath) {
|
||||
|
|
@ -79,8 +83,7 @@ class WikiImageExtractor implements ImageExtractor {
|
|||
* 处理格式:
|
||||
*/
|
||||
class MarkdownImageExtractor implements ImageExtractor {
|
||||
// 修改正则:不匹配前面是 ]] 的情况(混合格式)
|
||||
private readonly regex = /(?<!]])!\[.*?\]\((.*?)\)/g;
|
||||
private readonly regex = /!\[.*?]\((.*?)\)/g;
|
||||
|
||||
extract(content: string): string[] {
|
||||
const images: string[] = [];
|
||||
|
|
@ -111,43 +114,6 @@ class MarkdownImageExtractor implements ImageExtractor {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单图片链接提取器
|
||||
* 处理格式:
|
||||
*/
|
||||
class SimpleImageExtractor implements ImageExtractor {
|
||||
private readonly regex = /!\[]\((.*?)\)/g;
|
||||
|
||||
extract(content: string): string[] {
|
||||
const images: string[] = [];
|
||||
let match;
|
||||
|
||||
while ((match = this.regex.exec(content)) !== null) {
|
||||
if (match[1]) {
|
||||
const imagePath = this.processImagePath(match[1]);
|
||||
if (imagePath) {
|
||||
images.push(imagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
private processImagePath(path: string): string {
|
||||
const trimmedPath = path.trim();
|
||||
// 移除路径中的引号(如果存在)
|
||||
const cleanPath = trimmedPath.replace(/['"]/g, '');
|
||||
|
||||
// 过滤掉 markdown 文件
|
||||
if (cleanPath.toLowerCase().endsWith('.md')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return cleanPath;
|
||||
}
|
||||
}
|
||||
|
||||
export class ImageExtractorService {
|
||||
private readonly extractors: ImageExtractor[];
|
||||
|
||||
|
|
@ -157,8 +123,7 @@ export class ImageExtractorService {
|
|||
this.extractors = [
|
||||
new HybridImageExtractor(),
|
||||
new WikiImageExtractor(),
|
||||
new MarkdownImageExtractor(),
|
||||
new SimpleImageExtractor()
|
||||
new MarkdownImageExtractor()
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,17 +90,6 @@ export class ObsidianImageLoader {
|
|||
result.push(`${activeFile.parent.path}/_attachments/${filename}`);
|
||||
result.push(`${activeFile.parent.path}/attachments/${filename}`);
|
||||
}
|
||||
|
||||
const matchingFiles = this.app.vault.getFiles().filter(f => f.name === filename);
|
||||
for (const file of matchingFiles) {
|
||||
result.push(file.path);
|
||||
}
|
||||
}
|
||||
|
||||
const allFiles = this.app.vault.getFiles();
|
||||
const matchingPathFiles = allFiles.filter(f => f.path.includes(originalPath));
|
||||
for (const file of matchingPathFiles) {
|
||||
result.push(file.path);
|
||||
}
|
||||
|
||||
return [...new Set(result)];
|
||||
|
|
|
|||
|
|
@ -2,5 +2,7 @@
|
|||
"1.0.0": "0.15.0",
|
||||
"1.0.1": "1.8.4",
|
||||
"1.0.2": "1.8.4",
|
||||
"1.0.3": "1.8.4"
|
||||
"1.0.3": "1.8.4",
|
||||
"1.0.11": "1.9.14",
|
||||
"1.1.0": "1.9.14"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue