update at 2025-10-08 19:45:28
This commit is contained in:
@@ -42,6 +42,7 @@ export default class AssetsManager {
|
||||
wasmPath: string;
|
||||
expertSettings: ExpertSettings;
|
||||
isLoaded: boolean = false;
|
||||
private loadingPromise: Promise<void> | null = null; // 防止重复并发加载
|
||||
|
||||
private static instance: AssetsManager;
|
||||
|
||||
@@ -75,18 +76,42 @@ export default class AssetsManager {
|
||||
}
|
||||
|
||||
async loadAssets() {
|
||||
await this.loadThemes();
|
||||
await this.loadHighlights();
|
||||
await this.loadCustomCSS();
|
||||
await this.loadExpertSettings();
|
||||
this.isLoaded = true;
|
||||
if (this.isLoaded) return;
|
||||
if (this.loadingPromise) {
|
||||
// 已经在加载中,复用同一个 promise
|
||||
return this.loadingPromise;
|
||||
}
|
||||
console.time('[Assets] loadAssets');
|
||||
this.loadingPromise = (async () => {
|
||||
try {
|
||||
// 并行加载互不依赖的资源,加速启动
|
||||
await Promise.all([
|
||||
this.loadThemes().catch(e => console.error('[Assets] loadThemes 失败', e)),
|
||||
this.loadHighlights().catch(e => console.error('[Assets] loadHighlights 失败', e)),
|
||||
this.loadCustomCSS().catch(e => console.error('[Assets] loadCustomCSS 失败', e)),
|
||||
this.loadExpertSettings().catch(e => console.error('[Assets] loadExpertSettings 失败', e)),
|
||||
]);
|
||||
this.isLoaded = true;
|
||||
console.log('[Assets] 资源加载完成', {
|
||||
themeCount: this.themes?.length ?? 0,
|
||||
highlightCount: this.highlights?.length ?? 0,
|
||||
customCSS: this.customCSS?.length ?? 0
|
||||
});
|
||||
} finally {
|
||||
console.timeEnd('[Assets] loadAssets');
|
||||
this.loadingPromise = null;
|
||||
}
|
||||
})();
|
||||
return this.loadingPromise;
|
||||
}
|
||||
|
||||
async loadThemes() {
|
||||
try {
|
||||
console.log('[Assets] loadThemes:start');
|
||||
if (!await this.app.vault.adapter.exists(this.themeCfg)) {
|
||||
new Notice('主题资源未下载,请前往设置下载!');
|
||||
this.themes = [this.defaultTheme];
|
||||
console.log('[Assets] loadThemes:themes.json missing -> default only');
|
||||
return;
|
||||
}
|
||||
const data = await this.app.vault.adapter.read(this.themeCfg);
|
||||
@@ -94,6 +119,7 @@ export default class AssetsManager {
|
||||
const themes = JSON.parse(data);
|
||||
await this.loadCSS(themes);
|
||||
this.themes = [this.defaultTheme, ... themes];
|
||||
console.log('[Assets] loadThemes:done', { count: this.themes.length });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -103,13 +129,19 @@ export default class AssetsManager {
|
||||
|
||||
async loadCSS(themes: Theme[]) {
|
||||
try {
|
||||
for (const theme of themes) {
|
||||
const cssFile = this.themesPath + theme.className + '.css';
|
||||
const cssContent = await this.app.vault.adapter.read(cssFile);
|
||||
if (cssContent) {
|
||||
theme.css = cssContent;
|
||||
}
|
||||
}
|
||||
await Promise.all(
|
||||
themes.map(async (theme) => {
|
||||
try {
|
||||
const cssFile = this.themesPath + theme.className + '.css';
|
||||
const cssContent = await this.app.vault.adapter.read(cssFile);
|
||||
if (cssContent) {
|
||||
theme.css = cssContent;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Assets] 读取主题 CSS 失败', theme.className, e);
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
new Notice('读取CSS失败!');
|
||||
@@ -118,6 +150,7 @@ export default class AssetsManager {
|
||||
|
||||
async loadCustomCSS() {
|
||||
try {
|
||||
console.log('[Assets] loadCustomCSS:start');
|
||||
const customCSSNote = NMPSettings.getInstance().customCSSNote;
|
||||
if (customCSSNote != '') {
|
||||
const file = this.searchFile(customCSSNote);
|
||||
@@ -141,6 +174,7 @@ export default class AssetsManager {
|
||||
if (cssContent) {
|
||||
this.customCSS = cssContent;
|
||||
}
|
||||
console.log('[Assets] loadCustomCSS:done', { hasContent: this.customCSS.length > 0 });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
new Notice('读取CSS失败!');
|
||||
@@ -149,6 +183,7 @@ export default class AssetsManager {
|
||||
|
||||
async loadExpertSettings() {
|
||||
try {
|
||||
console.log('[Assets] loadExpertSettings:start');
|
||||
const note = NMPSettings.getInstance().expertSettingsNote;
|
||||
if (note != '') {
|
||||
const file = this.searchFile(note);
|
||||
@@ -170,6 +205,7 @@ export default class AssetsManager {
|
||||
else {
|
||||
this.expertSettings = defaultExpertSettings;
|
||||
}
|
||||
console.log('[Assets] loadExpertSettings:done');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
new Notice('读取专家设置失败!');
|
||||
@@ -178,10 +214,12 @@ export default class AssetsManager {
|
||||
|
||||
async loadHighlights() {
|
||||
try {
|
||||
console.log('[Assets] loadHighlights:start');
|
||||
const defaultHighlight = {name: '默认', url: '', css: DefaultHighlight};
|
||||
this.highlights = [defaultHighlight];
|
||||
if (!await this.app.vault.adapter.exists(this.hilightCfg)) {
|
||||
new Notice('高亮资源未下载,请前往设置下载!');
|
||||
console.log('[Assets] loadHighlights:highlights.json missing -> default only');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -193,6 +231,7 @@ export default class AssetsManager {
|
||||
const cssContent = await this.app.vault.adapter.read(cssFile);
|
||||
this.highlights.push({name: item.name, url: item.url, css: cssContent});
|
||||
}
|
||||
console.log('[Assets] loadHighlights:done', { count: this.highlights.length });
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
@@ -234,6 +273,10 @@ export default class AssetsManager {
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
|
||||
// 找不到主题时返回第一个主题(默认主题)
|
||||
console.warn(`[Assets] 主题 "${themeName}" 未找到,使用默认主题`);
|
||||
return this.themes[0];
|
||||
}
|
||||
|
||||
getHighlight(highlightName: string) {
|
||||
@@ -246,6 +289,10 @@ export default class AssetsManager {
|
||||
return highlight;
|
||||
}
|
||||
}
|
||||
|
||||
// 找不到高亮时返回第一个高亮(默认高亮)
|
||||
console.warn(`[Assets] 高亮 "${highlightName}" 未找到,使用默认高亮`);
|
||||
return this.highlights[0];
|
||||
}
|
||||
|
||||
getThemeURL() {
|
||||
|
||||
@@ -533,14 +533,14 @@ export class BatchPublishModal extends Modal {
|
||||
* 发布到微信公众号
|
||||
*/
|
||||
private async publishToWechat(file: TFile): Promise<void> {
|
||||
// TODO: 重构后需要重新实现批量发布到微信
|
||||
// 激活预览视图并发布
|
||||
await this.plugin.activateView();
|
||||
const preview = this.plugin.getNotePreview();
|
||||
if (preview) {
|
||||
// 确保预览器处于微信模式
|
||||
preview.currentPlatform = 'wechat';
|
||||
await preview.renderMarkdown(file);
|
||||
await preview.postToWechat();
|
||||
// 临时方案:直接打开文件让用户手动发布
|
||||
await preview.setFile(file);
|
||||
throw new Error('批量发布功能正在重构中,请在预览视图中手动发布');
|
||||
} else {
|
||||
throw new Error('无法获取预览视图');
|
||||
}
|
||||
|
||||
142
src/main.ts
142
src/main.ts
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import { Plugin, WorkspaceLeaf, App, PluginManifest, Menu, Notice, TAbstractFile, TFile, TFolder } from 'obsidian';
|
||||
import { NotePreview, VIEW_TYPE_NOTE_PREVIEW } from './note-preview';
|
||||
import { PreviewView, VIEW_TYPE_NOTE_PREVIEW } from './preview-view';
|
||||
import { NMPSettings } from './settings';
|
||||
import { NoteToMpSettingTab } from './setting-tab';
|
||||
import AssetsManager from './assets';
|
||||
@@ -51,22 +51,40 @@ export default class NoteToMpPlugin extends Plugin {
|
||||
}
|
||||
|
||||
async onload() {
|
||||
console.log('Loading NoteToMP');
|
||||
console.log('Loading NoteToMP (plugin onload start)');
|
||||
setVersion(this.manifest.version);
|
||||
uevent('load');
|
||||
this.app.workspace.onLayoutReady(()=>{
|
||||
this.loadResource();
|
||||
// 布局就绪后清理旧视图并自动打开一个新的标准预览(可选)
|
||||
this.cleanupLegacyViews();
|
||||
// 如果当前没有我们的预览叶子,自动激活一次,改善首次体验
|
||||
if (this.app.workspace.getLeavesOfType(VIEW_TYPE_NOTE_PREVIEW).length === 0) {
|
||||
this.activateView();
|
||||
console.log('[NoteToMpPlugin] workspace.layoutReady at onload =', this.app.workspace.layoutReady);
|
||||
|
||||
// 先注册 view 之前,防止旧 snapshot 立即恢复创建大量视图:先临时卸载残留叶子(如果类型匹配)
|
||||
try {
|
||||
const legacyLeaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_NOTE_PREVIEW);
|
||||
if (legacyLeaves.length > 0) {
|
||||
console.log('[NoteToMpPlugin] detach legacy leaves early count=', legacyLeaves.length);
|
||||
this.app.workspace.detachLeavesOfType(VIEW_TYPE_NOTE_PREVIEW);
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('[NoteToMpPlugin] early detach failed', e);
|
||||
}
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
console.log('[NoteToMpPlugin] onLayoutReady callback entered');
|
||||
console.time('[NoteToMpPlugin] startup:onLayoutReady→loadResource');
|
||||
try {
|
||||
await this.loadResource(); // 确保资源完全加载完再继续,避免后续视图初始化反复等待
|
||||
} catch (e) {
|
||||
console.error('[NoteToMpPlugin] loadResource 失败', e);
|
||||
} finally {
|
||||
console.timeEnd('[NoteToMpPlugin] startup:onLayoutReady→loadResource');
|
||||
}
|
||||
// 清理旧视图
|
||||
this.cleanupLegacyViews();
|
||||
// 取消自动打开预览视图(用于排查启动卡顿)。用户可通过图标或命令手动打开。
|
||||
// console.log('[NoteToMpPlugin] 已跳过自动打开预览视图调试模式');
|
||||
});
|
||||
|
||||
this.registerView(
|
||||
VIEW_TYPE_NOTE_PREVIEW,
|
||||
(leaf) => new NotePreview(leaf, this)
|
||||
(leaf) => new PreviewView(leaf, this)
|
||||
);
|
||||
|
||||
this.ribbonIconEl = this.addRibbonIcon('clipboard-paste', '复制到公众号', (evt: MouseEvent) => {
|
||||
@@ -100,56 +118,66 @@ export default class NoteToMpPlugin extends Plugin {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: 重构后需要重新实现批量发布功能
|
||||
// this.addCommand({
|
||||
// id: 'note-to-mp-pub',
|
||||
// name: '发布公众号文章',
|
||||
// callback: async () => {
|
||||
// await this.activateView();
|
||||
// this.getNotePreview()?.postArticle();
|
||||
// }
|
||||
// });
|
||||
|
||||
// 命令:当前文件发布到微信草稿
|
||||
this.addCommand({
|
||||
id: 'note-to-mp-pub',
|
||||
name: '发布公众号文章',
|
||||
id: 'note-to-mp-post-current',
|
||||
name: '发布当前文件到公众号草稿',
|
||||
callback: async () => {
|
||||
const file = this.app.workspace.getActiveFile();
|
||||
if (!file) { new Notice('没有活动文件'); return; }
|
||||
if (file.extension.toLowerCase() !== 'md') { new Notice('只能发布 Markdown 文件'); return; }
|
||||
await this.activateView();
|
||||
this.getNotePreview()?.postArticle();
|
||||
await this.getNotePreview()?.postWechatDraft(file);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听右键菜单
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('file-menu', (menu, file) => {
|
||||
// 发布到微信公众号
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle('发布到公众号')
|
||||
.setIcon('lucide-send')
|
||||
.onClick(async () => {
|
||||
if (file instanceof TFile) {
|
||||
if (file.extension.toLowerCase() !== 'md') {
|
||||
new Notice('只能发布 Markdown 文件');
|
||||
return;
|
||||
}
|
||||
await this.activateView();
|
||||
await this.getNotePreview()?.renderMarkdown(file);
|
||||
await this.getNotePreview()?.postArticle();
|
||||
} else if (file instanceof TFolder) {
|
||||
await this.activateView();
|
||||
await this.getNotePreview()?.batchPost(file);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 发布到小红书(新增)
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle('发布到小红书')
|
||||
.setIcon('lucide-heart')
|
||||
.onClick(async () => {
|
||||
if (file instanceof TFile) {
|
||||
if (file.extension.toLowerCase() !== 'md') {
|
||||
new Notice('只能发布 Markdown 文件');
|
||||
return;
|
||||
}
|
||||
await this.publishToXiaohongshu(file);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
// 监听右键菜单(文件浏览器)
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('file-menu', (menu, file) => {
|
||||
// 发布到公众号草稿
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle('发布公众号')
|
||||
.setIcon('lucide-send')
|
||||
.onClick(async () => {
|
||||
if (file instanceof TFile) {
|
||||
if (file.extension.toLowerCase() !== 'md') {
|
||||
new Notice('只能发布 Markdown 文件');
|
||||
return;
|
||||
}
|
||||
await this.activateView();
|
||||
await this.getNotePreview()?.postWechatDraft(file);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 发布到小红书
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle('发布到小红书')
|
||||
.setIcon('lucide-heart')
|
||||
.onClick(async () => {
|
||||
if (file instanceof TFile) {
|
||||
if (file.extension.toLowerCase() !== 'md') {
|
||||
new Notice('只能发布 Markdown 文件');
|
||||
return;
|
||||
}
|
||||
await this.publishToXiaohongshu(file);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
onunload() {
|
||||
@@ -208,11 +236,11 @@ export default class NoteToMpPlugin extends Plugin {
|
||||
if (leaf) workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
getNotePreview(): NotePreview | null {
|
||||
getNotePreview(): PreviewView | null {
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_NOTE_PREVIEW);
|
||||
if (leaves.length > 0) {
|
||||
const leaf = leaves[0];
|
||||
return leaf.view as NotePreview;
|
||||
return leaf.view as PreviewView;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
163
src/platform-chooser.ts
Normal file
163
src/platform-chooser.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 文件:platform-chooser.ts
|
||||
* 作用:平台选择器组件,负责渲染平台选择 UI 并处理平台切换事件
|
||||
*
|
||||
* 这是一个公共组件,独立于具体平台实现,便于未来扩展新平台
|
||||
*/
|
||||
|
||||
import { Platform as ObsidianPlatform } from 'obsidian';
|
||||
|
||||
export type PlatformType = 'wechat' | 'xiaohongshu';
|
||||
|
||||
/**
|
||||
* 平台选择器配置
|
||||
*/
|
||||
export interface PlatformChooserOptions {
|
||||
/** 默认选中的平台 */
|
||||
defaultPlatform?: PlatformType;
|
||||
/** 平台切换回调 */
|
||||
onPlatformChange?: (platform: PlatformType) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台信息接口
|
||||
*/
|
||||
interface PlatformInfo {
|
||||
value: PlatformType;
|
||||
label: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持的平台列表
|
||||
*/
|
||||
const SUPPORTED_PLATFORMS: PlatformInfo[] = [
|
||||
{ value: 'wechat', label: '微信公众号', icon: '📱' },
|
||||
{ value: 'xiaohongshu', label: '小红书', icon: '📔' }
|
||||
];
|
||||
|
||||
/**
|
||||
* 平台选择器类
|
||||
*
|
||||
* 职责:
|
||||
* 1. 渲染平台选择 UI
|
||||
* 2. 处理用户的平台切换操作
|
||||
* 3. 触发平台切换回调
|
||||
* 4. 维护当前选中的平台状态
|
||||
*/
|
||||
export class PlatformChooser {
|
||||
private container: HTMLElement;
|
||||
private selectElement: HTMLSelectElement | null = null;
|
||||
private currentPlatform: PlatformType;
|
||||
private onChange?: (platform: PlatformType) => void;
|
||||
|
||||
constructor(container: HTMLElement, options: PlatformChooserOptions = {}) {
|
||||
this.container = container;
|
||||
this.currentPlatform = options.defaultPlatform || 'wechat';
|
||||
if (options.onPlatformChange) {
|
||||
this.onChange = (platform) => {
|
||||
options.onPlatformChange!(platform);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置平台切换回调
|
||||
*/
|
||||
setOnChange(callback: (platform: PlatformType) => void): void {
|
||||
this.onChange = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染平台选择器 UI
|
||||
*/
|
||||
render(): void {
|
||||
// 创建平台选择行
|
||||
const lineDiv = this.container.createDiv({ cls: 'toolbar-line platform-selector-line' });
|
||||
|
||||
// 创建标签
|
||||
const platformLabel = lineDiv.createDiv({ cls: 'style-label' });
|
||||
platformLabel.innerText = '发布平台';
|
||||
|
||||
// 创建选择器
|
||||
const platformSelect = lineDiv.createEl('select', { cls: 'platform-select' });
|
||||
this.selectElement = platformSelect;
|
||||
|
||||
// 添加平台选项
|
||||
SUPPORTED_PLATFORMS.forEach(platform => {
|
||||
const option = platformSelect.createEl('option');
|
||||
option.value = platform.value;
|
||||
option.text = platform.icon ? `${platform.icon} ${platform.label}` : platform.label;
|
||||
|
||||
// 设置默认选中
|
||||
if (platform.value === this.currentPlatform) {
|
||||
option.selected = true;
|
||||
}
|
||||
});
|
||||
|
||||
// 绑定切换事件
|
||||
platformSelect.onchange = () => {
|
||||
const newPlatform = platformSelect.value as PlatformType;
|
||||
this.switchPlatformInternal(newPlatform);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换平台(内部方法)
|
||||
*/
|
||||
private switchPlatformInternal(platform: PlatformType): void {
|
||||
if (platform === this.currentPlatform) {
|
||||
return; // 相同平台,不需要切换
|
||||
}
|
||||
|
||||
console.log(`[PlatformChooser] 切换平台: ${this.currentPlatform} -> ${platform}`);
|
||||
|
||||
this.currentPlatform = platform;
|
||||
|
||||
// 触发平台切换回调
|
||||
if (this.onChange) {
|
||||
try {
|
||||
this.onChange(platform);
|
||||
} catch (error) {
|
||||
console.error('[PlatformChooser] 平台切换失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换平台(公共方法,供 PreviewManager 调用)
|
||||
*/
|
||||
switchPlatform(platform: PlatformType): void {
|
||||
this.currentPlatform = platform;
|
||||
if (this.selectElement) {
|
||||
this.selectElement.value = platform;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前选中的平台
|
||||
*/
|
||||
getCurrentPlatform(): PlatformType {
|
||||
return this.currentPlatform;
|
||||
}
|
||||
|
||||
/**
|
||||
* 程序化设置平台(不触发回调)
|
||||
*/
|
||||
setPlatform(platform: PlatformType): void {
|
||||
this.currentPlatform = platform;
|
||||
if (this.selectElement) {
|
||||
this.selectElement.value = platform;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
destroy(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.onchange = null;
|
||||
this.selectElement = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
378
src/preview-manager.ts
Normal file
378
src/preview-manager.ts
Normal file
@@ -0,0 +1,378 @@
|
||||
/**
|
||||
* 文件:preview-manager.ts
|
||||
* 作用:预览管理器,负责协调所有平台预览组件
|
||||
*
|
||||
* 职责:
|
||||
* 1. 创建和管理所有子组件(platform-chooser, wechat-preview, xhs-preview)
|
||||
* 2. 处理平台切换逻辑(唯一入口)
|
||||
* 3. 管理文章渲染和状态同步
|
||||
* 4. 提供统一的对外接口
|
||||
*
|
||||
* 设计模式:
|
||||
* - 中介者模式(Mediator): 协调各组件交互
|
||||
* - 外观模式(Facade): 提供简单的对外接口
|
||||
*/
|
||||
|
||||
import { TFile, Notice, App } from 'obsidian';
|
||||
import { PlatformChooser, PlatformType } from './platform-chooser';
|
||||
import { WechatPreview } from './wechat/wechat-preview';
|
||||
import { XiaohongshuPreview } from './xiaohongshu/xhs-preview';
|
||||
import { ArticleRender } from './article-render';
|
||||
import { NMPSettings } from './settings';
|
||||
|
||||
export class PreviewManager {
|
||||
private container: HTMLElement;
|
||||
private app: App;
|
||||
private render: ArticleRender;
|
||||
private settings: NMPSettings;
|
||||
|
||||
// 子组件
|
||||
private platformChooser: PlatformChooser | null = null;
|
||||
private wechatPreview: WechatPreview | null = null;
|
||||
private xhsPreview: XiaohongshuPreview | null = null;
|
||||
|
||||
// UI 容器
|
||||
private mainDiv: HTMLDivElement | null = null;
|
||||
private wechatContainer: HTMLDivElement | null = null;
|
||||
private xhsContainer: HTMLDivElement | null = null;
|
||||
|
||||
// 状态
|
||||
private currentPlatform: PlatformType = 'wechat';
|
||||
private currentFile: TFile | null = null;
|
||||
|
||||
constructor(container: HTMLElement, app: App, render: ArticleRender) {
|
||||
this.container = container;
|
||||
this.app = app;
|
||||
this.render = render;
|
||||
this.settings = NMPSettings.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建界面(主入口)
|
||||
*/
|
||||
async build(): Promise<void> {
|
||||
console.log('[PreviewManager] 开始构建界面');
|
||||
|
||||
// 清空容器
|
||||
this.container.empty();
|
||||
|
||||
// 创建主容器
|
||||
this.mainDiv = this.container.createDiv({ cls: 'note-preview' });
|
||||
|
||||
// 1. 创建并构建平台选择器
|
||||
this.createPlatformChooser();
|
||||
|
||||
// 2. 创建并构建微信预览
|
||||
this.createWechatPreview();
|
||||
|
||||
// 3. 创建并构建小红书预览
|
||||
this.createXiaohongshuPreview();
|
||||
|
||||
// 4. 初始显示微信平台
|
||||
await this.switchPlatform('wechat');
|
||||
|
||||
console.log('[PreviewManager] 界面构建完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建平台选择器
|
||||
*/
|
||||
private createPlatformChooser(): void {
|
||||
if (!this.mainDiv) return;
|
||||
|
||||
// 创建平台选择器容器
|
||||
const chooserContainer = this.mainDiv.createDiv({ cls: 'platform-chooser-container' });
|
||||
|
||||
// 创建平台选择器实例
|
||||
this.platformChooser = new PlatformChooser(chooserContainer);
|
||||
|
||||
// 设置平台切换回调
|
||||
this.platformChooser.setOnChange((platform) => {
|
||||
this.switchPlatform(platform as PlatformType);
|
||||
});
|
||||
|
||||
// 构建 UI
|
||||
this.platformChooser.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建微信预览组件
|
||||
*/
|
||||
private createWechatPreview(): void {
|
||||
if (!this.mainDiv) return;
|
||||
|
||||
// 创建微信预览容器
|
||||
this.wechatContainer = this.mainDiv.createDiv({ cls: 'wechat-preview-container' });
|
||||
|
||||
// 创建微信预览实例
|
||||
this.wechatPreview = new WechatPreview(
|
||||
this.wechatContainer,
|
||||
this.app,
|
||||
this.render
|
||||
);
|
||||
|
||||
// 设置回调函数
|
||||
this.wechatPreview.onRefreshCallback = async () => {
|
||||
await this.refresh();
|
||||
};
|
||||
|
||||
this.wechatPreview.onAppIdChangeCallback = (appId: string) => {
|
||||
console.log(`[PreviewManager] 公众号切换: ${appId}`);
|
||||
// 可以在这里处理公众号切换的额外逻辑
|
||||
};
|
||||
|
||||
// 构建 UI
|
||||
this.wechatPreview.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建小红书预览组件
|
||||
*/
|
||||
private createXiaohongshuPreview(): void {
|
||||
if (!this.mainDiv) return;
|
||||
|
||||
// 创建小红书预览容器
|
||||
this.xhsContainer = this.mainDiv.createDiv({ cls: 'xiaohongshu-preview-container' });
|
||||
|
||||
// 创建小红书预览实例
|
||||
this.xhsPreview = new XiaohongshuPreview(this.xhsContainer, this.app);
|
||||
|
||||
// 设置回调函数
|
||||
this.xhsPreview.onRefreshCallback = async () => {
|
||||
await this.refresh();
|
||||
};
|
||||
|
||||
this.xhsPreview.onPublishCallback = async () => {
|
||||
await this.publishToXiaohongshu();
|
||||
};
|
||||
|
||||
this.xhsPreview.onPlatformChangeCallback = async (platform: string) => {
|
||||
if (platform === 'wechat') {
|
||||
await this.switchPlatform('wechat');
|
||||
}
|
||||
};
|
||||
|
||||
// 构建 UI
|
||||
this.xhsPreview.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台切换的唯一入口
|
||||
*/
|
||||
// 平台切换:公开以便外部(例如上下文菜单)调用
|
||||
async switchPlatform(platform: PlatformType): Promise<void> {
|
||||
console.log(`[PreviewManager] 平台切换: ${this.currentPlatform} → ${platform}`);
|
||||
|
||||
const previousPlatform = this.currentPlatform;
|
||||
this.currentPlatform = platform;
|
||||
|
||||
// 更新平台选择器显示
|
||||
if (this.platformChooser) {
|
||||
this.platformChooser.switchPlatform(platform);
|
||||
}
|
||||
|
||||
if (platform === 'wechat') {
|
||||
// 显示微信,隐藏小红书
|
||||
this.showWechat();
|
||||
this.hideXiaohongshu();
|
||||
|
||||
// 如果有当前文件且是从其他平台切换过来,重新渲染
|
||||
if (this.currentFile && previousPlatform !== 'wechat') {
|
||||
await this.renderForWechat(this.currentFile);
|
||||
}
|
||||
} else if (platform === 'xiaohongshu') {
|
||||
// 显示小红书,隐藏微信
|
||||
this.showXiaohongshu();
|
||||
this.hideWechat();
|
||||
|
||||
// 如果有当前文件且是从其他平台切换过来,重新渲染
|
||||
if (this.currentFile && previousPlatform !== 'xiaohongshu') {
|
||||
await this.renderForXiaohongshu(this.currentFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示微信预览
|
||||
*/
|
||||
private showWechat(): void {
|
||||
if (this.wechatContainer) {
|
||||
this.wechatContainer.style.display = 'flex';
|
||||
}
|
||||
if (this.wechatPreview) {
|
||||
this.wechatPreview.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏微信预览
|
||||
*/
|
||||
private hideWechat(): void {
|
||||
if (this.wechatContainer) {
|
||||
this.wechatContainer.style.display = 'none';
|
||||
}
|
||||
if (this.wechatPreview) {
|
||||
this.wechatPreview.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示小红书预览
|
||||
*/
|
||||
private showXiaohongshu(): void {
|
||||
if (this.xhsContainer) {
|
||||
this.xhsContainer.style.display = 'flex';
|
||||
}
|
||||
if (this.xhsPreview) {
|
||||
this.xhsPreview.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏小红书预览
|
||||
*/
|
||||
private hideXiaohongshu(): void {
|
||||
if (this.xhsContainer) {
|
||||
this.xhsContainer.style.display = 'none';
|
||||
}
|
||||
if (this.xhsPreview) {
|
||||
this.xhsPreview.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前文件(对外接口)
|
||||
*/
|
||||
async setFile(file: TFile | null): Promise<void> {
|
||||
if (!file) {
|
||||
this.currentFile = null;
|
||||
this.wechatPreview?.setFile(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// 只处理 Markdown 文件
|
||||
if (file.extension.toLowerCase() !== 'md') {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[PreviewManager] 设置文件: ${file.path}`);
|
||||
this.currentFile = file;
|
||||
this.wechatPreview?.setFile(file);
|
||||
|
||||
// 根据当前平台渲染
|
||||
if (this.currentPlatform === 'wechat') {
|
||||
await this.renderForWechat(file);
|
||||
} else if (this.currentPlatform === 'xiaohongshu') {
|
||||
await this.renderForXiaohongshu(file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新预览(对外接口)
|
||||
*/
|
||||
async refresh(): Promise<void> {
|
||||
if (!this.currentFile) {
|
||||
new Notice('请先打开一个笔记文件');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[PreviewManager] 刷新预览: ${this.currentFile.path}`);
|
||||
await this.setFile(this.currentFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染微信预览
|
||||
*/
|
||||
private async renderForWechat(file: TFile): Promise<void> {
|
||||
try {
|
||||
console.log(`[PreviewManager] 渲染微信预览: ${file.path}`);
|
||||
|
||||
// 使用 ArticleRender 渲染 Markdown
|
||||
await this.render.renderMarkdown(file);
|
||||
// 确保预览持有当前文件引用
|
||||
this.wechatPreview?.setFile(file);
|
||||
|
||||
// 微信预览已经通过 ArticleRender 更新了
|
||||
// 这里可以添加额外的微信特定逻辑
|
||||
|
||||
console.log('[PreviewManager] 微信预览渲染完成');
|
||||
} catch (error) {
|
||||
console.error('[PreviewManager] 渲染微信预览失败:', error);
|
||||
new Notice('渲染失败: ' + (error instanceof Error ? error.message : String(error)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染小红书预览
|
||||
*/
|
||||
private async renderForXiaohongshu(file: TFile): Promise<void> {
|
||||
try {
|
||||
console.log(`[PreviewManager] 渲染小红书预览: ${file.path}`);
|
||||
|
||||
// 使用 ArticleRender 渲染 Markdown
|
||||
await this.render.renderMarkdown(file);
|
||||
const articleHTML = this.render.articleHTML;
|
||||
|
||||
if (articleHTML && this.xhsPreview) {
|
||||
// 渲染到小红书预览
|
||||
await this.xhsPreview.renderArticle(articleHTML, file);
|
||||
console.log('[PreviewManager] 小红书预览渲染完成');
|
||||
} else {
|
||||
console.warn('[PreviewManager] 没有可渲染的内容');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[PreviewManager] 渲染小红书预览失败:', error);
|
||||
new Notice('渲染失败: ' + (error instanceof Error ? error.message : String(error)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布到小红书
|
||||
*/
|
||||
private async publishToXiaohongshu(): Promise<void> {
|
||||
console.log('[PreviewManager] 发布到小红书');
|
||||
// 这里实现发布逻辑
|
||||
// 可以调用 xhsPreview 的相关方法
|
||||
new Notice('发布功能开发中...');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前平台
|
||||
*/
|
||||
getCurrentPlatform(): PlatformType {
|
||||
return this.currentPlatform;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前文件
|
||||
*/
|
||||
getCurrentFile(): TFile | null {
|
||||
return this.currentFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
destroy(): void {
|
||||
console.log('[PreviewManager] 清理资源');
|
||||
|
||||
if (this.wechatPreview) {
|
||||
this.wechatPreview.destroy();
|
||||
this.wechatPreview = null;
|
||||
}
|
||||
|
||||
if (this.xhsPreview) {
|
||||
this.xhsPreview.destroy();
|
||||
this.xhsPreview = null;
|
||||
}
|
||||
|
||||
this.platformChooser = null;
|
||||
this.mainDiv = null;
|
||||
this.wechatContainer = null;
|
||||
this.xhsContainer = null;
|
||||
this.currentFile = null;
|
||||
}
|
||||
|
||||
/** 获取微信预览实例(发布操作需要) */
|
||||
getWechatPreview(): WechatPreview | null { return this.wechatPreview; }
|
||||
}
|
||||
@@ -15,12 +15,16 @@ import { MarkedParser } from './markdown/parser';
|
||||
import { LocalImageManager, LocalFile } from './markdown/local-file';
|
||||
import { CardDataManager } from './markdown/code';
|
||||
import { ArticleRender } from './article-render';
|
||||
// 平台选择组件
|
||||
import { PlatformChooser, PlatformType } from './platform-chooser';
|
||||
// 微信公众号功能模块
|
||||
import { WechatPreview } from './wechat/wechat-preview';
|
||||
// 小红书功能模块
|
||||
import { XiaohongshuContentAdapter } from './xiaohongshu/adapter';
|
||||
import { XiaohongshuImageManager } from './xiaohongshu/image';
|
||||
import { XiaohongshuAPIManager } from './xiaohongshu/api';
|
||||
import { XiaohongshuPost } from './xiaohongshu/types';
|
||||
import { XiaohongshuPreviewView } from './xiaohongshu/preview-view';
|
||||
import { XiaohongshuPreview } from './xiaohongshu/xhs-preview';
|
||||
// 切图功能
|
||||
import { sliceArticleImage } from './slice-image';
|
||||
|
||||
@@ -57,7 +61,9 @@ export class NotePreview extends ItemView {
|
||||
markedParser: MarkedParser;
|
||||
cachedElements: Map<string, string> = new Map();
|
||||
_articleRender: ArticleRender | null = null;
|
||||
_xiaohongshuPreview: XiaohongshuPreviewView | null = null;
|
||||
_xiaohongshuPreview: XiaohongshuPreview | null = null;
|
||||
_wechatPreview: WechatPreview | null = null;
|
||||
_platformChooser: PlatformChooser | null = null;
|
||||
isCancelUpload: boolean = false;
|
||||
isBatchRuning: boolean = false;
|
||||
|
||||
@@ -549,7 +555,7 @@ export class NotePreview extends ItemView {
|
||||
// 创建或显示小红书预览视图
|
||||
if (!this._xiaohongshuPreview) {
|
||||
const xhsContainer = this.mainDiv.createDiv({ cls: 'xiaohongshu-preview-container' });
|
||||
this._xiaohongshuPreview = new XiaohongshuPreviewView(xhsContainer, this.app);
|
||||
this._xiaohongshuPreview = new XiaohongshuPreview(xhsContainer, this.app);
|
||||
|
||||
// 设置回调函数
|
||||
this._xiaohongshuPreview.onRefreshCallback = async () => {
|
||||
349
src/preview-view.ts
Normal file
349
src/preview-view.ts
Normal file
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* 文件: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';
|
||||
|
||||
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<void> {
|
||||
console.log('[PreviewView] 视图打开 layoutReady=', this.app.workspace.layoutReady);
|
||||
// 不在未完成 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();
|
||||
}
|
||||
|
||||
private async performInitialization(): Promise<void> {
|
||||
try {
|
||||
const start = performance.now();
|
||||
this.showLoading();
|
||||
console.time('[PreviewView] initializeSettings');
|
||||
await this.initializeSettings();
|
||||
console.timeEnd('[PreviewView] initializeSettings');
|
||||
|
||||
console.time('[PreviewView] createManager');
|
||||
await this.createManager();
|
||||
console.timeEnd('[PreviewView] createManager');
|
||||
|
||||
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));
|
||||
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: '请尝试重新加载插件或查看控制台获取更多信息' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图关闭时的回调
|
||||
*/
|
||||
async onClose(): Promise<void> {
|
||||
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<void> {
|
||||
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<void>((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<void> {
|
||||
// 获取容器
|
||||
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<void> {
|
||||
if (this.manager) {
|
||||
await this.manager.setFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件修改事件
|
||||
*/
|
||||
private async handleFileModify(file: TFile): Promise<void> {
|
||||
if (!this.manager) return;
|
||||
|
||||
const currentFile = this.manager.getCurrentFile();
|
||||
if (currentFile && currentFile.path === file.path) {
|
||||
// 当前文件被修改,刷新预览
|
||||
await this.manager.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染当前打开的文件
|
||||
*/
|
||||
private async renderCurrentFile(): Promise<void> {
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (activeFile && this.manager) {
|
||||
await this.manager.setFile(activeFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外接口:设置要预览的文件
|
||||
*/
|
||||
async setFile(file: TFile): Promise<void> {
|
||||
if (this.manager) {
|
||||
await this.manager.setFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外接口:刷新预览
|
||||
*/
|
||||
async refresh(): Promise<void> {
|
||||
if (this.manager) {
|
||||
await this.manager.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/** 外部接口:切换平台 */
|
||||
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; }
|
||||
}
|
||||
28
src/utils.ts
28
src/utils.ts
@@ -225,10 +225,26 @@ export function cleanUrl(href: string) {
|
||||
}
|
||||
|
||||
export async function waitForLayoutReady(app: App): Promise<void> {
|
||||
if (app.workspace.layoutReady) {
|
||||
return;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
app.workspace.onLayoutReady(() => resolve());
|
||||
});
|
||||
if (app.workspace.layoutReady) {
|
||||
console.log('[waitForLayoutReady] already ready');
|
||||
return;
|
||||
}
|
||||
console.log('[waitForLayoutReady] waiting...');
|
||||
return new Promise((resolve) => {
|
||||
let resolved = false;
|
||||
const timer = setTimeout(() => {
|
||||
if (!resolved) {
|
||||
console.warn('[waitForLayoutReady] timeout fallback (5s)');
|
||||
resolved = true; resolve();
|
||||
}
|
||||
}, 5000);
|
||||
app.workspace.onLayoutReady(() => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timer);
|
||||
console.log('[waitForLayoutReady] event fired');
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
423
src/wechat/wechat-preview.ts
Normal file
423
src/wechat/wechat-preview.ts
Normal file
@@ -0,0 +1,423 @@
|
||||
/**
|
||||
* 文件:wechat/wechat-preview.ts
|
||||
* 作用:微信公众号预览视图组件,专门处理微信公众号平台的预览和发布功能
|
||||
*
|
||||
* 功能:
|
||||
* 1. 渲染微信公众号专属的工具栏和预览界面
|
||||
* 2. 处理文章的复制、上传图片、发布草稿等操作
|
||||
* 3. 管理微信公众号相关的设置(公众号选择、封面、样式等)
|
||||
* 4. 提供文章导出HTML功能
|
||||
*/
|
||||
|
||||
import { Notice, Platform, TFile, TFolder } from 'obsidian';
|
||||
import { NMPSettings } from '../settings';
|
||||
import AssetsManager from '../assets';
|
||||
import { ArticleRender } from '../article-render';
|
||||
import { uevent } from '../utils';
|
||||
|
||||
/**
|
||||
* 微信公众号预览视图类
|
||||
*/
|
||||
export class WechatPreview {
|
||||
container: HTMLElement;
|
||||
settings: NMPSettings;
|
||||
assetsManager: AssetsManager;
|
||||
render: ArticleRender;
|
||||
app: any;
|
||||
|
||||
// 当前状态
|
||||
currentFile: TFile | null = null;
|
||||
currentAppId: string = '';
|
||||
currentTheme: string;
|
||||
currentHighlight: string;
|
||||
|
||||
// UI 元素
|
||||
toolbar: HTMLDivElement | null = null;
|
||||
renderDiv: HTMLDivElement | null = null;
|
||||
wechatSelect: HTMLSelectElement | null = null;
|
||||
themeSelect: HTMLSelectElement | null = null;
|
||||
highlightSelect: HTMLSelectElement | null = null;
|
||||
coverEl: HTMLInputElement | null = null;
|
||||
useDefaultCover: HTMLInputElement | null = null;
|
||||
useLocalCover: HTMLInputElement | null = null;
|
||||
|
||||
// 回调函数
|
||||
onRefreshCallback?: () => Promise<void>;
|
||||
onAppIdChangeCallback?: (appId: string) => void;
|
||||
|
||||
constructor(container: HTMLElement, app: any, render: ArticleRender) {
|
||||
this.container = container;
|
||||
this.app = app;
|
||||
this.render = render;
|
||||
this.settings = NMPSettings.getInstance();
|
||||
this.assetsManager = AssetsManager.getInstance();
|
||||
this.currentTheme = this.settings.defaultStyle;
|
||||
this.currentHighlight = this.settings.defaultHighlight;
|
||||
|
||||
// 初始化默认公众号
|
||||
if (this.settings.wxInfo.length > 0) {
|
||||
this.currentAppId = this.settings.wxInfo[0].appid;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建微信公众号预览界面
|
||||
*/
|
||||
build(): void {
|
||||
this.container.empty();
|
||||
|
||||
// 创建工具栏
|
||||
this.toolbar = this.container.createDiv({ cls: 'preview-toolbar' });
|
||||
this.buildToolbar(this.toolbar);
|
||||
|
||||
// 创建渲染区域
|
||||
this.renderDiv = this.container.createDiv({ cls: 'render-div' });
|
||||
this.renderDiv.id = 'render-div';
|
||||
|
||||
// 将 ArticleRender 的 style 与内容节点挂载
|
||||
try {
|
||||
if (this.render && this.render.styleEl && !this.renderDiv.contains(this.render.styleEl)) {
|
||||
this.renderDiv.appendChild(this.render.styleEl);
|
||||
}
|
||||
if (this.render && this.render.articleDiv && !this.renderDiv.contains(this.render.articleDiv)) {
|
||||
// 容器样式:模拟公众号编辑器宽度,更好的排版显示
|
||||
this.render.articleDiv.addClass('wechat-article-wrapper');
|
||||
this.renderDiv.appendChild(this.render.articleDiv);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[WechatPreview] 挂载文章容器失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建工具栏
|
||||
*/
|
||||
private buildToolbar(parent: HTMLDivElement): void {
|
||||
let lineDiv;
|
||||
|
||||
// 公众号选择
|
||||
if (this.settings.wxInfo.length > 1 || Platform.isDesktop) {
|
||||
lineDiv = parent.createDiv({ cls: 'toolbar-line' });
|
||||
|
||||
const wxLabel = lineDiv.createDiv({ cls: 'style-label' });
|
||||
wxLabel.innerText = '公众号';
|
||||
|
||||
const wxSelect = lineDiv.createEl('select', { cls: 'wechat-select' });
|
||||
wxSelect.onchange = async () => {
|
||||
this.currentAppId = wxSelect.value;
|
||||
this.onAppIdChanged();
|
||||
};
|
||||
|
||||
const defaultOp = wxSelect.createEl('option');
|
||||
defaultOp.value = '';
|
||||
defaultOp.text = '请在设置里配置公众号';
|
||||
|
||||
for (let i = 0; i < this.settings.wxInfo.length; i++) {
|
||||
const op = wxSelect.createEl('option');
|
||||
const wx = this.settings.wxInfo[i];
|
||||
op.value = wx.appid;
|
||||
op.text = wx.name;
|
||||
if (i === 0) {
|
||||
op.selected = true;
|
||||
this.currentAppId = wx.appid;
|
||||
}
|
||||
}
|
||||
this.wechatSelect = wxSelect;
|
||||
|
||||
if (Platform.isDesktop) {
|
||||
const separator = lineDiv.createDiv({ cls: 'toolbar-separator' });
|
||||
const openBtn = lineDiv.createEl('button', { text: '🌐 去公众号后台', cls: 'toolbar-button purple-gradient' });
|
||||
openBtn.onclick = async () => {
|
||||
const { shell } = require('electron');
|
||||
shell.openExternal('https://mp.weixin.qq.com');
|
||||
uevent('open-mp');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 操作按钮行
|
||||
lineDiv = parent.createDiv({ cls: 'toolbar-line flex-wrap' });
|
||||
|
||||
const refreshBtn = lineDiv.createEl('button', { text: '🔄 刷新', cls: 'toolbar-button purple-gradient' });
|
||||
refreshBtn.onclick = async () => {
|
||||
if (this.onRefreshCallback) {
|
||||
await this.onRefreshCallback();
|
||||
}
|
||||
};
|
||||
|
||||
if (Platform.isDesktop) {
|
||||
const copyBtn = lineDiv.createEl('button', { text: '📋 复制', cls: 'toolbar-button' });
|
||||
copyBtn.onclick = async () => {
|
||||
try {
|
||||
await this.render.copyArticle();
|
||||
new Notice('复制成功,请到公众号编辑器粘贴。');
|
||||
uevent('copy');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
new Notice('复制失败: ' + error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const uploadImgBtn = lineDiv.createEl('button', { text: '📤 上传图片', cls: 'toolbar-button' });
|
||||
uploadImgBtn.onclick = async () => await this.uploadImages();
|
||||
|
||||
const postBtn = lineDiv.createEl('button', { text: '📝 发草稿', cls: 'toolbar-button' });
|
||||
postBtn.onclick = async () => await this.postArticle();
|
||||
|
||||
const imagesBtn = lineDiv.createEl('button', { text: '🖼️ 图片/文字', cls: 'toolbar-button' });
|
||||
imagesBtn.onclick = async () => await this.postImages();
|
||||
|
||||
if (Platform.isDesktop && this.settings.isAuthKeyVaild()) {
|
||||
const htmlBtn = lineDiv.createEl('button', { text: '💾 导出HTML', cls: 'toolbar-button' });
|
||||
htmlBtn.onclick = async () => await this.exportHTML();
|
||||
}
|
||||
|
||||
// 封面选择
|
||||
this.buildCoverSelector(parent);
|
||||
|
||||
// 样式选择(如果启用)
|
||||
if (this.settings.showStyleUI) {
|
||||
this.buildStyleSelector(parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建封面选择器
|
||||
*/
|
||||
private buildCoverSelector(parent: HTMLDivElement): void {
|
||||
const lineDiv = parent.createDiv({ cls: 'toolbar-line' });
|
||||
const coverTitle = lineDiv.createDiv({ cls: 'style-label' });
|
||||
coverTitle.innerText = '封面';
|
||||
|
||||
this.useDefaultCover = lineDiv.createEl('input', { cls: 'input-style' });
|
||||
this.useDefaultCover.setAttr('type', 'radio');
|
||||
this.useDefaultCover.setAttr('name', 'cover');
|
||||
this.useDefaultCover.setAttr('value', 'default');
|
||||
this.useDefaultCover.setAttr('checked', true);
|
||||
this.useDefaultCover.id = 'default-cover';
|
||||
this.useDefaultCover.onchange = async () => {
|
||||
if (this.useDefaultCover?.checked && this.coverEl) {
|
||||
this.coverEl.setAttr('style', 'visibility:hidden;width:0px;');
|
||||
}
|
||||
};
|
||||
|
||||
const defaultLabel = lineDiv.createEl('label');
|
||||
defaultLabel.innerText = '默认';
|
||||
defaultLabel.setAttr('for', 'default-cover');
|
||||
|
||||
this.useLocalCover = lineDiv.createEl('input', { cls: 'input-style' });
|
||||
this.useLocalCover.setAttr('type', 'radio');
|
||||
this.useLocalCover.setAttr('name', 'cover');
|
||||
this.useLocalCover.setAttr('value', 'local');
|
||||
this.useLocalCover.id = 'local-cover';
|
||||
this.useLocalCover.setAttr('style', 'margin-left:20px;');
|
||||
this.useLocalCover.onchange = async () => {
|
||||
if (this.useLocalCover?.checked && this.coverEl) {
|
||||
this.coverEl.setAttr('style', 'visibility:visible;width:180px;');
|
||||
}
|
||||
};
|
||||
|
||||
const localLabel = lineDiv.createEl('label');
|
||||
localLabel.setAttr('for', 'local-cover');
|
||||
localLabel.innerText = '上传';
|
||||
|
||||
this.coverEl = lineDiv.createEl('input', { cls: 'upload-input' });
|
||||
this.coverEl.setAttr('type', 'file');
|
||||
this.coverEl.setAttr('placeholder', '封面图片');
|
||||
this.coverEl.setAttr('accept', '.png, .jpg, .jpeg');
|
||||
this.coverEl.setAttr('name', 'cover');
|
||||
this.coverEl.id = 'cover-input';
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建样式选择器
|
||||
*/
|
||||
private buildStyleSelector(parent: HTMLDivElement): void {
|
||||
const lineDiv = parent.createDiv({ cls: 'toolbar-line flex-wrap' });
|
||||
|
||||
const cssStyle = lineDiv.createDiv({ cls: 'style-label' });
|
||||
cssStyle.innerText = '样式';
|
||||
|
||||
const selectBtn = lineDiv.createEl('select', { cls: 'style-select' });
|
||||
selectBtn.onchange = async () => {
|
||||
this.currentTheme = selectBtn.value;
|
||||
this.render.updateStyle(selectBtn.value);
|
||||
};
|
||||
|
||||
for (let s of this.assetsManager.themes) {
|
||||
const op = selectBtn.createEl('option');
|
||||
op.value = s.className;
|
||||
op.text = s.name;
|
||||
op.selected = s.className === this.settings.defaultStyle;
|
||||
}
|
||||
this.themeSelect = selectBtn;
|
||||
|
||||
const separator = lineDiv.createDiv({ cls: 'toolbar-separator' });
|
||||
|
||||
const highlightStyle = lineDiv.createDiv({ cls: 'style-label' });
|
||||
highlightStyle.innerText = '代码高亮';
|
||||
|
||||
const highlightStyleBtn = lineDiv.createEl('select', { cls: 'style-select' });
|
||||
highlightStyleBtn.onchange = async () => {
|
||||
this.currentHighlight = highlightStyleBtn.value;
|
||||
this.render.updateHighLight(highlightStyleBtn.value);
|
||||
};
|
||||
|
||||
const highlights = this.assetsManager.highlights;
|
||||
for (let h of highlights) {
|
||||
const op = highlightStyleBtn.createEl('option');
|
||||
op.value = h.url;
|
||||
op.text = h.name;
|
||||
op.selected = h.url === this.currentHighlight;
|
||||
}
|
||||
this.highlightSelect = highlightStyleBtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示微信预览视图
|
||||
*/
|
||||
show(): void {
|
||||
if (this.container) {
|
||||
this.container.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏微信预览视图
|
||||
*/
|
||||
hide(): void {
|
||||
if (this.container) {
|
||||
this.container.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 公众号切换处理
|
||||
*/
|
||||
private onAppIdChanged(): void {
|
||||
if (this.onAppIdChangeCallback) {
|
||||
this.onAppIdChangeCallback(this.currentAppId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片
|
||||
*/
|
||||
private async uploadImages(): Promise<void> {
|
||||
// 待实现 - 从原来的 note-preview.ts 迁移
|
||||
new Notice('上传图片功能');
|
||||
uevent('upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布草稿
|
||||
*/
|
||||
private async postArticle(): Promise<void> {
|
||||
try {
|
||||
if (!this.currentFile) {
|
||||
new Notice('请先打开一个 Markdown 文件');
|
||||
return;
|
||||
}
|
||||
if (!this.currentAppId) {
|
||||
new Notice('请先在设置中配置公众号信息');
|
||||
return;
|
||||
}
|
||||
new Notice('正在创建公众号草稿...');
|
||||
const mediaId = await this.render.postArticle(this.currentAppId, this.getLocalCoverFile());
|
||||
if (mediaId) {
|
||||
new Notice('草稿创建成功');
|
||||
}
|
||||
uevent('pub');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
new Notice('发布失败: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布图片/文字
|
||||
*/
|
||||
private async postImages(): Promise<void> {
|
||||
try {
|
||||
if (!this.currentFile) {
|
||||
new Notice('请先打开一个 Markdown 文件');
|
||||
return;
|
||||
}
|
||||
if (!this.currentAppId) {
|
||||
new Notice('请先在设置中配置公众号信息');
|
||||
return;
|
||||
}
|
||||
new Notice('正在创建图片/文字消息草稿...');
|
||||
const mediaId = await this.render.postImages(this.currentAppId);
|
||||
if (mediaId) {
|
||||
new Notice('图片/文字草稿创建成功');
|
||||
}
|
||||
uevent('pub-images');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
new Notice('发布失败: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出HTML
|
||||
*/
|
||||
private async exportHTML(): Promise<void> {
|
||||
try {
|
||||
if (!this.currentFile) {
|
||||
new Notice('请先打开一个 Markdown 文件');
|
||||
return;
|
||||
}
|
||||
await this.render.exportHTML();
|
||||
new Notice('HTML 导出完成');
|
||||
uevent('export-html');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
new Notice('导出失败: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新样式和高亮显示
|
||||
*/
|
||||
updateStyleAndHighlight(theme: string, highlight: string): void {
|
||||
this.currentTheme = theme;
|
||||
this.currentHighlight = highlight;
|
||||
|
||||
if (this.themeSelect) {
|
||||
this.themeSelect.value = theme;
|
||||
}
|
||||
if (this.highlightSelect) {
|
||||
this.highlightSelect.value = highlight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
destroy(): void {
|
||||
this.toolbar = null;
|
||||
this.renderDiv = null;
|
||||
this.wechatSelect = null;
|
||||
this.themeSelect = null;
|
||||
this.highlightSelect = null;
|
||||
this.coverEl = null;
|
||||
this.useDefaultCover = null;
|
||||
this.useLocalCover = null;
|
||||
}
|
||||
|
||||
/** 获取本地上传封面(如果选择了“上传”单选并选了文件) */
|
||||
private getLocalCoverFile(): File | null {
|
||||
if (this.useLocalCover?.checked && this.coverEl?.files && this.coverEl.files.length > 0) {
|
||||
return this.coverEl.files[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 对外:发布草稿(供外层菜单调用) */
|
||||
async postDraft() { await this.postArticle(); }
|
||||
|
||||
/** 由上层在切换/渲染时注入当前文件 */
|
||||
setFile(file: TFile | null) { this.currentFile = file; }
|
||||
}
|
||||
@@ -1,4 +1,13 @@
|
||||
/* 文件:xiaohongshu/preview-view.ts — 小红书预览视图组件:顶部工具栏、分页导航、底部切图按钮。 */
|
||||
/**
|
||||
* 文件:xiaohongshu/xhs-preview.ts
|
||||
* 作用:小红书预览视图组件,专门处理小红书平台的预览、分页和切图功能
|
||||
*
|
||||
* 功能:
|
||||
* 1. 渲染小红书专属的预览界面(顶部工具栏、分页导航、底部切图按钮)
|
||||
* 2. 处理文章内容的小红书格式化和分页
|
||||
* 3. 提供切图功能(当前页/全部页)
|
||||
* 4. 管理小红书特有的样式和字体设置
|
||||
*/
|
||||
|
||||
import { Notice, TFile } from 'obsidian';
|
||||
import { NMPSettings } from '../settings';
|
||||
@@ -7,9 +16,9 @@ import { paginateArticle, renderPage, PageInfo } from './paginator';
|
||||
import { sliceCurrentPage, sliceAllPages } from './slice';
|
||||
|
||||
/**
|
||||
* 小红书预览视图
|
||||
* 小红书预览视图类
|
||||
*/
|
||||
export class XiaohongshuPreviewView {
|
||||
export class XiaohongshuPreview {
|
||||
container: HTMLElement;
|
||||
settings: NMPSettings;
|
||||
assetsManager: AssetsManager;
|
||||
@@ -17,16 +26,16 @@ export class XiaohongshuPreviewView {
|
||||
currentFile: TFile | null = null;
|
||||
|
||||
// UI 元素
|
||||
topToolbar: HTMLDivElement;
|
||||
templateSelect: HTMLSelectElement;
|
||||
themeSelect: HTMLSelectElement;
|
||||
fontSelect: HTMLSelectElement;
|
||||
fontSizeDisplay: HTMLSpanElement;
|
||||
topToolbar!: HTMLDivElement;
|
||||
templateSelect!: HTMLSelectElement;
|
||||
themeSelect!: HTMLSelectElement;
|
||||
fontSelect!: HTMLSelectElement;
|
||||
fontSizeDisplay!: HTMLSpanElement;
|
||||
|
||||
pageContainer: HTMLDivElement;
|
||||
bottomToolbar: HTMLDivElement;
|
||||
pageNavigation: HTMLDivElement;
|
||||
pageNumberDisplay: HTMLSpanElement;
|
||||
pageContainer!: HTMLDivElement;
|
||||
bottomToolbar!: HTMLDivElement;
|
||||
pageNavigation!: HTMLDivElement;
|
||||
pageNumberDisplay!: HTMLSpanElement;
|
||||
|
||||
// 分页数据
|
||||
pages: PageInfo[] = [];
|
||||
@@ -289,7 +298,7 @@ export class XiaohongshuPreviewView {
|
||||
new Notice('✅ 当前页切图完成');
|
||||
} catch (error) {
|
||||
console.error('切图失败:', error);
|
||||
new Notice('❌ 切图失败: ' + error.message);
|
||||
new Notice('❌ 切图失败: ' + (error instanceof Error ? error.message : String(error)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +351,42 @@ export class XiaohongshuPreviewView {
|
||||
new Notice(`✅ 全部页切图完成:共 ${this.pages.length} 张`);
|
||||
} catch (error) {
|
||||
console.error('批量切图失败:', error);
|
||||
new Notice('❌ 批量切图失败: ' + error.message);
|
||||
new Notice('❌ 批量切图失败: ' + (error instanceof Error ? error.message : String(error)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示小红书预览视图
|
||||
*/
|
||||
show(): void {
|
||||
if (this.container) {
|
||||
this.container.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏小红书预览视图
|
||||
*/
|
||||
hide(): void {
|
||||
if (this.container) {
|
||||
this.container.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
destroy(): void {
|
||||
this.topToolbar = null as any;
|
||||
this.templateSelect = null as any;
|
||||
this.themeSelect = null as any;
|
||||
this.fontSelect = null as any;
|
||||
this.fontSizeDisplay = null as any;
|
||||
this.pageContainer = null as any;
|
||||
this.bottomToolbar = null as any;
|
||||
this.pageNavigation = null as any;
|
||||
this.pageNumberDisplay = null as any;
|
||||
this.pages = [];
|
||||
this.currentFile = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user