update at 2025-10-09 12:39:24

This commit is contained in:
douboer
2025-10-09 12:39:24 +08:00
parent a891153be0
commit 6f51916b50
44 changed files with 332 additions and 226 deletions

View File

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 sunbooshi Copyright (c) 2025 Gavin Chan
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

View File

@@ -2,7 +2,15 @@
set -e # 出错立即退出 set -e # 出错立即退出
# 1. 构建 # 1. 构建
npm run build echo "🏗️ 开始构建..."
if npm run build; then
echo "✅ 构建成功"
echo
else
echo "❌ 构建失败,脚本终止"
echo
exit 1
fi
# 2. 目标目录 # 2. 目标目录
PLUGIN_DIR=~/myweb/.obsidian/plugins/note-to-mp PLUGIN_DIR=~/myweb/.obsidian/plugins/note-to-mp
@@ -16,12 +24,12 @@ for FILE in "${FILES[@]}"; do
if [ -f "$TARGET" ]; then if [ -f "$TARGET" ]; then
mkdir -p "$(dirname "$BACKUP")" mkdir -p "$(dirname "$BACKUP")"
cp -f "$TARGET" "$BACKUP" cp -f "$TARGET" "$BACKUP"
echo "已备份 $TARGET -> $BACKUP" echo "💾 已备份 $TARGET -> $BACKUP"
fi fi
if [ -f "$FILE" ]; then if [ -f "$FILE" ]; then
cp -f "$FILE" "$TARGET" cp -f "$FILE" "$TARGET"
echo "已更新 $TARGET" echo "📂 已更新 $TARGET"
else else
echo "⚠️ 源文件 $FILE 不存在,跳过" echo "⚠️ 源文件 $FILE 不存在,跳过"
fi fi
@@ -31,7 +39,12 @@ done
if [ -d "assets" ]; then if [ -d "assets" ]; then
mkdir -p "$PLUGIN_DIR/assets" mkdir -p "$PLUGIN_DIR/assets"
rsync -a --delete assets/ "$PLUGIN_DIR/assets/" >/dev/null rsync -a --delete assets/ "$PLUGIN_DIR/assets/" >/dev/null
echo "已同步 assets -> $PLUGIN_DIR/assets/" echo "🎨 已同步 assets -> $PLUGIN_DIR/assets/"
echo
else else
echo "⚠️ 源目录 assets 不存在,跳过" echo "⚠️ 源目录 assets 不存在,跳过"
echo
fi fi
echo "✅ 部署完成!"
echo

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -1,10 +1,10 @@
{ {
"id": "note-to-mp", "id": "note-to-mp",
"name": "NoteToMP", "name": "NoteToAny",
"version": "1.3.0", "version": "1.3.4",
"minAppVersion": "1.4.5", "minAppVersion": "1.4.5",
"description": "Send notes to WeChat MP drafts, or copy notes to WeChat MP editor, perfect preservation of note styles, support code highlighting, line numbers in code, and support local image uploads.", "description": "xiaohongshu/mp publisher ",
"author": "Sun Booshi", "author": "Gavin chan",
"authorUrl": "https://sunboshi.tech", "authorUrl": "https://biboer.cn",
"isDesktopOnly": false "isDesktopOnly": false
} }

View File

