2 Commits

Author SHA1 Message Date
douboer
1d52f79e0c update at 2025-10-10 21:55:46 2025-10-10 21:55:46 +08:00
douboer
0ab20de880 update at 2025-10-10 21:54:05 2025-10-10 21:54:05 +08:00
10 changed files with 352 additions and 228 deletions

View File

@@ -12,6 +12,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
### Changed ### Changed
- README新增图片方向处理说明、Gallery 参数使用示例。 - README新增图片方向处理说明、Gallery 参数使用示例。
- 预览布局:微信与小红书界面改为统一的网格化布局(`wechat-board` / `xhs-board`),组件按区域栅格排列,便于维护与对齐。
### Notes ### Notes
- 若遇到其他 EXIF 方向值(除 1/3/6/8当前保持原样可后续扩展。 - 若遇到其他 EXIF 方向值(除 1/3/6/8当前保持原样可后续扩展。
@@ -35,4 +36,3 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- 发布新版本:更新 `package.json` / `manifest.json` 的版本号;追加 `versions.json`;将当前 Unreleased 条目移动为新的版本号,并添加日期;再创建新的 Unreleased 模板。 - 发布新版本:更新 `package.json` / `manifest.json` 的版本号;追加 `versions.json`;将当前 Unreleased 条目移动为新的版本号,并添加日期;再创建新的 Unreleased 模板。
- 提交信息建议:`feat: ...`, `fix: ...`, `docs: ...`, `refactor: ...` 等 Conventional Commits 风格。 - 提交信息建议:`feat: ...`, `fix: ...`, `docs: ...`, `refactor: ...` 等 Conventional Commits 风格。

View File

@@ -21,7 +21,7 @@
如n=2取出的图片为xx.jpg,yy.png那么把{{<gallery dir="/img/guanzhan/1" figcaption="毕业展"/>}}{{<load-photoswipe>}}替换为: 如n=2取出的图片为xx.jpg,yy.png那么把{{<gallery dir="/img/guanzhan/1" figcaption="毕业展"/>}}{{<load-photoswipe>}}替换为:
![[xx.jpg]] ![[xx.jpg]]
![[yy.png]] ![[yy.png]]
3. 3.
对如下: 对如下:
@@ -46,7 +46,7 @@ src可能使用link
4. 4.
参考以下代码,渲染[fig content/],|| content,||r content,||g content,||b content等标签 参考以下代码,渲染[fig content/],|| content,||r content,||g content,||b content等标签
`\[fig([^>]*?)/\]` `<span style="font-style: italic; font-size: 14px; background-color: #f5f5f5; padding: 2px;">$1</span>` `\[fig([^>]*?)/\]` `<span style="font-style: italic; font-size: 14px; background-color: #f5f5f5; padding: 2px;">$1</span>`
`\|\| (.*)` `<p style="font-family:'Microsoft YaHei',sans-serif;background-color:#E5E4E2 ;padding:10px;border-radius:20px;line-height:30px;">$1</p>` `\|\| (.*)` `<p style="font-family:'Microsoft YaHei',sans-serif;background-color:#E5E4E2 ;padding:10px;border-radius:20px;line-height:30px;">$1</p>`
`\|\|r (.*)` `<p style="font-family:'Microsoft YaHei',sans-serif;color:white;background-color:#6F4E37;padding:10px;border-radius:20px;line-height:30px;">$1</p>` `\|\|r (.*)` `<p style="font-family:'Microsoft YaHei',sans-serif;color:white;background-color:#6F4E37;padding:10px;border-radius:20px;line-height:30px;">$1</p>`
@@ -105,7 +105,9 @@ views:
``` ```
10. 默认选择“原创”“允许留言”。 10. 默认选择
- “允许留言” ✅
- “原创”
11. gallery短代码增加是否使用dir中的所有图片的开关。mppickall=1选取dir中的所有图片mppickall=0按“选取图片数”配置选取图片数量。 11. gallery短代码增加是否使用dir中的所有图片的开关。mppickall=1选取dir中的所有图片mppickall=0按“选取图片数”配置选取图片数量。
{{<gallery dir="/img/guanzhan/1" figcaption="毕业展" mppickall=1/>}}{{<load-photoswipe>}} {{<gallery dir="/img/guanzhan/1" figcaption="毕业展" mppickall=1/>}}{{<load-photoswipe>}}
@@ -136,3 +138,7 @@ Orientation : 6 -- 图片左旋90度需右选90才正常。
为了规避这个问题图片不做旋转处理直接转为png上传公众号。解决。因为PNG不带orientation信息。 为了规避这个问题图片不做旋转处理直接转为png上传公众号。解决。因为PNG不带orientation信息。
- 封面图片没有正确旋转。
公众号上传仍然有图片旋转问题同样需要取png图片避免旋转问题

View File

@@ -64,3 +64,8 @@
## v1.3.10 ## v1.3.10
重构xhs和wechat布局统一使用grid便于维护。 重构xhs和wechat布局统一使用grid便于维护。
## v1.3.11
修改wechat封面旋转问题。对全局配置进行了重构分页显示更加清晰。
封面图片先转位png解决旋转问题。

View File

@@ -381,7 +381,9 @@ export class ArticleRender implements MDRendererCallback {
res.cover = undefined; // 忽略 frontmatter res.cover = undefined; // 忽略 frontmatter
} }
res.thumb_media_id = this.getFrontmatterValue(frontmatter, keys.thumb_media_id); res.thumb_media_id = this.getFrontmatterValue(frontmatter, keys.thumb_media_id);
res.need_open_comment = frontmatter[keys.need_open_comment] ? 1 : undefined; if (frontmatter[keys.need_open_comment] !== undefined) {
res.need_open_comment = frontmatter[keys.need_open_comment] ? 1 : 0;
}
res.only_fans_can_comment = frontmatter[keys.only_fans_can_comment] ? 1 : undefined; res.only_fans_can_comment = frontmatter[keys.only_fans_can_comment] ? 1 : undefined;
res.appid = this.getFrontmatterValue(frontmatter, keys.appid); res.appid = this.getFrontmatterValue(frontmatter, keys.appid);
if (res.appid && !res.appid.startsWith('wx')) { if (res.appid && !res.appid.startsWith('wx')) {
@@ -478,6 +480,9 @@ export class ArticleRender implements MDRendererCallback {
} }
} }
} }
if (res.need_open_comment === undefined) {
res.need_open_comment = this.settings.needOpenComment ? 1 : 0;
}
return res; return res;
} }
@@ -717,7 +722,7 @@ export class ArticleRender implements MDRendererCallback {
article_type: 'newspic', article_type: 'newspic',
title: metadata.title || this.title, title: metadata.title || this.title,
content: content, content: content,
need_open_commnet: metadata.need_open_comment || 0, need_open_commnet: metadata.need_open_comment ?? 0,
only_fans_can_comment: metadata.only_fans_can_comment || 0, only_fans_can_comment: metadata.only_fans_can_comment || 0,
image_info: { image_info: {
image_list: imageList, image_list: imageList,

View File

@@ -8,6 +8,7 @@ import { wxUploadImage } from "./wechat/weixin-api";
import { NMPSettings } from "./settings"; import { NMPSettings } from "./settings";
import { IsWasmReady, LoadWasm } from "./wasm/wasm"; import { IsWasmReady, LoadWasm } from "./wasm/wasm";
import AssetsManager from "./assets"; import AssetsManager from "./assets";
import { convertJpegIfNeeded } from "./exif-orientation";
declare function GoWebpToJPG(data: Uint8Array): Uint8Array; // wasm 返回 Uint8Array declare function GoWebpToJPG(data: Uint8Array): Uint8Array; // wasm 返回 Uint8Array
declare function GoWebpToPNG(data: Uint8Array): Uint8Array; declare function GoWebpToPNG(data: Uint8Array): Uint8Array;
@@ -37,6 +38,18 @@ export async function UploadImageToWx(data: Blob, filename: string, token: strin
if (!IsImageLibReady()) { if (!IsImageLibReady()) {
await PrepareImageLib(); await PrepareImageLib();
} }
try {
// 公众号端仍然存在基于 EXIF 的旋转问题:
// 统一将待上传的 JPEG 转为 PNG 并忽略 Orientation避免出现倒置/倾斜。
const converted = await convertJpegIfNeeded(data, filename);
if (converted.changed) {
data = converted.blob;
filename = converted.filename;
}
} catch (error) {
console.warn('[UploadImageToWx] convert to PNG failed, fallback to original', error);
}
const watermark = NMPSettings.getInstance().watermark; const watermark = NMPSettings.getInstance().watermark;
if (watermark != null && watermark != '') { if (watermark != null && watermark != '') {
@@ -44,11 +57,11 @@ export async function UploadImageToWx(data: Blob, filename: string, token: strin
if (watermarkData == null) { if (watermarkData == null) {
throw new Error('水印图片不存在: ' + watermark); throw new Error('水印图片不存在: ' + watermark);
} }
const watermarkImg = AddWatermark(await data.arrayBuffer(), watermarkData); const watermarkImg = AddWatermark(await data.arrayBuffer(), watermarkData);
// AddWatermark 返回 Uint8ArrayBlob 的类型签名对某些 TS 配置可能对 ArrayBufferLike 有严格区分 // AddWatermark 返回 Uint8ArrayBlob 的类型签名对某些 TS 配置可能对 ArrayBufferLike 有严格区分
// 此处使用其底层 ArrayBuffer 来构造 Blob避免类型不兼容错误 // 此处使用其底层 ArrayBuffer 来构造 Blob避免类型不兼容错误
const bufferPart = watermarkImg.buffer as ArrayBuffer; const bufferPart = watermarkImg.buffer as ArrayBuffer;
data = new Blob([bufferPart], { type: data.type }); data = new Blob([bufferPart], { type: data.type });
} }
return await wxUploadImage(data, filename, token, type); return await wxUploadImage(data, filename, token, type);
} }

View File

@@ -43,7 +43,7 @@ interface PlatformInfo {
* 支持的平台列表 * 支持的平台列表
*/ */
const SUPPORTED_PLATFORMS: PlatformInfo[] = [ const SUPPORTED_PLATFORMS: PlatformInfo[] = [
{ value: 'wechat', label: '微信公众号', icon: '📱' }, { value: 'wechat', label: '公众号', icon: '📱' },
{ value: 'xiaohongshu', label: '小红书', icon: '📔' } { value: 'xiaohongshu', label: '小红书', icon: '📔' }
]; ];

View File

@@ -160,99 +160,141 @@ export class NoteToMpSettingTab extends PluginSettingTab {
this.wxInfo = this.parseWXInfo(); this.wxInfo = this.parseWXInfo();
const helpEl = containerEl.createEl('div', { cls: 'setting-help-section' }); const tabs = [
helpEl.createEl('h2', {text: '帮助文档', cls: 'setting-help-title'}); { id: 'style', label: '样式', render: (panel: HTMLElement) => this.renderStyleTab(panel) },
helpEl.createEl('a', {text: 'https://sunboshi.tech/doc', attr: {href: 'https://sunboshi.tech/doc'}}); { id: 'shortcode', label: '短代码', render: (panel: HTMLElement) => this.renderShortcodeTab(panel) },
{ id: 'theme', label: '主题', render: (panel: HTMLElement) => this.renderThemeTab(panel) },
{ id: 'image', label: '图片', render: (panel: HTMLElement) => this.renderImageTab(panel) },
{ id: 'user', label: '用户信息', render: (panel: HTMLElement) => this.renderUserTab(panel) },
];
containerEl.createEl('h2', {text: '插件设置'}); const tabBar = containerEl.createDiv({ cls: 'nmp-settings-tabs' });
const panelsWrapper = containerEl.createDiv({ cls: 'nmp-settings-panels' });
new Setting(containerEl) const panelMap = new Map<string, HTMLElement>();
const buttonMap = new Map<string, HTMLButtonElement>();
const activate = (id: string) => {
buttonMap.forEach((btn, key) => btn.toggleClass('is-active', key === id));
panelMap.forEach((panel, key) => panel.toggleClass('is-active', key === id));
};
tabs.forEach((tab) => {
const button = tabBar.createEl('button', { text: tab.label, cls: 'nmp-settings-tab-button' });
button.onclick = () => activate(tab.id);
buttonMap.set(tab.id, button);
const panel = panelsWrapper.createDiv({ cls: 'nmp-settings-panel', attr: { 'data-tab': tab.id } });
panelMap.set(tab.id, panel);
tab.render(panel);
});
if (tabs.length > 0) {
activate(tabs[0].id);
}
}
private renderStyleTab(panel: HTMLElement): void {
new Setting(panel)
.setName('默认样式') .setName('默认样式')
.addDropdown(dropdown => { .addDropdown(dropdown => {
const styles = this.plugin.assetsManager.themes; const styles = this.plugin.assetsManager.themes;
for (let s of styles) { for (const s of styles) {
dropdown.addOption(s.className, s.name); dropdown.addOption(s.className, s.name);
} }
dropdown.setValue(this.settings.defaultStyle); dropdown.setValue(this.settings.defaultStyle);
dropdown.onChange(async (value) => { dropdown.onChange(async (value) => {
this.settings.defaultStyle = value; this.settings.defaultStyle = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('代码高亮')
.addDropdown(dropdown => {
const styles = this.plugin.assetsManager.highlights;
for (let s of styles) {
dropdown.addOption(s.name, s.name);
}
dropdown.setValue(this.settings.defaultHighlight);
dropdown.onChange(async (value) => {
this.settings.defaultHighlight = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('在工具栏展示样式选择')
.setDesc('建议在移动端关闭,可以增大文章预览区域')
.addToggle(toggle => {
toggle.setValue(this.settings.showStyleUI);
toggle.onChange(async (value) => {
this.settings.showStyleUI = value;
await this.plugin.saveSettings();
}); });
}); });
new Setting(containerEl) new Setting(panel)
.setName('代码高亮')
.addDropdown(dropdown => {
const styles = this.plugin.assetsManager.highlights;
for (const s of styles) {
dropdown.addOption(s.name, s.name);
}
dropdown.setValue(this.settings.defaultHighlight);
dropdown.onChange(async (value) => {
this.settings.defaultHighlight = value;
await this.plugin.saveSettings();
});
});
new Setting(panel)
.setName('链接展示样式') .setName('链接展示样式')
.addDropdown(dropdown => { .addDropdown(dropdown => {
dropdown.addOption('inline', '内嵌'); dropdown.addOption('inline', '内嵌');
dropdown.addOption('footnote', '脚注'); dropdown.addOption('footnote', '脚注');
dropdown.setValue(this.settings.linkStyle); dropdown.setValue(this.settings.linkStyle);
dropdown.onChange(async (value) => { dropdown.onChange(async (value) => {
this.settings.linkStyle = value; this.settings.linkStyle = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}); });
}); });
new Setting(containerEl) new Setting(panel)
.setName('文件嵌入展示样式') .setName('文件嵌入展示样式')
.addDropdown(dropdown => { .addDropdown(dropdown => {
dropdown.addOption('quote', '引用'); dropdown.addOption('quote', '引用');
dropdown.addOption('content', '正文'); dropdown.addOption('content', '正文');
dropdown.setValue(this.settings.embedStyle); dropdown.setValue(this.settings.embedStyle);
dropdown.onChange(async (value) => { dropdown.onChange(async (value) => {
this.settings.embedStyle = value; this.settings.embedStyle = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}); });
}); });
new Setting(containerEl) new Setting(panel)
.setName('数学公式语法') .setName('数学公式语法')
.addDropdown(dropdown => { .addDropdown(dropdown => {
dropdown.addOption('latex', 'latex'); dropdown.addOption('latex', 'latex');
dropdown.addOption('asciimath', 'asciimath'); dropdown.addOption('asciimath', 'asciimath');
dropdown.setValue(this.settings.math); dropdown.setValue(this.settings.math);
dropdown.onChange(async (value) => { dropdown.onChange(async (value) => {
this.settings.math = value; this.settings.math = value;
cleanMathCache(); cleanMathCache();
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}); });
}); });
new Setting(containerEl) new Setting(panel)
.setName('显示代码行号') .setName('显示代码行号')
.addToggle(toggle => { .addToggle(toggle => {
toggle.setValue(this.settings.lineNumber); toggle.setValue(this.settings.lineNumber);
toggle.onChange(async (value) => { toggle.onChange(async (value) => {
this.settings.lineNumber = value; this.settings.lineNumber = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}); });
}) });
new Setting(containerEl) new Setting(panel)
.setName('启用空行渲染')
.addToggle(toggle => {
toggle.setValue(this.settings.enableEmptyLine);
toggle.onChange(async (value) => {
this.settings.enableEmptyLine = value;
await this.plugin.saveSettings();
});
});
new Setting(panel)
.setName('默认开启评论')
.setDesc('发布到公众号时默认开启评论,可在 frontmatter 使用 need_open_comment 关闭')
.addToggle(toggle => {
toggle.setValue(this.settings.needOpenComment);
toggle.onChange(async (value) => {
this.settings.needOpenComment = value;
await this.plugin.saveSettings();
});
});
}
private renderShortcodeTab(panel: HTMLElement): void {
new Setting(panel)
.setName('Gallery 根路径') .setName('Gallery 根路径')
.setDesc('用于 {{<gallery dir="..."/>}} 短代码解析;需指向本地图片根目录') .setDesc('用于 {{<gallery dir="..."/>}} 短代码解析;需指向本地图片根目录')
.addText(text => { .addText(text => {
@@ -265,7 +307,7 @@ export class NoteToMpSettingTab extends PluginSettingTab {
text.inputEl.setAttr('style', 'width: 360px;'); text.inputEl.setAttr('style', 'width: 360px;');
}); });
new Setting(containerEl) new Setting(panel)
.setName('Gallery 选取图片数') .setName('Gallery 选取图片数')
.setDesc('每个 gallery 短代码最多替换为前 N 张图片') .setDesc('每个 gallery 短代码最多替换为前 N 张图片')
.addText(text => { .addText(text => {
@@ -281,7 +323,18 @@ export class NoteToMpSettingTab extends PluginSettingTab {
text.inputEl.setAttr('style', 'width: 120px;'); text.inputEl.setAttr('style', 'width: 120px;');
}); });
new Setting(containerEl) new Setting(panel)
.setName('忽略 frontmatter 封面')
.setDesc('开启后不使用 frontmatter 中 cover/image 字段封面将按正文首图→gallery→默认封面回退')
.addToggle(toggle => {
toggle.setValue(this.settings.ignoreFrontmatterImage);
toggle.onChange(async (value) => {
this.settings.ignoreFrontmatterImage = value;
await this.plugin.saveSettings();
});
});
new Setting(panel)
.setName('默认封面图片') .setName('默认封面图片')
.setDesc('当文章无任何图片/短代码时使用;可填 wikilink 文件名或 http(s) URL') .setDesc('当文章无任何图片/短代码时使用;可填 wikilink 文件名或 http(s) URL')
.addText(text => { .addText(text => {
@@ -293,33 +346,84 @@ export class NoteToMpSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttr('style', 'width: 360px;'); text.inputEl.setAttr('style', 'width: 360px;');
}); });
}
new Setting(containerEl) private renderThemeTab(panel: HTMLElement): void {
.setName('忽略 frontmatter 封面') new Setting(panel)
.setDesc('开启后不使用 frontmatter 中 cover/image 字段封面将按正文首图→gallery→默认封面回退') .setName('获取更多主题')
.addToggle(toggle => { .addButton(button => {
toggle.setValue(this.settings.ignoreFrontmatterImage); button.setButtonText('下载');
toggle.onChange(async (value) => { button.onClick(async () => {
this.settings.ignoreFrontmatterImage = value; button.setButtonText('下载中...');
await this.plugin.assetsManager.downloadThemes();
button.setButtonText('下载完成');
});
})
.addButton(button => {
button.setIcon('folder-open');
button.onClick(async () => {
await this.plugin.assetsManager.openAssets();
});
});
new Setting(panel)
.setName('清空主题')
.addButton(button => {
button.setButtonText('清空');
button.onClick(async () => {
await this.plugin.assetsManager.removeThemes();
this.settings.resetStyelAndHighlight();
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}); });
}); });
new Setting(panel)
.setName('全局CSS属性')
.setDesc('只能填写CSS属性不能写选择器')
.addTextArea(text => {
this.wxTextArea = text;
text.setPlaceholder('请输入CSS属性background: #fff;padding: 10px;')
.setValue(this.settings.baseCSS)
.onChange(async (value) => {
this.settings.baseCSS = value;
await this.plugin.saveSettings();
})
.inputEl.setAttr('style', 'width: 520px; height: 60px;');
});
new Setting(containerEl) const customCSSDoc = '使用指南:<a href="https://sunboshi.tech/customcss">https://sunboshi.tech/customcss</a>';
.setName('启用空行渲染') new Setting(panel)
.addToggle(toggle => { .setName('自定义CSS笔记')
toggle.setValue(this.settings.enableEmptyLine); .setDesc(sanitizeHTMLToDom(customCSSDoc))
toggle.onChange(async (value) => { .addText(text => {
this.settings.enableEmptyLine = value; text.setPlaceholder('请输入自定义CSS笔记标题')
await this.plugin.saveSettings(); .setValue(this.settings.customCSSNote)
}); .onChange(async (value) => {
}) this.settings.customCSSNote = value.trim();
await this.plugin.saveSettings();
await this.plugin.assetsManager.loadCustomCSS();
})
.inputEl.setAttr('style', 'width: 320px;');
});
// 切图配置区块 const expertDoc = '使用指南:<a href="https://sunboshi.tech/expert">https://sunboshi.tech/expert</a>';
containerEl.createEl('h2', {text: '切图配置'}); new Setting(panel)
.setName('专家设置笔记')
.setDesc(sanitizeHTMLToDom(expertDoc))
.addText(text => {
text.setPlaceholder('请输入专家设置笔记标题')
.setValue(this.settings.expertSettingsNote)
.onChange(async (value) => {
this.settings.expertSettingsNote = value.trim();
await this.plugin.saveSettings();
await this.plugin.assetsManager.loadExpertSettings();
})
.inputEl.setAttr('style', 'width: 320px;');
});
}
new Setting(containerEl) private renderImageTab(panel: HTMLElement): void {
new Setting(panel)
.setName('切图保存路径') .setName('切图保存路径')
.setDesc('切图文件的保存目录,默认:/Users/gavin/note2mp/images/xhs') .setDesc('切图文件的保存目录,默认:/Users/gavin/note2mp/images/xhs')
.addText(text => { .addText(text => {
@@ -332,7 +436,7 @@ export class NoteToMpSettingTab extends PluginSettingTab {
text.inputEl.setAttr('style', 'width: 360px;'); text.inputEl.setAttr('style', 'width: 360px;');
}); });
new Setting(containerEl) new Setting(panel)
.setName('切图宽度') .setName('切图宽度')
.setDesc('长图及切图的宽度像素默认1080') .setDesc('长图及切图的宽度像素默认1080')
.addText(text => { .addText(text => {
@@ -348,7 +452,7 @@ export class NoteToMpSettingTab extends PluginSettingTab {
text.inputEl.setAttr('style', 'width: 120px;'); text.inputEl.setAttr('style', 'width: 120px;');
}); });
new Setting(containerEl) new Setting(panel)
.setName('切图横竖比例') .setName('切图横竖比例')
.setDesc('格式:宽:高,例如 3:4 表示竖图16:9 表示横图') .setDesc('格式:宽:高,例如 3:4 表示竖图16:9 表示横图')
.addText(text => { .addText(text => {
@@ -360,182 +464,119 @@ export class NoteToMpSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttr('style', 'width: 120px;'); text.inputEl.setAttr('style', 'width: 120px;');
}); });
new Setting(containerEl)
.setName('渲染图片标题')
.addToggle(toggle => {
toggle.setValue(this.settings.useFigcaption);
toggle.onChange(async (value) => {
this.settings.useFigcaption = value;
await this.plugin.saveSettings();
});
})
new Setting(containerEl) new Setting(panel)
.setName('Excalidraw 渲染为 PNG 图片') .setName('渲染图片标题')
.setDesc('开启:将 Excalidraw 笔记/嵌入转换为位图 PNG 插入;关闭:保持原始 SVG/矢量渲染(更清晰,体积更小)。') .addToggle(toggle => {
.addToggle(toggle => { toggle.setValue(this.settings.useFigcaption);
toggle.setValue(this.settings.excalidrawToPNG); toggle.onChange(async (value) => {
toggle.onChange(async (value) => { this.settings.useFigcaption = value;
this.settings.excalidrawToPNG = value; await this.plugin.saveSettings();
await this.plugin.saveSettings(); });
}); });
})
new Setting(containerEl) new Setting(panel)
.setName('Excalidraw 渲染为 PNG 图片')
.setDesc('开启:将 Excalidraw 笔记/嵌入转换为位图 PNG 插入;关闭:保持原始 SVG/矢量渲染。')
.addToggle(toggle => {
toggle.setValue(this.settings.excalidrawToPNG);
toggle.onChange(async (value) => {
this.settings.excalidrawToPNG = value;
await this.plugin.saveSettings();
});
});
new Setting(panel)
.setName('水印图片') .setName('水印图片')
.setDesc('可填写文件名或 URL')
.addText(text => { .addText(text => {
text.setPlaceholder('请输入图片名称') text.setPlaceholder('例如 watermark.png')
.setValue(this.settings.watermark) .setValue(this.settings.watermark)
.onChange(async (value) => { .onChange(async (value) => {
this.settings.watermark = value.trim(); this.settings.watermark = value.trim();
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}) })
.inputEl.setAttr('style', 'width: 320px;') .inputEl.setAttr('style', 'width: 320px;');
})
new Setting(containerEl)
.setName('获取更多主题')
.addButton(button => {
button.setButtonText('下载');
button.onClick(async () => {
button.setButtonText('下载中...');
await this.plugin.assetsManager.downloadThemes();
button.setButtonText('下载完成');
});
})
.addButton(button => {
button.setIcon('folder-open');
button.onClick(async () => {
await this.plugin.assetsManager.openAssets();
});
}); });
}
new Setting(containerEl) private renderUserTab(panel: HTMLElement): void {
.setName('清空主题')
.addButton(button => {
button.setButtonText('清空');
button.onClick(async () => {
await this.plugin.assetsManager.removeThemes();
this.settings.resetStyelAndHighlight();
await this.plugin.saveSettings();
});
})
new Setting(containerEl)
.setName('全局CSS属性')
.setDesc('只能填写CSS属性不能写选择器')
.addTextArea(text => {
this.wxTextArea = text;
text.setPlaceholder('请输入CSS属性background: #fff;padding: 10px;')
.setValue(this.settings.baseCSS)
.onChange(async (value) => {
this.settings.baseCSS = value;
await this.plugin.saveSettings();
})
.inputEl.setAttr('style', 'width: 520px; height: 60px;');
})
const customCSSDoc = '使用指南:<a href="https://sunboshi.tech/customcss">https://sunboshi.tech/customcss</a>';
new Setting(containerEl)
.setName('自定义CSS笔记')
.setDesc(sanitizeHTMLToDom(customCSSDoc))
.addText(text => {
text.setPlaceholder('请输入自定义CSS笔记标题')
.setValue(this.settings.customCSSNote)
.onChange(async (value) => {
this.settings.customCSSNote = value.trim();
await this.plugin.saveSettings();
await this.plugin.assetsManager.loadCustomCSS();
})
.inputEl.setAttr('style', 'width: 320px;')
});
const expertDoc = '使用指南:<a href="https://sunboshi.tech/expert">https://sunboshi.tech/expert</a>';
new Setting(containerEl)
.setName('专家设置笔记')
.setDesc(sanitizeHTMLToDom(expertDoc))
.addText(text => {
text.setPlaceholder('请输入专家设置笔记标题')
.setValue(this.settings.expertSettingsNote)
.onChange(async (value) => {
this.settings.expertSettingsNote = value.trim();
await this.plugin.saveSettings();
await this.plugin.assetsManager.loadExpertSettings();
})
.inputEl.setAttr('style', 'width: 320px;')
});
let descHtml = '详情说明:<a href="https://sunboshi.tech/subscribe">https://sunboshi.tech/subscribe</a>'; let descHtml = '详情说明:<a href="https://sunboshi.tech/subscribe">https://sunboshi.tech/subscribe</a>';
if (this.settings.isVip) { if (this.settings.isVip) {
descHtml = '<span style="color:rgb(245, 70, 85);font-weight: bold;">👑永久会员</span><br/>' + descHtml; descHtml = '<span style="color:rgb(245, 70, 85);font-weight: bold;">👑永久会员</span><br/>' + descHtml;
} }
else if (this.settings.expireat) { else if (this.settings.expireat) {
const timestr = this.settings.expireat.toLocaleString(); const timestr = this.settings.expireat.toLocaleString();
descHtml = `有效期至:${timestr} <br/>${descHtml}` descHtml = `有效期至:${timestr} <br/>${descHtml}`;
} }
new Setting(containerEl)
new Setting(panel)
.setName('注册码AuthKey') .setName('注册码AuthKey')
.setDesc(sanitizeHTMLToDom(descHtml)) .setDesc(sanitizeHTMLToDom(descHtml))
.addText(text => { .addText(text => {
text.setPlaceholder('请输入注册码') text.setPlaceholder('请输入注册码')
.setValue(this.settings.authKey) .setValue(this.settings.authKey)
.onChange(async (value) => { .onChange(async (value) => {
this.settings.authKey = value.trim(); this.settings.authKey = value.trim();
this.settings.getExpiredDate(); this.settings.getExpiredDate();
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}) })
.inputEl.setAttr('style', 'width: 320px;') .inputEl.setAttr('style', 'width: 320px;');
}).descEl.setAttr('style', '-webkit-user-select: text; user-select: text;') }).descEl.setAttr('style', '-webkit-user-select: text; user-select: text;');
let isClear = this.settings.wxInfo.length > 0; let isClear = this.settings.wxInfo.length > 0;
let isRealClear = false; let isRealClear = false;
const buttonText = isClear ? '清空公众号信息' : '保存公众号信息'; const buttonText = isClear ? '清空公众号信息' : '保存公众号信息';
new Setting(containerEl)
new Setting(panel)
.setName('公众号信息') .setName('公众号信息')
.addTextArea(text => { .addTextArea(text => {
this.wxTextArea = text; this.wxTextArea = text;
text.setPlaceholder('请输入公众号信息\n格式公众号名称|公众号AppID|公众号AppSecret\n多个公众号请换行输入\n输入完成后点击加密按钮') text.setPlaceholder('请输入公众号信息\n格式公众号名称|公众号AppID|公众号AppSecret\n多个公众号请换行输入\n输入完成后点击加密按钮')
.setValue(this.wxInfo) .setValue(this.wxInfo)
.onChange(value => { .onChange(value => {
this.wxInfo = value; this.wxInfo = value;
}) })
.inputEl.setAttr('style', 'width: 520px; height: 120px;'); .inputEl.setAttr('style', 'width: 520px; height: 120px;');
}) })
.addButton(button => {
new Setting(containerEl).addButton(button => { button.setButtonText(buttonText);
button.setButtonText(buttonText); button.onClick(async () => {
button.onClick(async () => { if (isClear) {
if (isClear) { isRealClear = true;
isRealClear = true; isClear = false;
isClear = false; button.setButtonText('确认清空?');
button.setButtonText('确认清空?');
}
else if (isRealClear) {
isRealClear = false;
isClear = false;
this.clear();
button.setButtonText('保存公众号信息');
}
else {
button.setButtonText('保存中...');
if (await this.encrypt()) {
isClear = true;
isRealClear = false;
button.setButtonText('清空公众号信息');
} }
else { else if (isRealClear) {
isRealClear = false;
isClear = false;
this.clear();
button.setButtonText('保存公众号信息'); button.setButtonText('保存公众号信息');
} }
} else {
}); button.setButtonText('保存中...');
}) if (await this.encrypt()) {
.addButton(button => { isClear = true;
button.setButtonText('测试公众号'); isRealClear = false;
button.onClick(async () => { button.setButtonText('清空公众号信息');
button.setButtonText('测试中...'); }
await this.testWXInfo(); else {
button.setButtonText('测试公众号'); button.setButtonText('保存公众号信息');
}
}
});
}) })
}) .addButton(button => {
button.setButtonText('测试公众号');
button.onClick(async () => {
button.setButtonText('测试中...');
await this.testWXInfo();
button.setButtonText('测试公众号');
});
});
//
const helpEl = panel.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' } });
} }
} }

View File

@@ -32,6 +32,7 @@ export class NMPSettings {
excalidrawToPNG: boolean; excalidrawToPNG: boolean;
isLoaded: boolean = false; isLoaded: boolean = false;
enableEmptyLine: boolean = false; enableEmptyLine: boolean = false;
needOpenComment: boolean = true;
enableMarkdownImageToWikilink: boolean = true; // 自动将 ![alt](path/file.ext) 转为 ![[file.ext]] enableMarkdownImageToWikilink: boolean = true; // 自动将 ![alt](path/file.ext) 转为 ![[file.ext]]
// gallery 相关配置:根目录前缀 & 选取图片数量 // gallery 相关配置:根目录前缀 & 选取图片数量
galleryPrePath: string; galleryPrePath: string;
@@ -81,6 +82,7 @@ export class NMPSettings {
this.excalidrawToPNG = false; this.excalidrawToPNG = false;
this.expertSettingsNote = ''; this.expertSettingsNote = '';
this.enableEmptyLine = false; this.enableEmptyLine = false;
this.needOpenComment = true;
this.enableMarkdownImageToWikilink = true; this.enableMarkdownImageToWikilink = true;
// 默认值:用户原先硬编码路径 & 前 2 张 // 默认值:用户原先硬编码路径 & 前 2 张
this.galleryPrePath = '/Users/gavin/myweb/static'; this.galleryPrePath = '/Users/gavin/myweb/static';
@@ -131,6 +133,7 @@ export class NMPSettings {
expertSettingsNote, expertSettingsNote,
ignoreEmptyLine, ignoreEmptyLine,
enableMarkdownImageToWikilink, enableMarkdownImageToWikilink,
needOpenComment,
galleryPrePath, galleryPrePath,
galleryNumPic, galleryNumPic,
defaultCoverPic, defaultCoverPic,
@@ -160,6 +163,7 @@ export class NMPSettings {
if (excalidrawToPNG !== undefined) settings.excalidrawToPNG = excalidrawToPNG; if (excalidrawToPNG !== undefined) settings.excalidrawToPNG = excalidrawToPNG;
if (expertSettingsNote) settings.expertSettingsNote = expertSettingsNote; if (expertSettingsNote) settings.expertSettingsNote = expertSettingsNote;
if (ignoreEmptyLine !== undefined) settings.enableEmptyLine = !!ignoreEmptyLine; if (ignoreEmptyLine !== undefined) settings.enableEmptyLine = !!ignoreEmptyLine;
if (needOpenComment !== undefined) settings.needOpenComment = !!needOpenComment;
if (enableMarkdownImageToWikilink !== undefined) settings.enableMarkdownImageToWikilink = !!enableMarkdownImageToWikilink; if (enableMarkdownImageToWikilink !== undefined) settings.enableMarkdownImageToWikilink = !!enableMarkdownImageToWikilink;
if (galleryPrePath) settings.galleryPrePath = galleryPrePath; if (galleryPrePath) settings.galleryPrePath = galleryPrePath;
if (galleryNumPic !== undefined && Number.isFinite(galleryNumPic)) settings.galleryNumPic = Math.max(1, parseInt(galleryNumPic)); if (galleryNumPic !== undefined && Number.isFinite(galleryNumPic)) settings.galleryNumPic = Math.max(1, parseInt(galleryNumPic));
@@ -197,6 +201,7 @@ export class NMPSettings {
'excalidrawToPNG': settings.excalidrawToPNG, 'excalidrawToPNG': settings.excalidrawToPNG,
'expertSettingsNote': settings.expertSettingsNote, 'expertSettingsNote': settings.expertSettingsNote,
'ignoreEmptyLine': settings.enableEmptyLine, 'ignoreEmptyLine': settings.enableEmptyLine,
'needOpenComment': settings.needOpenComment,
'enableMarkdownImageToWikilink': settings.enableMarkdownImageToWikilink, 'enableMarkdownImageToWikilink': settings.enableMarkdownImageToWikilink,
'galleryPrePath': settings.galleryPrePath, 'galleryPrePath': settings.galleryPrePath,
'galleryNumPic': settings.galleryNumPic, 'galleryNumPic': settings.galleryNumPic,

View File

@@ -183,6 +183,53 @@ label:hover { 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);
} }
.nmp-settings-tabs {
display: flex;
gap: 8px;
margin-top: 16px;
border-bottom: 1px solid var(--c-border);
padding-bottom: 8px;
flex-wrap: wrap;
}
.nmp-settings-tab-button {
border: none;
border-radius: 6px;
padding: 6px 14px;
font-size: 13px;
font-weight: 500;
color: var(--c-text-muted);
background: transparent;
cursor: pointer;
transition: all 0.2s ease;
}
.nmp-settings-tab-button:hover {
color: var(--c-primary);
background: rgba(30, 136, 229, 0.08);
}
.nmp-settings-tab-button.is-active {
color: var(--c-primary);
background: white;
box-shadow: var(--shadow-sm);
}
.nmp-settings-panels {
margin-top: 16px;
}
.nmp-settings-panel {
display: none;
gap: 12px;
flex-direction: column;
}
.nmp-settings-panel.is-active {
display: flex;
flex-direction: column;
}
/* focus 规则见与 .upload-input:focus 的组合声明 */ /* focus 规则见与 .upload-input:focus 的组合声明 */
.msg-view { .msg-view {
@@ -351,10 +398,10 @@ label:hover { color: var(--c-primary); }
"cover-select cover-select cover-select cover-select cover-input cover-input" "cover-select cover-select cover-select cover-select cover-input cover-input"
"style-label style-select highlight-label highlight-select highlight-select highlight-select" "style-label style-select highlight-label highlight-select highlight-select highlight-select"
"content content content content content content"; "content content content content content content";
gap: 5px; gap: 12px;
background: var(--grad-toolbar); background: var(--grad-toolbar);
border-radius: 12px; border-radius: 12px;
padding: 5px; padding: 16px;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
min-height: 0; min-height: 0;
} }

View File

@@ -56,6 +56,9 @@
- 点击"全部页切图"把所有html页面转为png图片图片保存路径和命名按此前设置。 - 点击"全部页切图"把所有html页面转为png图片图片保存路径和命名按此前设置。
3. 整合xhs登陆和发布功能。![登录项目](https://biboer.cn/gitea/gavin/xhslogin/src/branch/main/README.md)
-
## 问题 ## 问题
1. "发布平台"首次选“小红书”时,预览页面没有加载当前文章。 1. "发布平台"首次选“小红书”时,预览页面没有加载当前文章。
@@ -126,4 +129,3 @@ SOLVEobsidian控制台打印信息定位在哪里阻塞AI修复。
自己写布局demo原型让codex参考布局修改(原来元素美化的css可保留)。 自己写布局demo原型让codex参考布局修改(原来元素美化的css可保留)。
demo原型可以手绘后拍照让chatgpt生成在此基础上自己修改。 demo原型可以手绘后拍照让chatgpt生成在此基础上自己修改。