From 1c8449b04a3bb87600e67c47ce5bfe22bc56fe7a Mon Sep 17 00:00:00 2001 From: douboer Date: Wed, 8 Oct 2025 17:06:31 +0800 Subject: [PATCH] update at 2025-10-08 17:06:31 --- build.sh | 30 +- src/batch-publish-modal.ts | 2 +- src/main.ts | 41 ++- src/{mp-preview.ts => note-preview.ts} | 90 ++--- src/platform-chooser.ts | 134 ------- src/platform-chooser.ts.bak | 108 ------ src/platform-manager.ts | 173 --------- src/types.ts | 63 ---- src/wechat-preview.ts | 330 ------------------ .../{xhs-preview.ts => preview-view.ts} | 41 +-- todolist.md | 2 +- 11 files changed, 113 insertions(+), 901 deletions(-) rename src/{mp-preview.ts => note-preview.ts} (92%) delete mode 100644 src/platform-chooser.ts delete mode 100644 src/platform-chooser.ts.bak delete mode 100644 src/platform-manager.ts delete mode 100644 src/types.ts delete mode 100644 src/wechat-preview.ts rename src/xiaohongshu/{xhs-preview.ts => preview-view.ts} (94%) diff --git a/build.sh b/build.sh index 4ce9696..613dcca 100755 --- a/build.sh +++ b/build.sh @@ -4,16 +4,24 @@ set -e # 出错立即退出 # 1. 构建 npm run build -# 2. 目标路径 -TARGET=~/myweb/.obsidian/plugins/note-to-mp/main.js -BACKUP=~/myweb/.obsidian/plugins/note-to-mp/main.js.bk +# 2. 目标目录 +PLUGIN_DIR=~/myweb/.obsidian/plugins/note-to-mp +FILES=("main.js" "styles.css" "manifest.json") -# 3. 如果存在 main.js,先备份 -if [ -f "$TARGET" ]; then - cp -f "$TARGET" "$BACKUP" - echo "已备份 $TARGET -> $BACKUP" -fi +# 3. 遍历文件,逐一备份并覆盖 +for FILE in "${FILES[@]}"; do + TARGET="$PLUGIN_DIR/$FILE" + BACKUP="$PLUGIN_DIR/$FILE.bk" -# 4. 覆盖复制新的 main.js -cp -f main.js "$TARGET" -echo "已更新 $TARGET" + if [ -f "$TARGET" ]; then + cp -f "$TARGET" "$BACKUP" + echo "已备份 $TARGET -> $BACKUP" + fi + + if [ -f "$FILE" ]; then + cp -f "$FILE" "$TARGET" + echo "已更新 $TARGET" + else + echo "⚠️ 源文件 $FILE 不存在,跳过" + fi +done diff --git a/src/batch-publish-modal.ts b/src/batch-publish-modal.ts index f619be7..862639f 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.setCurrentPlatform('wechat'); + preview.currentPlatform = 'wechat'; await preview.renderMarkdown(file); await preview.postToWechat(); } else { diff --git a/src/main.ts b/src/main.ts index f4c9895..b7b8cb2 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 './mp-preview'; +import { NotePreview, VIEW_TYPE_NOTE_PREVIEW } from './note-preview'; import { NMPSettings } from './settings'; import { NoteToMpSettingTab } from './setting-tab'; import AssetsManager from './assets'; @@ -38,6 +38,7 @@ import { XiaohongshuAPIManager } from './xiaohongshu/api'; export default class NoteToMpPlugin extends Plugin { settings: NMPSettings; assetsManager: AssetsManager; + ribbonIconEl: HTMLElement | null = null; constructor(app: App, manifest: PluginManifest) { super(app, manifest); AssetsManager.setup(app, manifest); @@ -55,6 +56,12 @@ export default class NoteToMpPlugin extends Plugin { uevent('load'); this.app.workspace.onLayoutReady(()=>{ this.loadResource(); + // 布局就绪后清理旧视图并自动打开一个新的标准预览(可选) + this.cleanupLegacyViews(); + // 如果当前没有我们的预览叶子,自动激活一次,改善首次体验 + if (this.app.workspace.getLeavesOfType(VIEW_TYPE_NOTE_PREVIEW).length === 0) { + this.activateView(); + } }) this.registerView( @@ -62,10 +69,10 @@ export default class NoteToMpPlugin extends Plugin { (leaf) => new NotePreview(leaf, this) ); - const ribbonIconEl = this.addRibbonIcon('clipboard-paste', '复制到公众号', (evt: MouseEvent) => { + this.ribbonIconEl = this.addRibbonIcon('clipboard-paste', '复制到公众号', (evt: MouseEvent) => { this.activateView(); }); - ribbonIconEl.addClass('note-to-mp-plugin-ribbon-class'); + this.ribbonIconEl.addClass('note-to-mp-plugin-ribbon-class'); this.addCommand({ id: 'note-to-mp-preview', @@ -146,7 +153,35 @@ export default class NoteToMpPlugin extends Plugin { } onunload() { + console.log('Unloading NoteToMP'); + // 移除 ribbon icon,避免重载插件时重复创建 + if (this.ribbonIconEl) { + this.ribbonIconEl.remove(); + this.ribbonIconEl = null; + } + } + /** + * 清理历史失效视图: + * 某些用户可能曾使用过旧插件构建(例如 note-mp-preview-manager),升级后残留的标签页会提示“插件不再活动”。 + * 这里做一次性清理,避免用户手动关标签造成困扰。 + */ + private cleanupLegacyViews() { + try { + const legacyIds = ['note-mp-preview-manager']; // 可扩展 + const { workspace } = this.app; + // 遍历所有叶子,关闭可能的失效 view(无法直接匹配 id 时,仅检测报错视图类型) + workspace.getLeavesOfType(VIEW_TYPE_NOTE_PREVIEW).forEach(l => { + // 如果 view 的 plugin 不存在或 manifest id 不匹配我们当前的 id,则关闭 + const anyView: any = l.view; + const vid = (anyView?.plugin?.manifest?.id) || ''; + if (vid && vid !== this.manifest.id && legacyIds.includes(vid)) { + workspace.detachLeavesOfType(VIEW_TYPE_NOTE_PREVIEW); + } + }); + } catch (e) { + console.warn('[NoteToMp] cleanupLegacyViews 失败', e); + } } async loadSettings() { diff --git a/src/mp-preview.ts b/src/note-preview.ts similarity index 92% rename from src/mp-preview.ts rename to src/note-preview.ts index 0f0f4d7..c226610 100644 --- a/src/mp-preview.ts +++ b/src/note-preview.ts @@ -7,7 +7,7 @@ * - 与批量发布/图片处理集成预留 */ -import { EventRef, ItemView, Workspace, WorkspaceLeaf, Notice, Platform as ObsidianPlatform, TFile, TFolder, TAbstractFile, Plugin } from 'obsidian'; +import { EventRef, ItemView, Workspace, WorkspaceLeaf, Notice, Platform, TFile, TFolder, TAbstractFile, Plugin } from 'obsidian'; import { uevent, debounce, waitForLayoutReady } from './utils'; import { NMPSettings } from './settings'; import AssetsManager from './assets'; @@ -20,9 +20,7 @@ import { XiaohongshuContentAdapter } from './xiaohongshu/adapter'; import { XiaohongshuImageManager } from './xiaohongshu/image'; import { XiaohongshuAPIManager } from './xiaohongshu/api'; import { XiaohongshuPost } from './xiaohongshu/types'; -import { XiaohongshuPreviewView } from './xiaohongshu/xhs-preview'; -import { PlatformChooser } from './platform-chooser'; -import { Platform } from './types'; +import { XiaohongshuPreviewView } from './xiaohongshu/preview-view'; // 切图功能 import { sliceArticleImage } from './slice-image'; @@ -42,7 +40,7 @@ export class NotePreview extends ItemView { useLocalCover: HTMLInputElement; msgView: HTMLDivElement; wechatSelect: HTMLSelectElement; - platformChooser: PlatformChooser; // 平台选择器组件 + platformSelect: HTMLSelectElement; // 新增:平台选择器 themeSelect: HTMLSelectElement; highlightSelect: HTMLSelectElement; listeners?: EventRef[]; @@ -55,7 +53,7 @@ export class NotePreview extends ItemView { currentTheme: string; currentHighlight: string; currentAppId: string; - currentPlatform: Platform = 'wechat'; // 当前选择的平台,默认微信 + currentPlatform: string = 'wechat'; // 新增:当前选择的平台,默认微信 markedParser: MarkedParser; cachedElements: Map = new Map(); _articleRender: ArticleRender | null = null; @@ -86,14 +84,6 @@ 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); @@ -208,16 +198,36 @@ export class NotePreview extends ItemView { this.toolbar = parent.createDiv({ cls: 'preview-toolbar' }); let lineDiv; - // 使用平台选择器组件 - this.platformChooser = new PlatformChooser(this.toolbar, this.currentPlatform); - this.platformChooser.build(); - this.platformChooser.onPlatformChange(async (platform: Platform) => { - this.currentPlatform = platform; + // 平台选择器(新增)- 始终显示 + 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; await this.onPlatformChanged(); - }); + }; + + this.platformSelect = platformSelect; // 公众号 - if (this.settings.wxInfo.length > 1 || ObsidianPlatform.isDesktop) { + if (this.settings.wxInfo.length > 1 || Platform.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);'; @@ -246,7 +256,7 @@ export class NotePreview extends ItemView { } this.wechatSelect = wxSelect; - if (ObsidianPlatform.isDesktop) { + if (Platform.isDesktop) { // 分隔线 const separator = lineDiv.createDiv(); separator.style.cssText = 'width: 1px; height: 24px; background: #dadce0; margin: 0 4px;'; @@ -283,7 +293,7 @@ export class NotePreview extends ItemView { await this.renderMarkdown(); uevent('refresh'); } - if (ObsidianPlatform.isDesktop) { + if (Platform.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)'; @@ -331,7 +341,7 @@ export class NotePreview extends ItemView { uevent('pub-images'); } - if (ObsidianPlatform.isDesktop && this.settings.isAuthKeyVaild()) { + if (Platform.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)'; @@ -559,21 +569,22 @@ 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'; }); - } - - // 平台选择器保持显示 - if (this.platformChooser) { - this.platformChooser.show(); + + // 也隐藏平台选择器行 + // const platformLine = this.toolbar.querySelector('.platform-selector-line') as HTMLElement; + // if (platformLine) { + // platformLine.style.display = 'none'; + // } } // 隐藏渲染区域 - //if (this.renderDiv) this.renderDiv.style.display = 'none'; + if (this.renderDiv) this.renderDiv.style.display = 'none'; // 创建或显示小红书预览视图 if (!this._xiaohongshuPreview) { @@ -588,7 +599,7 @@ export class NotePreview extends ItemView { this._xiaohongshuPreview.onPublishCallback = async () => { await this.onXiaohongshuPublish(); }; - this._xiaohongshuPreview.onPlatformChangeCallback = async (platform: Platform) => { + this._xiaohongshuPreview.onPlatformChangeCallback = async (platform: string) => { this.currentPlatform = platform; if (platform === 'wechat') { await this.onPlatformChanged(); @@ -611,17 +622,18 @@ 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'; }); - } - - // 平台选择器保持显示 - if (this.platformChooser) { - this.platformChooser.show(); + + // 也显示平台选择器行 + const platformLine = this.toolbar.querySelector('.platform-selector-line') as HTMLElement; + if (platformLine) { + platformLine.style.display = 'flex'; + } } // 显示渲染区域 @@ -906,4 +918,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 deleted file mode 100644 index 6cae912..0000000 --- a/src/platform-chooser.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * 平台选择器组件 - * 提供统一的平台选择界面和切换逻辑 - */ - -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 deleted file mode 100644 index 826d68e..0000000 --- a/src/platform-chooser.ts.bak +++ /dev/null @@ -1,108 +0,0 @@ -/** - * 平台选择器组件 - * 提供统一的平台选择界面,配合 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 deleted file mode 100644 index b5a5f48..0000000 --- a/src/platform-manager.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * 平台管理器 - * 负责管理和协调不同平台的预览视图 - */ - -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 deleted file mode 100644 index a46d122..0000000 --- a/src/types.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * 公共类型定义文件 - * 定义平台类型、回调接口等公共类型 - */ - -/** - * 支持的发布平台类型 - */ -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 deleted file mode 100644 index 4e2cec3..0000000 --- a/src/wechat-preview.ts +++ /dev/null @@ -1,330 +0,0 @@ -/** - * 微信公众号预览视图 - * 专门处理微信公众号的预览和发布逻辑 - */ - -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/xhs-preview.ts b/src/xiaohongshu/preview-view.ts similarity index 94% rename from src/xiaohongshu/xhs-preview.ts rename to src/xiaohongshu/preview-view.ts index 4791bcd..1086e07 100644 --- a/src/xiaohongshu/xhs-preview.ts +++ b/src/xiaohongshu/preview-view.ts @@ -1,17 +1,15 @@ -/* 文件:xiaohongshu/xhs-preview.ts — 小红书预览视图组件:顶部工具栏、分页导航、底部切图按钮。 */ +/* 文件:xiaohongshu/preview-view.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 implements IPlatformPreview { +export class XiaohongshuPreviewView { container: HTMLElement; settings: NMPSettings; assetsManager: AssetsManager; @@ -39,7 +37,7 @@ export class XiaohongshuPreviewView implements IPlatformPreview { // 回调函数 onRefreshCallback?: () => Promise; onPublishCallback?: () => Promise; - onPlatformChangeCallback?: (platform: Platform) => Promise; + onPlatformChangeCallback?: (platform: string) => Promise; constructor(container: HTMLElement, app: any) { this.container = container; @@ -414,37 +412,4 @@ export class XiaohongshuPreviewView implements IPlatformPreview { 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 d3df96a..ea056ba 100644 --- a/todolist.md +++ b/todolist.md @@ -55,7 +55,7 @@ ## 问题 -1. "发布平台"选“小红书”时,预览页面没有加载当前文章。 +1. "发布平台"首次选“小红书”时,预览页面没有加载当前文章。 2. 顶部按钮适应窗口宽度,超出窗口,折行显示。 3. 页预览不完整,改为 4. 修改: