312 lines
8.5 KiB
Markdown
312 lines
8.5 KiB
Markdown
# 修复:Obsidian 加载卡住和样式加载失败问题
|
||
|
||
## 🐛 问题描述
|
||
|
||
**症状1**:Obsidian 一直处于"加载工作区中"状态
|
||
**症状2**:进入安全模式后关闭安全模式,插件能加载但提示:
|
||
> "获取样式失败defaultldefault,请检查主题是否正确安装。"
|
||
|
||
## 🔍 根本原因分析
|
||
|
||
### 问题1: `getTheme()` 和 `getHighlight()` 返回 undefined
|
||
|
||
**位置**:`src/assets.ts`
|
||
|
||
```typescript
|
||
// 原代码
|
||
getTheme(themeName: string) {
|
||
if (themeName === '') {
|
||
return this.themes[0];
|
||
}
|
||
for (const theme of this.themes) {
|
||
if (theme.name.toLowerCase() === themeName.toLowerCase() ||
|
||
theme.className.toLowerCase() === themeName.toLowerCase()) {
|
||
return theme;
|
||
}
|
||
}
|
||
// ❌ 找不到主题时没有返回值!返回 undefined
|
||
}
|
||
```
|
||
|
||
**问题**:
|
||
- 当 `themeName` 为 `'default'` 但主题列表中没有完全匹配的主题时
|
||
- 方法返回 `undefined`
|
||
- 在 `article-render.ts` 的 `getCSS()` 中访问 `theme!.css` 时出错
|
||
- 导致整个插件初始化失败
|
||
|
||
### 问题2: ArticleRender 初始化时使用硬编码的 'default'
|
||
|
||
**位置**:`src/article-render.ts`
|
||
|
||
```typescript
|
||
constructor(app, itemView, styleEl, articleDiv) {
|
||
// ...
|
||
this._currentTheme = 'default'; // ❌ 硬编码
|
||
this._currentHighlight = 'default'; // ❌ 硬编码
|
||
}
|
||
```
|
||
|
||
**问题**:
|
||
- 不使用用户配置的默认主题 `settings.defaultStyle`
|
||
- 如果主题列表中没有名为 'default' 的主题,就会失败
|
||
|
||
### 问题3: preview-view.ts 没有设置 ArticleRender 的主题
|
||
|
||
**位置**:`src/preview-view.ts`
|
||
|
||
```typescript
|
||
get render(): ArticleRender {
|
||
if (!this._articleRender) {
|
||
this._articleRender = new ArticleRender(...);
|
||
// ❌ 没有设置 currentTheme 和 currentHighlight
|
||
}
|
||
return this._articleRender;
|
||
}
|
||
```
|
||
|
||
### 问题4: 初始化失败时没有错误处理
|
||
|
||
**位置**:`src/preview-view.ts`
|
||
|
||
```typescript
|
||
async onOpen(): Promise<void> {
|
||
// ❌ 如果初始化失败,会导致 Obsidian 一直卡在加载中
|
||
await this.initializeSettings();
|
||
await this.createManager();
|
||
// ...
|
||
}
|
||
```
|
||
|
||
## ✅ 修复方案
|
||
|
||
### 修复1: 为 getTheme() 和 getHighlight() 添加默认返回值
|
||
|
||
**文件**:`src/assets.ts`
|
||
|
||
```typescript
|
||
getTheme(themeName: string) {
|
||
if (themeName === '') {
|
||
return this.themes[0];
|
||
}
|
||
|
||
for (const theme of this.themes) {
|
||
if (theme.name.toLowerCase() === themeName.toLowerCase() ||
|
||
theme.className.toLowerCase() === themeName.toLowerCase()) {
|
||
return theme;
|
||
}
|
||
}
|
||
|
||
// ✅ 找不到主题时返回第一个主题(默认主题)
|
||
console.warn(`[Assets] 主题 "${themeName}" 未找到,使用默认主题`);
|
||
return this.themes[0];
|
||
}
|
||
|
||
getHighlight(highlightName: string) {
|
||
if (highlightName === '') {
|
||
return this.highlights[0];
|
||
}
|
||
|
||
for (const highlight of this.highlights) {
|
||
if (highlight.name.toLowerCase() === highlightName.toLowerCase()) {
|
||
return highlight;
|
||
}
|
||
}
|
||
|
||
// ✅ 找不到高亮时返回第一个高亮(默认高亮)
|
||
console.warn(`[Assets] 高亮 "${highlightName}" 未找到,使用默认高亮`);
|
||
return this.highlights[0];
|
||
}
|
||
```
|
||
|
||
**效果**:
|
||
- 即使找不到指定的主题,也会返回一个有效的主题对象
|
||
- 避免返回 `undefined` 导致的错误
|
||
- 添加警告日志,便于调试
|
||
|
||
### 修复2: 在 preview-view.ts 中设置正确的主题
|
||
|
||
**文件**:`src/preview-view.ts`
|
||
|
||
```typescript
|
||
get render(): ArticleRender {
|
||
if (!this._articleRender) {
|
||
// 创建临时容器用于 ArticleRender
|
||
if (!this.styleEl) {
|
||
this.styleEl = document.createElement('style');
|
||
}
|
||
if (!this.articleDiv) {
|
||
this.articleDiv = document.createElement('div');
|
||
}
|
||
|
||
this._articleRender = new ArticleRender(
|
||
this.app,
|
||
this,
|
||
this.styleEl,
|
||
this.articleDiv
|
||
);
|
||
|
||
// ✅ 设置默认主题和高亮(使用用户配置)
|
||
this._articleRender.currentTheme = this.settings.defaultStyle;
|
||
this._articleRender.currentHighlight = this.settings.defaultHighlight;
|
||
}
|
||
return this._articleRender;
|
||
}
|
||
```
|
||
|
||
**效果**:
|
||
- 使用用户配置的默认主题,而不是硬编码的 'default'
|
||
- 确保 ArticleRender 初始化时有正确的主题设置
|
||
|
||
### 修复3: 添加错误处理,避免卡在加载中
|
||
|
||
**文件**:`src/preview-view.ts`
|
||
|
||
```typescript
|
||
async onOpen(): Promise<void> {
|
||
console.log('[PreviewView] 视图打开');
|
||
|
||
try {
|
||
// 显示加载动画
|
||
this.showLoading();
|
||
|
||
// 初始化设置和资源
|
||
await this.initializeSettings();
|
||
|
||
// 创建预览管理器
|
||
await this.createManager();
|
||
|
||
// 注册事件监听
|
||
this.registerEventListeners();
|
||
|
||
// 渲染当前文件
|
||
await this.renderCurrentFile();
|
||
|
||
uevent('open');
|
||
} catch (error) {
|
||
// ✅ 捕获错误,避免卡住
|
||
console.error('[PreviewView] 初始化失败:', error);
|
||
new Notice('预览视图初始化失败: ' +
|
||
(error instanceof Error ? error.message : String(error)));
|
||
|
||
// ✅ 显示友好的错误信息
|
||
const container = this.containerEl.children[1] as HTMLElement;
|
||
container.empty();
|
||
const errorDiv = container.createDiv({ cls: 'preview-error' });
|
||
errorDiv.createEl('h3', { text: '预览视图初始化失败' });
|
||
errorDiv.createEl('p', {
|
||
text: error instanceof Error ? error.message : String(error)
|
||
});
|
||
errorDiv.createEl('p', {
|
||
text: '请尝试重新加载插件或查看控制台获取更多信息'
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
**效果**:
|
||
- 即使初始化失败,也不会导致 Obsidian 卡住
|
||
- 显示友好的错误信息,方便用户排查问题
|
||
- 错误信息会打印到控制台,便于开发者调试
|
||
|
||
### 修复4: 添加 Notice 导入
|
||
|
||
**文件**:`src/preview-view.ts`
|
||
|
||
```typescript
|
||
import { EventRef, ItemView, WorkspaceLeaf, Plugin, TFile, Notice } from 'obsidian';
|
||
```
|
||
|
||
## 🧪 测试验证
|
||
|
||
### 测试场景1:正常加载
|
||
1. 启动 Obsidian
|
||
2. 插件正常加载
|
||
3. 预览视图正常显示
|
||
4. 样式正确应用
|
||
|
||
### 测试场景2:主题不存在
|
||
1. 设置中配置了不存在的主题名称
|
||
2. 插件依然能正常加载
|
||
3. 使用默认主题(第一个主题)
|
||
4. 控制台显示警告信息
|
||
|
||
### 测试场景3:初始化失败
|
||
1. 模拟某个组件初始化失败
|
||
2. Obsidian 不会卡住
|
||
3. 显示错误提示
|
||
4. 用户可以继续使用 Obsidian
|
||
|
||
## 📝 预防措施
|
||
|
||
### 1. 防御性编程
|
||
```typescript
|
||
// ✅ 好的做法:总是返回有效值
|
||
getTheme(themeName: string): Theme {
|
||
// ... 查找逻辑
|
||
return this.themes[0]; // 保底返回默认值
|
||
}
|
||
|
||
// ❌ 避免:可能返回 undefined
|
||
getTheme(themeName: string): Theme | undefined {
|
||
// ... 只在找到时返回
|
||
}
|
||
```
|
||
|
||
### 2. 优雅的错误处理
|
||
```typescript
|
||
// ✅ 好的做法:捕获并处理错误
|
||
try {
|
||
await dangerousOperation();
|
||
} catch (error) {
|
||
console.error('操作失败:', error);
|
||
showUserFriendlyMessage();
|
||
}
|
||
|
||
// ❌ 避免:让错误传播导致卡住
|
||
await dangerousOperation(); // 如果失败会卡住整个应用
|
||
```
|
||
|
||
### 3. 使用配置而非硬编码
|
||
```typescript
|
||
// ✅ 好的做法:使用用户配置
|
||
this.currentTheme = this.settings.defaultStyle;
|
||
|
||
// ❌ 避免:硬编码默认值
|
||
this.currentTheme = 'default';
|
||
```
|
||
|
||
## 📊 修复效果
|
||
|
||
| 问题 | 修复前 | 修复后 |
|
||
|------|--------|--------|
|
||
| **Obsidian 卡在加载中** | ❌ 一直卡住 | ✅ 正常加载 |
|
||
| **样式加载失败错误** | ❌ 显示错误提示 | ✅ 使用默认主题 |
|
||
| **主题不存在时** | ❌ 插件崩溃 | ✅ 回退到默认主题 |
|
||
| **错误信息** | ❌ 无提示或卡住 | ✅ 友好的错误提示 |
|
||
| **调试信息** | ❌ 无日志 | ✅ 控制台警告 |
|
||
|
||
## 🔄 后续优化建议
|
||
|
||
1. **添加主题验证**
|
||
- 在设置保存时验证主题是否存在
|
||
- 提供主题选择下拉框,避免输入错误
|
||
|
||
2. **改进错误提示**
|
||
- 提供更详细的错误信息
|
||
- 添加解决方案建议
|
||
|
||
3. **添加重试机制**
|
||
- 初始化失败时提供重试按钮
|
||
- 自动重试资源加载
|
||
|
||
4. **完善日志系统**
|
||
- 统一的日志格式
|
||
- 日志级别控制(DEBUG, INFO, WARN, ERROR)
|
||
|
||
---
|
||
|
||
**修复时间**:2025年1月
|
||
**影响范围**:插件初始化流程
|
||
**风险等级**:低(只是添加容错处理)
|
||
**测试状态**:✅ 编译通过
|