update at 2025-10-08 14:08:36

This commit is contained in:
douboer
2025-10-08 14:08:36 +08:00
parent 719021bc67
commit cbf32b3f0b
10 changed files with 899 additions and 57 deletions

View File

@@ -538,7 +538,7 @@ export class BatchPublishModal extends Modal {
const preview = this.plugin.getNotePreview(); const preview = this.plugin.getNotePreview();
if (preview) { if (preview) {
// 确保预览器处于微信模式 // 确保预览器处于微信模式
preview.currentPlatform = 'wechat'; preview.setCurrentPlatform('wechat');
await preview.renderMarkdown(file); await preview.renderMarkdown(file);
await preview.postToWechat(); await preview.postToWechat();
} else { } else {

View File

@@ -8,7 +8,7 @@
*/ */
import { Plugin, WorkspaceLeaf, App, PluginManifest, Menu, Notice, TAbstractFile, TFile, TFolder } from 'obsidian'; 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 { NMPSettings } from './settings';
import { NoteToMpSettingTab } from './setting-tab'; import { NoteToMpSettingTab } from './setting-tab';
import AssetsManager from './assets'; import AssetsManager from './assets';

View File

@@ -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 { uevent, debounce, waitForLayoutReady } from './utils';
import { NMPSettings } from './settings'; import { NMPSettings } from './settings';
import AssetsManager from './assets'; import AssetsManager from './assets';
@@ -20,7 +20,9 @@ import { XiaohongshuContentAdapter } from './xiaohongshu/adapter';
import { XiaohongshuImageManager } from './xiaohongshu/image'; import { XiaohongshuImageManager } from './xiaohongshu/image';
import { XiaohongshuAPIManager } from './xiaohongshu/api'; import { XiaohongshuAPIManager } from './xiaohongshu/api';
import { XiaohongshuPost } from './xiaohongshu/types'; 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'; import { sliceArticleImage } from './slice-image';
@@ -40,7 +42,7 @@ export class NotePreview extends ItemView {
useLocalCover: HTMLInputElement; useLocalCover: HTMLInputElement;
msgView: HTMLDivElement; msgView: HTMLDivElement;
wechatSelect: HTMLSelectElement; wechatSelect: HTMLSelectElement;
platformSelect: HTMLSelectElement; // 新增:平台选择器 platformChooser: PlatformChooser; // 平台选择器组件
themeSelect: HTMLSelectElement; themeSelect: HTMLSelectElement;
highlightSelect: HTMLSelectElement; highlightSelect: HTMLSelectElement;
listeners?: EventRef[]; listeners?: EventRef[];
@@ -53,7 +55,7 @@ export class NotePreview extends ItemView {
currentTheme: string; currentTheme: string;
currentHighlight: string; currentHighlight: string;
currentAppId: string; currentAppId: string;
currentPlatform: string = 'wechat'; // 新增:当前选择的平台,默认微信 currentPlatform: Platform = 'wechat'; // 当前选择的平台,默认微信
markedParser: MarkedParser; markedParser: MarkedParser;
cachedElements: Map<string, string> = new Map(); cachedElements: Map<string, string> = new Map();
_articleRender: ArticleRender | null = null; _articleRender: ArticleRender | null = null;
@@ -84,6 +86,14 @@ export class NotePreview extends ItemView {
return '笔记预览'; return '笔记预览';
} }
getCurrentPlatform(): Platform {
return this.currentPlatform;
}
setCurrentPlatform(platform: Platform): void {
this.currentPlatform = platform;
}
get render() { get render() {
if (!this._articleRender) { if (!this._articleRender) {
this._articleRender = new ArticleRender(this.app, this, this.styleEl, this.articleDiv); 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' }); this.toolbar = parent.createDiv({ cls: 'preview-toolbar' });
let lineDiv; let lineDiv;
// 平台选择器(新增)- 始终显示 // 使用平台选择器组件
lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line platform-selector-line' }); this.platformChooser = new PlatformChooser(this.toolbar, this.currentPlatform);
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;'; this.platformChooser.build();
this.platformChooser.onPlatformChange(async (platform: Platform) => {
const platformLabel = lineDiv.createDiv({ cls: 'style-label' }); this.currentPlatform = platform;
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(); 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 = 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);'; 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; this.wechatSelect = wxSelect;
if (Platform.isDesktop) { if (ObsidianPlatform.isDesktop) {
// 分隔线 // 分隔线
const separator = lineDiv.createDiv(); const separator = lineDiv.createDiv();
separator.style.cssText = 'width: 1px; height: 24px; background: #dadce0; margin: 0 4px;'; separator.style.cssText = 'width: 1px; height: 24px; background: #dadce0; margin: 0 4px;';
@@ -293,7 +283,7 @@ export class NotePreview extends ItemView {
await this.renderMarkdown(); await this.renderMarkdown();
uevent('refresh'); uevent('refresh');
} }
if (Platform.isDesktop) { if (ObsidianPlatform.isDesktop) {
const copyBtn = lineDiv.createEl('button', { text: '📋 复制' }); 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.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.onmouseenter = () => copyBtn.style.transform = 'translateY(-1px)';
@@ -341,7 +331,7 @@ export class NotePreview extends ItemView {
uevent('pub-images'); uevent('pub-images');
} }
if (Platform.isDesktop && this.settings.isAuthKeyVaild()) { if (ObsidianPlatform.isDesktop && this.settings.isAuthKeyVaild()) {
const htmlBtn = lineDiv.createEl('button', { text: '💾 导出HTML' }); 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.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)'; htmlBtn.onmouseenter = () => htmlBtn.style.transform = 'translateY(-1px)';
@@ -569,22 +559,21 @@ export class NotePreview extends ItemView {
* *
*/ */
private switchToXiaohongshuMode() { private switchToXiaohongshuMode() {
// 隐藏微信相关的工具栏行和平台选择器 // 隐藏微信相关的工具栏行
if (this.toolbar) { if (this.toolbar) {
const wechatLines = this.toolbar.querySelectorAll('.wechat-only'); const wechatLines = this.toolbar.querySelectorAll('.wechat-only');
wechatLines.forEach((line: HTMLElement) => { wechatLines.forEach((line: HTMLElement) => {
line.style.display = 'none'; line.style.display = 'none';
}); });
}
// 也隐藏平台选择器 // 平台选择器保持显示
// const platformLine = this.toolbar.querySelector('.platform-selector-line') as HTMLElement; if (this.platformChooser) {
// if (platformLine) { this.platformChooser.show();
// platformLine.style.display = 'none';
// }
} }
// 隐藏渲染区域 // 隐藏渲染区域
if (this.renderDiv) this.renderDiv.style.display = 'none'; //if (this.renderDiv) this.renderDiv.style.display = 'none';
// 创建或显示小红书预览视图 // 创建或显示小红书预览视图
if (!this._xiaohongshuPreview) { if (!this._xiaohongshuPreview) {
@@ -599,7 +588,7 @@ export class NotePreview extends ItemView {
this._xiaohongshuPreview.onPublishCallback = async () => { this._xiaohongshuPreview.onPublishCallback = async () => {
await this.onXiaohongshuPublish(); await this.onXiaohongshuPublish();
}; };
this._xiaohongshuPreview.onPlatformChangeCallback = async (platform: string) => { this._xiaohongshuPreview.onPlatformChangeCallback = async (platform: Platform) => {
this.currentPlatform = platform; this.currentPlatform = platform;
if (platform === 'wechat') { if (platform === 'wechat') {
await this.onPlatformChanged(); await this.onPlatformChanged();
@@ -622,18 +611,17 @@ export class NotePreview extends ItemView {
* *
*/ */
private switchToWechatMode() { private switchToWechatMode() {
// 显示微信相关的工具栏行和平台选择器 // 显示微信相关的工具栏行
if (this.toolbar) { if (this.toolbar) {
const wechatLines = this.toolbar.querySelectorAll('.wechat-only'); const wechatLines = this.toolbar.querySelectorAll('.wechat-only');
wechatLines.forEach((line: HTMLElement) => { wechatLines.forEach((line: HTMLElement) => {
line.style.display = 'flex'; line.style.display = 'flex';
}); });
}
// 也显示平台选择器 // 平台选择器保持显示
const platformLine = this.toolbar.querySelector('.platform-selector-line') as HTMLElement; if (this.platformChooser) {
if (platformLine) { this.platformChooser.show();
platformLine.style.display = 'flex';
}
} }
// 显示渲染区域 // 显示渲染区域

134
src/platform-chooser.ts Normal file
View File

@@ -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<void> {
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;
}
}

108
src/platform-chooser.ts.bak Normal file
View File

@@ -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<void> {
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 负责
}
}

173
src/platform-manager.ts Normal file
View File

@@ -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<Platform, IPlatformPreview>;
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<void> {
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();
}
}

63
src/types.ts Normal file
View File

@@ -0,0 +1,63 @@
/**
* 公共类型定义文件
* 定义平台类型、回调接口等公共类型
*/
/**
* 支持的发布平台类型
*/
export type Platform = 'wechat' | 'xiaohongshu';
/**
* 平台信息接口
*/
export interface PlatformInfo {
id: Platform;
name: string;
icon?: string;
}
/**
* 平台切换回调接口
*/
export interface PlatformChangeCallback {
(platform: Platform): Promise<void>;
}
/**
* 平台预览视图接口
*/
export interface IPlatformPreview {
/**
* 构建预览界面
*/
build(): void;
/**
* 渲染文章内容
*/
renderArticle(html: string, file: any): Promise<void>;
/**
* 显示预览
*/
show(): void;
/**
* 隐藏预览
*/
hide(): void;
/**
* 清理资源
*/
cleanup(): void;
}
/**
* 支持的平台列表
*/
export const SUPPORTED_PLATFORMS: PlatformInfo[] = [
{ id: 'wechat', name: '微信公众号', icon: '📱' },
{ id: 'xiaohongshu', name: '小红书', icon: '📕' }
];

330
src/wechat-preview.ts Normal file
View File

@@ -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<void>;
public onCopyCallback?: () => Promise<void>;
public onUploadCallback?: () => Promise<void>;
public onPublishCallback?: () => Promise<void>;
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<void> {
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);
}
}

View File

@@ -1,15 +1,17 @@
/* 文件xiaohongshu/preview-view.ts — 小红书预览视图组件:顶部工具栏、分页导航、底部切图按钮。 */ /* 文件xiaohongshu/xhs-preview.ts — 小红书预览视图组件:顶部工具栏、分页导航、底部切图按钮。 */
import { Notice, TFile } from 'obsidian'; import { Notice, TFile } from 'obsidian';
import { NMPSettings } from '../settings'; import { NMPSettings } from '../settings';
import AssetsManager from '../assets'; import AssetsManager from '../assets';
import { paginateArticle, renderPage, PageInfo } from './paginator'; import { paginateArticle, renderPage, PageInfo } from './paginator';
import { sliceCurrentPage, sliceAllPages } from './slice'; 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; container: HTMLElement;
settings: NMPSettings; settings: NMPSettings;
assetsManager: AssetsManager; assetsManager: AssetsManager;
@@ -37,7 +39,7 @@ export class XiaohongshuPreviewView {
// 回调函数 // 回调函数
onRefreshCallback?: () => Promise<void>; onRefreshCallback?: () => Promise<void>;
onPublishCallback?: () => Promise<void>; onPublishCallback?: () => Promise<void>;
onPlatformChangeCallback?: (platform: string) => Promise<void>; onPlatformChangeCallback?: (platform: Platform) => Promise<void>;
constructor(container: HTMLElement, app: any) { constructor(container: HTMLElement, app: any) {
this.container = container; this.container = container;
@@ -412,4 +414,37 @@ export class XiaohongshuPreviewView {
new Notice('❌ 批量切图失败: ' + error.message); 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 = '';
}
} }

View File

@@ -58,6 +58,17 @@
1. "发布平台"选“小红书”时,预览页面没有加载当前文章。 1. "发布平台"选“小红书”时,预览页面没有加载当前文章。
2. 顶部按钮适应窗口宽度,超出窗口,折行显示。 2. 顶部按钮适应窗口宽度,超出窗口,折行显示。
3. 页预览不完整,改为 3. 页预览不完整,改为
4. 修改:
- 公共部分独立出来如“发布平台”放在新建platform-choose.ts中“发布平台”选择切换平台逻辑放在该模块中便于以后其他平台扩展。
- 其他所有组件独立。node-preview.ts改为mp-preview.ts, 专门用于处理微信公众号模式下的页面和逻辑处理preview-view.ts改为xhs-preview.ts专门用于小红书模式下的页面和逻辑处理。
效果不理想。❌,需求修改如下:
目前mp-preview.ts中既实现微信公众号micro-publicmp的处理逻辑又实现小红书xiaohongshuxhs的处理逻辑。优化
- 平台选择的逻辑放在platform-choose.ts中。
平台选择后依据选择模式调用mp-preview.ts(微信公众号mp)或xhs-preview.ts(小红书xhs)中的方法。
- mp-preview.ts中保留微信公众号模式(micro-public,mp)相关的处理逻辑。
- mp-preview.ts中去掉小红书处理逻辑(移到xhs-preview.ts中)。