mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 22:09:48 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e68aa64a4 | |||
| 059ebc759c | |||
| d4a5a34865 | |||
| b3b2f8ea36 | |||
| f19ed00cb2 | |||
| 8e58855047 |
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||
"version": "1.55.0"
|
||||
"version": "1.56.1"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,20 @@
|
||||
|
||||
English | [中文](./CHANGELOG.zh.md)
|
||||
|
||||
## 1.56.1 - 2026-03-08
|
||||
|
||||
### Fixes
|
||||
- `baoyu-post-to-weibo`: simplify article image insertion with Backspace-based placeholder deletion for ProseMirror compatibility
|
||||
|
||||
## 1.56.0 - 2026-03-08
|
||||
|
||||
### Features
|
||||
- `baoyu-article-illustrator`: preset-first selection flow with categorized style presets by content type
|
||||
- `baoyu-xhs-images`: streamline workflow from 6 to 4 steps with Smart Confirm (Quick/Customize/Detailed paths)
|
||||
|
||||
### Fixes
|
||||
- `baoyu-post-to-wechat`: improve image upload reliability with file chooser interception and fallback
|
||||
|
||||
## 1.55.0 - 2026-03-08
|
||||
|
||||
### Features
|
||||
|
||||
@@ -2,6 +2,20 @@
|
||||
|
||||
[English](./CHANGELOG.md) | 中文
|
||||
|
||||
## 1.56.1 - 2026-03-08
|
||||
|
||||
### 修复
|
||||
- `baoyu-post-to-weibo`:简化头条文章图片插入逻辑,使用 Backspace 按键替代复杂的 deleteContents 方案,兼容 ProseMirror 编辑器
|
||||
|
||||
## 1.56.0 - 2026-03-08
|
||||
|
||||
### 新功能
|
||||
- `baoyu-article-illustrator`:预设优先选择流程,按内容类型分类的风格预设
|
||||
- `baoyu-xhs-images`:精简工作流从 6 步到 4 步,新增智能确认(快速/自定义/详细三种路径)
|
||||
|
||||
### 修复
|
||||
- `baoyu-post-to-wechat`:通过文件选择器拦截改进图片上传可靠性
|
||||
|
||||
## 1.55.0 - 2026-03-08
|
||||
|
||||
### 新功能
|
||||
|
||||
@@ -82,13 +82,13 @@ Full procedures: [references/workflow.md](references/workflow.md#step-2-setup--a
|
||||
|
||||
### Step 3: Confirm Settings ⚠️
|
||||
|
||||
**ONE AskUserQuestion, max 4 Qs. Q1-Q3 REQUIRED.**
|
||||
**ONE AskUserQuestion, max 4 Qs. Q1-Q2 REQUIRED. Q3 required unless preset chosen.**
|
||||
|
||||
| Q | Options |
|
||||
|---|---------|
|
||||
| **Q1: Type** | [Recommended], infographic, scene, flowchart, comparison, framework, timeline, mixed |
|
||||
| **Q1: Preset or Type** | [Recommended preset], [alt preset], or manual: infographic, scene, flowchart, comparison, framework, timeline, mixed |
|
||||
| **Q2: Density** | minimal (1-2), balanced (3-5), per-section (Recommended), rich (6+) |
|
||||
| **Q3: Style** | [Recommended], minimal-flat, sci-fi, hand-drawn, editorial, scene, poster, Other |
|
||||
| **Q3: Style** | [Recommended], minimal-flat, sci-fi, hand-drawn, editorial, scene, poster, Other — **skip if preset chosen** |
|
||||
| Q4: Language | When article language ≠ EXTEND.md setting |
|
||||
|
||||
Full procedures: [references/workflow.md](references/workflow.md#step-3-confirm-settings-)
|
||||
|
||||
@@ -2,26 +2,67 @@
|
||||
|
||||
`--preset X` expands to a type + style combination. Users can override either dimension.
|
||||
|
||||
| --preset | Type | Style |
|
||||
|----------|------|-------|
|
||||
| `tech-explainer` | `infographic` | `blueprint` |
|
||||
| `knowledge-base` | `infographic` | `vector-illustration` |
|
||||
| `saas-guide` | `infographic` | `notion` |
|
||||
| `data-report` | `infographic` | `editorial` |
|
||||
| `science-paper` | `infographic` | `scientific` |
|
||||
| `tutorial` | `flowchart` | `vector-illustration` |
|
||||
| `process-flow` | `flowchart` | `notion` |
|
||||
| `system-design` | `framework` | `blueprint` |
|
||||
| `architecture` | `framework` | `vector-illustration` |
|
||||
| `versus` | `comparison` | `vector-illustration` |
|
||||
| `business-compare` | `comparison` | `elegant` |
|
||||
| `storytelling` | `scene` | `warm` |
|
||||
| `lifestyle` | `scene` | `watercolor` |
|
||||
| `history` | `timeline` | `elegant` |
|
||||
| `evolution` | `timeline` | `warm` |
|
||||
| `opinion-piece` | `scene` | `screen-print` |
|
||||
| `editorial-poster` | `comparison` | `screen-print` |
|
||||
| `cinematic` | `scene` | `screen-print` |
|
||||
## By Category
|
||||
|
||||
### Technical & Engineering
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `tech-explainer` | `infographic` | `blueprint` | API docs, system metrics, technical deep-dives |
|
||||
| `system-design` | `framework` | `blueprint` | Architecture diagrams, system design |
|
||||
| `architecture` | `framework` | `vector-illustration` | Component relationships, module structure |
|
||||
| `science-paper` | `infographic` | `scientific` | Research findings, lab results, academic |
|
||||
|
||||
### Knowledge & Education
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `knowledge-base` | `infographic` | `vector-illustration` | Concept explainers, tutorials, how-to |
|
||||
| `saas-guide` | `infographic` | `notion` | Product guides, SaaS docs, tool walkthroughs |
|
||||
| `tutorial` | `flowchart` | `vector-illustration` | Step-by-step tutorials, setup guides |
|
||||
| `process-flow` | `flowchart` | `notion` | Workflow documentation, onboarding flows |
|
||||
|
||||
### Data & Analysis
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `data-report` | `infographic` | `editorial` | Data journalism, metrics reports, dashboards |
|
||||
| `versus` | `comparison` | `vector-illustration` | Tech comparisons, framework shootouts |
|
||||
| `business-compare` | `comparison` | `elegant` | Product evaluations, strategy options |
|
||||
|
||||
### Narrative & Creative
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `storytelling` | `scene` | `warm` | Personal essays, reflections, growth stories |
|
||||
| `lifestyle` | `scene` | `watercolor` | Travel, wellness, lifestyle, creative |
|
||||
| `history` | `timeline` | `elegant` | Historical overviews, milestones |
|
||||
| `evolution` | `timeline` | `warm` | Progress narratives, growth journeys |
|
||||
|
||||
### Editorial & Opinion
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `opinion-piece` | `scene` | `screen-print` | Op-eds, commentary, critical essays |
|
||||
| `editorial-poster` | `comparison` | `screen-print` | Debate, contrasting viewpoints |
|
||||
| `cinematic` | `scene` | `screen-print` | Dramatic narratives, cultural essays |
|
||||
|
||||
## Content Type → Preset Recommendations
|
||||
|
||||
Use this table during Step 3 to recommend presets based on Step 2 content analysis:
|
||||
|
||||
| Content Type (Step 2) | Primary Preset | Alternatives |
|
||||
|------------------------|----------------|--------------|
|
||||
| Technical | `tech-explainer` | `system-design`, `architecture` |
|
||||
| Tutorial | `tutorial` | `process-flow`, `knowledge-base` |
|
||||
| Methodology / Framework | `system-design` | `architecture`, `process-flow` |
|
||||
| Data / Metrics | `data-report` | `versus`, `tech-explainer` |
|
||||
| Comparison / Review | `versus` | `business-compare`, `editorial-poster` |
|
||||
| Narrative / Personal | `storytelling` | `lifestyle`, `evolution` |
|
||||
| Opinion / Editorial | `opinion-piece` | `cinematic`, `editorial-poster` |
|
||||
| Historical / Timeline | `history` | `evolution` |
|
||||
| Academic / Research | `science-paper` | `tech-explainer`, `data-report` |
|
||||
| SaaS / Product | `saas-guide` | `knowledge-base`, `process-flow` |
|
||||
|
||||
## Override Examples
|
||||
|
||||
|
||||
@@ -165,9 +165,16 @@ For each reference image:
|
||||
|
||||
**Do NOT skip.** Use ONE AskUserQuestion call with max 4 questions. **Q1, Q2, Q3 are ALL REQUIRED.**
|
||||
|
||||
### Q1: Illustration Type ⚠️ REQUIRED
|
||||
- [Recommended based on analysis] (Recommended)
|
||||
- infographic / scene / flowchart / comparison / framework / timeline / mixed
|
||||
### Q1: Preset or Type ⚠️ REQUIRED
|
||||
|
||||
Based on Step 2 content analysis, recommend a preset first (sets both type & style). Look up [style-presets.md](style-presets.md) "Content Type → Preset Recommendations" table.
|
||||
|
||||
- [Recommended preset] — [brief: type + style + why] (Recommended)
|
||||
- [Alternative preset] — [brief]
|
||||
- Or choose type manually: infographic / scene / flowchart / comparison / framework / timeline / mixed
|
||||
|
||||
**If user picks a preset → skip Q3** (type & style both resolved).
|
||||
**If user picks a type → Q3 is REQUIRED.**
|
||||
|
||||
### Q2: Density ⚠️ REQUIRED - DO NOT SKIP
|
||||
- minimal (1-2) - Core concepts only
|
||||
@@ -175,7 +182,7 @@ For each reference image:
|
||||
- per-section - At least 1 per section/chapter (Recommended)
|
||||
- rich (6+) - Comprehensive coverage
|
||||
|
||||
### Q3: Style ⚠️ REQUIRED (ALWAYS ask, even with preferred_style in EXTEND.md)
|
||||
### Q3: Style ⚠️ REQUIRED (skip if preset chosen in Q1)
|
||||
|
||||
If EXTEND.md has `preferred_style`:
|
||||
- [Custom style name + brief description] (Recommended)
|
||||
@@ -191,14 +198,14 @@ If no `preferred_style` (present Core Styles first):
|
||||
|
||||
**Core Styles** (simplified selection):
|
||||
|
||||
| Core Style | Best For |
|
||||
|------------|----------|
|
||||
| `minimal-flat` | General, knowledge sharing, SaaS |
|
||||
| `sci-fi` | AI, frontier tech, system design |
|
||||
| `hand-drawn` | Relaxed, reflective, casual |
|
||||
| `editorial` | Processes, data, journalism |
|
||||
| `scene` | Narratives, emotional, lifestyle |
|
||||
| `poster` | Opinion, editorial, cultural, cinematic |
|
||||
| Core Style | Maps To | Best For |
|
||||
|------------|---------|----------|
|
||||
| `minimal-flat` | notion | General, knowledge sharing, SaaS |
|
||||
| `sci-fi` | blueprint | AI, frontier tech, system design |
|
||||
| `hand-drawn` | sketch/warm | Relaxed, reflective, casual |
|
||||
| `editorial` | editorial | Processes, data, journalism |
|
||||
| `scene` | warm/watercolor | Narratives, emotional, lifestyle |
|
||||
| `poster` | screen-print | Opinion, editorial, cultural, cinematic |
|
||||
|
||||
Style selection based on Type × Style compatibility matrix (styles.md).
|
||||
Full specs: `styles/<style>.md`
|
||||
|
||||
@@ -551,37 +551,126 @@ export async function postToWeChat(options: WeChatBrowserOptions): Promise<void>
|
||||
const absolutePaths = images.map(p => path.isAbsolute(p) ? p : path.resolve(process.cwd(), p));
|
||||
console.log(`[wechat-browser] Images: ${absolutePaths.join(', ')}`);
|
||||
|
||||
const { root } = await cdp.send<{ root: { nodeId: number } }>('DOM.getDocument', {}, { sessionId });
|
||||
// --- PRIMARY approach: intercept file chooser dialog ---
|
||||
let uploadSuccess = false;
|
||||
try {
|
||||
console.log('[wechat-browser] [primary] Enabling file chooser interception...');
|
||||
await cdp.send('Page.setInterceptFileChooserDialog', { enabled: true }, { sessionId });
|
||||
|
||||
// Try primary selector, then fallback to any multi-file image input
|
||||
let { nodeId } = await cdp.send<{ nodeId: number }>('DOM.querySelector', {
|
||||
nodeId: root.nodeId,
|
||||
selector: '.js_upload_btn_container input[type=file]',
|
||||
}, { sessionId });
|
||||
// Set up listener for file chooser opened event BEFORE clicking
|
||||
const fileChooserPromise = new Promise<{ backendNodeId: number; mode: string }>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('File chooser dialog not opened within 10s')), 10_000);
|
||||
cdp!.on('Page.fileChooserOpened', (params: unknown) => {
|
||||
clearTimeout(timeout);
|
||||
const p = params as { backendNodeId: number; mode: string };
|
||||
console.log(`[wechat-browser] [primary] File chooser opened: backendNodeId=${p.backendNodeId}, mode=${p.mode}`);
|
||||
resolve(p);
|
||||
});
|
||||
});
|
||||
|
||||
if (!nodeId) {
|
||||
console.log('[wechat-browser] Primary file input not found, trying fallback selector...');
|
||||
const fallback = await cdp.send<{ nodeId: number }>('DOM.querySelector', {
|
||||
nodeId: root.nodeId,
|
||||
selector: 'input[type=file][multiple][accept*="image"]',
|
||||
// Trigger file chooser by calling .click() on the file input with userGesture
|
||||
const fileInputSelectors = [
|
||||
'.js_upload_btn_container input[type=file]',
|
||||
'input[type=file][multiple][accept*="image"]',
|
||||
'input[type=file][accept*="image"]',
|
||||
'input[type=file][multiple]',
|
||||
'input[type=file]',
|
||||
];
|
||||
|
||||
console.log('[wechat-browser] [primary] Clicking file input via JS .click() with userGesture...');
|
||||
const clickResult = await cdp.send<{ result: { value: string } }>('Runtime.evaluate', {
|
||||
expression: `
|
||||
(function() {
|
||||
const selectors = ${JSON.stringify(fileInputSelectors)};
|
||||
for (const sel of selectors) {
|
||||
const el = document.querySelector(sel);
|
||||
if (el) {
|
||||
el.click();
|
||||
return JSON.stringify({ clicked: sel });
|
||||
}
|
||||
}
|
||||
const debug = [];
|
||||
document.querySelectorAll('input[type=file]').forEach((inp, i) => {
|
||||
debug.push({ i, accept: inp.accept, multiple: inp.multiple, parentClass: inp.parentElement?.className?.slice(0, 60) });
|
||||
});
|
||||
return JSON.stringify({ error: 'no file input found', fileInputs: debug });
|
||||
})()
|
||||
`,
|
||||
returnByValue: true,
|
||||
userGesture: true,
|
||||
}, { sessionId });
|
||||
nodeId = fallback.nodeId;
|
||||
console.log(`[wechat-browser] [primary] Click result: ${clickResult.result.value}`);
|
||||
|
||||
const clickStatus = JSON.parse(clickResult.result.value);
|
||||
if (clickStatus.error) {
|
||||
throw new Error(`File input not found: ${clickStatus.error}`);
|
||||
}
|
||||
|
||||
// Wait for the file chooser event
|
||||
console.log('[wechat-browser] [primary] Waiting for file chooser dialog...');
|
||||
const chooser = await fileChooserPromise;
|
||||
|
||||
console.log(`[wechat-browser] [primary] Setting files via backendNodeId=${chooser.backendNodeId}...`);
|
||||
await cdp.send('DOM.setFileInputFiles', {
|
||||
files: absolutePaths,
|
||||
backendNodeId: chooser.backendNodeId,
|
||||
}, { sessionId });
|
||||
console.log('[wechat-browser] [primary] Files set successfully via file chooser interception');
|
||||
uploadSuccess = true;
|
||||
} catch (primaryErr) {
|
||||
console.log(`[wechat-browser] [primary] File chooser approach failed: ${primaryErr instanceof Error ? primaryErr.message : String(primaryErr)}`);
|
||||
// Disable interception before falling back
|
||||
try { await cdp.send('Page.setInterceptFileChooserDialog', { enabled: false }, { sessionId }); } catch {}
|
||||
}
|
||||
|
||||
if (!nodeId) throw new Error('File input not found');
|
||||
// --- FALLBACK approach: direct DOM.setFileInputFiles on nodeId ---
|
||||
if (!uploadSuccess) {
|
||||
console.log('[wechat-browser] [fallback] Trying direct DOM.setFileInputFiles...');
|
||||
const { root } = await cdp.send<{ root: { nodeId: number } }>('DOM.getDocument', {}, { sessionId });
|
||||
|
||||
await cdp.send('DOM.setFileInputFiles', {
|
||||
nodeId,
|
||||
files: absolutePaths,
|
||||
}, { sessionId });
|
||||
const fileInputSelectors = [
|
||||
'.js_upload_btn_container input[type=file]',
|
||||
'input[type=file][multiple][accept*="image"]',
|
||||
'input[type=file][accept*="image"]',
|
||||
'input[type=file][multiple]',
|
||||
'input[type=file]',
|
||||
];
|
||||
|
||||
// Dispatch change event to trigger the upload
|
||||
await cdp.send('Runtime.evaluate', {
|
||||
expression: `
|
||||
const fileInput = document.querySelector('.js_upload_btn_container input[type=file]') || document.querySelector('input[type=file][multiple][accept*="image"]');
|
||||
if (fileInput) fileInput.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
`,
|
||||
}, { sessionId });
|
||||
let nodeId = 0;
|
||||
for (const sel of fileInputSelectors) {
|
||||
const result = await cdp.send<{ nodeId: number }>('DOM.querySelector', { nodeId: root.nodeId, selector: sel }, { sessionId });
|
||||
if (result.nodeId) {
|
||||
console.log(`[wechat-browser] [fallback] Found file input with selector: ${sel}`);
|
||||
nodeId = result.nodeId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nodeId) throw new Error('File input not found with any selector');
|
||||
|
||||
await cdp.send('DOM.setFileInputFiles', { nodeId, files: absolutePaths }, { sessionId });
|
||||
console.log('[wechat-browser] [fallback] Files set via nodeId');
|
||||
|
||||
// Dispatch change event
|
||||
await cdp.send('Runtime.evaluate', {
|
||||
expression: `
|
||||
(function() {
|
||||
const selectors = ${JSON.stringify(fileInputSelectors)};
|
||||
for (const sel of selectors) {
|
||||
const el = document.querySelector(sel);
|
||||
if (el) {
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
return 'dispatched on ' + sel;
|
||||
}
|
||||
}
|
||||
return 'no input found for event dispatch';
|
||||
})()
|
||||
`,
|
||||
returnByValue: true,
|
||||
}, { sessionId });
|
||||
console.log('[wechat-browser] [fallback] Change event dispatched');
|
||||
}
|
||||
|
||||
// Wait for images to upload
|
||||
console.log('[wechat-browser] Waiting for images to upload...');
|
||||
@@ -589,11 +678,16 @@ export async function postToWeChat(options: WeChatBrowserOptions): Promise<void>
|
||||
for (let i = 0; i < 30; i++) {
|
||||
await sleep(2000);
|
||||
const uploadCheck = await cdp.send<{ result: { value: string } }>('Runtime.evaluate', {
|
||||
expression: `JSON.stringify({ uploaded: document.querySelectorAll('.weui-desktop-upload__thumb, .pic_item, [class*=upload_thumb]').length })`,
|
||||
expression: `
|
||||
JSON.stringify({
|
||||
uploaded: document.querySelectorAll('.weui-desktop-upload__thumb, .pic_item, [class*=upload_thumb], [class*="pic_item"], [class*="upload__thumb"]').length,
|
||||
loading: document.querySelectorAll('[class*="upload_loading"], [class*="uploading"], .weui-desktop-upload__loading').length
|
||||
})
|
||||
`,
|
||||
returnByValue: true,
|
||||
}, { sessionId });
|
||||
const status = JSON.parse(uploadCheck.result.value);
|
||||
console.log(`[wechat-browser] Upload progress: ${status.uploaded}/${targetCount}`);
|
||||
console.log(`[wechat-browser] Upload progress: ${status.uploaded}/${targetCount} (loading: ${status.loading})`);
|
||||
if (status.uploaded >= targetCount) break;
|
||||
}
|
||||
|
||||
|
||||
@@ -459,7 +459,7 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
|
||||
if (charAfter === undefined || !/\\d/.test(charAfter)) {
|
||||
const parentElement = node.parentElement;
|
||||
if (parentElement) {
|
||||
parentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
parentElement.scrollIntoView({ behavior: 'instant', block: 'center' });
|
||||
}
|
||||
|
||||
const range = document.createRange();
|
||||
@@ -500,112 +500,56 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Step A: Copy image to clipboard first (slow due to Swift compilation)
|
||||
console.log(`[weibo-article] Copying image to clipboard: ${path.basename(img.localPath)}`);
|
||||
if (!copyImageToClipboard(img.localPath)) {
|
||||
console.warn(`[weibo-article] Failed to copy image to clipboard`);
|
||||
continue;
|
||||
}
|
||||
await sleep(500);
|
||||
|
||||
// Step B: Select placeholder text (paste will replace the selection)
|
||||
const selected = await selectPlaceholder(3);
|
||||
if (!selected) {
|
||||
console.warn(`[weibo-article] Skipping image - could not select placeholder: ${img.placeholder}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[weibo-article] Copying image: ${path.basename(img.localPath)}`);
|
||||
|
||||
if (!copyImageToClipboard(img.localPath)) {
|
||||
console.warn(`[weibo-article] Failed to copy image to clipboard`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
// Delete placeholder by replacing selection with empty text
|
||||
console.log(`[weibo-article] Deleting placeholder...`);
|
||||
await cdp.send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const sel = window.getSelection();
|
||||
if (!sel || sel.isCollapsed) return false;
|
||||
const range = sel.getRangeAt(0);
|
||||
range.deleteContents();
|
||||
sel.collapseToStart();
|
||||
return true;
|
||||
})()`,
|
||||
returnByValue: true,
|
||||
}, { sessionId });
|
||||
|
||||
await sleep(300);
|
||||
|
||||
// Fallback: send Backspace key if placeholder still exists
|
||||
const stillExists = await cdp!.send<{ result: { value: boolean } }>('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const editor = document.querySelector('div[contenteditable="true"]');
|
||||
if (!editor) return false;
|
||||
const placeholder = ${JSON.stringify(img.placeholder)};
|
||||
const regex = new RegExp(placeholder + '(?!\\\\d)');
|
||||
return regex.test(editor.innerText);
|
||||
})()`,
|
||||
returnByValue: true,
|
||||
}, { sessionId });
|
||||
|
||||
if (stillExists.result.value) {
|
||||
console.log('[weibo-article] Placeholder survived deleteContents, trying Input.insertText replacement...');
|
||||
// Re-select and replace with empty via Input.insertText
|
||||
await cdp!.send('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const editor = document.querySelector('div[contenteditable="true"]');
|
||||
if (!editor) return false;
|
||||
const placeholder = ${JSON.stringify(img.placeholder)};
|
||||
const walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT, null, false);
|
||||
let node;
|
||||
while ((node = walker.nextNode())) {
|
||||
const text = node.textContent || '';
|
||||
let searchStart = 0;
|
||||
let idx;
|
||||
while ((idx = text.indexOf(placeholder, searchStart)) !== -1) {
|
||||
const afterIdx = idx + placeholder.length;
|
||||
const charAfter = text[afterIdx];
|
||||
if (charAfter === undefined || !/\\d/.test(charAfter)) {
|
||||
const range = document.createRange();
|
||||
range.setStart(node, idx);
|
||||
range.setEnd(node, idx + placeholder.length);
|
||||
const sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
return true;
|
||||
}
|
||||
searchStart = afterIdx;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})()`,
|
||||
}, { sessionId });
|
||||
await sleep(200);
|
||||
await cdp!.send('Input.insertText', { text: '' }, { sessionId });
|
||||
await sleep(300);
|
||||
}
|
||||
|
||||
await sleep(200);
|
||||
// Step C: Delete selected placeholder via Backspace (ProseMirror-compatible)
|
||||
console.log(`[weibo-article] Deleting placeholder via Backspace...`);
|
||||
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
|
||||
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
|
||||
await sleep(500);
|
||||
|
||||
// Verify placeholder was deleted
|
||||
const afterDelete = await cdp.send<{ result: { value: boolean } }>('Runtime.evaluate', {
|
||||
const placeholderGone = await cdp.send<{ result: { value: boolean } }>('Runtime.evaluate', {
|
||||
expression: `(() => {
|
||||
const editor = document.querySelector('div[contenteditable="true"]');
|
||||
if (!editor) return true;
|
||||
const text = editor.innerText;
|
||||
const placeholder = ${JSON.stringify(img.placeholder)};
|
||||
const regex = new RegExp(placeholder + '(?!\\\\d)');
|
||||
return !regex.test(text);
|
||||
return !regex.test(editor.innerText);
|
||||
})()`,
|
||||
returnByValue: true,
|
||||
}, { sessionId });
|
||||
|
||||
if (afterDelete.result.value) {
|
||||
if (placeholderGone.result.value) {
|
||||
console.log(`[weibo-article] Placeholder deleted`);
|
||||
} else {
|
||||
console.warn(`[weibo-article] Placeholder may still exist after delete`);
|
||||
console.warn(`[weibo-article] Placeholder may still exist, trying execCommand delete...`);
|
||||
// Re-select and delete via execCommand
|
||||
await selectPlaceholder(1);
|
||||
await cdp.send('Runtime.evaluate', {
|
||||
expression: `document.execCommand('delete')`,
|
||||
}, { sessionId });
|
||||
await sleep(300);
|
||||
}
|
||||
|
||||
// Focus editor for paste
|
||||
// Step D: Focus editor and paste image
|
||||
await cdp.send('Runtime.evaluate', {
|
||||
expression: `document.querySelector('div[contenteditable="true"]')?.focus()`,
|
||||
}, { sessionId });
|
||||
await sleep(300);
|
||||
await sleep(200);
|
||||
|
||||
// Count images before paste
|
||||
const imgCountBefore = await cdp.send<{ result: { value: number } }>('Runtime.evaluate', {
|
||||
@@ -613,7 +557,7 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
|
||||
returnByValue: true,
|
||||
}, { sessionId });
|
||||
|
||||
// Paste image
|
||||
// Paste image at cursor position (where placeholder was)
|
||||
console.log(`[weibo-article] Pasting image...`);
|
||||
if (pasteFromClipboard('Google Chrome', 5, 1000)) {
|
||||
console.log(`[weibo-article] Paste keystroke sent for: ${path.basename(img.localPath)}`);
|
||||
|
||||
@@ -76,7 +76,7 @@ Detailed style definitions: `references/presets/<style>.md`
|
||||
|
||||
## Preset Gallery
|
||||
|
||||
Quick-start presets by content scenario. Use `--preset <name>` or recommend during Step 4.
|
||||
Quick-start presets by content scenario. Use `--preset <name>` or recommend during Step 2.
|
||||
|
||||
**Knowledge & Learning**:
|
||||
|
||||
@@ -237,11 +237,12 @@ XHS Infographic Progress:
|
||||
- [ ] Found → load preferences → continue
|
||||
- [ ] Not found → run first-time setup → MUST complete before Step 1
|
||||
- [ ] Step 1: Analyze content → analysis.md
|
||||
- [ ] Step 2: Confirmation 1 - Content understanding ⚠️ REQUIRED
|
||||
- [ ] Step 3: Generate 3 outline + style variants
|
||||
- [ ] Step 4: Confirmation 2 - Outline & style & elements selection ⚠️ REQUIRED
|
||||
- [ ] Step 5: Generate images (sequential)
|
||||
- [ ] Step 6: Completion report
|
||||
- [ ] Step 2: Smart Confirm ⚠️ REQUIRED
|
||||
- [ ] Path A: Quick confirm → generate recommended outline
|
||||
- [ ] Path B: Customize → adjust then generate outline
|
||||
- [ ] Path C: Detailed → 3 outlines → second confirm → generate outline
|
||||
- [ ] Step 3: Generate images (sequential)
|
||||
- [ ] Step 4: Completion report
|
||||
```
|
||||
|
||||
### Flow
|
||||
@@ -255,7 +256,11 @@ Input → [Step 0: Preferences] ─┬─ Found → Continue
|
||||
│
|
||||
┌───────────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
Analyze → [Confirm 1] → 3 Outlines → [Confirm 2: Outline + Style + Elements] → Generate → Complete
|
||||
Analyze → [Smart Confirm] ─┬─ Quick: confirm recommended → outline.md → Generate → Complete
|
||||
│
|
||||
├─ Customize: adjust options → outline.md → Generate → Complete
|
||||
│
|
||||
└─ Detailed: 3 outlines → [Confirm 2] → outline.md → Generate → Complete
|
||||
```
|
||||
|
||||
### Step 0: Load Preferences (EXTEND.md) ⛔ BLOCKING
|
||||
@@ -323,18 +328,63 @@ Read source content, save it if needed, and perform deep analysis.
|
||||
- Swipe flow design
|
||||
4. Detect source language
|
||||
5. Determine recommended image count (2-10)
|
||||
6. **Generate clarifying questions** (see Step 2)
|
||||
6. **Auto-recommend** best strategy + style + layout based on content signals
|
||||
7. **Save to `analysis.md`**
|
||||
|
||||
### Step 2: Confirmation 1 - Content Understanding ⚠️
|
||||
### Step 2: Smart Confirm ⚠️
|
||||
|
||||
**Purpose**: Validate understanding + collect missing info. **Do NOT skip.**
|
||||
**Purpose**: Present auto-recommended plan, let user confirm or adjust. **Do NOT skip.**
|
||||
|
||||
**Display summary**:
|
||||
- Content type + topic identified
|
||||
- Key points extracted
|
||||
- Tone detected
|
||||
- Source images count
|
||||
**Auto-Recommendation Logic**:
|
||||
1. Use Auto Selection table to match content signals → best strategy + style + layout
|
||||
2. Infer optimal image count from content density
|
||||
3. Load style's default elements from preset
|
||||
|
||||
**Display** (analysis summary + recommended plan):
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📋 内容分析
|
||||
主题:[topic] | 类型:[content_type]
|
||||
要点:[key points summary]
|
||||
受众:[target audience]
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🎨 推荐方案(自动匹配)
|
||||
策略:[A/B/C] [strategy name]([reason])
|
||||
风格:[style] · 布局:[layout] · 预设:[preset]
|
||||
图片:[N]张(封面+[N-2]内容+结尾)
|
||||
元素:[background] / [decorations] / [emphasis]
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
**Use AskUserQuestion** with single question:
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| 1. ✅ 确认,直接生成(推荐) | Trust auto-recommendation, proceed immediately |
|
||||
| 2. 🎛️ 自定义调整 | Modify strategy/style/layout/count in one step |
|
||||
| 3. 📋 详细模式 | Generate 3 outlines, then choose (two confirmations) |
|
||||
|
||||
#### Path A: Quick Confirm (Option 1)
|
||||
|
||||
Generate single outline using recommended strategy + style → save to `outline.md` → Step 3.
|
||||
|
||||
#### Path B: Customize (Option 2)
|
||||
|
||||
**Use AskUserQuestion** with adjustable options (leave blank = keep recommended):
|
||||
|
||||
1. **策略风格**: Current: [strategy + style]. Options: A Story-Driven(warm) | B Information-Dense(notion) | C Visual-First(screen-print). Or specify style directly: cute/fresh/warm/bold/minimal/retro/pop/notion/chalkboard/study-notes/screen-print. Or use preset: knowledge-card / checklist / tutorial / poster / cinematic / etc.
|
||||
2. **布局**: Current: [layout]. Options: sparse | balanced | dense | list | comparison | flow | mindmap | quadrant
|
||||
3. **图片数量**: Current: [N]. Range: 2-10
|
||||
4. **补充说明**(可选): Selling point emphasis, audience adjustment, color preference, etc.
|
||||
|
||||
**After response**: Generate single outline with user's choices → save to `outline.md` → Step 3.
|
||||
|
||||
#### Path C: Detailed Mode (Option 3)
|
||||
|
||||
Full two-confirmation flow for maximum control:
|
||||
|
||||
**Step 2a: Content Understanding**
|
||||
|
||||
**Use AskUserQuestion** for:
|
||||
1. Core selling point (multiSelect: true)
|
||||
@@ -342,13 +392,9 @@ Read source content, save it if needed, and perform deep analysis.
|
||||
3. Style preference: Authentic sharing / Professional review / Aesthetic mood / Auto
|
||||
4. Additional context (optional)
|
||||
|
||||
**After response**: Update `analysis.md` → Step 3
|
||||
**After response**: Update `analysis.md`.
|
||||
|
||||
### Step 3: Generate 3 Outline + Style Variants
|
||||
|
||||
Based on analysis + user context, create three distinct strategy variants. Each variant includes both **outline structure** and **visual style recommendation**.
|
||||
|
||||
**For each strategy**:
|
||||
**Step 2b: Generate 3 Outline Variants**
|
||||
|
||||
| Strategy | Filename | Outline | Recommended Style |
|
||||
|----------|----------|---------|-------------------|
|
||||
@@ -363,7 +409,7 @@ strategy: a # a, b, or c
|
||||
name: Story-Driven
|
||||
style: warm # recommended style for this strategy
|
||||
style_reason: "Warm tones enhance emotional storytelling and personal connection"
|
||||
elements: # from style preset, can be customized in Step 4
|
||||
elements: # from style preset, can be customized
|
||||
background: solid-pastel
|
||||
decorations: [clouds, stars-sparkles]
|
||||
emphasis: star-burst
|
||||
@@ -391,50 +437,22 @@ image_count: 5
|
||||
- Each strategy MUST have different outline structure AND different recommended style
|
||||
- Adapt page count: A typically 4-6, B typically 3-5, C typically 3-4
|
||||
- Include `style_reason` explaining why this style fits the strategy
|
||||
- Consider user's style preference from Step 2
|
||||
|
||||
Reference: `references/workflows/outline-template.md`
|
||||
|
||||
### Step 4: Confirmation 2 - Outline & Style & Elements Selection ⚠️
|
||||
|
||||
**Purpose**: User chooses outline strategy, confirms visual style, and customizes elements. **Do NOT skip.**
|
||||
|
||||
**Display each strategy**:
|
||||
- Strategy name + page count + recommended style
|
||||
- Page-by-page summary (P1 → P2 → P3...)
|
||||
**Step 2c: Outline & Style Selection**
|
||||
|
||||
**Use AskUserQuestion** with three questions:
|
||||
|
||||
**Question 1: Outline Strategy**
|
||||
- Strategy A (Recommended if "authentic sharing")
|
||||
- Strategy B (Recommended if "professional review")
|
||||
- Strategy C (Recommended if "aesthetic mood")
|
||||
- Combine: specify pages from each
|
||||
**Q1: Outline Strategy**: A / B / C / Combine (specify pages from each)
|
||||
|
||||
**Question 2: Visual Style**
|
||||
- Use strategy's recommended style (show which style)
|
||||
- Or use a preset: knowledge-card / checklist / tutorial / poster / cinematic / etc. (see [Style Presets](references/style-presets.md))
|
||||
- Or select from: cute / fresh / warm / bold / minimal / retro / pop / notion / chalkboard / screen-print
|
||||
- Or type custom style description
|
||||
**Q2: Visual Style**: Use recommended | Select preset | Select style | Custom description
|
||||
|
||||
**Question 3: Visual Elements** (show after style selection)
|
||||
Display the selected style's default elements from preset, then ask:
|
||||
- Use style defaults (Recommended) - show preview: background, decorations, emphasis
|
||||
- Adjust background - options: solid-pastel / solid-saturated / gradient-linear / gradient-radial / paper-texture / grid
|
||||
- Adjust decorations - options: hearts / stars-sparkles / flowers / clouds / leaves / confetti
|
||||
- Type custom element preferences
|
||||
**Q3: Visual Elements**: Use defaults (Recommended) | Adjust background | Adjust decorations | Custom
|
||||
|
||||
**After response**:
|
||||
- Single strategy → copy to `outline.md` with confirmed style
|
||||
- Combination → merge specified pages with confirmed style
|
||||
- Custom request → regenerate based on feedback
|
||||
- Style defaults → use preset's Element Combination as-is
|
||||
- Background adjustment → update elements.background with user choice
|
||||
- Decorations adjustment → update elements.decorations with user choice
|
||||
- Custom elements → parse user's preferences into elements fields
|
||||
- Update `outline.md` frontmatter with final style and elements
|
||||
**After response**: Save selected/merged outline to `outline.md` with confirmed style and elements → Step 3.
|
||||
|
||||
### Step 5: Generate Images
|
||||
### Step 3: Generate Images
|
||||
|
||||
With confirmed outline + style + layout:
|
||||
|
||||
@@ -474,12 +492,13 @@ If image generation skill supports `--sessionId`:
|
||||
2. Use same session ID for all images
|
||||
3. Combined with reference image chain, ensures maximum visual consistency
|
||||
|
||||
### Step 6: Completion Report
|
||||
### Step 4: Completion Report
|
||||
|
||||
```
|
||||
Xiaohongshu Infographic Series Complete!
|
||||
|
||||
Topic: [topic]
|
||||
Mode: [Quick / Custom / Detailed]
|
||||
Strategy: [A/B/C/Combined]
|
||||
Style: [style name]
|
||||
Layout: [layout name or "varies"]
|
||||
@@ -487,10 +506,8 @@ Location: [directory path]
|
||||
Images: N total
|
||||
|
||||
✓ analysis.md
|
||||
✓ outline-strategy-a.md
|
||||
✓ outline-strategy-b.md
|
||||
✓ outline-strategy-c.md
|
||||
✓ outline.md (selected: [strategy])
|
||||
✓ outline.md
|
||||
✓ outline-strategy-a/b/c.md (detailed mode only)
|
||||
|
||||
Files:
|
||||
- 01-cover-[slug].png ✓ Cover (sparse)
|
||||
@@ -559,7 +576,7 @@ Detailed templates in `references/` directory:
|
||||
|
||||
- Auto-retry once on failure | Cartoon alternatives for sensitive figures
|
||||
- Use confirmed language preference | Maintain style consistency
|
||||
- **Two confirmation points required** (Steps 2 & 4) - do not skip
|
||||
- **Smart Confirm required** (Step 2) - do not skip; detailed mode uses two sub-confirmations
|
||||
|
||||
## Extension Support
|
||||
|
||||
|
||||
@@ -195,4 +195,4 @@ Before proceeding to outline generation:
|
||||
- [ ] Have I identified save/share triggers?
|
||||
- [ ] Are there clear visual opportunities?
|
||||
- [ ] Is the swipe flow planned?
|
||||
- [ ] Have I selected 3 style+layout combinations?
|
||||
- [ ] Have I identified the best style+layout recommendation?
|
||||
|
||||
Reference in New Issue
Block a user