diff --git a/src/batch-publish-modal.ts b/src/batch-publish-modal.ts index 862639f..f619be7 100644 --- a/src/batch-publish-modal.ts +++ b/src/batch-publish-modal.ts @@ -538,7 +538,7 @@ export class BatchPublishModal extends Modal { const preview = this.plugin.getNotePreview(); if (preview) { // 确保预览器处于微信模式 - preview.currentPlatform = 'wechat'; + preview.setCurrentPlatform('wechat'); await preview.renderMarkdown(file); await preview.postToWechat(); } else { diff --git a/src/main.ts b/src/main.ts index 4b2126d..f4c9895 100644 --- a/src/main.ts +++ b/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 { NotePreview, VIEW_TYPE_NOTE_PREVIEW } from './mp-preview'; import { NMPSettings } from './settings'; import { NoteToMpSettingTab } from './setting-tab'; import AssetsManager from './assets'; diff --git a/src/note-preview.ts b/src/mp-preview.ts similarity index 92% rename from src/note-preview.ts rename to src/mp-preview.ts index c226610..0f0f4d7 100644 --- a/src/note-preview.ts +++ b/src/mp-preview.ts @@ -7,7 +7,7 @@ * - 与批量发布/图片处理集成预留 */ -import { EventRef, ItemView, Workspace, WorkspaceLeaf, Notice, Platform, TFile, TFolder, TAbstractFile, Plugin } from 'obsidian'; +import { EventRef, ItemView, Workspace, WorkspaceLeaf, Notice, Platform as ObsidianPlatform, TFile, TFolder, TAbstractFile, Plugin } from 'obsidian'; import { uevent, debounce, waitForLayoutReady } from './utils'; import { NMPSettings } from './settings'; import AssetsManager from './assets'; @@ -20,7 +20,9 @@ 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 { XiaohongshuPreviewView } from './xiaohongshu/xhs-preview'; +import { PlatformChooser } from './platform-chooser'; +import { Platform } from './types'; // 切图功能 import { sliceArticleImage } from './slice-image'; @@ -40,7 +42,7 @@ export class NotePreview extends ItemView { useLocalCover: HTMLInputElement; msgView: HTMLDivElement; wechatSelect: HTMLSelectElement; - platformSelect: HTMLSelectElement; // 新增:平台选择器 + platformChooser: PlatformChooser; // 平台选择器组件 themeSelect: HTMLSelectElement; highlightSelect: HTMLSelectElement; listeners?: EventRef[]; @@ -53,7 +55,7 @@ export class NotePreview extends ItemView { currentTheme: string; currentHighlight: string; currentAppId: string; - currentPlatform: string = 'wechat'; // 新增:当前选择的平台,默认微信 + currentPlatform: Platform = 'wechat'; // 当前选择的平台,默认微信 markedParser: MarkedParser; cachedElements: Map = new Map(); _articleRender: ArticleRender | null = null; @@ -84,6 +86,14 @@ export class NotePreview extends ItemView { return '笔记预览'; } + getCurrentPlatform(): Platform { + return this.currentPlatform; + } + + setCurrentPlatform(platform: Platform): void { + this.currentPlatform = platform; + } + get render() { if (!this._articleRender) { this._articleRender = new ArticleRender(this.app, this, this.styleEl, this.articleDiv); @@ -198,36 +208,16 @@ export class NotePreview extends ItemView { this.toolbar = parent.createDiv({ cls: 'preview-toolbar' }); let lineDiv; - // 平台选择器(新增)- 始终显示 - lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line platform-selector-line' }); - lineDiv.style.cssText = 'display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%); border-left: 4px solid #1e88e5; border-radius: 6px; margin: 8px 10px;'; - - const platformLabel = lineDiv.createDiv({ cls: 'style-label' }); - platformLabel.innerText = '发布平台'; - platformLabel.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;'; - - const platformSelect = lineDiv.createEl('select', { cls: 'style-select' }); - platformSelect.style.cssText = 'padding: 6px 12px; border: 1px solid #dadce0; border-radius: 6px; background: white; font-size: 13px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.1); min-width: 150px; font-weight: 500;'; - - // 添加平台选项 - const wechatOption = platformSelect.createEl('option'); - wechatOption.value = 'wechat'; - wechatOption.text = '微信公众号'; - wechatOption.selected = true; - - const xiaohongshuOption = platformSelect.createEl('option'); - xiaohongshuOption.value = 'xiaohongshu'; - xiaohongshuOption.text = '小红书'; - - platformSelect.onchange = async () => { - this.currentPlatform = platformSelect.value; + // 使用平台选择器组件 + this.platformChooser = new PlatformChooser(this.toolbar, this.currentPlatform); + this.platformChooser.build(); + this.platformChooser.onPlatformChange(async (platform: Platform) => { + this.currentPlatform = platform; await this.onPlatformChanged(); - }; - - this.platformSelect = platformSelect; + }); // 公众号 - if (this.settings.wxInfo.length > 1 || Platform.isDesktop) { + if (this.settings.wxInfo.length > 1 || ObsidianPlatform.isDesktop) { lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line wechat-only' }); lineDiv.style.cssText = 'display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: white; border-radius: 6px; margin: 8px 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.08);'; @@ -256,7 +246,7 @@ export class NotePreview extends ItemView { } this.wechatSelect = wxSelect; - if (Platform.isDesktop) { + if (ObsidianPlatform.isDesktop) { // 分隔线 const separator = lineDiv.createDiv(); separator.style.cssText = 'width: 1px; height: 24px; background: #dadce0; margin: 0 4px;'; @@ -293,7 +283,7 @@ export class NotePreview extends ItemView { await this.renderMarkdown(); uevent('refresh'); } - if (Platform.isDesktop) { + if (ObsidianPlatform.isDesktop) { const copyBtn = lineDiv.createEl('button', { text: '📋 复制' }); copyBtn.style.cssText = 'padding: 6px 14px; background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(30, 136, 229, 0.3);'; copyBtn.onmouseenter = () => copyBtn.style.transform = 'translateY(-1px)'; @@ -341,7 +331,7 @@ export class NotePreview extends ItemView { uevent('pub-images'); } - if (Platform.isDesktop && this.settings.isAuthKeyVaild()) { + if (ObsidianPlatform.isDesktop && this.settings.isAuthKeyVaild()) { const htmlBtn = lineDiv.createEl('button', { text: '💾 导出HTML' }); htmlBtn.style.cssText = 'padding: 6px 14px; background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(30, 136, 229, 0.3);'; htmlBtn.onmouseenter = () => htmlBtn.style.transform = 'translateY(-1px)'; @@ -569,22 +559,21 @@ export class NotePreview extends ItemView { * 切换到小红书预览模式 */ private switchToXiaohongshuMode() { - // 隐藏微信相关的工具栏行和平台选择器 + // 隐藏微信相关的工具栏行 if (this.toolbar) { const wechatLines = this.toolbar.querySelectorAll('.wechat-only'); wechatLines.forEach((line: HTMLElement) => { line.style.display = 'none'; }); - - // 也隐藏平台选择器行 - // const platformLine = this.toolbar.querySelector('.platform-selector-line') as HTMLElement; - // if (platformLine) { - // platformLine.style.display = 'none'; - // } + } + + // 平台选择器保持显示 + if (this.platformChooser) { + this.platformChooser.show(); } // 隐藏渲染区域 - if (this.renderDiv) this.renderDiv.style.display = 'none'; + //if (this.renderDiv) this.renderDiv.style.display = 'none'; // 创建或显示小红书预览视图 if (!this._xiaohongshuPreview) { @@ -599,7 +588,7 @@ export class NotePreview extends ItemView { this._xiaohongshuPreview.onPublishCallback = async () => { await this.onXiaohongshuPublish(); }; - this._xiaohongshuPreview.onPlatformChangeCallback = async (platform: string) => { + this._xiaohongshuPreview.onPlatformChangeCallback = async (platform: Platform) => { this.currentPlatform = platform; if (platform === 'wechat') { await this.onPlatformChanged(); @@ -622,18 +611,17 @@ export class NotePreview extends ItemView { * 切换到微信公众号模式 */ private switchToWechatMode() { - // 显示微信相关的工具栏行和平台选择器 + // 显示微信相关的工具栏行 if (this.toolbar) { const wechatLines = this.toolbar.querySelectorAll('.wechat-only'); wechatLines.forEach((line: HTMLElement) => { line.style.display = 'flex'; }); - - // 也显示平台选择器行 - const platformLine = this.toolbar.querySelector('.platform-selector-line') as HTMLElement; - if (platformLine) { - platformLine.style.display = 'flex'; - } + } + + // 平台选择器保持显示 + if (this.platformChooser) { + this.platformChooser.show(); } // 显示渲染区域 @@ -918,4 +906,4 @@ export class NotePreview extends ItemView { this.isCancelUpload = false; } } -} \ No newline at end of file +} diff --git a/src/platform-chooser.ts b/src/platform-chooser.ts new file mode 100644 index 0000000..6cae912 --- /dev/null +++ b/src/platform-chooser.ts @@ -0,0 +1,134 @@ +/** + * 平台选择器组件 + * 提供统一的平台选择界面和切换逻辑 + */ + +import { Platform, PlatformInfo, PlatformChangeCallback, SUPPORTED_PLATFORMS } from './types'; + +export class PlatformChooser { + private container: HTMLElement; + private selectElement: HTMLSelectElement; + private currentPlatform: Platform; + private onChangeCallback?: PlatformChangeCallback; + + constructor(container: HTMLElement, defaultPlatform: Platform = 'wechat') { + this.container = container; + this.currentPlatform = defaultPlatform; + } + + /** + * 构建平台选择器UI + */ + public build(): HTMLElement { + const lineDiv = this.container.createDiv({ cls: 'toolbar-line platform-selector-line' }); + lineDiv.style.cssText = 'display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%); border-left: 4px solid #1e88e5; border-radius: 6px; margin: 8px 10px;'; + + // 标签 + const platformLabel = lineDiv.createDiv({ cls: 'style-label' }); + platformLabel.innerText = '发布平台'; + platformLabel.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;'; + + // 选择器 + this.selectElement = lineDiv.createEl('select', { cls: 'style-select' }); + this.selectElement.style.cssText = 'padding: 6px 12px; border: 1px solid #dadce0; border-radius: 6px; background: white; font-size: 13px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.1); min-width: 150px; font-weight: 500;'; + + // 添加平台选项 + SUPPORTED_PLATFORMS.forEach(platform => { + const option = this.selectElement.createEl('option'); + option.value = platform.id; + option.text = `${platform.icon || ''} ${platform.name}`.trim(); + if (platform.id === this.currentPlatform) { + option.selected = true; + } + }); + + // 绑定切换事件 + this.selectElement.onchange = async () => { + const newPlatform = this.selectElement.value as Platform; + await this.switchPlatform(newPlatform); + }; + + return lineDiv; + } + + /** + * 设置平台切换回调 + */ + public onPlatformChange(callback: PlatformChangeCallback): void { + this.onChangeCallback = callback; + } + + /** + * 获取当前选择的平台 + */ + public getCurrentPlatform(): Platform { + return this.currentPlatform; + } + + /** + * 设置当前平台(程序化切换) + */ + public setCurrentPlatform(platform: Platform): void { + this.currentPlatform = platform; + if (this.selectElement) { + this.selectElement.value = platform; + } + } + + /** + * 切换平台 + */ + private async switchPlatform(platform: Platform): Promise { + if (platform === this.currentPlatform) { + return; + } + + console.log(`[PlatformChooser] 切换平台: ${this.currentPlatform} -> ${platform}`); + + const oldPlatform = this.currentPlatform; + this.currentPlatform = platform; + + // 调用回调函数 + if (this.onChangeCallback) { + try { + await this.onChangeCallback(platform); + } catch (error) { + console.error('[PlatformChooser] 平台切换失败:', error); + // 回滚到旧平台 + this.currentPlatform = oldPlatform; + this.selectElement.value = oldPlatform; + } + } + } + + /** + * 显示选择器 + */ + public show(): void { + if (this.container) { + const line = this.container.querySelector('.platform-selector-line') as HTMLElement; + if (line) { + line.style.display = 'flex'; + } + } + } + + /** + * 隐藏选择器 + */ + public hide(): void { + if (this.container) { + const line = this.container.querySelector('.platform-selector-line') as HTMLElement; + if (line) { + line.style.display = 'none'; + } + } + } + + /** + * 清理资源 + */ + public cleanup(): void { + this.onChangeCallback = undefined; + } +} diff --git a/src/platform-chooser.ts.bak b/src/platform-chooser.ts.bak new file mode 100644 index 0000000..826d68e --- /dev/null +++ b/src/platform-chooser.ts.bak @@ -0,0 +1,108 @@ +/** + * 平台选择器组件 + * 提供统一的平台选择界面,配合 PlatformManager 处理切换逻辑 + */ + +import { Platform, PlatformInfo, SUPPORTED_PLATFORMS } from './types'; +import { PlatformManager } from './platform-manager'; + +export class PlatformChooser { + private container: HTMLElement; + private selectElement: HTMLSelectElement; + private platformManager: PlatformManager; + + constructor(container: HTMLElement, platformManager: PlatformManager) { + this.container = container; + this.platformManager = platformManager; + } + + /** + * 构建平台选择器UI + */ + public build(): HTMLElement { + const lineDiv = this.container.createDiv({ cls: 'toolbar-line platform-selector-line' }); + lineDiv.style.cssText = 'display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%); border-left: 4px solid #1e88e5; border-radius: 6px; margin: 8px 10px;'; + + // 标签 + const platformLabel = lineDiv.createDiv({ cls: 'style-label' }); + platformLabel.innerText = '发布平台'; + platformLabel.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;'; + + // 选择器 + this.selectElement = lineDiv.createEl('select', { cls: 'style-select' }); + this.selectElement.style.cssText = 'padding: 6px 12px; border: 1px solid #dadce0; border-radius: 6px; background: white; font-size: 13px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.1); min-width: 150px; font-weight: 500;'; + + // 添加平台选项 + const currentPlatform = this.platformManager.getCurrentPlatform(); + SUPPORTED_PLATFORMS.forEach(platform => { + const option = this.selectElement.createEl('option'); + option.value = platform.id; + option.text = `${platform.icon || ''} ${platform.name}`.trim(); + if (platform.id === currentPlatform) { + option.selected = true; + } + }); + + // 绑定切换事件 - 直接调用 PlatformManager + this.selectElement.onchange = async () => { + const newPlatform = this.selectElement.value as Platform; + try { + await this.platformManager.switchToPlatform(newPlatform); + } catch (error) { + console.error('[PlatformChooser] 平台切换失败:', error); + // 回滚选择器 + this.selectElement.value = this.platformManager.getCurrentPlatform(); + } + }; + + return lineDiv; + } + + /** + * 获取当前选择的平台 + */ + public getCurrentPlatform(): Platform { + return this.platformManager.getCurrentPlatform(); + } + + /** + * 设置当前平台(程序化切换) + */ + public async setCurrentPlatform(platform: Platform): Promise { + await this.platformManager.switchToPlatform(platform); + if (this.selectElement) { + this.selectElement.value = platform; + } + } + + /** + * 显示选择器 + */ + public show(): void { + if (this.container) { + const line = this.container.querySelector('.platform-selector-line') as HTMLElement; + if (line) { + line.style.display = 'flex'; + } + } + } + + /** + * 隐藏选择器 + */ + public hide(): void { + if (this.container) { + const line = this.container.querySelector('.platform-selector-line') as HTMLElement; + if (line) { + line.style.display = 'none'; + } + } + } + + /** + * 清理资源 + */ + public cleanup(): void { + // 清理工作由 PlatformManager 负责 + } +} diff --git a/src/platform-manager.ts b/src/platform-manager.ts new file mode 100644 index 0000000..b5a5f48 --- /dev/null +++ b/src/platform-manager.ts @@ -0,0 +1,173 @@ +/** + * 平台管理器 + * 负责管理和协调不同平台的预览视图 + */ + +import { Platform, IPlatformPreview } from './types'; +import { PlatformChooser } from './platform-chooser'; + +export class PlatformManager { + private currentPlatform: Platform; + private platformChooser: PlatformChooser; + private platformPreviews: Map; + private container: HTMLElement; + + constructor(container: HTMLElement, defaultPlatform: Platform = 'wechat') { + this.currentPlatform = defaultPlatform; + this.container = container; + this.platformPreviews = new Map(); + } + + /** + * 初始化平台选择器 + */ + public initChooser(toolbarContainer: HTMLElement): PlatformChooser { + this.platformChooser = new PlatformChooser(toolbarContainer, this.currentPlatform); + this.platformChooser.build(); + this.platformChooser.onPlatformChange(async (platform: Platform) => { + await this.switchToPlatform(platform); + }); + return this.platformChooser; + } + + /** + * 注册平台预览视图 + */ + public registerPlatformPreview(platform: Platform, preview: IPlatformPreview): void { + this.platformPreviews.set(platform, preview); + } + + /** + * 获取当前平台 + */ + public getCurrentPlatform(): Platform { + return this.currentPlatform; + } + + /** + * 获取指定平台的预览视图 + */ + public getPlatformPreview(platform: Platform): IPlatformPreview | undefined { + return this.platformPreviews.get(platform); + } + + /** + * 获取当前平台的预览视图 + */ + public getCurrentPreview(): IPlatformPreview | undefined { + return this.platformPreviews.get(this.currentPlatform); + } + + /** + * 切换到指定平台 + */ + public async switchToPlatform(platform: Platform): Promise { + if (platform === this.currentPlatform) { + return; + } + + console.log(`[PlatformManager] 切换平台: ${this.currentPlatform} -> ${platform}`); + + // 隐藏当前平台的预览 + const currentPreview = this.platformPreviews.get(this.currentPlatform); + if (currentPreview) { + this.hidePreview(this.currentPlatform); + } + + // 更新当前平台 + const oldPlatform = this.currentPlatform; + this.currentPlatform = platform; + + // 显示新平台的预览 + const newPreview = this.platformPreviews.get(platform); + if (newPreview) { + this.showPreview(platform); + } else { + console.warn(`[PlatformManager] 平台 ${platform} 的预览视图未注册`); + // 回滚 + this.currentPlatform = oldPlatform; + if (this.platformChooser) { + this.platformChooser.setCurrentPlatform(oldPlatform); + } + return; + } + + // 更新选择器 + if (this.platformChooser) { + this.platformChooser.setCurrentPlatform(platform); + } + } + + /** + * 显示指定平台的预览 + */ + private showPreview(platform: Platform): void { + const preview = this.platformPreviews.get(platform); + if (preview) { + // 调用预览视图的显示方法 + const container = this.getPreviewContainer(platform); + if (container) { + container.style.display = 'flex'; + } + } + } + + /** + * 隐藏指定平台的预览 + */ + private hidePreview(platform: Platform): void { + const preview = this.platformPreviews.get(platform); + if (preview) { + // 调用预览视图的隐藏方法 + const container = this.getPreviewContainer(platform); + if (container) { + container.style.display = 'none'; + } + } + } + + /** + * 获取平台的容器元素 + */ + private getPreviewContainer(platform: Platform): HTMLElement | null { + switch (platform) { + case 'wechat': + return this.container.querySelector('.wechat-preview-container') as HTMLElement; + case 'xiaohongshu': + return this.container.querySelector('.xiaohongshu-preview-container') as HTMLElement; + default: + return null; + } + } + + /** + * 显示平台选择器 + */ + public showChooser(): void { + if (this.platformChooser) { + this.platformChooser.show(); + } + } + + /** + * 隐藏平台选择器 + */ + public hideChooser(): void { + if (this.platformChooser) { + this.platformChooser.hide(); + } + } + + /** + * 清理资源 + */ + public cleanup(): void { + if (this.platformChooser) { + this.platformChooser.cleanup(); + } + this.platformPreviews.forEach(preview => { + preview.cleanup(); + }); + this.platformPreviews.clear(); + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..a46d122 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,63 @@ +/** + * 公共类型定义文件 + * 定义平台类型、回调接口等公共类型 + */ + +/** + * 支持的发布平台类型 + */ +export type Platform = 'wechat' | 'xiaohongshu'; + +/** + * 平台信息接口 + */ +export interface PlatformInfo { + id: Platform; + name: string; + icon?: string; +} + +/** + * 平台切换回调接口 + */ +export interface PlatformChangeCallback { + (platform: Platform): Promise; +} + +/** + * 平台预览视图接口 + */ +export interface IPlatformPreview { + /** + * 构建预览界面 + */ + build(): void; + + /** + * 渲染文章内容 + */ + renderArticle(html: string, file: any): Promise; + + /** + * 显示预览 + */ + show(): void; + + /** + * 隐藏预览 + */ + hide(): void; + + /** + * 清理资源 + */ + cleanup(): void; +} + +/** + * 支持的平台列表 + */ +export const SUPPORTED_PLATFORMS: PlatformInfo[] = [ + { id: 'wechat', name: '微信公众号', icon: '📱' }, + { id: 'xiaohongshu', name: '小红书', icon: '📕' } +]; diff --git a/src/wechat-preview.ts b/src/wechat-preview.ts new file mode 100644 index 0000000..4e2cec3 --- /dev/null +++ b/src/wechat-preview.ts @@ -0,0 +1,330 @@ +/** + * 微信公众号预览视图 + * 专门处理微信公众号的预览和发布逻辑 + */ + +import { Notice, Platform as ObsidianPlatform, TFile } from 'obsidian'; +import { IPlatformPreview } from './types'; +import { NMPSettings } from './settings'; +import AssetsManager from './assets'; +import { ArticleRender } from './article-render'; + +export class WechatPreview implements IPlatformPreview { + private container: HTMLElement; + private toolbar: HTMLElement; + private renderDiv: HTMLElement; + private articleDiv: HTMLElement; + private settings: NMPSettings; + private assetsManager: AssetsManager; + private render: ArticleRender | null = null; + private app: any; + private itemView: any; + + // UI 元素 + private wechatSelect: HTMLSelectElement; + private themeSelect: HTMLSelectElement; + private highlightSelect: HTMLSelectElement; + private coverEl: HTMLInputElement; + private useDefaultCover: HTMLInputElement; + private useLocalCover: HTMLInputElement; + + // 数据 + private currentAppId: string; + private currentTheme: string; + private currentHighlight: string; + private currentFile?: TFile; + private articleHTML: string = ''; + + // 回调 + public onRefreshCallback?: () => Promise; + public onCopyCallback?: () => Promise; + public onUploadCallback?: () => Promise; + public onPublishCallback?: () => Promise; + + constructor(container: HTMLElement, app: any, itemView: any) { + this.container = container; + this.app = app; + this.itemView = itemView; + this.settings = NMPSettings.getInstance(); + this.assetsManager = AssetsManager.getInstance(); + this.currentTheme = this.settings.defaultStyle; + this.currentHighlight = this.settings.defaultHighlight; + } + + /** + * 构建微信公众号预览界面 + */ + public build(): void { + this.container.empty(); + this.container.addClass('wechat-preview-container'); + this.container.style.cssText = 'width: 100%; height: 100%; display: flex; flex-direction: column;'; + + // 构建工具栏 + this.buildToolbar(); + + // 构建渲染区域 + this.buildRenderArea(); + } + + /** + * 构建工具栏 + */ + private buildToolbar(): void { + this.toolbar = this.container.createDiv({ cls: 'preview-toolbar wechat-toolbar' }); + let lineDiv; + + // 公众号选择 + if (this.settings.wxInfo.length > 1 || ObsidianPlatform.isDesktop) { + lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line wechat-only' }); + lineDiv.style.cssText = 'display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: white; border-radius: 6px; margin: 8px 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.08);'; + + const wxLabel = lineDiv.createDiv({ cls: 'style-label' }); + wxLabel.innerText = '公众号'; + wxLabel.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;'; + + this.wechatSelect = lineDiv.createEl('select', { cls: 'style-select' }); + this.wechatSelect.style.cssText = 'padding: 6px 12px; border: 1px solid #dadce0; border-radius: 6px; background: white; font-size: 13px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.1); min-width: 200px;'; + + this.wechatSelect.onchange = () => { + this.currentAppId = this.wechatSelect.value; + this.onAppIdChanged(); + }; + + const defaultOp = this.wechatSelect.createEl('option'); + defaultOp.value = ''; + defaultOp.text = '请在设置里配置公众号'; + + for (let i = 0; i < this.settings.wxInfo.length; i++) { + const op = this.wechatSelect.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; + } + } + + if (ObsidianPlatform.isDesktop) { + // 分隔线 + const separator = lineDiv.createDiv(); + separator.style.cssText = 'width: 1px; height: 24px; background: #dadce0; margin: 0 4px;'; + + const openBtn = lineDiv.createEl('button', { text: '🌐 去公众号后台' }); + openBtn.style.cssText = 'padding: 6px 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);'; + openBtn.onmouseenter = () => openBtn.style.transform = 'translateY(-1px)'; + openBtn.onmouseleave = () => openBtn.style.transform = 'translateY(0)'; + openBtn.onclick = () => { + const { shell } = require('electron'); + shell.openExternal('https://mp.weixin.qq.com'); + }; + } + } else if (this.settings.wxInfo.length > 0) { + this.currentAppId = this.settings.wxInfo[0].appid; + } + + // 功能按钮行 + lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line wechat-only' }); + lineDiv.style.cssText = 'display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: white; border-radius: 6px; margin: 8px 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); flex-wrap: wrap;'; + + // 刷新按钮 + const refreshBtn = lineDiv.createEl('button', { text: '🔄 刷新' }); + refreshBtn.style.cssText = 'padding: 6px 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);'; + refreshBtn.onmouseenter = () => refreshBtn.style.transform = 'translateY(-1px)'; + refreshBtn.onmouseleave = () => refreshBtn.style.transform = 'translateY(0)'; + refreshBtn.onclick = async () => { + if (this.onRefreshCallback) { + await this.onRefreshCallback(); + } + }; + + // 复制按钮 + if (ObsidianPlatform.isDesktop) { + const copyBtn = lineDiv.createEl('button', { text: '📋 复制' }); + copyBtn.style.cssText = 'padding: 6px 14px; background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(30, 136, 229, 0.3);'; + copyBtn.onmouseenter = () => copyBtn.style.transform = 'translateY(-1px)'; + copyBtn.onmouseleave = () => copyBtn.style.transform = 'translateY(0)'; + copyBtn.onclick = async () => { + if (this.onCopyCallback) { + await this.onCopyCallback(); + } + }; + } + + // 上传图片按钮 + const uploadImgBtn = lineDiv.createEl('button', { text: '📤 上传图片' }); + uploadImgBtn.style.cssText = 'padding: 6px 14px; background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(30, 136, 229, 0.3);'; + uploadImgBtn.onmouseenter = () => uploadImgBtn.style.transform = 'translateY(-1px)'; + uploadImgBtn.onmouseleave = () => uploadImgBtn.style.transform = 'translateY(0)'; + uploadImgBtn.onclick = async () => { + if (this.onUploadCallback) { + await this.onUploadCallback(); + } + }; + + // 发草稿按钮 + const postBtn = lineDiv.createEl('button', { text: '📝 发草稿' }); + postBtn.style.cssText = 'padding: 6px 14px; background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(30, 136, 229, 0.3);'; + postBtn.onmouseenter = () => postBtn.style.transform = 'translateY(-1px)'; + postBtn.onmouseleave = () => postBtn.style.transform = 'translateY(0)'; + postBtn.onclick = async () => { + if (this.onPublishCallback) { + await this.onPublishCallback(); + } + }; + + // 样式选择(如果启用) + if (this.settings.showStyleUI) { + lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line wechat-only' }); + lineDiv.style.cssText = 'display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: white; border-radius: 6px; margin: 8px 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); flex-wrap: wrap;'; + + const cssStyle = lineDiv.createDiv({ cls: 'style-label' }); + cssStyle.innerText = '样式'; + cssStyle.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;'; + + this.themeSelect = lineDiv.createEl('select', { cls: 'style-select' }); + this.themeSelect.style.cssText = 'padding: 6px 12px; border: 1px solid #dadce0; border-radius: 6px; background: white; font-size: 13px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.1); min-width: 120px;'; + + this.themeSelect.onchange = () => { + this.currentTheme = this.themeSelect.value; + if (this.render) { + this.render.updateStyle(this.themeSelect.value); + } + }; + + for (let s of this.assetsManager.themes) { + const op = this.themeSelect.createEl('option'); + op.value = s.className; + op.text = s.name; + op.selected = s.className === this.settings.defaultStyle; + } + + // 分隔线 + const separator = lineDiv.createDiv(); + separator.style.cssText = 'width: 1px; height: 24px; background: #dadce0; margin: 0 4px;'; + + const highlightStyle = lineDiv.createDiv({ cls: 'style-label' }); + highlightStyle.innerText = '代码高亮'; + highlightStyle.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;'; + + this.highlightSelect = lineDiv.createEl('select', { cls: 'style-select' }); + this.highlightSelect.style.cssText = 'padding: 6px 12px; border: 1px solid #dadce0; border-radius: 6px; background: white; font-size: 13px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.1); min-width: 120px;'; + + this.highlightSelect.onchange = () => { + this.currentHighlight = this.highlightSelect.value; + if (this.render) { + this.render.updateHighLight(this.highlightSelect.value); + } + }; + + for (let s of this.assetsManager.highlights) { + const op = this.highlightSelect.createEl('option'); + op.value = s.name; + op.text = s.name; + op.selected = s.name === this.settings.defaultHighlight; + } + } + } + + /** + * 构建渲染区域 + */ + private buildRenderArea(): void { + this.renderDiv = this.container.createDiv({ cls: 'render-div' }); + this.articleDiv = this.renderDiv.createDiv({ cls: 'article' }); + + // 创建样式元素 + const styleEl = this.container.createEl('style') as HTMLElement; + + // 初始化渲染器 + this.render = new ArticleRender(this.app, this.itemView, styleEl, this.articleDiv as HTMLDivElement); + } + + /** + * 渲染文章内容 + */ + public async renderArticle(html: string, file: TFile): Promise { + this.articleHTML = html; + this.currentFile = file; + + // 使用渲染器渲染 + if (this.render) { + await this.render.renderMarkdown(file); + } + } + + /** + * 显示预览 + */ + public show(): void { + if (this.container) { + this.container.style.display = 'flex'; + } + // 显示所有微信相关的工具栏 + if (this.toolbar) { + const wechatLines = this.toolbar.querySelectorAll('.wechat-only'); + wechatLines.forEach((line: HTMLElement) => { + line.style.display = 'flex'; + }); + } + if (this.renderDiv) { + this.renderDiv.style.display = 'block'; + } + } + + /** + * 隐藏预览 + */ + public hide(): void { + if (this.container) { + this.container.style.display = 'none'; + } + // 隐藏所有微信相关的工具栏 + if (this.toolbar) { + const wechatLines = this.toolbar.querySelectorAll('.wechat-only'); + wechatLines.forEach((line: HTMLElement) => { + line.style.display = 'none'; + }); + } + if (this.renderDiv) { + this.renderDiv.style.display = 'none'; + } + } + + /** + * 清理资源 + */ + public cleanup(): void { + // 清理回调 + this.onRefreshCallback = undefined; + this.onCopyCallback = undefined; + this.onUploadCallback = undefined; + this.onPublishCallback = undefined; + + // 清理数据 + this.currentFile = undefined; + this.articleHTML = ''; + } + + /** + * 获取当前AppID + */ + public getCurrentAppId(): string { + return this.currentAppId; + } + + /** + * 获取渲染器 + */ + public getRender(): ArticleRender | null { + return this.render; + } + + /** + * 公众号切换事件 + */ + private onAppIdChanged(): void { + // 可以在这里添加切换公众号后的逻辑 + console.log('[WechatPreview] 切换到公众号:', this.currentAppId); + } +} diff --git a/src/xiaohongshu/preview-view.ts b/src/xiaohongshu/xhs-preview.ts similarity index 94% rename from src/xiaohongshu/preview-view.ts rename to src/xiaohongshu/xhs-preview.ts index 1086e07..4791bcd 100644 --- a/src/xiaohongshu/preview-view.ts +++ b/src/xiaohongshu/xhs-preview.ts @@ -1,15 +1,17 @@ -/* 文件:xiaohongshu/preview-view.ts — 小红书预览视图组件:顶部工具栏、分页导航、底部切图按钮。 */ +/* 文件:xiaohongshu/xhs-preview.ts — 小红书预览视图组件:顶部工具栏、分页导航、底部切图按钮。 */ import { Notice, TFile } from 'obsidian'; import { NMPSettings } from '../settings'; import AssetsManager from '../assets'; import { paginateArticle, renderPage, PageInfo } from './paginator'; import { sliceCurrentPage, sliceAllPages } from './slice'; +import { PlatformChooser } from '../platform-chooser'; +import { Platform, IPlatformPreview } from '../types'; /** * 小红书预览视图 */ -export class XiaohongshuPreviewView { +export class XiaohongshuPreviewView implements IPlatformPreview { container: HTMLElement; settings: NMPSettings; assetsManager: AssetsManager; @@ -37,7 +39,7 @@ export class XiaohongshuPreviewView { // 回调函数 onRefreshCallback?: () => Promise; onPublishCallback?: () => Promise; - onPlatformChangeCallback?: (platform: string) => Promise; + onPlatformChangeCallback?: (platform: Platform) => Promise; constructor(container: HTMLElement, app: any) { this.container = container; @@ -412,4 +414,37 @@ export class XiaohongshuPreviewView { new Notice('❌ 批量切图失败: ' + error.message); } } + + /** + * 显示预览(实现 IPlatformPreview 接口) + */ + public show(): void { + if (this.container) { + this.container.style.display = 'flex'; + } + } + + /** + * 隐藏预览(实现 IPlatformPreview 接口) + */ + public hide(): void { + if (this.container) { + this.container.style.display = 'none'; + } + } + + /** + * 清理资源(实现 IPlatformPreview 接口) + */ + public cleanup(): void { + // 清理回调 + this.onRefreshCallback = undefined; + this.onPublishCallback = undefined; + this.onPlatformChangeCallback = undefined; + + // 清理数据 + this.pages = []; + this.currentFile = null; + this.articleHTML = ''; + } } diff --git a/todolist.md b/todolist.md index d87ef39..d3df96a 100644 --- a/todolist.md +++ b/todolist.md @@ -57,7 +57,18 @@ ## 问题 1. "发布平台"选“小红书”时,预览页面没有加载当前文章。 2. 顶部按钮适应窗口宽度,超出窗口,折行显示。 -3. 页预览不完整,改为 +3. 页预览不完整,改为 +4. 修改: + - 公共部分独立出来,如“发布平台”,放在新建platform-choose.ts中,“发布平台”选择切换平台逻辑放在该模块中,便于以后其他平台扩展。 + - 其他所有组件独立。node-preview.ts改为mp-preview.ts, 专门用于处理微信公众号模式下的页面和逻辑处理;preview-view.ts改为xhs-preview.ts,专门用于小红书模式下的页面和逻辑处理。 + + 效果不理想。❌,需求修改如下: + + 目前mp-preview.ts中既实现微信公众号(micro-public,mp)的处理逻辑,又实现小红书(xiaohongshu,xhs)的处理逻辑。优化: + - 平台选择的逻辑放在platform-choose.ts中。 + 平台选择后,依据选择模式,调用mp-preview.ts(微信公众号mp)或xhs-preview.ts(小红书,xhs)中的方法。 + - mp-preview.ts中保留微信公众号模式(micro-public,mp)相关的处理逻辑。 + - mp-preview.ts中去掉小红书处理逻辑(移到xhs-preview.ts中)。