/** * 文件:preview-view.ts * 作用:Obsidian 视图容器,负责与 Obsidian 框架交互 * * 职责: * 1. 实现 ItemView 接口,集成 Obsidian 视图系统 * 2. 管理视图生命周期(onOpen/onClose) * 3. 监听文件变化事件 * 4. 将实际业务逻辑委托给 PreviewManager * * 设计原则: * - 极简化:只保留 Obsidian 视图必需的代码 * - 单一职责:只负责视图层包装 * - 委托模式:所有业务逻辑委托给 PreviewManager */ import { EventRef, ItemView, WorkspaceLeaf, Plugin, TFile, Notice } from 'obsidian'; import { PreviewManager } from './preview-manager'; import { ArticleRender } from './article-render'; import { NMPSettings } from './settings'; import AssetsManager from './assets'; import { waitForLayoutReady, uevent } from './utils'; import { LocalFile } from './markdown/local-file'; import { ErrorHandler } from './core/error-handler'; import { ProgressIndicator } from './core/progress-indicator'; import { ConfigManager } from './core/config-manager'; import { ContentProcessor } from './core/content-processor'; export const VIEW_TYPE_NOTE_PREVIEW = 'note-preview'; /** * 笔记预览视图类 * * 这是一个简化的视图容器,实际的预览逻辑由 PreviewManager 处理 */ export class PreviewView extends ItemView { private plugin: Plugin; private manager: PreviewManager | null = null; private settings: NMPSettings; private assetsManager: AssetsManager; private listeners: EventRef[] = []; // ArticleRender 相关 private styleEl: HTMLElement | null = null; private articleDiv: HTMLDivElement | null = null; private _articleRender: ArticleRender | null = null; constructor(leaf: WorkspaceLeaf, plugin: Plugin) { super(leaf); this.plugin = plugin; this.settings = NMPSettings.getInstance(); this.assetsManager = AssetsManager.getInstance(); } /** * 获取视图类型 */ getViewType(): string { return VIEW_TYPE_NOTE_PREVIEW; } /** * 获取显示名称 */ getDisplayText(): string { return '笔记预览'; } /** * 获取图标 */ getIcon(): string { return 'book-open'; } /** * 获取 ArticleRender 实例 */ 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; } /** * 视图打开时的回调 */ async onOpen(): Promise { console.log('[PreviewView] 视图打开 layoutReady=', this.app.workspace.layoutReady); try { // 不在未完成 layoutReady 时做重初始化,改为延迟 if (!this.app.workspace.layoutReady) { this.showLoading(); console.log('[PreviewView] defer initialization until layoutReady'); this.app.workspace.onLayoutReady(() => { // 使用微任务再推进,确保其它插件也完成 setTimeout(() => this.performInitialization(), 0); }); return; } await this.performInitialization(); } catch (error) { ErrorHandler.handle(error as Error, 'PreviewView.onOpen'); } } private async performInitialization(): Promise { const progress = new ProgressIndicator(); progress.start('初始化预览视图'); try { const start = performance.now(); this.showLoading(); progress.update('初始化设置'); console.time('[PreviewView] initializeSettings'); await this.initializeSettings(); console.timeEnd('[PreviewView] initializeSettings'); progress.update('创建管理器'); console.time('[PreviewView] createManager'); await this.createManager(); console.timeEnd('[PreviewView] createManager'); progress.update('注册事件监听器'); console.time('[PreviewView] registerEventListeners'); this.registerEventListeners(); console.timeEnd('[PreviewView] registerEventListeners'); // 初始不渲染正文,等用户真正激活 / 文件切换时再渲染(懒加载) const activeFile = this.app.workspace.getActiveFile(); if (activeFile) { // 轻量延迟,避免首屏阻塞 setTimeout(() => { if (this.manager) { this.manager.setFile(activeFile); } }, 200); } console.log('[PreviewView] 初始化耗时(ms):', (performance.now() - start).toFixed(1)); progress.finish('预览视图初始化完成'); uevent('open'); } catch (error) { progress.error('预览视图初始化失败'); ErrorHandler.handle(error as Error, 'PreviewView.performInitialization'); console.error('[PreviewView] 初始化失败', error); this.showError('预览视图初始化失败,请检查插件设置'); } } /** * 视图关闭时的回调 */ async onClose(): Promise { console.log('[PreviewView] 视图关闭'); // 清理事件监听 this.listeners.forEach(listener => { this.app.workspace.offref(listener); }); this.listeners = []; // 清理管理器 if (this.manager) { this.manager.destroy(); this.manager = null; } // 清理缓存 LocalFile.fileCache.clear(); uevent('close'); } /** * 显示加载动画 */ private showLoading(): void { const container = this.containerEl.children[1]; container.empty(); const loading = container.createDiv({ cls: 'loading-wrapper' }); loading.createDiv({ cls: 'loading-spinner' }); } /** * 初始化设置和资源 */ private async initializeSettings(): Promise { console.log('[PreviewView]initSettings:start'); const t0 = performance.now(); try { // 等待布局就绪 console.log('[PreviewView]initSettings:waitForLayoutReady'); await waitForLayoutReady(this.app); console.log('[PreviewView]initSettings:layoutReady'); // 加载设置 if (!this.settings.isLoaded) { console.log('[PreviewView]initSettings:loadData:start'); const data = await this.plugin.loadData(); NMPSettings.loadSettings(data); console.log('[PreviewView]initSettings:loadData:done'); } else { console.log('[PreviewView]initSettings:settingsAlreadyLoaded'); } // 加载资源(加超时降级) if (!this.assetsManager.isLoaded) { console.log('[PreviewView]initSettings:assets:load:start'); const assetPromise = this.assetsManager.loadAssets(); const timeoutMs = 8000; // 8 秒防护 let timedOut = false; let timer: number | null = null; const timeout = new Promise((resolve) => { timer = window.setTimeout(() => { timedOut = true; console.warn('[PreviewView]initSettings:assets:timeout, fallback to minimal defaults'); resolve(); }, timeoutMs); }); await Promise.race([assetPromise.then(()=>{ /* 成功加载 */ }), timeout]); if (!timedOut && timer !== null) { clearTimeout(timer); } console.log('[PreviewView]initSettings:assets:load:end', { timedOut }); } else { console.log('[PreviewView]initSettings:assetsAlreadyLoaded'); } } catch (e) { console.error('[PreviewView]initSettings:error', e); throw e; } finally { console.log('[PreviewView]initSettings:done in', (performance.now() - t0).toFixed(1), 'ms'); } } /** * 创建预览管理器 */ private async createManager(): Promise { // 获取容器 const container = this.containerEl.children[1] as HTMLElement; container.empty(); // 创建预览管理器 this.manager = new PreviewManager( container, this.app, this.render ); // 构建界面 await this.manager.build(); } /** * 注册事件监听 */ private registerEventListeners(): void { // 监听文件切换 this.listeners.push( this.app.workspace.on('file-open', async (file: TFile | null) => { await this.handleFileOpen(file); }) ); // 监听文件修改 this.listeners.push( this.app.vault.on('modify', async (file) => { if (file instanceof TFile) { await this.handleFileModify(file); } }) ); } /** * 处理文件打开事件 */ private async handleFileOpen(file: TFile | null): Promise { if (this.manager) { await this.manager.setFile(file); } } /** * 处理文件修改事件 */ private async handleFileModify(file: TFile): Promise { if (!this.manager) return; const currentFile = this.manager.getCurrentFile(); if (currentFile && currentFile.path === file.path) { // 当前文件被修改,刷新预览 await this.manager.refresh(); } } /** * 渲染当前打开的文件 */ private async renderCurrentFile(): Promise { const activeFile = this.app.workspace.getActiveFile(); if (activeFile && this.manager) { await this.manager.setFile(activeFile); } } /** * 对外接口:设置要预览的文件 */ async setFile(file: TFile): Promise { if (this.manager) { await this.manager.setFile(file); } } /** * 对外接口:刷新预览 */ async refresh(): Promise { if (this.manager) { await this.manager.refresh(); } } /** * 显示错误信息 */ private showError(message: string): void { 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: message }); errorDiv.createEl('p', { text: '请尝试重新加载插件或查看控制台获取更多信息' }); } /** 外部接口:切换平台 */ async changePlatform(platform: 'wechat' | 'xiaohongshu') { await this.manager?.switchPlatform(platform as any); } /** 外部接口:设置当前文件并发布到微信草稿 */ async postWechatDraft(file: TFile) { await this.setFile(file); await this.changePlatform('wechat'); const wechat = this.manager?.getWechatPreview(); if (!wechat) throw new Error('微信预览未初始化'); await wechat.postDraft(); } getManager() { return this.manager; } }