@@ -53,7 +53,7 @@ export class PlatformChooser {
constructor(container: HTMLElement, options: PlatformChooserOptions = {}) { constructor(container: HTMLElement, options: PlatformChooserOptions = {}) {
this.container = container; this.container = container;
this.currentPlatform = options.defaultPlatform || 'wechat'; this.currentPlatform = options.defaultPlatform || 'xiaohongshu';
if (options.onPlatformChange) { if (options.onPlatformChange) {
this.onChange = (platform) => { this.onChange = (platform) => {
options.onPlatformChange!(platform); options.onPlatformChange!(platform);
@@ -72,15 +72,16 @@ export class PlatformChooser {
* 渲染平台选择器 UI * 渲染平台选择器 UI
*/ */
render(): void { render(): void {
// 创建平台选择行 // 将容器作为单层 Grid 行使用
const lineDiv = this.container.createDiv({ cls: 'toolbar-line platform-selector-line' }); this.container.addClass('platform-selector-line');
this.container.addClass('platform-chooser-grid');
// 创建标签 // 创建标签
const platformLabel = lineDiv.createDiv({ cls: 'style-label' }); const platformLabel = this.container.createDiv({ cls: 'style-label' });
platformLabel.innerText = '发布平台'; platformLabel.innerText = '发布平台';
// 创建选择器 // 创建选择器
const platformSelect = lineDiv.createEl('select', { cls: 'platform-select' }); const platformSelect = this.container.createEl('select', { cls: 'platform-select' });
this.selectElement = platformSelect; this.selectElement = platformSelect;
// 添加平台选项 // 添加平台选项

View File

@@ -37,7 +37,7 @@ export class PreviewManager {
private xhsContainer: HTMLDivElement | null = null; private xhsContainer: HTMLDivElement | null = null;
// 状态 // 状态
private currentPlatform: PlatformType = 'wechat'; private currentPlatform: PlatformType = 'xiaohongshu';
private currentFile: TFile | null = null; private currentFile: TFile | null = null;
constructor(container: HTMLElement, app: App, render: ArticleRender) { constructor(container: HTMLElement, app: App, render: ArticleRender) {
@@ -68,8 +68,8 @@ export class PreviewManager {
// 3. 创建并构建小红书预览 // 3. 创建并构建小红书预览
this.createXiaohongshuPreview(); this.createXiaohongshuPreview();
// 4. 初始显示微信平台 // 4. 初始显示小红书平台
await this.switchPlatform('wechat'); await this.switchPlatform('xiaohongshu');
console.log('[PreviewManager] 界面构建完成'); console.log('[PreviewManager] 界面构建完成');
} }

View File

@@ -51,6 +51,7 @@ export class NMPSettings {
sliceImageSavePath: string; // 切图保存路径 sliceImageSavePath: string; // 切图保存路径
sliceImageWidth: number; // 切图宽度(像素) sliceImageWidth: number; // 切图宽度(像素)
sliceImageAspectRatio: string; // 横竖比例,格式 "3:4" sliceImageAspectRatio: string; // 横竖比例,格式 "3:4"
xhsPreviewWidth: number; // 小红书预览宽度(像素)
private static instance: NMPSettings; private static instance: NMPSettings;
@@ -100,6 +101,7 @@ export class NMPSettings {
this.sliceImageSavePath = '/Users/gavin/note2mp/images/xhs'; this.sliceImageSavePath = '/Users/gavin/note2mp/images/xhs';
this.sliceImageWidth = 1080; this.sliceImageWidth = 1080;
this.sliceImageAspectRatio = '3:4'; this.sliceImageAspectRatio = '3:4';
this.xhsPreviewWidth = 540;
} }
resetStyelAndHighlight() { resetStyelAndHighlight() {
@@ -136,7 +138,8 @@ export class NMPSettings {
batchPublishPresets = [], batchPublishPresets = [],
sliceImageSavePath, sliceImageSavePath,
sliceImageWidth, sliceImageWidth,
sliceImageAspectRatio sliceImageAspectRatio,
xhsPreviewWidth
} = data; } = data;
const settings = NMPSettings.getInstance(); const settings = NMPSettings.getInstance();
@@ -166,6 +169,9 @@ export class NMPSettings {
if (sliceImageSavePath) settings.sliceImageSavePath = sliceImageSavePath; if (sliceImageSavePath) settings.sliceImageSavePath = sliceImageSavePath;
if (sliceImageWidth !== undefined && Number.isFinite(sliceImageWidth)) settings.sliceImageWidth = Math.max(100, parseInt(sliceImageWidth)); if (sliceImageWidth !== undefined && Number.isFinite(sliceImageWidth)) settings.sliceImageWidth = Math.max(100, parseInt(sliceImageWidth));
if (sliceImageAspectRatio) settings.sliceImageAspectRatio = sliceImageAspectRatio; if (sliceImageAspectRatio) settings.sliceImageAspectRatio = sliceImageAspectRatio;
if (xhsPreviewWidth !== undefined && Number.isFinite(xhsPreviewWidth)) {
settings.xhsPreviewWidth = Math.max(100, parseInt(xhsPreviewWidth));
}
settings.getExpiredDate(); settings.getExpiredDate();
settings.isLoaded = true; settings.isLoaded = true;
@@ -200,6 +206,7 @@ export class NMPSettings {
'sliceImageSavePath': settings.sliceImageSavePath, 'sliceImageSavePath': settings.sliceImageSavePath,
'sliceImageWidth': settings.sliceImageWidth, 'sliceImageWidth': settings.sliceImageWidth,
'sliceImageAspectRatio': settings.sliceImageAspectRatio, 'sliceImageAspectRatio': settings.sliceImageAspectRatio,
'xhsPreviewWidth': settings.xhsPreviewWidth,
} }
} }
@@ -221,4 +228,4 @@ export class NMPSettings {
if (this.expireat == null) return false; if (this.expireat == null) return false;
return this.expireat > new Date(); return this.expireat > new Date();
} }
} }

View File

@@ -93,25 +93,23 @@ export class WechatPreview {
* 构建工具栏 * 构建工具栏
*/ */
private buildToolbar(parent: HTMLDivElement): void { private buildToolbar(parent: HTMLDivElement): void {
let lineDiv; // 单层 Grid所有控件直接挂到 parent.preview-toolbar
// 公众号选择 // 公众号选择
if (this.settings.wxInfo.length > 1 || Platform.isDesktop) { if (this.settings.wxInfo.length > 1 || Platform.isDesktop) {
lineDiv = parent.createDiv({ cls: 'toolbar-line' }); const wxLabel = parent.createDiv({ cls: 'style-label' });
const wxLabel = lineDiv.createDiv({ cls: 'style-label' });
wxLabel.innerText = '公众号'; wxLabel.innerText = '公众号';
const wxSelect = lineDiv.createEl('select', { cls: 'wechat-select' }); const wxSelect = parent.createEl('select', { cls: 'wechat-select' });
wxSelect.onchange = async () => { wxSelect.onchange = async () => {
this.currentAppId = wxSelect.value; this.currentAppId = wxSelect.value;
this.onAppIdChanged(); this.onAppIdChanged();
}; };
const defaultOp = wxSelect.createEl('option'); const defaultOp = wxSelect.createEl('option');
defaultOp.value = ''; defaultOp.value = '';
defaultOp.text = '请在设置里配置公众号'; defaultOp.text = '请在设置里配置公众号';
for (let i = 0; i < this.settings.wxInfo.length; i++) { for (let i = 0; i < this.settings.wxInfo.length; i++) {
const op = wxSelect.createEl('option'); const op = wxSelect.createEl('option');
const wx = this.settings.wxInfo[i]; const wx = this.settings.wxInfo[i];
@@ -125,8 +123,8 @@ export class WechatPreview {
this.wechatSelect = wxSelect; this.wechatSelect = wxSelect;
if (Platform.isDesktop) { if (Platform.isDesktop) {
const separator = lineDiv.createDiv({ cls: 'toolbar-separator' }); //parent.createDiv({ cls: 'toolbar-separator' });
const openBtn = lineDiv.createEl('button', { text: '🌐 去公众号后台', cls: 'toolbar-button purple-gradient' }); const openBtn = parent.createEl('button', { text: '🌐 去公众号后台', cls: 'toolbar-button purple-gradient' });
openBtn.onclick = async () => { openBtn.onclick = async () => {
const { shell } = require('electron'); const { shell } = require('electron');
shell.openExternal('https://mp.weixin.qq.com'); shell.openExternal('https://mp.weixin.qq.com');
@@ -135,21 +133,15 @@ export class WechatPreview {
} }
} }
// 操作按钮 // 操作按钮(直接平铺在 Grid 中)
lineDiv = parent.createDiv({ cls: 'toolbar-line flex-wrap' }); const refreshBtn = parent.createEl('button', { text: '🔄 刷新', cls: 'toolbar-button purple-gradient' });
refreshBtn.onclick = async () => { if (this.onRefreshCallback) await this.onRefreshCallback(); };
const refreshBtn = lineDiv.createEl('button', { text: '🔄 刷新', cls: 'toolbar-button purple-gradient' });
refreshBtn.onclick = async () => {
if (this.onRefreshCallback) {
await this.onRefreshCallback();
}
};
const postBtn = lineDiv.createEl('button', { text: '📝 发布', cls: 'toolbar-button' }); const postBtn = parent.createEl('button', { text: '📝 发布', cls: 'toolbar-button' });
postBtn.onclick = async () => await this.postArticle(); postBtn.onclick = async () => await this.postArticle();
if (Platform.isDesktop && this.settings.isAuthKeyVaild()) { if (Platform.isDesktop && this.settings.isAuthKeyVaild()) {
const htmlBtn = lineDiv.createEl('button', { text: '💾 导出HTML', cls: 'toolbar-button' }); const htmlBtn = parent.createEl('button', { text: '💾 导出HTML', cls: 'toolbar-button' });
htmlBtn.onclick = async () => await this.exportHTML(); htmlBtn.onclick = async () => await this.exportHTML();
} }
@@ -166,11 +158,10 @@ export class WechatPreview {
* 构建封面选择器 * 构建封面选择器
*/ */
private buildCoverSelector(parent: HTMLDivElement): void { private buildCoverSelector(parent: HTMLDivElement): void {
const lineDiv = parent.createDiv({ cls: 'toolbar-line' }); const coverTitle = parent.createDiv({ cls: 'style-label' });
const coverTitle = lineDiv.createDiv({ cls: 'style-label' });
coverTitle.innerText = '封面'; coverTitle.innerText = '封面';
this.useDefaultCover = lineDiv.createEl('input', { cls: 'input-style' }); this.useDefaultCover = parent.createEl('input', { cls: 'input-style' });
this.useDefaultCover.setAttr('type', 'radio'); this.useDefaultCover.setAttr('type', 'radio');
this.useDefaultCover.setAttr('name', 'cover'); this.useDefaultCover.setAttr('name', 'cover');
this.useDefaultCover.setAttr('value', 'default'); this.useDefaultCover.setAttr('value', 'default');
@@ -182,11 +173,11 @@ export class WechatPreview {
} }
}; };
const defaultLabel = lineDiv.createEl('label'); const defaultLabel = parent.createEl('label');
defaultLabel.innerText = '默认'; defaultLabel.innerText = '默认';
defaultLabel.setAttr('for', 'default-cover'); defaultLabel.setAttr('for', 'default-cover');
this.useLocalCover = lineDiv.createEl('input', { cls: 'input-style' }); this.useLocalCover = parent.createEl('input', { cls: 'input-style' });
this.useLocalCover.setAttr('type', 'radio'); this.useLocalCover.setAttr('type', 'radio');
this.useLocalCover.setAttr('name', 'cover'); this.useLocalCover.setAttr('name', 'cover');
this.useLocalCover.setAttr('value', 'local'); this.useLocalCover.setAttr('value', 'local');
@@ -198,11 +189,11 @@ export class WechatPreview {
} }
}; };
const localLabel = lineDiv.createEl('label'); const localLabel = parent.createEl('label');
localLabel.setAttr('for', 'local-cover'); localLabel.setAttr('for', 'local-cover');
localLabel.innerText = '上传'; localLabel.innerText = '上传';
this.coverEl = lineDiv.createEl('input', { cls: 'upload-input' }); this.coverEl = parent.createEl('input', { cls: 'upload-input' });
this.coverEl.setAttr('type', 'file'); this.coverEl.setAttr('type', 'file');
this.coverEl.setAttr('placeholder', '封面图片'); this.coverEl.setAttr('placeholder', '封面图片');
this.coverEl.setAttr('accept', '.png, .jpg, .jpeg'); this.coverEl.setAttr('accept', '.png, .jpg, .jpeg');
@@ -214,12 +205,10 @@ export class WechatPreview {
* 构建样式选择器 * 构建样式选择器
*/ */
private buildStyleSelector(parent: HTMLDivElement): void { private buildStyleSelector(parent: HTMLDivElement): void {
const lineDiv = parent.createDiv({ cls: 'toolbar-line flex-wrap' }); const cssStyle = parent.createDiv({ cls: 'style-label' });
const cssStyle = lineDiv.createDiv({ cls: 'style-label' });
cssStyle.innerText = '样式'; cssStyle.innerText = '样式';
const selectBtn = lineDiv.createEl('select', { cls: 'style-select' }); const selectBtn = parent.createEl('select', { cls: 'style-select' });
selectBtn.onchange = async () => { selectBtn.onchange = async () => {
this.currentTheme = selectBtn.value; this.currentTheme = selectBtn.value;
this.render.updateStyle(selectBtn.value); this.render.updateStyle(selectBtn.value);
@@ -233,12 +222,12 @@ export class WechatPreview {
} }
this.themeSelect = selectBtn; this.themeSelect = selectBtn;
const separator = lineDiv.createDiv({ cls: 'toolbar-separator' }); parent.createDiv({ cls: 'toolbar-separator' });
const highlightStyle = lineDiv.createDiv({ cls: 'style-label' }); const highlightStyle = parent.createDiv({ cls: 'style-label' });
highlightStyle.innerText = '代码高亮'; highlightStyle.innerText = '代码高亮';
const highlightStyleBtn = lineDiv.createEl('select', { cls: 'style-select' }); const highlightStyleBtn = parent.createEl('select', { cls: 'style-select' });
highlightStyleBtn.onchange = async () => { highlightStyleBtn.onchange = async () => {
this.currentHighlight = highlightStyleBtn.value; this.currentHighlight = highlightStyleBtn.value;
this.render.updateHighLight(highlightStyleBtn.value); this.render.updateHighLight(highlightStyleBtn.value);

View File

@@ -15,6 +15,9 @@ 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';
const XHS_PREVIEW_DEFAULT_WIDTH = 540;
const XHS_PREVIEW_WIDTH_OPTIONS = [1080, 720, 540, 360];
/** /**
* 小红书预览视图类 * 小红书预览视图类
*/ */
@@ -29,6 +32,7 @@ export class XiaohongshuPreview {
topToolbar!: HTMLDivElement; topToolbar!: HTMLDivElement;
templateSelect!: HTMLSelectElement; templateSelect!: HTMLSelectElement;
fontSizeInput!: HTMLInputElement; fontSizeInput!: HTMLInputElement;
previewWidthSelect!: HTMLSelectElement;
pageContainer!: HTMLDivElement; pageContainer!: HTMLDivElement;
bottomToolbar!: HTMLDivElement; bottomToolbar!: HTMLDivElement;
@@ -110,6 +114,30 @@ export class XiaohongshuPreview {
option.text = name; option.text = name;
}); });
const previewWidthLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' });
previewWidthLabel.innerText = '预览宽度';
this.previewWidthSelect = this.topToolbar.createEl('select', { cls: 'xhs-select' });
const currentPreviewWidth = this.settings.xhsPreviewWidth || XHS_PREVIEW_DEFAULT_WIDTH;
XHS_PREVIEW_WIDTH_OPTIONS.forEach(value => {
const option = this.previewWidthSelect.createEl('option');
option.value = String(value);
option.text = `${value}px`;
});
if (!XHS_PREVIEW_WIDTH_OPTIONS.includes(currentPreviewWidth)) {
const customOption = this.previewWidthSelect.createEl('option');
customOption.value = String(currentPreviewWidth);
customOption.text = `${currentPreviewWidth}px`;
}
this.previewWidthSelect.value = String(currentPreviewWidth);
this.previewWidthSelect.onchange = async () => {
const value = parseInt(this.previewWidthSelect.value, 10);
if (Number.isFinite(value) && value > 0) {
await this.onPreviewWidthChanged(value);
} else {
this.previewWidthSelect.value = String(this.settings.xhsPreviewWidth || XHS_PREVIEW_DEFAULT_WIDTH);
}
};
// 字号控制(可直接编辑) // 字号控制(可直接编辑)
const fontSizeLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' }); const fontSizeLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' });
fontSizeLabel.innerText = '字号'; fontSizeLabel.innerText = '字号';
@@ -205,6 +233,7 @@ export class XiaohongshuPreview {
if (this.currentThemeClass) classes.push('note-to-mp'); if (this.currentThemeClass) classes.push('note-to-mp');
const pageElement = wrapper.createDiv({ cls: classes.join(' ') }); const pageElement = wrapper.createDiv({ cls: classes.join(' ') });
renderPage(pageElement, page.content, this.settings); renderPage(pageElement, page.content, this.settings);
this.applyPreviewSizing(wrapper, pageElement);
// 应用字体设置 // 应用字体设置
this.applyFontSettings(pageElement); this.applyFontSettings(pageElement);
@@ -213,6 +242,49 @@ export class XiaohongshuPreview {
this.pageNumberDisplay.innerText = `${this.currentPageIndex + 1}/${this.pages.length}`; this.pageNumberDisplay.innerText = `${this.currentPageIndex + 1}/${this.pages.length}`;
} }
/**
* 根据设置的宽度和横竖比应用预览尺寸与缩放
*/
private applyPreviewSizing(wrapper: HTMLElement, pageElement: HTMLElement): void {
const configuredWidth = this.settings.sliceImageWidth || 1080;
const actualWidth = Math.max(1, configuredWidth);
const ratio = this.parseAspectRatio(this.settings.sliceImageAspectRatio);
const actualHeight = Math.round((actualWidth * ratio.height) / ratio.width);
const previewWidthSetting = this.settings.xhsPreviewWidth || XHS_PREVIEW_DEFAULT_WIDTH;
const previewWidth = Math.max(1, previewWidthSetting);
const scale = Math.max(previewWidth / actualWidth, 0.01);
const previewHeight = Math.max(1, Math.round(actualHeight * scale));
wrapper.style.width = `${previewWidth}px`;
wrapper.style.height = `${previewHeight}px`;
pageElement.style.width = `${actualWidth}px`;
pageElement.style.height = `${actualHeight}px`;
pageElement.style.transform = `scale(${scale})`;
pageElement.style.position = 'absolute';
pageElement.style.top = '0';
pageElement.style.left = '0';
}
private async onPreviewWidthChanged(newWidth: number): Promise<void> {
if (newWidth <= 0) return;
if (this.settings.xhsPreviewWidth === newWidth) return;
this.settings.xhsPreviewWidth = newWidth;
await this.persistSettings();
this.renderCurrentPage();
}
/**
* 解析横竖比例字符串
*/
private parseAspectRatio(ratio: string | undefined): { width: number; height: number } {
const parts = (ratio ?? '').split(':').map(part => parseFloat(part.trim()));
if (parts.length === 2 && isFinite(parts[0]) && isFinite(parts[1]) && parts[0] > 0 && parts[1] > 0) {
return { width: parts[0], height: parts[1] };
}
return { width: 3, height: 4 };
}
/** /**
* 应用字体设置(仅字号,字体从主题读取) * 应用字体设置(仅字号,字体从主题读取)
*/ */
@@ -341,6 +413,17 @@ export class XiaohongshuPreview {
} }
} }
private async persistSettings(): Promise<void> {
try {
const plugin = (this.app as any)?.plugins?.getPlugin?.('note-to-mp');
if (plugin?.saveSettings) {
await plugin.saveSettings();
}
} catch (error) {
console.warn('[XiaohongshuPreview] 保存设置失败', error);
}
}
/** /**
* 显示小红书预览视图 * 显示小红书预览视图
*/ */
@@ -365,6 +448,7 @@ export class XiaohongshuPreview {
destroy(): void { destroy(): void {
this.topToolbar = null as any; this.topToolbar = null as any;
this.templateSelect = null as any; this.templateSelect = null as any;
this.previewWidthSelect = null as any;
this.fontSizeInput = null as any; this.fontSizeInput = null as any;
this.pageContainer = null as any; this.pageContainer = null as any;
this.bottomToolbar = null as any; this.bottomToolbar = null as any;

View File

@@ -2,26 +2,94 @@
/* =========================================================== */ /* =========================================================== */
/* UI 样式 */ /* UI 样式 */
/* 共用样式与去重 */
/* =========================================================== */ /* =========================================================== */
/* 主题变量统一常用色值/阴影/渐变 */
:root {
--c-bg: #ffffff;
--c-border: #dadce0;
--c-text-muted: #5f6368;
--c-primary: #1e88e5;
--c-primary-dark: #1565c0;
--c-purple: #667eea;
--c-purple-dark: #764ba2;
--c-blue-2: #42a5f5;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
--shadow-overlay: 0 2px 4px rgba(0,0,0,0.04);
--shadow-primary-2: 0 2px 6px rgba(30, 136, 229, 0.3);
--shadow-primary-4: 0 4px 8px rgba(30, 136, 229, 0.4);
--shadow-purple-2: 0 2px 6px rgba(102, 126, 234, 0.3);
--shadow-purple-4: 0 4px 8px rgba(102, 126, 234, 0.4);
--grad-primary: linear-gradient(135deg, var(--c-primary) 0%, var(--c-primary-dark) 100%);
--grad-purple: linear-gradient(135deg, var(--c-purple) 0%, var(--c-purple-dark) 100%);
--grad-blue: linear-gradient(135deg, var(--c-blue-2) 0%, var(--c-primary) 100%);
--grad-toolbar: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
--grad-toolbar-bottom: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
--grad-xhs-bg: linear-gradient(135deg, #f5f7fa 0%, #e8eaf6 100%);
}
/* 通用按钮外观(不含背景与尺寸) */
.copy-button,
.refresh-button,
.toolbar-button,
.msg-ok-btn,
.xhs-slice-btn {
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
/* 通用按钮 hover 的位移效果(各自保留独立阴影) */
.copy-button:hover,
.refresh-button:hover,
.toolbar-button:hover,
.msg-ok-btn:hover {
transform: translateY(-1px);
}
/* 下拉选择的通用外观(各自保留尺寸差异) */
.platform-select,
.wechat-select,
.style-select {
border: 1px solid var(--c-border);
border-radius: 6px;
background: white;
font-size: 13px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: var(--shadow-sm);
}
/* 平台与公众号选择的相同 hover/focus 效果style-select 单独增强) */
.platform-select:hover,
.wechat-select:hover { border-color: var(--c-primary); }
.platform-select:focus,
.wechat-select:focus { outline: none; border-color: var(--c-primary); }
.note-preview { .note-preview {
grid-template-rows: auto 1fr;
grid-template-columns: 1fr;
display: grid;
min-height: 100%; min-height: 100%;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #fff; background-color: var(--c-bg);
display: flex;
flex-direction: column;
} }
/* 预览内部平台容器需要可伸缩: */ /* 预览内部平台容器需要可伸缩: */
.wechat-preview-container, .xiaohongshu-preview-container { .wechat-preview-container:not([style*="display: none"]),
.xiaohongshu-preview-container:not([style*="display: none"]) {
flex: 1; flex: 1;
display: flex; display: grid !important;
flex-direction: column; grid-template-rows: auto 1fr;
min-height: 0; /* 允许内部滚动区域正确计算高度 */ min-height: 0; /* 允许内部滚动区域正确计算高度 */
} }
.render-div { .render-div {
flex: 1;
overflow-y: auto; overflow-y: auto;
padding: 10px; padding: 10px;
-webkit-user-select: text; -webkit-user-select: text;
@@ -44,55 +112,43 @@
.preview-toolbar { .preview-toolbar {
position: relative; position: relative;
min-height: 100px; display: grid;
padding: 4px 0; grid-template-columns: repeat(auto-fit, minmax(160px, max-content));
gap: 12px;
align-items: center;
min-height: auto;
padding: 8px 12px;
border-bottom: 1px solid #e8eaed; border-bottom: 1px solid #e8eaed;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); background: var(--grad-toolbar);
box-shadow: 0 2px 4px rgba(0,0,0,0.04); box-shadow: var(--shadow-overlay);
} }
.copy-button { .copy-button {
margin-right: 10px; margin-right: 10px;
padding: 6px 14px; padding: 6px 14px;
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); background: var(--grad-primary);
color: white; color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px; font-size: 13px;
font-weight: 500; box-shadow: var(--shadow-primary-2);
transition: all 0.2s ease;
box-shadow: 0 2px 6px rgba(30, 136, 229, 0.3);
} }
.copy-button:hover { .copy-button:hover { box-shadow: var(--shadow-primary-4); }
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(30, 136, 229, 0.4);
}
.refresh-button { .refresh-button {
margin-right: 10px; margin-right: 10px;
padding: 6px 14px; padding: 6px 14px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--grad-purple);
color: white; color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px; font-size: 13px;
font-weight: 500; box-shadow: var(--shadow-purple-2);
transition: all 0.2s ease;
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);
} }
.refresh-button:hover { .refresh-button:hover { box-shadow: var(--shadow-purple-4); }
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.4);
}
.upload-input { .upload-input {
margin-left: 10px; margin-left: 10px;
padding: 6px 10px; padding: 6px 10px;
border: 1px solid #dadce0; border: 1px solid var(--c-border);
border-radius: 6px; border-radius: 6px;
font-size: 13px; font-size: 13px;
transition: all 0.2s ease; transition: all 0.2s ease;
@@ -102,9 +158,10 @@
cursor: pointer; cursor: pointer;
} }
.upload-input:focus { .upload-input:focus,
.style-select:focus {
outline: none; outline: none;
border-color: #1e88e5; border-color: var(--c-primary);
box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.1); box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.1);
} }
@@ -114,26 +171,24 @@
height: 16px; height: 16px;
margin: 0 6px 0 0; margin: 0 6px 0 0;
cursor: pointer; cursor: pointer;
accent-color: #1e88e5; accent-color: var(--c-primary);
} }
/* Label 标签样式 */ /* Label 标签样式 */
label { label {
font-size: 13px; font-size: 13px;
color: #5f6368; color: var(--c-text-muted);
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
transition: color 0.2s ease; transition: color 0.2s ease;
} }
label:hover { label:hover { color: var(--c-primary); }
color: #1e88e5;
}
.style-label { .style-label {
margin-right: 10px; margin-right: 10px;
font-size: 13px; font-size: 13px;
color: #5f6368; color: var(--c-text-muted);
font-weight: 500; font-weight: 500;
white-space: nowrap; white-space: nowrap;
} }
@@ -142,25 +197,14 @@ label:hover {
margin-right: 10px; margin-right: 10px;
width: 120px; width: 120px;
padding: 6px 10px; padding: 6px 10px;
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);
} }
.style-select:hover { .style-select:hover {
border-color: #1e88e5; border-color: var(--c-primary);
box-shadow: 0 2px 6px rgba(30, 136, 229, 0.2); box-shadow: 0 2px 6px rgba(30, 136, 229, 0.2);
} }
.style-select:focus { /* focus 规则见与 .upload-input:focus 的组合声明 */
outline: none;
border-color: #1e88e5;
box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.1);
}
.msg-view { .msg-view {
position: absolute; position: absolute;
@@ -186,22 +230,14 @@ label:hover {
.msg-ok-btn { .msg-ok-btn {
padding: 10px 24px; padding: 10px 24px;
margin: 0 8px; margin: 0 8px;
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); background: var(--grad-primary);
color: white; color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px; font-size: 14px;
font-weight: 500; box-shadow: var(--shadow-primary-2);
transition: all 0.2s ease;
box-shadow: 0 2px 6px rgba(30, 136, 229, 0.3);
min-width: 80px; min-width: 80px;
} }
.msg-ok-btn:hover { .msg-ok-btn:hover { box-shadow: var(--shadow-primary-4); }
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(30, 136, 229, 0.4);
}
.msg-ok-btn:active { .msg-ok-btn:active {
transform: translateY(0); transform: translateY(0);
@@ -214,7 +250,9 @@ label:hover {
border-radius: 10px; border-radius: 10px;
} }
.note-mpcard-content { .note-mpcard-content {
display: flex; display: grid;
grid-auto-flow: column;
align-items: center;
} }
.note-mpcard-headimg { .note-mpcard-headimg {
border: none !important; border: none !important;
@@ -246,11 +284,10 @@ label:hover {
} }
.loading-wrapper { .loading-wrapper {
display: flex; display: grid;
width: 100%; width: 100%;
height: 100%; height: 100%;
align-items: center; place-items: center;
justify-content: center;
} }
.loading-spinner { .loading-spinner {
@@ -276,23 +313,38 @@ label:hover {
/* =========================================================== */ /* =========================================================== */
.toolbar-line { .toolbar-line {
display: flex; display: grid;
grid-auto-flow: column;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
padding: 8px 12px; padding: 8px 12px;
background: white; background: white;
border-radius: 6px; border-radius: 6px;
margin: 8px 10px; margin: 8px 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08); box-shadow: var(--shadow-sm);
} }
.toolbar-line.flex-wrap { .toolbar-line.flex-wrap {
flex-wrap: wrap; grid-auto-flow: row;
grid-template-columns: repeat(auto-fit, minmax(160px, max-content));
} }
.platform-selector-line { .platform-selector-line {
background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%) !important; background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%) !important;
border-left: 4px solid #1e88e5; border-left: 4px solid var(--c-primary);
}
/* 平台选择容器:单层 Grid 排列 */
.platform-chooser-container.platform-chooser-grid {
display: grid;
grid-auto-flow: column;
align-items: center;
gap: 12px;
padding: 8px 12px;
background: white; /* 被 .platform-selector-line 的背景覆写 */
border-radius: 6px;
margin: 8px 10px;
box-shadow: var(--shadow-sm);
} }
/* =========================================================== */ /* =========================================================== */
@@ -301,81 +353,39 @@ label:hover {
.platform-select { .platform-select {
padding: 6px 12px; 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; min-width: 150px;
font-weight: 500; font-weight: 500;
} }
.platform-select:hover {
border-color: #1e88e5;
}
.platform-select:focus {
outline: none;
border-color: #1e88e5;
}
/* =========================================================== */ /* =========================================================== */
/* 微信公众号选择器样式 */ /* 微信公众号选择器样式 */
/* =========================================================== */ /* =========================================================== */
.wechat-select { .wechat-select {
padding: 6px 12px; 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; min-width: 200px;
} }
.wechat-select:hover {
border-color: #1e88e5;
}
.wechat-select:focus {
outline: none;
border-color: #1e88e5;
}
/* =========================================================== */ /* =========================================================== */
/* 按钮样式 */ /* 按钮样式 */
/* =========================================================== */ /* =========================================================== */
.toolbar-button { .toolbar-button {
padding: 6px 14px; padding: 6px 14px;
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); background: var(--grad-primary);
color: white; color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px; font-size: 13px;
font-weight: 500; box-shadow: var(--shadow-primary-2);
transition: all 0.2s ease;
box-shadow: 0 2px 6px rgba(30, 136, 229, 0.3);
} }
.toolbar-button:hover { .toolbar-button:hover { box-shadow: var(--shadow-primary-4); }
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(30, 136, 229, 0.4);
}
.toolbar-button.purple-gradient { .toolbar-button.purple-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--grad-purple);
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3); box-shadow: var(--shadow-purple-2);
} }
.toolbar-button.purple-gradient:hover { .toolbar-button.purple-gradient:hover { box-shadow: var(--shadow-purple-4); }
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.4);
}
/* =========================================================== */ /* =========================================================== */
/* 分隔线样式 */ /* 分隔线样式 */
@@ -384,7 +394,7 @@ label:hover {
.toolbar-separator { .toolbar-separator {
width: 1px; width: 1px;
height: 24px; height: 24px;
background: #dadce0; background: var(--c-border);
margin: 0 4px; margin: 0 4px;
} }
@@ -398,8 +408,10 @@ label:hover {
} }
.doc-modal-content { .doc-modal-content {
display: flex; display: grid;
flex-direction: column; grid-template-rows: auto auto 1fr;
row-gap: 8px;
min-height: 0;
} }
.doc-modal-title { .doc-modal-title {
@@ -413,7 +425,7 @@ label:hover {
} }
.doc-modal-iframe { .doc-modal-iframe {
flex: 1; min-height: 0;
} }
/* =========================================================== */ /* =========================================================== */
@@ -421,9 +433,10 @@ label:hover {
/* =========================================================== */ /* =========================================================== */
.setting-help-section { .setting-help-section {
display: flex; display: grid;
flex-direction: row; grid-auto-flow: column;
align-items: center; align-items: center;
column-gap: 10px;
} }
.setting-help-title { .setting-help-title {
@@ -449,40 +462,36 @@ label:hover {
height: 100%; height: 100%;
} }
.xhs-preview-container { .xhs-preview-container:not([style*="display: none"]) {
display: flex; display: grid !important;
flex-direction: column; grid-template-rows: auto 1fr auto;
height: 100%; height: 100%;
background: linear-gradient(135deg, #f5f7fa 0%, #e8eaf6 100%); background: var(--grad-xhs-bg);
min-height: 0;
} }
.xhs-page-container { .xhs-page-container {
flex: 1;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
display: flex; display: grid;
flex-direction: column; align-content: start;
align-items: center; justify-content: center;
padding: 0px; padding: 0px;
background: radial-gradient(ellipse at top, rgba(255,255,255,0.1) 0%, transparent 70%); background: radial-gradient(ellipse at top, rgba(255,255,255,0.1) 0%, transparent 70%);
min-height: 0; /* 允许 flex 子项正确收缩和滚动 */ min-height: 0; /* 允许子项正确收缩和滚动 */
} }
/* 小红书单页包裹器:为缩放后的页面预留正确的布局空间 */ /* 小红书单页包裹器:为缩放后的页面预留正确的布局空间 */
.xhs-page-wrapper { .xhs-page-wrapper {
/* 显示尺寸缩放后540 × 720 */
width: 540px;
height: 720px;
margin: 0px auto; margin: 0px auto;
position: relative; position: relative;
overflow: visible; overflow: hidden;
} }
/* 小红书单页样式:实际尺寸 1080×1440通过 scale 缩放到 540×720 */ /* 小红书单页样式:实际尺寸 1080×1440通过 scale 缩放到 540×720 */
.xhs-page { .xhs-page {
/* 实际尺寸由 renderPage 设置1080×1440 */ /* 实际尺寸与缩放由代码在运行时设置 */
transform-origin: top left; transform-origin: top left;
transform: scale(0.5); /* 540/1080 = 0.5 */
background: white; background: white;
box-shadow: 0 4px 12px rgba(0,0,0,0.1); box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-radius: 8px; border-radius: 8px;
@@ -494,26 +503,26 @@ label:hover {
} }
.xhs-top-toolbar { .xhs-top-toolbar {
display: flex; display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
align-items: center; align-items: center;
gap: 12px; gap: 12px;
padding: 8px 12px; padding: 8px 12px;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); background: var(--grad-toolbar);
border-bottom: 1px solid #e8eaed; border-bottom: 1px solid #e8eaed;
box-shadow: 0 2px 4px rgba(0,0,0,0.04); box-shadow: var(--shadow-overlay);
flex-wrap: wrap;
} }
.toolbar-label { .toolbar-label {
font-size: 11px; font-size: 11px;
color: #5f6368; color: var(--c-text-muted);
font-weight: 500; font-weight: 500;
white-space: nowrap; white-space: nowrap;
} }
.xhs-select { .xhs-select {
padding: 4px 8px; padding: 4px 8px;
border: 1px solid #dadce0; border: 1px solid var(--c-border);
border-radius: 4px; border-radius: 4px;
background: white; background: white;
font-size: 11px; font-size: 11px;
@@ -522,20 +531,21 @@ label:hover {
} }
.xhs-select:hover { .xhs-select:hover {
border-color: #1e88e5; border-color: var(--c-primary);
} }
.xhs-select:focus { .xhs-select:focus {
outline: none; outline: none;
border-color: #1e88e5; border-color: var(--c-primary);
} }
.font-size-group { .font-size-group {
display: flex; display: grid;
grid-auto-flow: column;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
background: white; background: white;
border: 1px solid #dadce0; border: 1px solid var(--c-border);
border-radius: 4px; border-radius: 4px;
padding: 2px; padding: 2px;
} }
@@ -565,7 +575,8 @@ label:hover {
} }
.xhs-page-navigation { .xhs-page-navigation {
display: flex; display: grid;
grid-auto-flow: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 16px; gap: 16px;
@@ -577,20 +588,20 @@ label:hover {
.xhs-nav-btn { .xhs-nav-btn {
width: 36px; width: 36px;
height: 36px; height: 36px;
border: 1px solid #dadce0; border: 1px solid var(--c-border);
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
font-size: 20px; font-size: 20px;
background: white; background: white;
color: #5f6368; color: #5f6368;
transition: all 0.2s ease; transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0,0,0,0.08); box-shadow: var(--shadow-sm);
} }
.xhs-nav-btn:hover { .xhs-nav-btn:hover {
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); background: var(--grad-primary);
color: white; color: white;
border-color: #1e88e5; border-color: var(--c-primary);
} }
.xhs-page-number { .xhs-page-number {
@@ -602,26 +613,22 @@ label:hover {
} }
.xhs-bottom-toolbar { .xhs-bottom-toolbar {
display: flex; display: grid;
grid-auto-flow: column;
justify-content: center; justify-content: center;
gap: 12px; gap: 12px;
padding: 12px 16px; padding: 12px 16px;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); background: var(--grad-toolbar-bottom);
border-top: 1px solid #e8eaed; border-top: 1px solid #e8eaed;
box-shadow: 0 -2px 4px rgba(0,0,0,0.04); box-shadow: 0 -2px 4px rgba(0,0,0,0.04);
} }
.xhs-slice-btn { .xhs-slice-btn {
padding: 8px 20px; padding: 8px 20px;
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); background: var(--grad-primary);
color: white; color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px; font-size: 13px;
font-weight: 500; box-shadow: var(--shadow-primary-2);
transition: all 0.2s ease;
box-shadow: 0 2px 6px rgba(30, 136, 229, 0.3);
} }
.xhs-slice-btn:hover { .xhs-slice-btn:hover {
@@ -630,7 +637,7 @@ label:hover {
} }
.xhs-slice-btn.secondary { .xhs-slice-btn.secondary {
background: linear-gradient(135deg, #42a5f5 0%, #1e88e5 100%); background: var(--grad-blue);
box-shadow: 0 2px 6px rgba(66, 165, 245, 0.3); box-shadow: 0 2px 6px rgba(66, 165, 245, 0.3);
} }
@@ -660,7 +667,8 @@ label:hover {
} }
.xhs-code-container { .xhs-code-container {
display: flex; display: grid;
grid-template-columns: auto 1fr auto;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
margin-bottom: 20px; margin-bottom: 20px;
@@ -704,7 +712,8 @@ label:hover {
} }
.xhs-button-container { .xhs-button-container {
display: flex; display: grid;
grid-auto-flow: column;
justify-content: center; justify-content: center;
gap: 15px; gap: 15px;
margin-top: 20px; margin-top: 20px;

View File

@@ -103,7 +103,10 @@ SOLVEobsidian控制台打印信息定位在哪里阻塞AI修复。
- 字变大时,一页的内容放不下,重新分页应该会增加页数。但现在重新分页当前页放不下的内容只是被剪掉了。 - 字变大时,一页的内容放不下,重新分页应该会增加页数。但现在重新分页当前页放不下的内容只是被剪掉了。
- 表格显示不完整。 - 表格显示不完整。
9. styles.css中有很多冗余。改为grid布局。 9. styles.css中有很多冗余。改为grid布局。部分完成,这部分需要后面**手动调整**重构。🧶 ♻️ ❇️
问题:小红书预览布局有问题❓ 问题:小红书预览布局有问题❓
10. 新建docs文件夹把除了README和todolist以外的markdown文件放到docs中。