Compare commits

...

2 Commits

Author SHA1 Message Date
Jim Liu 宝玉 7c5e81fd15 chore: release v1.25.3 2026-01-28 22:31:47 -06:00
Jim Liu 宝玉 058d25d02c feat(baoyu-format-markdown): add content type detection and CJK punctuation handling 2026-01-28 22:31:25 -06:00
5 changed files with 207 additions and 17 deletions
+1 -1
View File
@@ -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.25.2" "version": "1.25.3"
}, },
"plugins": [ "plugins": [
{ {
+5
View File
@@ -2,6 +2,11 @@
English | [中文](./CHANGELOG.zh.md) English | [中文](./CHANGELOG.zh.md)
## 1.25.3 - 2026-01-28
### Features
- `baoyu-format-markdown`: add content type detection with user confirmation for markdown files; add CJK punctuation handling to move paired punctuation outside emphasis markers.
## 1.25.2 - 2026-01-28 ## 1.25.2 - 2026-01-28
### Documentation ### Documentation
+5
View File
@@ -2,6 +2,11 @@
[English](./CHANGELOG.md) | 中文 [English](./CHANGELOG.md) | 中文
## 1.25.3 - 2026-01-28
### 新功能
- `baoyu-format-markdown`:新增内容类型检测,对已有 markdown 格式的文件提供用户确认选项;新增 CJK 配对标点处理,将括号、引号等标点移出加粗标记外。
## 1.25.2 - 2026-01-28 ## 1.25.2 - 2026-01-28
### 文档 ### 文档
+53
View File
@@ -58,6 +58,55 @@ Claude performs content analysis and formatting (Steps 1-6), then runs the scrip
Read the user-specified markdown or plain text file. Read the user-specified markdown or plain text file.
### Step 1.5: Detect Content Type & Confirm
**Content Type Detection:**
| Indicator | Classification |
|-----------|----------------|
| Has `---` YAML frontmatter | Markdown |
| Has `#`, `##`, `###` headings | Markdown |
| Has `**bold**`, `*italic*` | Markdown |
| Has `- ` or `1. ` lists | Markdown |
| Has ``` code blocks | Markdown |
| Has `> ` blockquotes | Markdown |
| None of above | Plain text |
**Decision Flow:**
┌─────────────────┬────────────────────────────────────────────────┐
│ Content Type │ Action │
├─────────────────┼────────────────────────────────────────────────┤
│ Plain text │ Proceed to Step 2 (format to markdown) │
├─────────────────┼────────────────────────────────────────────────┤
│ Markdown │ Use AskUserQuestion to confirm optimization │
└─────────────────┴────────────────────────────────────────────────┘
**If Markdown detected, ask user:**
```
Detected existing markdown formatting. What would you like to do?
1. Optimize formatting (Recommended)
- Add/improve frontmatter, headings, bold, lists
- Run typography script (spacing, emphasis fixes)
- Output: {filename}-formatted.md
2. Keep original formatting
- Preserve existing markdown structure
- Run typography script (spacing, emphasis fixes)
- Output: {filename}-formatted.md
3. Typography fixes only
- Run typography script on original file in-place
- No copy created, modifies original file directly
```
**Based on user choice:**
- **Optimize**: Continue to Step 2-8 (full workflow)
- **Keep original**: Skip Steps 2-5, copy file → Step 6-8 (run script on copy)
- **Typography only**: Skip Steps 2-6, run Step 7 on original file directly
### Step 2: Analyze Content Structure ### Step 2: Analyze Content Structure
Identify: Identify:
@@ -138,6 +187,10 @@ Examples:
- `final.md``final-formatted.md` - `final.md``final-formatted.md`
- `draft-v1.md``draft-v1-formatted.md` - `draft-v1.md``draft-v1-formatted.md`
**If user chose "Keep original formatting" (from Step 1.5):**
- Copy original file to `{filename}-formatted.md` without modifications
- Proceed to Step 7 for typography fixes only
**Backup existing file:** **Backup existing file:**
If `{filename}-formatted.md` already exists, backup before overwriting: If `{filename}-formatted.md` already exists, backup before overwriting:
@@ -11,6 +11,21 @@ export const CJK_CHAR_RE = new RegExp(`[${CJK_SCRIPTS}]`, "u");
const PUNCT_OR_SYMBOL_RE = /[\p{P}\p{S}]/u; const PUNCT_OR_SYMBOL_RE = /[\p{P}\p{S}]/u;
const WORD_CHAR_RE = /[\p{L}\p{N}]/u; const WORD_CHAR_RE = /[\p{L}\p{N}]/u;
const CJK_PUNCT_PAIRS: Record<string, string> = {
"“": "”",
"": "",
"": "",
"": "",
"〖": "〗",
"〘": "〙",
"〚": "〛",
"「": "」",
"『": "』",
"〈": "〉",
"《": "》",
"【": "】",
};
function findInlineCodeRanges(text: string): Array<[number, number]> { function findInlineCodeRanges(text: string): Array<[number, number]> {
const ranges: Array<[number, number]> = []; const ranges: Array<[number, number]> = [];
let i = 0; let i = 0;
@@ -69,8 +84,120 @@ function isPunctuationOrSymbol(ch: string | undefined): boolean {
return !!ch && PUNCT_OR_SYMBOL_RE.test(ch); return !!ch && PUNCT_OR_SYMBOL_RE.test(ch);
} }
function fixCjkEmphasisSpacingInBlock(block: string): string { function isMatchingCjkPunct(open: string, close: string): boolean {
return CJK_PUNCT_PAIRS[open] === close;
}
function mapNonCodeSegments(
block: string,
mapper: (segment: string) => string
): string {
const codeRanges = findInlineCodeRanges(block); const codeRanges = findInlineCodeRanges(block);
if (codeRanges.length === 0) {
return mapper(block);
}
let result = "";
let lastIndex = 0;
for (const [start, end] of codeRanges) {
if (start > lastIndex) {
result += mapper(block.slice(lastIndex, start));
}
result += block.slice(start, end + 1);
lastIndex = end + 1;
}
if (lastIndex < block.length) {
result += mapper(block.slice(lastIndex));
}
return result;
}
function moveCjkPunctuationOutsideEmphasis(block: string): string {
return mapNonCodeSegments(block, (segment) => {
const delimiterPositions: number[] = [];
let cursor = 0;
while (cursor < segment.length - 1) {
if (
segment[cursor] === "*" &&
segment[cursor + 1] === "*" &&
segment[cursor - 1] !== "*" &&
segment[cursor + 2] !== "*" &&
!isEscaped(segment, cursor)
) {
delimiterPositions.push(cursor);
cursor += 2;
continue;
}
cursor += 1;
}
if (delimiterPositions.length < 2) return segment;
const stack: number[] = [];
const pairs: Array<{ open: number; close: number }> = [];
for (const pos of delimiterPositions) {
if (stack.length === 0) {
stack.push(pos);
} else {
const open = stack.pop() as number;
pairs.push({ open, close: pos });
}
}
const skip = new Set<number>();
const insertBefore = new Map<number, string>();
for (const pair of pairs) {
const openPunctPos = pair.open + 2;
const closePunctPos = pair.close - 1;
if (openPunctPos >= closePunctPos) continue;
const openPunct = segment[openPunctPos];
const closePunct = segment[closePunctPos];
if (!openPunct || !closePunct) continue;
if (!CJK_OPENING_PUNCT_RE.test(openPunct)) continue;
if (!CJK_CLOSING_PUNCT_RE.test(closePunct)) continue;
if (!isMatchingCjkPunct(openPunct, closePunct)) continue;
if (openPunctPos + 1 >= closePunctPos) continue;
const inner = segment.slice(openPunctPos + 1, closePunctPos);
if (inner.length === 0) continue;
skip.add(openPunctPos);
skip.add(closePunctPos);
insertBefore.set(pair.open, (insertBefore.get(pair.open) ?? "") + openPunct);
const afterClose = pair.close + 2;
insertBefore.set(
afterClose,
(insertBefore.get(afterClose) ?? "") + closePunct
);
}
if (skip.size === 0) return segment;
let result = "";
for (let idx = 0; idx < segment.length; idx += 1) {
const insert = insertBefore.get(idx);
if (insert) {
result += insert;
}
if (skip.has(idx)) {
continue;
}
result += segment[idx];
}
const tailInsert = insertBefore.get(segment.length);
if (tailInsert) {
result += tailInsert;
}
return result;
});
}
function fixCjkEmphasisSpacingInBlock(block: string): string {
const normalized = moveCjkPunctuationOutsideEmphasis(block);
const codeRanges = findInlineCodeRanges(normalized);
let rangeIndex = 0; let rangeIndex = 0;
const delimiters: Array<{ const delimiters: Array<{
@@ -79,7 +206,7 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
canClose: boolean; canClose: boolean;
}> = []; }> = [];
let cursor = 0; let cursor = 0;
while (cursor < block.length - 1) { while (cursor < normalized.length - 1) {
if (rangeIndex < codeRanges.length && cursor >= codeRanges[rangeIndex][0]) { if (rangeIndex < codeRanges.length && cursor >= codeRanges[rangeIndex][0]) {
if (cursor <= codeRanges[rangeIndex][1]) { if (cursor <= codeRanges[rangeIndex][1]) {
cursor = codeRanges[rangeIndex][1] + 1; cursor = codeRanges[rangeIndex][1] + 1;
@@ -90,14 +217,14 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
} }
if ( if (
block[cursor] === "*" && normalized[cursor] === "*" &&
block[cursor + 1] === "*" && normalized[cursor + 1] === "*" &&
block[cursor - 1] !== "*" && normalized[cursor - 1] !== "*" &&
block[cursor + 2] !== "*" && normalized[cursor + 2] !== "*" &&
!isEscaped(block, cursor) !isEscaped(normalized, cursor)
) { ) {
const before = block[cursor - 1]; const before = normalized[cursor - 1];
const after = block[cursor + 2]; const after = normalized[cursor + 2];
const beforeIsSpace = isWhitespaceChar(before); const beforeIsSpace = isWhitespaceChar(before);
const afterIsSpace = isWhitespaceChar(after); const afterIsSpace = isWhitespaceChar(after);
const beforeIsPunct = isPunctuationOrSymbol(before); const beforeIsPunct = isPunctuationOrSymbol(before);
@@ -140,12 +267,12 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
} }
} }
if (pairs.length === 0) return block; if (pairs.length === 0) return normalized;
const insertPositions = new Set<number>(); const insertPositions = new Set<number>();
for (const pair of pairs) { for (const pair of pairs) {
const insideLast = block[pair.close - 1]; const insideLast = normalized[pair.close - 1];
const afterClose = block[pair.close + 2]; const afterClose = normalized[pair.close + 2];
if (!afterClose) continue; if (!afterClose) continue;
if ( if (
CJK_CLOSING_PUNCT_RE.test(insideLast) && CJK_CLOSING_PUNCT_RE.test(insideLast) &&
@@ -155,16 +282,16 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
} }
} }
if (insertPositions.size === 0) return block; if (insertPositions.size === 0) return normalized;
let result = ""; let result = "";
for (let idx = 0; idx < block.length; idx += 1) { for (let idx = 0; idx < normalized.length; idx += 1) {
if (insertPositions.has(idx)) { if (insertPositions.has(idx)) {
result += " "; result += " ";
} }
result += block[idx]; result += normalized[idx];
} }
if (insertPositions.has(block.length)) { if (insertPositions.has(normalized.length)) {
result += " "; result += " ";
} }
return result; return result;