update at 2025-10-10 17:00:09

This commit is contained in:
douboer
2025-10-10 17:00:09 +08:00
parent 1309caddc3
commit 86c3beea49
14 changed files with 1248 additions and 81 deletions

View File

@@ -95,8 +95,6 @@
检查样式无误后,点击**复制**按钮,然后到公众号粘贴即可。
![](images/20240630221748.jpg)
**★ 公众号**
插件支持多公众号,在下拉菜单中进行不同公众号的切换。该功能需要订阅才能使用。
@@ -158,6 +156,15 @@ c=+-sqrt(a^2+b^2)
数学公式的渲染效果可以看这篇文章:[公众号文章里的数学公式排版秘籍](https://mp.weixin.qq.com/s/-kpT2U1gT_5W3TsDCAVgsw)👈️
### 自定义CSS使用指南
## 开发与构建
本仓库的构建脚本分为“未混淆”和“启用混淆”两个版本:
- `npm run build`:生成未混淆的 `main.js`,便于调试。
- `npm run build:obf`:在生产模式下启用 `javascript-obfuscator`,生成混淆后的 `main.js`。
- `./build.sh`:执行默认(未混淆)构建并同步到本地 Obsidian 插件目录。
- `./build.sh obf`:执行混淆构建后再进行同步。
如需自定义混淆行为,可在执行命令时设置环境变量(例如 `OBFUSCATE=1 npm run build:bundle`),详细参数见 `esbuild.config.mjs`。
新建一篇笔记,例如**自定义样式**,直接将如下内容粘贴进笔记:
````CSS
@@ -356,7 +363,7 @@ NoteToMP插件支持该语法。
- 你想要把所有标记为 `篆刻` 的文章筛选出来,批量上传到公众号草稿箱并逐条完善后发布。
- 按文件夹 `content/post` 筛选并批量发布该文件夹下的近期文章。
### 详细使用指南(一步步)
### 使用指南
1. 打开模态
- 命令面板Ctrl/Cmd+P→ 输入“批量发布文章”,回车打开模态窗口。
@@ -537,13 +544,6 @@ https://www.bilibili.com/video/BV15XWVeEEJa/
---
```
视频教程https://www.bilibili.com/video/BV15XWVeEEmA/
## 4、反馈交流群
**微信群:**
加微信:**Genius35Plus**,备注:**NoteToMP**
## 附:批量发布 - 快速交互速览与截图占位
如果你想把功能教学放到 README 中,这里是推荐的简短速览(已在模态中实现):
@@ -559,3 +559,6 @@ https://www.bilibili.com/video/BV15XWVeEEJa/
2. 在 README 中替换占位为图片预览并附带关键交互标注说明。
如果你更愿意手动截屏我也可以把一个标注模板SVG 或说明)发给你,方便手动粘贴到 `images/` 目录。

View File

@@ -2,8 +2,17 @@
set -e # 出错立即退出
# 1. 构建
echo "🏗️ 开始构建..."
if npm run build; then
MODE="$1"
BUILD_CMD=(npm run build)
if [[ "$MODE" == "obf" ]]; then
BUILD_CMD=(npm run build:obf)
echo "🏗️ 开始构建(启用混淆)..."
else
echo "🏗️ 开始构建(不启用混淆)..."
fi
if "${BUILD_CMD[@]}"; then
echo "✅ 构建成功"
echo
else

View File

@@ -75,14 +75,17 @@ git checkout -b release/v1.3.0
### 4. 构建项目
构建项目生成生产版本文件
构建项目生成发布所需的产物
```bash
# 执行项目构建
# 执行未混淆构建(推荐用于验证与调试)
npm run build
# 检查构建输出
ls -la main.js manifest.json
# 如需生成混淆后的发布包,可执行:
npm run build:obf
```
### 5. 创建归档目录

View File

@@ -1,6 +1,7 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
import javascriptObfuscatorPlugin from "./tools/esbuild-obfuscator-plugin.mjs";
const banner =
`/*
@@ -10,6 +11,25 @@ if you want to view the source, please visit the github repository of this plugi
`;
const prod = (process.argv[2] === "production");
const obfuscate = prod && process.env.OBFUSCATE === "1";
const plugins = [];
if (obfuscate) {
plugins.push(javascriptObfuscatorPlugin({
compact: false,
controlFlowFlattening: false,
deadCodeInjection: false,
debugProtection: false,
disableConsoleOutput: true,
identifierNamesGenerator: "hexadecimal",
log: false,
renameGlobals: false,
simplify: true,
splitStrings: false,
transformObjectKeys: true,
}));
}
const context = await esbuild.context({
banner: {
@@ -38,6 +58,7 @@ const context = await esbuild.context({
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
plugins,
});
if (prod) {

1020
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,9 @@
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"build": "npm run build:bundle --",
"build:obf": "OBFUSCATE=1 npm run build:bundle --",
"build:bundle": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"download": "node tools/download.mjs",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
@@ -18,6 +20,7 @@
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"javascript-obfuscator": "^4.1.1",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"

View File

@@ -19,6 +19,17 @@ export interface PlatformChooserOptions {
onPlatformChange?: (platform: PlatformType) => Promise<void>;
}
export interface PlatformActionLabels {
refresh: string;
publish: string;
}
export interface PlatformChooserActions {
onRefresh?: () => void | Promise<void>;
onPublish?: () => void | Promise<void>;
getLabels?: (platform: PlatformType) => PlatformActionLabels;
}
/**
* 平台信息接口
*/
@@ -48,8 +59,11 @@ const SUPPORTED_PLATFORMS: PlatformInfo[] = [
export class PlatformChooser {
private container: HTMLElement;
private selectElement: HTMLSelectElement | null = null;
private refreshButton: HTMLButtonElement | null = null;
private publishButton: HTMLButtonElement | null = null;
private currentPlatform: PlatformType;
private onChange?: (platform: PlatformType) => void;
private actions: PlatformChooserActions | null = null;
constructor(container: HTMLElement, options: PlatformChooserOptions = {}) {
this.container = container;
@@ -101,6 +115,16 @@ export class PlatformChooser {
const newPlatform = platformSelect.value as PlatformType;
this.switchPlatformInternal(newPlatform);
};
// 刷新按钮
this.refreshButton = this.container.createEl('button', { cls: 'toolbar-button purple-gradient' });
this.refreshButton.onclick = () => this.handleRefreshClick();
// 发布按钮
this.publishButton = this.container.createEl('button', { cls: 'toolbar-button' });
this.publishButton.onclick = () => this.handlePublishClick();
this.updateActionLabels();
}
/**
@@ -123,6 +147,8 @@ export class PlatformChooser {
console.error('[PlatformChooser] 平台切换失败:', error);
}
}
this.updateActionLabels();
}
/**
@@ -133,6 +159,7 @@ export class PlatformChooser {
if (this.selectElement) {
this.selectElement.value = platform;
}
this.updateActionLabels();
}
/**
@@ -160,5 +187,45 @@ export class PlatformChooser {
this.selectElement.onchange = null;
this.selectElement = null;
}
if (this.refreshButton) {
this.refreshButton.onclick = null;
this.refreshButton = null;
}
if (this.publishButton) {
this.publishButton.onclick = null;
this.publishButton = null;
}
}
/**
* 设置通用操作按钮
*/
setActions(actions: PlatformChooserActions): void {
this.actions = actions;
this.updateActionLabels();
}
private handleRefreshClick(): void {
if (!this.actions?.onRefresh) return;
Promise.resolve(this.actions.onRefresh()).catch((error) => {
console.error('[PlatformChooser] 刷新操作失败:', error);
});
}
private handlePublishClick(): void {
if (!this.actions?.onPublish) return;
Promise.resolve(this.actions.onPublish()).catch((error) => {
console.error('[PlatformChooser] 发布操作失败:', error);
});
}
private updateActionLabels(): void {
if (!this.refreshButton || !this.publishButton) return;
const labels = this.actions?.getLabels?.(this.currentPlatform) ?? {
refresh: '🔄 刷新',
publish: '📤 发布',
};
this.refreshButton.innerText = labels.refresh;
this.publishButton.innerText = labels.publish;
}
}

View File

@@ -93,6 +93,18 @@ export class PreviewManager {
// 构建 UI
this.platformChooser.render();
// 共享操作按钮
this.platformChooser.setActions({
onRefresh: () => this.refresh(),
onPublish: () => this.publishCurrentPlatform(),
getLabels: (platform) => {
if (platform === 'wechat') {
return { refresh: '🔄 刷新', publish: '📝 发布' };
}
return { refresh: '🔄 刷新', publish: '📤 发布' };
},
});
}
/**
@@ -125,6 +137,22 @@ export class PreviewManager {
this.wechatPreview.build();
}
private async publishCurrentPlatform(): Promise<void> {
if (this.currentPlatform === 'wechat') {
if (!this.wechatPreview) {
new Notice('微信预览未初始化');
return;
}
await this.wechatPreview.publish();
} else if (this.currentPlatform === 'xiaohongshu') {
if (!this.xhsPreview) {
new Notice('小红书预览未初始化');
return;
}
await this.xhsPreview.publish();
}
}
/**
* 创建小红书预览组件
*/

View File

@@ -3,7 +3,7 @@
* 作用:通用工具函数集合(事件、版本、字符串处理等)。
*/
import { App, sanitizeHTMLToDom, requestUrl, Platform } from "obsidian";
import { App, sanitizeHTMLToDom, Platform } from "obsidian";
import * as postcss from "./postcss/postcss"; // 内置 PostCSS runtime解析主题 CSS 用于内联样式
let PluginVersion = "0.0.0";
@@ -185,10 +185,7 @@ export function applyCSS(html: string, css: string) {
}
export function uevent(name: string) {
const url = `https://u.sunboshi.tech/event?name=${name}&platform=${PlugPlatform}&v=${PluginVersion}`;
requestUrl(url).then().catch(error => {
console.error("Failed to send event: " + url, error);
});
console.debug(`[uevent] ${name} @${PlugPlatform} v${PluginVersion}`);
}
/**

View File

@@ -134,19 +134,13 @@ export class WechatPreview {
}
// 操作按钮(直接平铺在 Grid 中)
const refreshBtn = parent.createEl('button', { text: '🔄 刷新', cls: 'toolbar-button purple-gradient' });
refreshBtn.onclick = async () => { if (this.onRefreshCallback) await this.onRefreshCallback(); };
const postBtn = parent.createEl('button', { text: '📝 发布', cls: 'toolbar-button' });
postBtn.onclick = async () => await this.postArticle();
if (Platform.isDesktop && this.settings.isAuthKeyVaild()) {
const htmlBtn = parent.createEl('button', { text: '💾 导出HTML', cls: 'toolbar-button' });
htmlBtn.onclick = async () => await this.exportHTML();
}
// 封面选择
this.buildCoverSelector(parent);
//this.buildCoverSelector(parent);
// 样式选择(如果启用)
if (this.settings.showStyleUI) {
@@ -387,6 +381,16 @@ export class WechatPreview {
/** 对外:发布草稿(供外层菜单调用) */
async postDraft() { await this.postArticle(); }
async publish(): Promise<void> {
await this.postDraft();
}
async refresh(): Promise<void> {
if (this.onRefreshCallback) {
await this.onRefreshCallback();
}
}
/** 由上层在切换/渲染时注入当前文件 */
setFile(file: TFile | null) { this.currentFile = file; }
}

View File

@@ -88,16 +88,8 @@ export class XiaohongshuPreview {
option.text = name;
});
const refreshCard = this.createGridCard(board, 'xhs-area-refresh');
const refreshBtn = refreshCard.createEl('button', { text: '🔄 刷新', cls: 'toolbar-button purple-gradient' });
refreshBtn.onclick = () => this.onRefresh();
const publishCard = this.createGridCard(board, 'xhs-area-publish');
const publishBtn = publishCard.createEl('button', { text: '📤 发布', cls: 'toolbar-button' });
publishBtn.onclick = () => this.onPublish();
const previewCard = this.createGridCard(board, 'xhs-area-preview');
const previewLabel = previewCard.createDiv({ cls: 'xhs-label', text: '预览宽度' });
const previewLabel = previewCard.createDiv({ cls: 'xhs-label', text: '宽度' });
this.previewWidthSelect = previewCard.createEl('select', { cls: 'xhs-select' });
const currentPreviewWidth = this.settings.xhsPreviewWidth || XHS_PREVIEW_DEFAULT_WIDTH;
XHS_PREVIEW_WIDTH_OPTIONS.forEach(value => {
@@ -121,9 +113,8 @@ export class XiaohongshuPreview {
};
const fontCard = this.createGridCard(board, 'xhs-area-font');
fontCard.createDiv({ cls: 'xhs-label', text: '字号' });
//fontCard.createDiv({ cls: 'xhs-label', text: '字号' });
const fontSizeGroup = fontCard.createDiv({ cls: 'font-size-group' });
const decreaseBtn = fontSizeGroup.createEl('button', { text: '', cls: 'font-size-btn' });
decreaseBtn.onclick = () => this.changeFontSize(-1);
@@ -174,10 +165,10 @@ export class XiaohongshuPreview {
nextBtn.onclick = () => this.nextPage();
const sliceCard = this.createGridCard(board, 'xhs-area-slice');
const sliceCurrentBtn = sliceCard.createEl('button', { text: '当前页切图', cls: 'xhs-slice-btn' });
const sliceCurrentBtn = sliceCard.createEl('button', { text: '当前页切图', cls: 'xhs-slice-btn' });
sliceCurrentBtn.onclick = () => this.sliceCurrentPage();
const sliceAllBtn = sliceCard.createEl('button', { text: '全部页切图', cls: 'xhs-slice-btn secondary' });
const sliceAllBtn = sliceCard.createEl('button', { text: '全部页切图', cls: 'xhs-slice-btn secondary' });
sliceAllBtn.onclick = () => this.sliceAllPages();
}
@@ -424,6 +415,14 @@ export class XiaohongshuPreview {
}
}
async refresh(): Promise<void> {
await this.onRefresh();
}
async publish(): Promise<void> {
await this.onPublish();
}
/**
* 全部页切图
*/

View File

@@ -337,7 +337,7 @@ label:hover { color: var(--c-primary); }
/* 平台选择容器:单层 Grid 排列 */
.platform-chooser-container.platform-chooser-grid {
display: grid;
grid-auto-flow: column;
grid-template-columns: auto minmax(160px, 1fr) auto auto;
align-items: center;
gap: 12px;
padding: 8px 12px;
@@ -347,6 +347,16 @@ label:hover { color: var(--c-primary); }
box-shadow: var(--shadow-sm);
}
.platform-chooser-container .toolbar-button {
justify-self: start;
}
@media (max-width: 720px) {
.platform-chooser-container.platform-chooser-grid {
grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
}
}
/* =========================================================== */
/* 平台选择器样式 */
/* =========================================================== */
@@ -467,14 +477,14 @@ label:hover { color: var(--c-primary); }
.xhs-board {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
grid-template-columns: repeat(6, minmax(0, 1fr));
grid-template-rows: auto auto auto 1fr auto;
grid-template-areas:
"platform platform platform refresh publish"
"template template preview preview font"
"content content content content content"
"content content content content content"
"pagination pagination slice slice slice";
"template template preview preview font font"
"content content content content content content"
"content content content content content content"
"content content content content content content"
"pagination pagination pagination slice slice slice";
gap: 12px;
width: 100%;
background: var(--grad-xhs-bg);
@@ -509,6 +519,7 @@ label:hover { color: var(--c-primary); }
background: white;
font-size: 13px;
cursor: pointer;
max-width: 100px;
transition: border-color 0.2s ease;
}
@@ -546,7 +557,7 @@ label:hover { color: var(--c-primary); }
.font-size-btn:hover { background: #eaf1fe; }
.font-size-input {
width: 60px;
width: 35px;
border: none;
background: transparent;
text-align: center;
@@ -556,17 +567,12 @@ label:hover { color: var(--c-primary); }
.font-size-input:focus { outline: none; }
.xhs-area-platform,
.xhs-board .platform-chooser-container,
.xhs-board .platform-selector-line {
grid-area: platform;
}
.xhs-area-template { grid-area: template; }
.xhs-area-preview { grid-area: preview; }
.xhs-area-refresh { grid-area: refresh; justify-content: center; }
.xhs-area-publish { grid-area: publish; justify-content: center; }
.xhs-area-font { grid-area: font; flex-wrap: wrap; }
.xhs-area-font {
grid-area: font;
flex-wrap: nowrap;
}
.xhs-area-pagination { grid-area: pagination; justify-content: center; gap: 16px; }
.xhs-area-slice { grid-area: slice; justify-content: center; gap: 16px; }
@@ -641,7 +647,7 @@ label:hover { color: var(--c-primary); }
}
.xhs-page-number-input {
width: 56px;
width: 35px;
padding: 4px 6px;
text-align: center;
border: 1px solid var(--c-border);

View File

@@ -126,3 +126,4 @@ SOLVEobsidian控制台打印信息定位在哪里阻塞AI修复。
自己写布局demo原型让codex参考布局修改(原来元素美化的css可保留)。
demo原型可以手绘后拍照让chatgpt生成在此基础上自己修改。

View File

@@ -0,0 +1,48 @@
import { promises as fs } from "fs";
// Esbuild plugin that optionally obfuscates the emitted bundle with javascript-obfuscator.
export default function javascriptObfuscatorPlugin(obfuscatorOptions = {}) {
let obfuscatorPromise;
const loadObfuscator = async () => {
if (!obfuscatorPromise) {
obfuscatorPromise = import("javascript-obfuscator")
.then((module) => module.default ?? module)
.catch((error) => {
console.warn("[esbuild] javascript-obfuscator unavailable, skipping obfuscation.", error);
return null;
});
}
return obfuscatorPromise;
};
// Default to preserving line breaks unless explicitly overridden.
if (typeof obfuscatorOptions.compact === "undefined") {
obfuscatorOptions.compact = false;
}
return {
name: "javascript-obfuscator",
setup(build) {
build.onEnd(async (result) => {
if (result.errors.length) return;
const outfile = build.initialOptions.outfile;
if (!outfile) return;
try {
const obfuscator = await loadObfuscator();
if (!obfuscator?.obfuscate) return;
const source = await fs.readFile(outfile, "utf8");
const obfuscatedCode = obfuscator
.obfuscate(source, obfuscatorOptions)
.getObfuscatedCode();
await fs.writeFile(outfile, obfuscatedCode, "utf8");
} catch (error) {
console.warn("[esbuild] javascript-obfuscator plugin failed, continuing without obfuscation.", error);
}
});
},
};
}