update at 2025-10-09 12:39:24
This commit is contained in:
311
docs/BUGFIX_LOADING_AND_STYLE_ISSUE.md
Normal file
311
docs/BUGFIX_LOADING_AND_STYLE_ISSUE.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# 修复: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月
|
||||
**影响范围**:插件初始化流程
|
||||
**风险等级**:低(只是添加容错处理)
|
||||
**测试状态**:✅ 编译通过
|
||||
Reference in New Issue
Block a user