mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 05:51:44 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc95e6fe05 | |||
| e7d9ed7917 | |||
| 876c6332f6 |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||||
"version": "1.28.3"
|
"version": "1.28.4"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
English | [中文](./CHANGELOG.zh.md)
|
English | [中文](./CHANGELOG.zh.md)
|
||||||
|
|
||||||
|
## 1.28.4 - 2026-02-03
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- `baoyu-markdown-to-html`: add author and description meta tags to generated HTML from YAML frontmatter; strip quotes from frontmatter values (supports both English and Chinese quotation marks).
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- `baoyu-post-to-wechat`: remove extra empty lines after image paste; fix summary field timing to fill after content paste (prevents being overwritten).
|
||||||
|
|
||||||
## 1.28.3 - 2026-02-03
|
## 1.28.3 - 2026-02-03
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
[English](./CHANGELOG.md) | 中文
|
[English](./CHANGELOG.md) | 中文
|
||||||
|
|
||||||
|
## 1.28.4 - 2026-02-03
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
- `baoyu-markdown-to-html`:从 YAML frontmatter 生成 author 和 description meta 标签;自动去除 frontmatter 值两端的引号(支持中英文引号)。
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- `baoyu-post-to-wechat`:移除图片粘贴后产生的多余空行;修复摘要填充时机,改为内容粘贴后填写(避免被覆盖)。
|
||||||
|
|
||||||
## 1.28.3 - 2026-02-03
|
## 1.28.3 - 2026-02-03
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|||||||
@@ -126,7 +126,18 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
|
|||||||
|
|
||||||
const { frontmatter, body } = parseFrontmatter(content);
|
const { frontmatter, body } = parseFrontmatter(content);
|
||||||
|
|
||||||
let title = options?.title ?? frontmatter.title ?? '';
|
const stripQuotes = (s?: string): string => {
|
||||||
|
if (!s) return '';
|
||||||
|
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
||||||
|
return s.slice(1, -1);
|
||||||
|
}
|
||||||
|
if ((s.startsWith('\u201c') && s.endsWith('\u201d')) || (s.startsWith('\u2018') && s.endsWith('\u2019'))) {
|
||||||
|
return s.slice(1, -1);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
let title = options?.title ?? stripQuotes(frontmatter.title) ?? '';
|
||||||
if (!title) {
|
if (!title) {
|
||||||
const lines = body.split('\n');
|
const lines = body.split('\n');
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
@@ -138,8 +149,8 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!title) title = path.basename(markdownPath, path.extname(markdownPath));
|
if (!title) title = path.basename(markdownPath, path.extname(markdownPath));
|
||||||
const author = frontmatter.author || '';
|
const author = stripQuotes(frontmatter.author);
|
||||||
let summary = frontmatter.description || frontmatter.summary || '';
|
let summary = stripQuotes(frontmatter.description) || stripQuotes(frontmatter.summary);
|
||||||
|
|
||||||
if (!summary) {
|
if (!summary) {
|
||||||
const lines = body.split('\n');
|
const lines = body.split('\n');
|
||||||
|
|||||||
@@ -708,14 +708,28 @@ function normalizeThemeCss(css: string): string {
|
|||||||
return stripOutputScope(css);
|
return stripOutputScope(css);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildHtmlDocument(title: string, css: string, html: string): string {
|
interface HtmlDocumentMeta {
|
||||||
return [
|
title: string;
|
||||||
|
author?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string): string {
|
||||||
|
const lines = [
|
||||||
"<!doctype html>",
|
"<!doctype html>",
|
||||||
"<html>",
|
"<html>",
|
||||||
"<head>",
|
"<head>",
|
||||||
' <meta charset="utf-8" />',
|
' <meta charset="utf-8" />',
|
||||||
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
||||||
` <title>${title}</title>`,
|
` <title>${meta.title}</title>`,
|
||||||
|
];
|
||||||
|
if (meta.author) {
|
||||||
|
lines.push(` <meta name="author" content="${meta.author}" />`);
|
||||||
|
}
|
||||||
|
if (meta.description) {
|
||||||
|
lines.push(` <meta name="description" content="${meta.description}" />`);
|
||||||
|
}
|
||||||
|
lines.push(
|
||||||
` <style>${css}</style>`,
|
` <style>${css}</style>`,
|
||||||
"</head>",
|
"</head>",
|
||||||
"<body>",
|
"<body>",
|
||||||
@@ -723,8 +737,9 @@ function buildHtmlDocument(title: string, css: string, html: string): string {
|
|||||||
html,
|
html,
|
||||||
" </div>",
|
" </div>",
|
||||||
"</body>",
|
"</body>",
|
||||||
"</html>",
|
"</html>"
|
||||||
].join("\n");
|
);
|
||||||
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function inlineCss(html: string): Promise<string> {
|
async function inlineCss(html: string): Promise<string> {
|
||||||
@@ -814,6 +829,7 @@ async function main(): Promise<void> {
|
|||||||
const markdown = fs.readFileSync(inputPath, "utf-8");
|
const markdown = fs.readFileSync(inputPath, "utf-8");
|
||||||
|
|
||||||
const renderer = initRenderer({});
|
const renderer = initRenderer({});
|
||||||
|
const { yamlData } = renderer.parseFrontMatterAndContent(markdown);
|
||||||
const { html: baseHtml, readingTime: readingTimeResult } = renderMarkdown(
|
const { html: baseHtml, readingTime: readingTimeResult } = renderMarkdown(
|
||||||
markdown,
|
markdown,
|
||||||
renderer
|
renderer
|
||||||
@@ -823,8 +839,23 @@ async function main(): Promise<void> {
|
|||||||
content = removeFirstHeading(content);
|
content = removeFirstHeading(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = path.basename(outputPath, ".html");
|
const stripQuotes = (s?: string): string | undefined => {
|
||||||
const html = buildHtmlDocument(title, css, content);
|
if (!s) return s;
|
||||||
|
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
||||||
|
return s.slice(1, -1);
|
||||||
|
}
|
||||||
|
if ((s.startsWith('\u201c') && s.endsWith('\u201d')) || (s.startsWith('\u2018') && s.endsWith('\u2019'))) {
|
||||||
|
return s.slice(1, -1);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
const meta: HtmlDocumentMeta = {
|
||||||
|
title: stripQuotes(yamlData.title) || path.basename(outputPath, ".html"),
|
||||||
|
author: stripQuotes(yamlData.author),
|
||||||
|
description: stripQuotes(yamlData.description) || stripQuotes(yamlData.summary),
|
||||||
|
};
|
||||||
|
const html = buildHtmlDocument(meta, css, content);
|
||||||
const inlinedHtml = normalizeInlineCss(await inlineCss(html));
|
const inlinedHtml = normalizeInlineCss(await inlineCss(html));
|
||||||
const finalHtml = modifyHtmlStructure(inlinedHtml);
|
const finalHtml = modifyHtmlStructure(inlinedHtml);
|
||||||
|
|
||||||
|
|||||||
@@ -315,6 +315,71 @@ async function pressDeleteKey(session: ChromeSession): Promise<void> {
|
|||||||
await session.cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId: session.sessionId });
|
await session.cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId: session.sessionId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeExtraEmptyLineAfterImage(session: ChromeSession): Promise<boolean> {
|
||||||
|
const removed = await evaluate<boolean>(session, `
|
||||||
|
(function() {
|
||||||
|
const editor = document.querySelector('.ProseMirror');
|
||||||
|
if (!editor) return false;
|
||||||
|
|
||||||
|
const sel = window.getSelection();
|
||||||
|
if (!sel || sel.rangeCount === 0) return false;
|
||||||
|
|
||||||
|
let node = sel.anchorNode;
|
||||||
|
if (!node) return false;
|
||||||
|
let element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
||||||
|
if (!element || !editor.contains(element)) return false;
|
||||||
|
|
||||||
|
const isEmptyParagraph = (el) => {
|
||||||
|
if (!el || el.tagName !== 'P') return false;
|
||||||
|
const text = (el.textContent || '').trim();
|
||||||
|
if (text.length > 0) return false;
|
||||||
|
return el.querySelectorAll('img, figure, video, iframe').length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasImage = (el) => {
|
||||||
|
if (!el) return false;
|
||||||
|
return !!el.querySelector('img, figure img, picture img');
|
||||||
|
};
|
||||||
|
|
||||||
|
const placeCursorAfter = (el) => {
|
||||||
|
if (!el) return;
|
||||||
|
const range = document.createRange();
|
||||||
|
range.setStartAfter(el);
|
||||||
|
range.collapse(true);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Case 1: caret is inside an empty paragraph right after an image block.
|
||||||
|
const emptyPara = element.closest('p');
|
||||||
|
if (emptyPara && editor.contains(emptyPara) && isEmptyParagraph(emptyPara)) {
|
||||||
|
const prev = emptyPara.previousElementSibling;
|
||||||
|
if (prev && hasImage(prev)) {
|
||||||
|
emptyPara.remove();
|
||||||
|
placeCursorAfter(prev);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: caret is on the image block itself; remove the next empty paragraph.
|
||||||
|
const imageBlock = element.closest('figure, p');
|
||||||
|
if (imageBlock && editor.contains(imageBlock) && hasImage(imageBlock)) {
|
||||||
|
const next = imageBlock.nextElementSibling;
|
||||||
|
if (next && isEmptyParagraph(next)) {
|
||||||
|
next.remove();
|
||||||
|
placeCursorAfter(imageBlock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})()
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (removed) console.log('[wechat] Removed extra empty line after image.');
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
export async function postArticle(options: ArticleOptions): Promise<void> {
|
export async function postArticle(options: ArticleOptions): Promise<void> {
|
||||||
const { title, content, htmlFile, markdownFile, theme, author, summary, images = [], submit = false, profileDir, cdpPort } = options;
|
const { title, content, htmlFile, markdownFile, theme, author, summary, images = [], submit = false, profileDir, cdpPort } = options;
|
||||||
let { contentImages = [] } = options;
|
let { contentImages = [] } = options;
|
||||||
@@ -454,11 +519,6 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
|
|||||||
await evaluate(session, `document.querySelector('#author').value = ${JSON.stringify(effectiveAuthor)}; document.querySelector('#author').dispatchEvent(new Event('input', { bubbles: true }));`);
|
await evaluate(session, `document.querySelector('#author').value = ${JSON.stringify(effectiveAuthor)}; document.querySelector('#author').dispatchEvent(new Event('input', { bubbles: true }));`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (effectiveSummary) {
|
|
||||||
console.log(`[wechat] Filling summary: ${effectiveSummary}`);
|
|
||||||
await evaluate(session, `document.querySelector('#js_description').value = ${JSON.stringify(effectiveSummary)}; document.querySelector('#js_description').dispatchEvent(new Event('input', { bubbles: true }));`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
|
|
||||||
if (effectiveTitle) {
|
if (effectiveTitle) {
|
||||||
@@ -470,15 +530,6 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (effectiveSummary) {
|
|
||||||
const actualSummary = await evaluate<string>(session, `document.querySelector('#js_description')?.value || ''`);
|
|
||||||
if (actualSummary === effectiveSummary) {
|
|
||||||
console.log('[wechat] Summary verified OK.');
|
|
||||||
} else {
|
|
||||||
console.warn(`[wechat] Summary verification failed. Expected: "${effectiveSummary}", got: "${actualSummary}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[wechat] Clicking on editor...');
|
console.log('[wechat] Clicking on editor...');
|
||||||
await clickElement(session, '.ProseMirror');
|
await clickElement(session, '.ProseMirror');
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
@@ -534,6 +585,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
|
|||||||
console.log('[wechat] Pasting image...');
|
console.log('[wechat] Pasting image...');
|
||||||
await pasteFromClipboardInEditor(session);
|
await pasteFromClipboardInEditor(session);
|
||||||
await sleep(3000);
|
await sleep(3000);
|
||||||
|
await removeExtraEmptyLineAfterImage(session);
|
||||||
}
|
}
|
||||||
console.log('[wechat] All images inserted.');
|
console.log('[wechat] All images inserted.');
|
||||||
}
|
}
|
||||||
@@ -545,6 +597,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
|
|||||||
await sleep(500);
|
await sleep(500);
|
||||||
await pasteInEditor(session);
|
await pasteInEditor(session);
|
||||||
await sleep(2000);
|
await sleep(2000);
|
||||||
|
await removeExtraEmptyLineAfterImage(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,6 +620,30 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (effectiveSummary) {
|
||||||
|
console.log(`[wechat] Filling summary (after content paste): ${effectiveSummary}`);
|
||||||
|
await evaluate(session, `
|
||||||
|
(function() {
|
||||||
|
const el = document.querySelector('#js_description');
|
||||||
|
if (!el) return;
|
||||||
|
el.focus();
|
||||||
|
el.select();
|
||||||
|
el.value = ${JSON.stringify(effectiveSummary)};
|
||||||
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
el.dispatchEvent(new Event('blur', { bubbles: true }));
|
||||||
|
})()
|
||||||
|
`);
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
const actualSummary = await evaluate<string>(session, `document.querySelector('#js_description')?.value || ''`);
|
||||||
|
if (actualSummary === effectiveSummary) {
|
||||||
|
console.log('[wechat] Summary verified OK.');
|
||||||
|
} else {
|
||||||
|
console.warn(`[wechat] Summary verification failed. Expected: "${effectiveSummary}", got: "${actualSummary}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[wechat] Saving as draft...');
|
console.log('[wechat] Saving as draft...');
|
||||||
await evaluate(session, `document.querySelector('#js_submit button').click()`);
|
await evaluate(session, `document.querySelector('#js_submit button').click()`);
|
||||||
await sleep(3000);
|
await sleep(3000);
|
||||||
|
|||||||
Reference in New Issue
Block a user