ソースを参照

feat(format): 一键排版接入 pangu 中英混排空格

在 formatChapterWriting 普通正文句使用 pangu.spacingText,结构行与代码围栏保持不变。

Co-authored-by: Cursor <cursoragent@cursor.com>
darknessomi 1 ヶ月 前
コミット
4fdaf89ded
5 ファイル変更69 行追加3 行削除
  1. 1 0
      .gitignore
  2. 15 2
      package-lock.json
  3. 1 0
      package.json
  4. 49 0
      src/lib/chapter-formatting.test.ts
  5. 3 1
      src/lib/chapter-formatting.ts

+ 1 - 0
.gitignore

@@ -33,6 +33,7 @@ src/test-helpers/*
 **/*.test.ts
 **/*.test.tsx
 !src/lib/chat-copy-content.test.ts
+!src/lib/chapter-formatting.test.ts
 !src/lib/dashboard-issue-actions.test.ts
 !src/lib/tauri-fetch.test.ts
 !src/lib/novel/start-review-run.test.ts

+ 15 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "qmai",
-  "version": "2.2.37",
+  "version": "3.0.2",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "qmai",
-      "version": "2.2.37",
+      "version": "3.0.2",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@base-ui/react": "^1.6.0",
@@ -39,6 +39,7 @@
         "lucide-react": "^1.23.0",
         "mermaid": "^11.16.0",
         "opencc-js": "^1.4.0",
+        "pangu": "^9.0.0",
         "pinyin-pro": "^3.28.1",
         "react": "^19.2.7",
         "react-dom": "^19.2.7",
@@ -8605,6 +8606,18 @@
         "mnemonist": "^0.39.2"
       }
     },
+    "node_modules/pangu": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/pangu/-/pangu-9.0.0.tgz",
+      "integrity": "sha512-VEkgcpBLXV6anBj/ZOgDhdWtmmewNd5OpJHC9L3Aa6WoLondpPF3uYlpZKBJt9qxv0pj23TB4knaQe+ntTZEYg==",
+      "license": "MIT",
+      "bin": {
+        "pangu": "dist/node/cli.js"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      }
+    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",

+ 1 - 0
package.json

@@ -48,6 +48,7 @@
     "lucide-react": "^1.23.0",
     "mermaid": "^11.16.0",
     "opencc-js": "^1.4.0",
+    "pangu": "^9.0.0",
     "pinyin-pro": "^3.28.1",
     "react": "^19.2.7",
     "react-dom": "^19.2.7",

+ 49 - 0
src/lib/chapter-formatting.test.ts

@@ -0,0 +1,49 @@
+import { expect, test } from "vitest"
+import { formatChapterWriting } from "./chapter-formatting"
+
+test("inserts spaces between CJK and Latin on normal paragraphs", () => {
+  const result = formatChapterWriting("中文English中文")
+
+  expect(result).toBe("  中文 English 中文")
+})
+
+test("keeps full-width first-line indent", () => {
+  const result = formatChapterWriting("  纯中文段落")
+
+  expect(result).toBe("  纯中文段落")
+  expect(result.startsWith("  ")).toBe(true)
+})
+
+test("does not alter fenced code content", () => {
+  const markdown = ["```", "中文English中文", "```"].join("\n")
+  const result = formatChapterWriting(markdown)
+
+  expect(result).toBe(markdown)
+})
+
+test("does not alter structural line text (no pangu spacing)", () => {
+  const markdown = ["# 标题English", "- 列表English", "> 引用English"].join("\n")
+  const result = formatChapterWriting(markdown)
+
+  expect(result).toContain("# 标题English")
+  expect(result).toContain("- 列表English")
+  expect(result).toContain("> 引用English")
+  expect(result).not.toContain("标题 English")
+  expect(result).not.toContain("列表 English")
+  expect(result).not.toContain("引用 English")
+})
+
+test("preserves frontmatter and spaces body prose", () => {
+  const markdown = [
+    "---",
+    "title: 第一章",
+    "---",
+    "",
+    "他说Hello World",
+  ].join("\n")
+
+  const result = formatChapterWriting(markdown)
+
+  expect(result.startsWith("---\ntitle: 第一章\n---\n")).toBe(true)
+  expect(result).toContain("  他说 Hello World")
+})

+ 3 - 1
src/lib/chapter-formatting.ts

@@ -1,3 +1,4 @@
+import pangu from "pangu"
 import { parseFrontmatter } from "@/lib/frontmatter"
 
 function isStructuralMarkdownLine(trimmed: string): boolean {
@@ -56,7 +57,8 @@ export function formatChapterWriting(markdown: string): string {
     ) {
       formatted.push("")
     }
-    formatted.push(`  ${trimmed.replace(/^[  ]+/, "")}`)
+    const content = trimmed.replace(/^[  ]+/, "")
+    formatted.push(`  ${pangu.spacingText(content)}`)
     pendingBlank = true
     lastKind = "normal"
   }