2 Commits

Author SHA1 Message Date
douboer
5d32c0f5e7 update at 2025-10-08 17:32:31 2025-10-08 17:32:31 +08:00
douboer
1c8449b04a update at 2025-10-08 17:06:31 2025-10-08 17:06:31 +08:00
21 changed files with 596 additions and 1102 deletions

View File

@@ -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/backup/$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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -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 {

View File

@@ -19,26 +19,21 @@ export class DocModal extends Modal {
onOpen() {
let { contentEl, modalEl } = this;
modalEl.style.width = '640px';
modalEl.style.height = '720px';
contentEl.style.display = 'flex';
contentEl.style.flexDirection = 'column';
modalEl.addClass('doc-modal');
contentEl.addClass('doc-modal-content');
const titleEl = contentEl.createEl('h2', { text: this.title });
titleEl.style.marginTop = '0.5em';
const content = contentEl.createEl('div');
content.setAttr('style', 'margin-bottom:1em;-webkit-user-select: text; user-select: text;');
const titleEl = contentEl.createEl('h2', { text: this.title, cls: 'doc-modal-title' });
const content = contentEl.createEl('div', { cls: 'doc-modal-desc' });
content.appendChild(sanitizeHTMLToDom(this.content));
const iframe = contentEl.createEl('iframe', {
cls: 'doc-modal-iframe',
attr: {
src: this.url,
width: '100%',
allow: 'clipboard-read; clipboard-write',
},
});
iframe.style.flex = '1';
}
onClose() {

View File

@@ -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() {

View File

@@ -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<string, string> = 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,25 +198,39 @@ 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' });
const platformLabel = lineDiv.createDiv({ cls: 'style-label' });
platformLabel.innerText = '发布平台';
const platformSelect = lineDiv.createEl('select', { cls: 'platform-select' });
// 添加平台选项
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);';
const wxLabel = lineDiv.createDiv({ cls: 'style-label' });
wxLabel.innerText = '公众号';
wxLabel.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;';
const wxSelect = lineDiv.createEl('select', { cls: 'style-select' });
wxSelect.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;';
const wxSelect = lineDiv.createEl('select', { cls: 'wechat-select' });
wxSelect.onchange = async () => {
this.currentAppId = wxSelect.value;
this.onAppIdChanged();
@@ -246,15 +250,11 @@ 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;';
const separator = lineDiv.createDiv({ cls: 'toolbar-separator' });
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)';
const openBtn = lineDiv.createEl('button', { text: '🌐 去公众号后台', cls: 'toolbar-button purple-gradient' });
openBtn.onclick = async () => {
const { shell } = require('electron');
@@ -268,13 +268,9 @@ export class NotePreview extends ItemView {
}
// 复制,刷新,带图片复制,发草稿箱
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;';
lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line wechat-only flex-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)';
const refreshBtn = lineDiv.createEl('button', { text: '🔄 刷新', cls: 'toolbar-button purple-gradient' });
refreshBtn.onclick = async () => {
await this.assetsManager.loadCustomCSS();
@@ -283,11 +279,8 @@ export class NotePreview extends ItemView {
await this.renderMarkdown();
uevent('refresh');
}
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)';
if (Platform.isDesktop) {
const copyBtn = lineDiv.createEl('button', { text: '📋 复制', cls: 'toolbar-button' });
copyBtn.onclick = async() => {
try {
@@ -301,41 +294,29 @@ export class NotePreview extends ItemView {
}
}
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)';
const uploadImgBtn = lineDiv.createEl('button', { text: '📤 上传图片', cls: 'toolbar-button' });
uploadImgBtn.onclick = async() => {
await this.uploadImages();
uevent('upload');
}
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)';
const postBtn = lineDiv.createEl('button', { text: '📝 发草稿', cls: 'toolbar-button' });
postBtn.onclick = async() => {
await this.postArticle();
uevent('pub');
}
const imagesBtn = lineDiv.createEl('button', { text: '🖼️ 图片/文字' });
imagesBtn.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);';
imagesBtn.onmouseenter = () => imagesBtn.style.transform = 'translateY(-1px)';
imagesBtn.onmouseleave = () => imagesBtn.style.transform = 'translateY(0)';
const imagesBtn = lineDiv.createEl('button', { text: '🖼️ 图片/文字', cls: 'toolbar-button' });
imagesBtn.onclick = async() => {
await this.postImages();
uevent('pub-images');
}
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)';
htmlBtn.onmouseleave = () => htmlBtn.style.transform = 'translateY(0)';
if (Platform.isDesktop && this.settings.isAuthKeyVaild()) {
const htmlBtn = lineDiv.createEl('button', { text: '💾 导出HTML', cls: 'toolbar-button' });
htmlBtn.onclick = async() => {
await this.exportHTML();
@@ -345,11 +326,9 @@ export class NotePreview extends ItemView {
// 封面
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 coverTitle = lineDiv.createDiv({ cls: 'style-label' });
coverTitle.innerText = '封面';
coverTitle.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;';
this.useDefaultCover = lineDiv.createEl('input', { cls: 'input-style' });
this.useDefaultCover.setAttr('type', 'radio');
@@ -368,7 +347,6 @@ export class NotePreview extends ItemView {
const defaultLable = lineDiv.createEl('label');
defaultLable.innerText = '默认';
defaultLable.setAttr('for', 'default-cover');
defaultLable.style.cssText = 'font-size: 13px; color: #5f6368; cursor: pointer; user-select: none;';
this.useLocalCover = lineDiv.createEl('input', { cls: 'input-style' });
this.useLocalCover.setAttr('type', 'radio');
@@ -388,7 +366,6 @@ export class NotePreview extends ItemView {
const localLabel = lineDiv.createEl('label');
localLabel.setAttr('for', 'local-cover');
localLabel.innerText = '上传';
localLabel.style.cssText = 'font-size: 13px; color: #5f6368; cursor: pointer; user-select: none;';
this.coverEl = lineDiv.createEl('input', { cls: 'upload-input' });
this.coverEl.setAttr('type', 'file');
@@ -399,15 +376,12 @@ export class NotePreview extends ItemView {
// 样式
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;';
lineDiv = this.toolbar.createDiv({ cls: 'toolbar-line wechat-only flex-wrap' });
const cssStyle = lineDiv.createDiv({ cls: 'style-label' });
cssStyle.innerText = '样式';
cssStyle.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;';
const selectBtn = lineDiv.createEl('select', { cls: 'style-select' });
selectBtn.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;';
selectBtn.onchange = async () => {
this.currentTheme = selectBtn.value;
@@ -424,15 +398,12 @@ export class NotePreview extends ItemView {
this.themeSelect = selectBtn;
// 分隔线
const separator = lineDiv.createDiv();
separator.style.cssText = 'width: 1px; height: 24px; background: #dadce0; margin: 0 4px;';
const separator = lineDiv.createDiv({ cls: 'toolbar-separator' });
const highlightStyle = lineDiv.createDiv({ cls: 'style-label' });
highlightStyle.innerText = '代码高亮';
highlightStyle.style.cssText = 'font-size: 13px; color: #5f6368; font-weight: 500; white-space: nowrap;';
const highlightStyleBtn = lineDiv.createEl('select', { cls: 'style-select' });
highlightStyleBtn.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;';
highlightStyleBtn.onchange = async () => {
this.currentHighlight = highlightStyleBtn.value;
@@ -462,7 +433,6 @@ export class NotePreview extends ItemView {
this.renderDiv = this.mainDiv.createDiv({cls: 'render-div'});
this.renderDiv.id = 'render-div';
this.renderDiv.setAttribute('style', '-webkit-user-select: text; user-select: text;');
this.styleEl = this.renderDiv.createEl('style');
this.styleEl.setAttr('title', 'note-to-mp-style');
this.articleDiv = this.renderDiv.createEl('div');
@@ -548,7 +518,7 @@ export class NotePreview extends ItemView {
if (this.currentPlatform === 'xiaohongshu') {
// 切换到小红书预览模式
this.switchToXiaohongshuMode();
await this.switchToXiaohongshuMode();
} else {
// 切换到微信公众号模式
this.switchToWechatMode();
@@ -558,27 +528,27 @@ export class NotePreview extends ItemView {
/**
*
*/
private switchToXiaohongshuMode() {
// 隐藏微信相关的工具栏行
private async 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) {
const xhsContainer = this.mainDiv.createDiv({ cls: 'xiaohongshu-preview-container' });
xhsContainer.style.cssText = 'width: 100%; height: 100%;';
this._xiaohongshuPreview = new XiaohongshuPreviewView(xhsContainer, this.app);
// 设置回调函数
@@ -588,7 +558,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();
@@ -602,8 +572,17 @@ export class NotePreview extends ItemView {
}
// 如果有当前文件,渲染小红书预览
if (this.currentFile && this.articleHTML) {
this._xiaohongshuPreview.renderArticle(this.articleHTML, this.currentFile);
if (this.currentFile) {
// 如果还没有生成 articleHTML先生成它
if (!this.articleHTML) {
await this.render.renderMarkdown(this.currentFile);
this.articleHTML = this.render.articleHTML;
}
// 渲染到小红书预览
if (this.articleHTML) {
await this._xiaohongshuPreview.renderArticle(this.articleHTML, this.currentFile);
}
}
}
@@ -611,17 +590,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 +886,4 @@ export class NotePreview extends ItemView {
this.isCancelUpload = false;
}
}
}
}

View File

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

View File

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

View File

@@ -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<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();
}
}

View File

@@ -160,9 +160,8 @@ export class NoteToMpSettingTab extends PluginSettingTab {
this.wxInfo = this.parseWXInfo();
const helpEl = containerEl.createEl('div');
helpEl.style.cssText = 'display: flex;flex-direction: row;align-items: center;';
helpEl.createEl('h2', {text: '帮助文档'}).style.cssText = 'margin-right: 10px;';
const helpEl = containerEl.createEl('div', { cls: 'setting-help-section' });
helpEl.createEl('h2', {text: '帮助文档', cls: 'setting-help-title'});
helpEl.createEl('a', {text: 'https://sunboshi.tech/doc', attr: {href: 'https://sunboshi.tech/doc'}});
containerEl.createEl('h2', {text: '插件设置'});

View File

@@ -1,63 +0,0 @@
/**
* 公共类型定义文件
* 定义平台类型、回调接口等公共类型
*/
/**
* 支持的发布平台类型
*/
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: '📕' }
];

View File

@@ -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<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

@@ -76,9 +76,7 @@ export class XiaohongshuWebAPI implements XiaohongshuAPI {
private initializeWebview(): void {
// 创建隐藏的webview元素
this.webview = document.createElement('webview');
this.webview.style.display = 'none';
this.webview.style.width = '1200px';
this.webview.style.height = '800px';
this.webview.addClass('xhs-webview');
// 设置webview属性
this.webview.setAttribute('nodeintegration', 'false');

View File

@@ -67,20 +67,16 @@ export class XiaohongshuLoginModal extends Modal {
contentEl.empty();
contentEl.addClass('xiaohongshu-login-modal');
// 设置对话框样式
contentEl.style.width = '400px';
contentEl.style.padding = '20px';
// 标题
contentEl.createEl('h2', {
text: '登录小红书',
attr: { style: 'text-align: center; margin-bottom: 20px; color: #ff4757;' }
cls: 'xhs-login-title'
});
// 说明文字
const descEl = contentEl.createEl('p', {
text: '请使用手机号码和验证码登录小红书',
attr: { style: 'text-align: center; color: #666; margin-bottom: 30px;' }
cls: 'xhs-login-desc'
});
// 手机号输入
@@ -97,23 +93,16 @@ export class XiaohongshuLoginModal extends Modal {
});
// 设置输入框样式
text.inputEl.style.width = '100%';
text.inputEl.style.fontSize = '16px';
text.inputEl.addClass('xhs-input-full');
});
// 验证码输入和发送按钮
const codeContainer = contentEl.createDiv({ cls: 'code-container' });
codeContainer.style.display = 'flex';
codeContainer.style.alignItems = 'center';
codeContainer.style.gap = '10px';
codeContainer.style.marginBottom = '20px';
const codeContainer = contentEl.createDiv({ cls: 'xhs-code-container' });
const codeLabel = codeContainer.createDiv({ cls: 'setting-item-name' });
const codeLabel = codeContainer.createDiv({ cls: 'setting-item-name xhs-code-label' });
codeLabel.textContent = '验证码';
codeLabel.style.minWidth = '80px';
const codeInputWrapper = codeContainer.createDiv();
codeInputWrapper.style.flex = '1';
const codeInputWrapper = codeContainer.createDiv({ cls: 'xhs-code-input-wrapper' });
new Setting(codeInputWrapper)
.addText(text => {
@@ -125,8 +114,7 @@ export class XiaohongshuLoginModal extends Modal {
this.updateLoginButtonState();
});
text.inputEl.style.width = '100%';
text.inputEl.style.fontSize = '16px';
text.inputEl.addClass('xhs-input-full');
text.inputEl.disabled = true; // 初始禁用
// 回车键登录
@@ -142,22 +130,13 @@ export class XiaohongshuLoginModal extends Modal {
.setButtonText('发送验证码')
.onClick(() => this.handleSendCode());
this.sendCodeButton.buttonEl.style.minWidth = '120px';
this.sendCodeButton.buttonEl.style.marginLeft = '10px';
this.sendCodeButton.buttonEl.addClass('xhs-send-code-btn');
// 状态显示区域
this.statusDiv = contentEl.createDiv({ cls: 'status-message' });
this.statusDiv.style.minHeight = '30px';
this.statusDiv.style.marginBottom = '20px';
this.statusDiv.style.textAlign = 'center';
this.statusDiv.style.fontSize = '14px';
this.statusDiv = contentEl.createDiv({ cls: 'xhs-status-message' });
// 按钮区域
const buttonContainer = contentEl.createDiv({ cls: 'button-container' });
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'center';
buttonContainer.style.gap = '15px';
buttonContainer.style.marginTop = '20px';
const buttonContainer = contentEl.createDiv({ cls: 'xhs-button-container' });
// 登录按钮
this.loginButton = new ButtonComponent(buttonContainer)
@@ -166,7 +145,7 @@ export class XiaohongshuLoginModal extends Modal {
.setDisabled(true)
.onClick(() => this.handleLogin());
this.loginButton.buttonEl.style.minWidth = '100px';
this.loginButton.buttonEl.addClass('xhs-login-btn');
// 取消按钮
new ButtonComponent(buttonContainer)
@@ -389,21 +368,7 @@ export class XiaohongshuLoginModal extends Modal {
private showStatus(message: string, type: 'info' | 'success' | 'error' = 'info') {
this.statusDiv.empty();
const messageEl = this.statusDiv.createSpan({ text: message });
// 设置不同类型的样式
switch (type) {
case 'success':
messageEl.style.color = '#27ae60';
break;
case 'error':
messageEl.style.color = '#e74c3c';
break;
case 'info':
default:
messageEl.style.color = '#3498db';
break;
}
const messageEl = this.statusDiv.createSpan({ text: message, cls: type || 'info' });
}
onClose() {

View File

@@ -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<void>;
onPublishCallback?: () => Promise<void>;
onPlatformChangeCallback?: (platform: Platform) => Promise<void>;
onPlatformChangeCallback?: (platform: string) => Promise<void>;
constructor(container: HTMLElement, app: any) {
this.container = container;
@@ -53,14 +51,13 @@ export class XiaohongshuPreviewView implements IPlatformPreview {
*/
build(): void {
this.container.empty();
this.container.style.cssText = 'display: flex; flex-direction: column; height: 100%; background: linear-gradient(135deg, #f5f7fa 0%, #e8eaf6 100%);';
this.container.addClass('xhs-preview-container');
// 顶部工具栏
this.buildTopToolbar();
// 页面容器
this.pageContainer = this.container.createDiv({ cls: 'xhs-page-container' });
this.pageContainer.style.cssText = 'flex: 1; overflow: auto; display: flex; justify-content: center; align-items: center; padding: 20px; background: radial-gradient(ellipse at top, rgba(255,255,255,0.1) 0%, transparent 70%);';
// 分页导航
this.buildPageNavigation();
@@ -74,32 +71,22 @@ export class XiaohongshuPreviewView implements IPlatformPreview {
*/
private buildTopToolbar(): void {
this.topToolbar = this.container.createDiv({ cls: 'xhs-top-toolbar' });
this.topToolbar.style.cssText = 'display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); border-bottom: 1px solid #e8eaed; box-shadow: 0 2px 4px rgba(0,0,0,0.04); flex-wrap: wrap;';
// 刷新按钮
const refreshBtn = this.topToolbar.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)';
const refreshBtn = this.topToolbar.createEl('button', { text: '🔄 刷新', cls: 'toolbar-button purple-gradient' });
refreshBtn.onclick = () => this.onRefresh();
// 发布按钮
const publishBtn = this.topToolbar.createEl('button', { text: '📤 发布' });
publishBtn.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);';
publishBtn.onmouseenter = () => publishBtn.style.transform = 'translateY(-1px)';
publishBtn.onmouseleave = () => publishBtn.style.transform = 'translateY(0)';
const publishBtn = this.topToolbar.createEl('button', { text: '📤 发布', cls: 'toolbar-button' });
publishBtn.onclick = () => this.onPublish();
// 分隔线
const separator2 = this.topToolbar.createDiv({ cls: 'toolbar-separator' });
separator2.style.cssText = 'width: 1px; height: 24px; background: #dadce0; margin: 0 4px;';
// 模板选择
const templateLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' });
templateLabel.innerText = '模板';
templateLabel.style.cssText = 'font-size: 11px; color: #5f6368; font-weight: 500; white-space: nowrap;';
this.templateSelect = this.topToolbar.createEl('select');
this.templateSelect.style.cssText = 'padding: 4px 8px; border: 1px solid #dadce0; border-radius: 4px; background: white; font-size: 11px; cursor: pointer; transition: border-color 0.2s ease;';
this.templateSelect = this.topToolbar.createEl('select', { cls: 'xhs-select' });
['默认模板', '简约模板', '杂志模板'].forEach(name => {
const option = this.templateSelect.createEl('option');
option.value = name;
@@ -109,9 +96,7 @@ export class XiaohongshuPreviewView implements IPlatformPreview {
// 主题选择
const themeLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' });
themeLabel.innerText = '主题';
themeLabel.style.cssText = 'font-size: 11px; color: #5f6368; font-weight: 500; white-space: nowrap;';
this.themeSelect = this.topToolbar.createEl('select');
this.themeSelect.style.cssText = 'padding: 4px 8px; border: 1px solid #dadce0; border-radius: 4px; background: white; font-size: 11px; cursor: pointer; transition: border-color 0.2s ease;';
this.themeSelect = this.topToolbar.createEl('select', { cls: 'xhs-select' });
const themes = this.assetsManager.themes;
themes.forEach(theme => {
const option = this.themeSelect.createEl('option');
@@ -124,9 +109,7 @@ export class XiaohongshuPreviewView implements IPlatformPreview {
// 字体选择
const fontLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' });
fontLabel.innerText = '字体';
fontLabel.style.cssText = 'font-size: 11px; color: #5f6368; font-weight: 500; white-space: nowrap;';
this.fontSelect = this.topToolbar.createEl('select');
this.fontSelect.style.cssText = 'padding: 4px 8px; border: 1px solid #dadce0; border-radius: 4px; background: white; font-size: 11px; cursor: pointer; transition: border-color 0.2s ease;';
this.fontSelect = this.topToolbar.createEl('select', { cls: 'xhs-select' });
['系统默认', '宋体', '黑体', '楷体', '仿宋'].forEach(name => {
const option = this.fontSelect.createEl('option');
option.value = name;
@@ -137,23 +120,14 @@ export class XiaohongshuPreviewView implements IPlatformPreview {
// 字号控制
const fontSizeLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' });
fontSizeLabel.innerText = '字号';
fontSizeLabel.style.cssText = 'font-size: 11px; color: #5f6368; font-weight: 500; white-space: nowrap;';
const fontSizeGroup = this.topToolbar.createDiv({ cls: 'font-size-group' });
fontSizeGroup.style.cssText = 'display: flex; align-items: center; gap: 6px; background: white; border: 1px solid #dadce0; border-radius: 4px; padding: 2px;';
const decreaseBtn = fontSizeGroup.createEl('button', { text: '' });
decreaseBtn.style.cssText = 'width: 24px; height: 24px; border: none; background: transparent; border-radius: 3px; cursor: pointer; font-size: 16px; color: #5f6368; transition: background 0.2s ease;';
decreaseBtn.onmouseenter = () => decreaseBtn.style.background = '#f1f3f4';
decreaseBtn.onmouseleave = () => decreaseBtn.style.background = 'transparent';
const decreaseBtn = fontSizeGroup.createEl('button', { text: '', cls: 'font-size-btn' });
decreaseBtn.onclick = () => this.changeFontSize(-1);
this.fontSizeDisplay = fontSizeGroup.createEl('span', { text: '16' });
this.fontSizeDisplay.style.cssText = 'min-width: 24px; text-align: center; font-size: 12px; color: #202124; font-weight: 500;';
this.fontSizeDisplay = fontSizeGroup.createEl('span', { text: '16', cls: 'font-size-display' });
const increaseBtn = fontSizeGroup.createEl('button', { text: '' });
increaseBtn.style.cssText = 'width: 24px; height: 24px; border: none; background: transparent; border-radius: 3px; cursor: pointer; font-size: 14px; color: #5f6368; transition: background 0.2s ease;';
increaseBtn.onmouseenter = () => increaseBtn.style.background = '#f1f3f4';
increaseBtn.onmouseleave = () => increaseBtn.style.background = 'transparent';
const increaseBtn = fontSizeGroup.createEl('button', { text: '', cls: 'font-size-btn' });
increaseBtn.onclick = () => this.changeFontSize(1);
}
@@ -162,37 +136,13 @@ export class XiaohongshuPreviewView implements IPlatformPreview {
*/
private buildPageNavigation(): void {
this.pageNavigation = this.container.createDiv({ cls: 'xhs-page-navigation' });
this.pageNavigation.style.cssText = 'display: flex; justify-content: center; align-items: center; gap: 16px; padding: 12px; background: white; border-bottom: 1px solid #e8eaed;';
const prevBtn = this.pageNavigation.createEl('button', { text: '' });
prevBtn.style.cssText = 'width: 36px; height: 36px; border: 1px solid #dadce0; border-radius: 50%; cursor: pointer; font-size: 20px; background: white; color: #5f6368; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.08);';
prevBtn.onmouseenter = () => {
prevBtn.style.background = 'linear-gradient(135deg, #1e88e5 0%, #1565c0 100%)';
prevBtn.style.color = 'white';
prevBtn.style.borderColor = '#1e88e5';
};
prevBtn.onmouseleave = () => {
prevBtn.style.background = 'white';
prevBtn.style.color = '#5f6368';
prevBtn.style.borderColor = '#dadce0';
};
const prevBtn = this.pageNavigation.createEl('button', { text: '', cls: 'xhs-nav-btn' });
prevBtn.onclick = () => this.previousPage();
this.pageNumberDisplay = this.pageNavigation.createEl('span', { text: '1/1' });
this.pageNumberDisplay.style.cssText = 'font-size: 14px; min-width: 50px; text-align: center; color: #202124; font-weight: 500;';
this.pageNumberDisplay = this.pageNavigation.createEl('span', { text: '1/1', cls: 'xhs-page-number' });
const nextBtn = this.pageNavigation.createEl('button', { text: '' });
nextBtn.style.cssText = 'width: 36px; height: 36px; border: 1px solid #dadce0; border-radius: 50%; cursor: pointer; font-size: 20px; background: white; color: #5f6368; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.08);';
nextBtn.onmouseenter = () => {
nextBtn.style.background = 'linear-gradient(135deg, #1e88e5 0%, #1565c0 100%)';
nextBtn.style.color = 'white';
nextBtn.style.borderColor = '#1e88e5';
};
nextBtn.onmouseleave = () => {
nextBtn.style.background = 'white';
nextBtn.style.color = '#5f6368';
nextBtn.style.borderColor = '#dadce0';
};
const nextBtn = this.pageNavigation.createEl('button', { text: '', cls: 'xhs-nav-btn' });
nextBtn.onclick = () => this.nextPage();
}
@@ -201,30 +151,11 @@ export class XiaohongshuPreviewView implements IPlatformPreview {
*/
private buildBottomToolbar(): void {
this.bottomToolbar = this.container.createDiv({ cls: 'xhs-bottom-toolbar' });
this.bottomToolbar.style.cssText = 'display: flex; justify-content: center; gap: 12px; padding: 12px 16px; background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); border-top: 1px solid #e8eaed; box-shadow: 0 -2px 4px rgba(0,0,0,0.04);';
const currentPageBtn = this.bottomToolbar.createEl('button', { text: '⬇ 当前页切图' });
currentPageBtn.style.cssText = 'padding: 8px 20px; 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);';
currentPageBtn.onmouseenter = () => {
currentPageBtn.style.transform = 'translateY(-2px)';
currentPageBtn.style.boxShadow = '0 4px 12px rgba(30, 136, 229, 0.4)';
};
currentPageBtn.onmouseleave = () => {
currentPageBtn.style.transform = 'translateY(0)';
currentPageBtn.style.boxShadow = '0 2px 6px rgba(30, 136, 229, 0.3)';
};
const currentPageBtn = this.bottomToolbar.createEl('button', { text: '⬇ 当前页切图', cls: 'xhs-slice-btn' });
currentPageBtn.onclick = () => this.sliceCurrentPage();
const allPagesBtn = this.bottomToolbar.createEl('button', { text: '⇓ 全部页切图' });
allPagesBtn.style.cssText = 'padding: 8px 20px; background: linear-gradient(135deg, #42a5f5 0%, #1e88e5 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(66, 165, 245, 0.3);';
allPagesBtn.onmouseenter = () => {
allPagesBtn.style.transform = 'translateY(-2px)';
allPagesBtn.style.boxShadow = '0 4px 12px rgba(66, 165, 245, 0.4)';
};
allPagesBtn.onmouseleave = () => {
allPagesBtn.style.transform = 'translateY(0)';
allPagesBtn.style.boxShadow = '0 2px 6px rgba(66, 165, 245, 0.3)';
};
const allPagesBtn = this.bottomToolbar.createEl('button', { text: '⇓ 全部页切图', cls: 'xhs-slice-btn secondary' });
allPagesBtn.onclick = () => this.sliceAllPages();
}
@@ -240,7 +171,7 @@ export class XiaohongshuPreviewView implements IPlatformPreview {
// 创建临时容器用于分页
const tempContainer = document.createElement('div');
tempContainer.innerHTML = articleHTML;
tempContainer.style.cssText = `width: ${this.settings.sliceImageWidth}px;`;
tempContainer.style.width = `${this.settings.sliceImageWidth}px`;
document.body.appendChild(tempContainer);
try {
@@ -414,37 +345,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 = '';
}
}

View File

@@ -16,6 +16,8 @@
flex: 1;
overflow-y: auto;
padding: 10px;
-webkit-user-select: text;
user-select: text;
}
.preview-toolbar {
@@ -245,4 +247,420 @@ label:hover {
100% {
transform: rotate(360deg);
}
}
/* =========================================================== */
/* Toolbar 行样式 */
/* =========================================================== */
.toolbar-line {
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);
}
.toolbar-line.flex-wrap {
flex-wrap: wrap;
}
.platform-selector-line {
background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%) !important;
border-left: 4px solid #1e88e5;
}
/* =========================================================== */
/* 平台选择器样式 */
/* =========================================================== */
.platform-select {
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;
}
.platform-select:hover {
border-color: #1e88e5;
}
.platform-select:focus {
outline: none;
border-color: #1e88e5;
}
/* =========================================================== */
/* 微信公众号选择器样式 */
/* =========================================================== */
.wechat-select {
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;
}
.wechat-select:hover {
border-color: #1e88e5;
}
.wechat-select:focus {
outline: none;
border-color: #1e88e5;
}
/* =========================================================== */
/* 按钮样式 */
/* =========================================================== */
.toolbar-button {
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);
}
.toolbar-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(30, 136, 229, 0.4);
}
.toolbar-button.purple-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);
}
.toolbar-button.purple-gradient:hover {
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.4);
}
/* =========================================================== */
/* 分隔线样式 */
/* =========================================================== */
.toolbar-separator {
width: 1px;
height: 24px;
background: #dadce0;
margin: 0 4px;
}
/* =========================================================== */
/* Doc Modal 样式 */
/* =========================================================== */
.doc-modal {
width: 640px;
height: 720px;
}
.doc-modal-content {
display: flex;
flex-direction: column;
}
.doc-modal-title {
margin-top: 0.5em;
}
.doc-modal-desc {
margin-bottom: 1em;
-webkit-user-select: text;
user-select: text;
}
.doc-modal-iframe {
flex: 1;
}
/* =========================================================== */
/* Setting Tab 帮助文档样式 */
/* =========================================================== */
.setting-help-section {
display: flex;
flex-direction: row;
align-items: center;
}
.setting-help-title {
margin-right: 10px;
}
/* =========================================================== */
/* Xiaohongshu WebView 样式 */
/* =========================================================== */
.xhs-webview {
display: none;
width: 1200px;
height: 800px;
}
/* =========================================================== */
/* Xiaohongshu Preview View 样式 */
/* =========================================================== */
.xiaohongshu-preview-container {
width: 100%;
height: 100%;
}
.xhs-preview-container {
display: flex;
flex-direction: column;
height: 100%;
background: linear-gradient(135deg, #f5f7fa 0%, #e8eaf6 100%);
}
.xhs-page-container {
flex: 1;
overflow: auto;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
background: radial-gradient(ellipse at top, rgba(255,255,255,0.1) 0%, transparent 70%);
}
.xhs-top-toolbar {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border-bottom: 1px solid #e8eaed;
box-shadow: 0 2px 4px rgba(0,0,0,0.04);
flex-wrap: wrap;
}
.toolbar-label {
font-size: 11px;
color: #5f6368;
font-weight: 500;
white-space: nowrap;
}
.xhs-select {
padding: 4px 8px;
border: 1px solid #dadce0;
border-radius: 4px;
background: white;
font-size: 11px;
cursor: pointer;
transition: border-color 0.2s ease;
}
.xhs-select:hover {
border-color: #1e88e5;
}
.xhs-select:focus {
outline: none;
border-color: #1e88e5;
}
.font-size-group {
display: flex;
align-items: center;
gap: 6px;
background: white;
border: 1px solid #dadce0;
border-radius: 4px;
padding: 2px;
}
.font-size-btn {
width: 24px;
height: 24px;
border: none;
background: transparent;
border-radius: 3px;
cursor: pointer;
font-size: 16px;
color: #5f6368;
transition: background 0.2s ease;
}
.font-size-btn:hover {
background: #f1f3f4;
}
.font-size-display {
min-width: 24px;
text-align: center;
font-size: 12px;
color: #202124;
font-weight: 500;
}
.xhs-page-navigation {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
padding: 12px;
background: white;
border-bottom: 1px solid #e8eaed;
}
.xhs-nav-btn {
width: 36px;
height: 36px;
border: 1px solid #dadce0;
border-radius: 50%;
cursor: pointer;
font-size: 20px;
background: white;
color: #5f6368;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
.xhs-nav-btn:hover {
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
color: white;
border-color: #1e88e5;
}
.xhs-page-number {
font-size: 14px;
min-width: 50px;
text-align: center;
color: #202124;
font-weight: 500;
}
.xhs-bottom-toolbar {
display: flex;
justify-content: center;
gap: 12px;
padding: 12px 16px;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-top: 1px solid #e8eaed;
box-shadow: 0 -2px 4px rgba(0,0,0,0.04);
}
.xhs-slice-btn {
padding: 8px 20px;
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);
}
.xhs-slice-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(30, 136, 229, 0.4);
}
.xhs-slice-btn.secondary {
background: linear-gradient(135deg, #42a5f5 0%, #1e88e5 100%);
box-shadow: 0 2px 6px rgba(66, 165, 245, 0.3);
}
.xhs-slice-btn.secondary:hover {
box-shadow: 0 4px 12px rgba(66, 165, 245, 0.4);
}
/* =========================================================== */
/* Xiaohongshu Login Modal 样式 */
/* =========================================================== */
.xiaohongshu-login-modal {
width: 400px;
padding: 20px;
}
.xhs-login-title {
text-align: center;
margin-bottom: 20px;
color: #ff4757;
}
.xhs-login-desc {
text-align: center;
color: #666;
margin-bottom: 30px;
}
.xhs-code-container {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.xhs-code-label {
min-width: 80px;
}
.xhs-code-input-wrapper {
flex: 1;
}
.xhs-input-full {
width: 100%;
font-size: 16px;
}
.xhs-send-code-btn {
min-width: 120px;
margin-left: 10px;
}
.xhs-status-message {
min-height: 30px;
margin-bottom: 20px;
text-align: center;
font-size: 14px;
}
.xhs-status-message.success {
color: #27ae60;
}
.xhs-status-message.error {
color: #e74c3c;
}
.xhs-status-message.info {
color: #3498db;
}
.xhs-button-container {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.xhs-login-btn {
min-width: 100px;
}

View File

@@ -42,8 +42,10 @@
+------------------------------------------------------------+
| [⬇ 当前页切图] [⇓ 全部页切图] | ← 底部操作栏
+------------------------------------------------------------+
效果参考附图。
- 先对html分页在预览中分页显示。
- 页面适配切图比例的设置。
@@ -52,12 +54,15 @@
- 点击"+"所有字体加一号,点击"-"所有字体减一号。
- 点击"当前页切图"把当前html页面转为png图片图片保存路径和命名按此前设置。
- 点击"全部页切图"把所有html页面转为png图片图片保存路径和命名按此前设置。
## 问题
1. "发布平台"选“小红书”时,预览页面没有加载当前文章。
1. "发布平台"首次选“小红书”时,预览页面没有加载当前文章。
2. 顶部按钮适应窗口宽度,超出窗口,折行显示。
3. 页预览不完整,改为
3. 小红书模式,页预览不完整
4. 修改:
- 公共部分独立出来如“发布平台”放在新建platform-choose.ts中“发布平台”选择切换平台逻辑放在该模块中便于以后其他平台扩展。
- 其他所有组件独立。node-preview.ts改为mp-preview.ts, 专门用于处理微信公众号模式下的页面和逻辑处理preview-view.ts改为xhs-preview.ts专门用于小红书模式下的页面和逻辑处理。
@@ -69,6 +74,7 @@
平台选择后依据选择模式调用mp-preview.ts(微信公众号mp)或xhs-preview.ts(小红书xhs)中的方法。
- mp-preview.ts中保留微信公众号模式(micro-public,mp)相关的处理逻辑。
- mp-preview.ts中去掉小红书处理逻辑(移到xhs-preview.ts中)。
5. 把代码逻辑中的所有css移到styles.css中。✅