docrev 0.8.1 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/PLAN-tables-and-postprocess.md +850 -0
  3. package/README.md +33 -0
  4. package/bin/rev.js +12 -131
  5. package/bin/rev.ts +145 -0
  6. package/dist/bin/rev.d.ts +9 -0
  7. package/dist/bin/rev.d.ts.map +1 -0
  8. package/dist/bin/rev.js +118 -0
  9. package/dist/bin/rev.js.map +1 -0
  10. package/dist/lib/annotations.d.ts +91 -0
  11. package/dist/lib/annotations.d.ts.map +1 -0
  12. package/dist/lib/annotations.js +554 -0
  13. package/dist/lib/annotations.js.map +1 -0
  14. package/dist/lib/build.d.ts +171 -0
  15. package/dist/lib/build.d.ts.map +1 -0
  16. package/dist/lib/build.js +755 -0
  17. package/dist/lib/build.js.map +1 -0
  18. package/dist/lib/citations.d.ts +34 -0
  19. package/dist/lib/citations.d.ts.map +1 -0
  20. package/dist/lib/citations.js +140 -0
  21. package/dist/lib/citations.js.map +1 -0
  22. package/dist/lib/commands/build.d.ts +13 -0
  23. package/dist/lib/commands/build.d.ts.map +1 -0
  24. package/dist/lib/commands/build.js +678 -0
  25. package/dist/lib/commands/build.js.map +1 -0
  26. package/dist/lib/commands/citations.d.ts +11 -0
  27. package/dist/lib/commands/citations.d.ts.map +1 -0
  28. package/dist/lib/commands/citations.js +428 -0
  29. package/dist/lib/commands/citations.js.map +1 -0
  30. package/dist/lib/commands/comments.d.ts +11 -0
  31. package/dist/lib/commands/comments.d.ts.map +1 -0
  32. package/dist/lib/commands/comments.js +883 -0
  33. package/dist/lib/commands/comments.js.map +1 -0
  34. package/dist/lib/commands/context.d.ts +35 -0
  35. package/dist/lib/commands/context.d.ts.map +1 -0
  36. package/dist/lib/commands/context.js +59 -0
  37. package/dist/lib/commands/context.js.map +1 -0
  38. package/dist/lib/commands/core.d.ts +11 -0
  39. package/dist/lib/commands/core.d.ts.map +1 -0
  40. package/dist/lib/commands/core.js +246 -0
  41. package/dist/lib/commands/core.js.map +1 -0
  42. package/dist/lib/commands/doi.d.ts +11 -0
  43. package/dist/lib/commands/doi.d.ts.map +1 -0
  44. package/dist/lib/commands/doi.js +373 -0
  45. package/dist/lib/commands/doi.js.map +1 -0
  46. package/dist/lib/commands/history.d.ts +11 -0
  47. package/dist/lib/commands/history.d.ts.map +1 -0
  48. package/dist/lib/commands/history.js +245 -0
  49. package/dist/lib/commands/history.js.map +1 -0
  50. package/dist/lib/commands/index.d.ts +28 -0
  51. package/dist/lib/commands/index.d.ts.map +1 -0
  52. package/dist/lib/commands/index.js +35 -0
  53. package/dist/lib/commands/index.js.map +1 -0
  54. package/dist/lib/commands/init.d.ts +11 -0
  55. package/dist/lib/commands/init.d.ts.map +1 -0
  56. package/dist/lib/commands/init.js +209 -0
  57. package/dist/lib/commands/init.js.map +1 -0
  58. package/dist/lib/commands/response.d.ts +11 -0
  59. package/dist/lib/commands/response.d.ts.map +1 -0
  60. package/dist/lib/commands/response.js +317 -0
  61. package/dist/lib/commands/response.js.map +1 -0
  62. package/dist/lib/commands/sections.d.ts +11 -0
  63. package/dist/lib/commands/sections.d.ts.map +1 -0
  64. package/dist/lib/commands/sections.js +1071 -0
  65. package/dist/lib/commands/sections.js.map +1 -0
  66. package/dist/lib/commands/utilities.d.ts +19 -0
  67. package/dist/lib/commands/utilities.d.ts.map +1 -0
  68. package/dist/lib/commands/utilities.js +2009 -0
  69. package/dist/lib/commands/utilities.js.map +1 -0
  70. package/dist/lib/comment-realign.d.ts +50 -0
  71. package/dist/lib/comment-realign.d.ts.map +1 -0
  72. package/dist/lib/comment-realign.js +372 -0
  73. package/dist/lib/comment-realign.js.map +1 -0
  74. package/dist/lib/config.d.ts +41 -0
  75. package/dist/lib/config.d.ts.map +1 -0
  76. package/dist/lib/config.js +76 -0
  77. package/dist/lib/config.js.map +1 -0
  78. package/dist/lib/crossref.d.ts +108 -0
  79. package/dist/lib/crossref.d.ts.map +1 -0
  80. package/dist/lib/crossref.js +597 -0
  81. package/dist/lib/crossref.js.map +1 -0
  82. package/dist/lib/dependencies.d.ts +30 -0
  83. package/dist/lib/dependencies.d.ts.map +1 -0
  84. package/dist/lib/dependencies.js +95 -0
  85. package/dist/lib/dependencies.js.map +1 -0
  86. package/dist/lib/doi-cache.d.ts +29 -0
  87. package/dist/lib/doi-cache.d.ts.map +1 -0
  88. package/dist/lib/doi-cache.js +104 -0
  89. package/dist/lib/doi-cache.js.map +1 -0
  90. package/dist/lib/doi.d.ts +65 -0
  91. package/dist/lib/doi.d.ts.map +1 -0
  92. package/dist/lib/doi.js +710 -0
  93. package/dist/lib/doi.js.map +1 -0
  94. package/dist/lib/equations.d.ts +61 -0
  95. package/dist/lib/equations.d.ts.map +1 -0
  96. package/dist/lib/equations.js +445 -0
  97. package/dist/lib/equations.js.map +1 -0
  98. package/dist/lib/errors.d.ts +60 -0
  99. package/dist/lib/errors.d.ts.map +1 -0
  100. package/dist/lib/errors.js +303 -0
  101. package/dist/lib/errors.js.map +1 -0
  102. package/dist/lib/format.d.ts +104 -0
  103. package/dist/lib/format.d.ts.map +1 -0
  104. package/dist/lib/format.js +416 -0
  105. package/dist/lib/format.js.map +1 -0
  106. package/dist/lib/git.d.ts +88 -0
  107. package/dist/lib/git.d.ts.map +1 -0
  108. package/dist/lib/git.js +304 -0
  109. package/dist/lib/git.js.map +1 -0
  110. package/dist/lib/grammar.d.ts +62 -0
  111. package/dist/lib/grammar.d.ts.map +1 -0
  112. package/dist/lib/grammar.js +244 -0
  113. package/dist/lib/grammar.js.map +1 -0
  114. package/dist/lib/image-registry.d.ts +68 -0
  115. package/dist/lib/image-registry.d.ts.map +1 -0
  116. package/dist/lib/image-registry.js +112 -0
  117. package/dist/lib/image-registry.js.map +1 -0
  118. package/dist/lib/import.d.ts +184 -0
  119. package/dist/lib/import.d.ts.map +1 -0
  120. package/dist/lib/import.js +1581 -0
  121. package/dist/lib/import.js.map +1 -0
  122. package/dist/lib/journals.d.ts +55 -0
  123. package/dist/lib/journals.d.ts.map +1 -0
  124. package/dist/lib/journals.js +417 -0
  125. package/dist/lib/journals.js.map +1 -0
  126. package/dist/lib/merge.d.ts +138 -0
  127. package/dist/lib/merge.d.ts.map +1 -0
  128. package/dist/lib/merge.js +603 -0
  129. package/dist/lib/merge.js.map +1 -0
  130. package/dist/lib/orcid.d.ts +36 -0
  131. package/dist/lib/orcid.d.ts.map +1 -0
  132. package/dist/lib/orcid.js +117 -0
  133. package/dist/lib/orcid.js.map +1 -0
  134. package/dist/lib/pdf-comments.d.ts +95 -0
  135. package/dist/lib/pdf-comments.d.ts.map +1 -0
  136. package/dist/lib/pdf-comments.js +192 -0
  137. package/dist/lib/pdf-comments.js.map +1 -0
  138. package/dist/lib/pdf-import.d.ts +118 -0
  139. package/dist/lib/pdf-import.d.ts.map +1 -0
  140. package/dist/lib/pdf-import.js +397 -0
  141. package/dist/lib/pdf-import.js.map +1 -0
  142. package/dist/lib/plugins.d.ts +76 -0
  143. package/dist/lib/plugins.d.ts.map +1 -0
  144. package/dist/lib/plugins.js +235 -0
  145. package/dist/lib/plugins.js.map +1 -0
  146. package/dist/lib/postprocess.d.ts +42 -0
  147. package/dist/lib/postprocess.d.ts.map +1 -0
  148. package/dist/lib/postprocess.js +138 -0
  149. package/dist/lib/postprocess.js.map +1 -0
  150. package/dist/lib/pptx-template.d.ts +59 -0
  151. package/dist/lib/pptx-template.d.ts.map +1 -0
  152. package/dist/lib/pptx-template.js +613 -0
  153. package/dist/lib/pptx-template.js.map +1 -0
  154. package/dist/lib/pptx-themes.d.ts +80 -0
  155. package/dist/lib/pptx-themes.d.ts.map +1 -0
  156. package/dist/lib/pptx-themes.js +818 -0
  157. package/dist/lib/pptx-themes.js.map +1 -0
  158. package/dist/lib/protect-restore.d.ts +137 -0
  159. package/dist/lib/protect-restore.d.ts.map +1 -0
  160. package/dist/lib/protect-restore.js +394 -0
  161. package/dist/lib/protect-restore.js.map +1 -0
  162. package/dist/lib/rate-limiter.d.ts +27 -0
  163. package/dist/lib/rate-limiter.d.ts.map +1 -0
  164. package/dist/lib/rate-limiter.js +79 -0
  165. package/dist/lib/rate-limiter.js.map +1 -0
  166. package/dist/lib/response.d.ts +41 -0
  167. package/dist/lib/response.d.ts.map +1 -0
  168. package/dist/lib/response.js +150 -0
  169. package/dist/lib/response.js.map +1 -0
  170. package/dist/lib/review.d.ts +35 -0
  171. package/dist/lib/review.d.ts.map +1 -0
  172. package/dist/lib/review.js +263 -0
  173. package/dist/lib/review.js.map +1 -0
  174. package/dist/lib/schema.d.ts +66 -0
  175. package/dist/lib/schema.d.ts.map +1 -0
  176. package/dist/lib/schema.js +339 -0
  177. package/dist/lib/schema.js.map +1 -0
  178. package/dist/lib/scientific-words.d.ts +6 -0
  179. package/dist/lib/scientific-words.d.ts.map +1 -0
  180. package/dist/lib/scientific-words.js +66 -0
  181. package/dist/lib/scientific-words.js.map +1 -0
  182. package/dist/lib/sections.d.ts +40 -0
  183. package/dist/lib/sections.d.ts.map +1 -0
  184. package/dist/lib/sections.js +288 -0
  185. package/dist/lib/sections.js.map +1 -0
  186. package/dist/lib/slides.d.ts +86 -0
  187. package/dist/lib/slides.d.ts.map +1 -0
  188. package/dist/lib/slides.js +676 -0
  189. package/dist/lib/slides.js.map +1 -0
  190. package/dist/lib/spelling.d.ts +76 -0
  191. package/dist/lib/spelling.d.ts.map +1 -0
  192. package/dist/lib/spelling.js +272 -0
  193. package/dist/lib/spelling.js.map +1 -0
  194. package/dist/lib/templates.d.ts +30 -0
  195. package/dist/lib/templates.d.ts.map +1 -0
  196. package/dist/lib/templates.js +504 -0
  197. package/dist/lib/templates.js.map +1 -0
  198. package/dist/lib/themes.d.ts +85 -0
  199. package/dist/lib/themes.d.ts.map +1 -0
  200. package/dist/lib/themes.js +652 -0
  201. package/dist/lib/themes.js.map +1 -0
  202. package/dist/lib/trackchanges.d.ts +51 -0
  203. package/dist/lib/trackchanges.d.ts.map +1 -0
  204. package/dist/lib/trackchanges.js +202 -0
  205. package/dist/lib/trackchanges.js.map +1 -0
  206. package/dist/lib/tui.d.ts +76 -0
  207. package/dist/lib/tui.d.ts.map +1 -0
  208. package/dist/lib/tui.js +377 -0
  209. package/dist/lib/tui.js.map +1 -0
  210. package/dist/lib/types.d.ts +447 -0
  211. package/dist/lib/types.d.ts.map +1 -0
  212. package/dist/lib/types.js +6 -0
  213. package/dist/lib/types.js.map +1 -0
  214. package/dist/lib/undo.d.ts +57 -0
  215. package/dist/lib/undo.d.ts.map +1 -0
  216. package/dist/lib/undo.js +185 -0
  217. package/dist/lib/undo.js.map +1 -0
  218. package/dist/lib/utils.d.ts +16 -0
  219. package/dist/lib/utils.d.ts.map +1 -0
  220. package/dist/lib/utils.js +40 -0
  221. package/dist/lib/utils.js.map +1 -0
  222. package/dist/lib/variables.d.ts +42 -0
  223. package/dist/lib/variables.d.ts.map +1 -0
  224. package/dist/lib/variables.js +141 -0
  225. package/dist/lib/variables.js.map +1 -0
  226. package/dist/lib/word.d.ts +80 -0
  227. package/dist/lib/word.d.ts.map +1 -0
  228. package/dist/lib/word.js +360 -0
  229. package/dist/lib/word.js.map +1 -0
  230. package/dist/lib/wordcomments.d.ts +51 -0
  231. package/dist/lib/wordcomments.d.ts.map +1 -0
  232. package/dist/lib/wordcomments.js +587 -0
  233. package/dist/lib/wordcomments.js.map +1 -0
  234. package/eslint.config.js +27 -0
  235. package/lib/annotations.ts +622 -0
  236. package/lib/apply-buildup-colors.py +88 -0
  237. package/lib/build.ts +1013 -0
  238. package/lib/{citations.js → citations.ts} +38 -27
  239. package/lib/commands/{build.js → build.ts} +80 -27
  240. package/lib/commands/{citations.js → citations.ts} +36 -18
  241. package/lib/commands/{comments.js → comments.ts} +187 -54
  242. package/lib/commands/{context.js → context.ts} +18 -8
  243. package/lib/commands/{core.js → core.ts} +34 -20
  244. package/lib/commands/{doi.js → doi.ts} +32 -16
  245. package/lib/commands/{history.js → history.ts} +25 -12
  246. package/lib/commands/{index.js → index.ts} +9 -5
  247. package/lib/commands/{init.js → init.ts} +20 -8
  248. package/lib/commands/{response.js → response.ts} +47 -20
  249. package/lib/commands/{sections.js → sections.ts} +273 -68
  250. package/lib/commands/{utilities.js → utilities.ts} +338 -158
  251. package/lib/{comment-realign.js → comment-realign.ts} +117 -45
  252. package/lib/config.ts +84 -0
  253. package/lib/{crossref.js → crossref.ts} +213 -138
  254. package/lib/dependencies.ts +106 -0
  255. package/lib/doi-cache.ts +115 -0
  256. package/lib/{doi.js → doi.ts} +115 -281
  257. package/lib/{equations.js → equations.ts} +60 -64
  258. package/lib/{errors.js → errors.ts} +56 -48
  259. package/lib/{format.js → format.ts} +137 -63
  260. package/lib/{git.js → git.ts} +66 -63
  261. package/lib/{grammar.js → grammar.ts} +45 -32
  262. package/lib/image-registry.ts +180 -0
  263. package/lib/import.ts +2060 -0
  264. package/lib/journals.ts +505 -0
  265. package/lib/{merge.js → merge.ts} +185 -135
  266. package/lib/{orcid.js → orcid.ts} +17 -22
  267. package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
  268. package/lib/{pdf-import.js → pdf-import.ts} +148 -70
  269. package/lib/{plugins.js → plugins.ts} +82 -39
  270. package/lib/postprocess.ts +188 -0
  271. package/lib/pptx-color-filter.lua +37 -0
  272. package/lib/pptx-template.ts +625 -0
  273. package/lib/pptx-themes/academic.pptx +0 -0
  274. package/lib/pptx-themes/corporate.pptx +0 -0
  275. package/lib/pptx-themes/dark.pptx +0 -0
  276. package/lib/pptx-themes/default.pptx +0 -0
  277. package/lib/pptx-themes/minimal.pptx +0 -0
  278. package/lib/pptx-themes/plant.pptx +0 -0
  279. package/lib/pptx-themes.ts +896 -0
  280. package/lib/protect-restore.ts +516 -0
  281. package/lib/rate-limiter.ts +94 -0
  282. package/lib/{response.js → response.ts} +36 -21
  283. package/lib/{review.js → review.ts} +53 -43
  284. package/lib/{schema.js → schema.ts} +70 -25
  285. package/lib/{sections.js → sections.ts} +71 -76
  286. package/lib/slides.ts +793 -0
  287. package/lib/{spelling.js → spelling.ts} +43 -59
  288. package/lib/{templates.js → templates.ts} +20 -17
  289. package/lib/themes.ts +742 -0
  290. package/lib/{trackchanges.js → trackchanges.ts} +52 -23
  291. package/lib/types.ts +509 -0
  292. package/lib/{undo.js → undo.ts} +75 -52
  293. package/lib/utils.ts +41 -0
  294. package/lib/{variables.js → variables.ts} +60 -54
  295. package/lib/word.ts +428 -0
  296. package/lib/{wordcomments.js → wordcomments.ts} +94 -40
  297. package/package.json +15 -5
  298. package/skill/REFERENCE.md +67 -0
  299. package/tsconfig.json +26 -0
  300. package/lib/annotations.js +0 -414
  301. package/lib/build.js +0 -639
  302. package/lib/config.js +0 -79
  303. package/lib/import.js +0 -1145
  304. package/lib/journals.js +0 -629
  305. package/lib/word.js +0 -225
  306. /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
@@ -1,414 +0,0 @@
1
- /**
2
- * CriticMarkup annotation parsing and manipulation
3
- *
4
- * Syntax:
5
- * {++inserted text++} - Insertions
6
- * {--deleted text--} - Deletions
7
- * {~~old~>new~~} - Substitutions
8
- * {>>Author: comment<<} - Comments
9
- * {==text==} - Highlights
10
- */
11
-
12
- // Patterns for each annotation type
13
- const PATTERNS = {
14
- insert: /\{\+\+(.+?)\+\+\}/gs,
15
- delete: /\{--(.+?)--\}/gs,
16
- substitute: /\{~~(.+?)~>(.+?)~~\}/gs,
17
- comment: /\{>>(.+?)<<\}/gs,
18
- highlight: /\{==(.+?)==\}/gs,
19
- };
20
-
21
- /**
22
- * Check if a potential comment is actually a false positive
23
- * (e.g., figure caption, nested inside other annotation, code block, etc.)
24
- * @param {string} commentContent - The content inside {>>...<<}
25
- * @param {string} fullText - The full document text
26
- * @param {number} position - Position of the comment in the text
27
- * @returns {boolean} true if this is a false positive (not a real comment)
28
- */
29
- function isCommentFalsePositive(commentContent, fullText, position) {
30
- // Check if inside a code block (fenced or indented)
31
- const textBefore = fullText.slice(Math.max(0, position - 2000), position);
32
- const textAfter = fullText.slice(position, Math.min(fullText.length, position + 2000));
33
-
34
- // Count unclosed fenced code blocks (``` or ~~~)
35
- const fenceOpens = (textBefore.match(/^```|^~~~/gm) || []).length;
36
- const fenceCloses = (textBefore.match(/```$|~~~$/gm) || []).length;
37
- if (fenceOpens > fenceCloses) return true; // Inside code block
38
-
39
- // Check if on an indented line (4+ spaces or tab at line start = code)
40
- const lineStart = textBefore.lastIndexOf('\n') + 1;
41
- const linePrefix = fullText.slice(lineStart, position);
42
- if (/^(\t| )/.test(linePrefix)) return true; // Indented code
43
-
44
- // Check if inside inline code backticks
45
- const backticksBefore = (linePrefix.match(/`/g) || []).length;
46
- if (backticksBefore % 2 === 1) return true; // Inside inline code
47
-
48
- // Check if nested inside a deletion or insertion block
49
- const nearTextBefore = fullText.slice(Math.max(0, position - 500), position);
50
-
51
- // Count unclosed deletion markers
52
- const delOpens = (nearTextBefore.match(/\{--/g) || []).length;
53
- const delCloses = (nearTextBefore.match(/--\}/g) || []).length;
54
- if (delOpens > delCloses) return true; // Nested inside deletion
55
-
56
- // Count unclosed insertion markers
57
- const insOpens = (nearTextBefore.match(/\{\+\+/g) || []).length;
58
- const insCloses = (nearTextBefore.match(/\+\+\}/g) || []).length;
59
- if (insOpens > insCloses) return true; // Nested inside insertion
60
-
61
- // Heuristics for figure captions and other false positives:
62
-
63
- // Contains image/figure path patterns
64
- if (/\(figures?\/|\(images?\/|\.png|\.jpg|\.jpeg|\.gif|\.svg|\.pdf/i.test(commentContent)) return true;
65
-
66
- // Contains markdown figure reference syntax
67
- if (/\{#fig:|!\[/.test(commentContent)) return true;
68
-
69
- // Contains URL patterns (likely a link, not a comment)
70
- if (/https?:\/\/|www\./i.test(commentContent) && commentContent.length < 150) return true;
71
-
72
- // Looks like code (contains programming patterns)
73
- if (/function\s*\(|=>|import\s+|export\s+|const\s+|let\s+|var\s+/.test(commentContent)) return true;
74
-
75
- // Very long without clear author pattern (likely caption, not comment)
76
- // Real comments typically have "Author:" at start and are shorter
77
- const hasAuthorPrefix = /^[A-Za-z][A-Za-z\s]{0,20}:\s/.test(commentContent.trim());
78
- const hasResolvedMark = /^[✓✔]\s/.test(commentContent.trim());
79
- if (!hasAuthorPrefix && !hasResolvedMark && commentContent.length > 200) return true;
80
-
81
- // Looks like a figure caption (starts with "Fig" or contains typical caption words)
82
- if (/^(Fig\.?|Figure|Table|Sankey|Diagram|Proportion|Distribution|Map|Chart|Graph|Plot|Panel)/i.test(commentContent.trim())) {
83
- return true;
84
- }
85
-
86
- // Contains LaTeX-like patterns (likely equation, not comment)
87
- if (/\\[a-z]+\{|\\frac|\\sum|\\int|\\begin\{/.test(commentContent)) return true;
88
-
89
- // Looks like BibTeX entry (not a comment)
90
- if (/@article\{|@book\{|@inproceedings\{/i.test(commentContent)) return true;
91
-
92
- return false;
93
- }
94
-
95
- // Combined pattern for any track change (not comments)
96
- const TRACK_CHANGE_PATTERN = /(\{\+\+.+?\+\+\}|\{--.+?--\}|\{~~.+?~>.+?~~\})/gs;
97
-
98
- /**
99
- * Parse all annotations from text
100
- * @param {string} text
101
- * @returns {Array<{type: string, match: string, content: string, replacement?: string, author?: string, position: number, line: number}>}
102
- */
103
- export function parseAnnotations(text) {
104
- const annotations = [];
105
-
106
- // Build line number lookup
107
- const lines = text.split('\n');
108
- let pos = 0;
109
- const lineStarts = lines.map((line) => {
110
- const start = pos;
111
- pos += line.length + 1;
112
- return start;
113
- });
114
-
115
- function getLine(position) {
116
- for (let i = 0; i < lineStarts.length; i++) {
117
- if (lineStarts[i] > position) return i;
118
- }
119
- return lineStarts.length;
120
- }
121
-
122
- function getContext(position, length) {
123
- const start = Math.max(0, position - 50);
124
- const end = Math.min(text.length, position + length + 50);
125
- const before = text.slice(start, position).split('\n').pop() || '';
126
- const after = text.slice(position + length, end).split('\n')[0] || '';
127
- return { before, after };
128
- }
129
-
130
- // Parse insertions
131
- for (const match of text.matchAll(PATTERNS.insert)) {
132
- const ctx = getContext(match.index, match[0].length);
133
- annotations.push({
134
- type: 'insert',
135
- match: match[0],
136
- content: match[1],
137
- position: match.index,
138
- line: getLine(match.index),
139
- ...ctx,
140
- });
141
- }
142
-
143
- // Parse deletions
144
- for (const match of text.matchAll(PATTERNS.delete)) {
145
- const ctx = getContext(match.index, match[0].length);
146
- annotations.push({
147
- type: 'delete',
148
- match: match[0],
149
- content: match[1],
150
- position: match.index,
151
- line: getLine(match.index),
152
- ...ctx,
153
- });
154
- }
155
-
156
- // Parse substitutions
157
- for (const match of text.matchAll(PATTERNS.substitute)) {
158
- const ctx = getContext(match.index, match[0].length);
159
- annotations.push({
160
- type: 'substitute',
161
- match: match[0],
162
- content: match[1],
163
- replacement: match[2],
164
- position: match.index,
165
- line: getLine(match.index),
166
- ...ctx,
167
- });
168
- }
169
-
170
- // Parse comments (with false positive filtering)
171
- for (const match of text.matchAll(PATTERNS.comment)) {
172
- // Skip false positives (figure captions, nested annotations, etc.)
173
- if (isCommentFalsePositive(match[1], text, match.index)) {
174
- continue;
175
- }
176
-
177
- const ctx = getContext(match.index, match[0].length);
178
- let commentText = match[1];
179
- let author = '';
180
-
181
- // Extract author if present (format: "Author: comment")
182
- const colonIdx = commentText.indexOf(':');
183
- if (colonIdx > 0 && colonIdx < 30) {
184
- author = commentText.slice(0, colonIdx).trim();
185
- commentText = commentText.slice(colonIdx + 1).trim();
186
- }
187
-
188
- annotations.push({
189
- type: 'comment',
190
- match: match[0],
191
- content: commentText,
192
- author,
193
- position: match.index,
194
- line: getLine(match.index),
195
- ...ctx,
196
- });
197
- }
198
-
199
- // Sort by position
200
- annotations.sort((a, b) => a.position - b.position);
201
- return annotations;
202
- }
203
-
204
- /**
205
- * Strip annotations from text, applying changes
206
- * Handles nested annotations by iterating until stable
207
- * @param {string} text
208
- * @param {{keepComments?: boolean}} options
209
- * @returns {string}
210
- */
211
- export function stripAnnotations(text, options = {}) {
212
- const { keepComments = false } = options;
213
-
214
- // Iterate until no more changes (handles nested annotations)
215
- let prev;
216
- let iterations = 0;
217
- const maxIterations = 20; // Safety limit
218
-
219
- do {
220
- prev = text;
221
-
222
- // Apply substitutions: {~~old~>new~~} → new
223
- text = text.replace(PATTERNS.substitute, '$2');
224
-
225
- // Apply insertions: {++text++} → text
226
- text = text.replace(PATTERNS.insert, '$1');
227
-
228
- // Apply deletions: {--text--} → nothing
229
- text = text.replace(PATTERNS.delete, '');
230
-
231
- // Remove highlights: {==text==} → text
232
- text = text.replace(PATTERNS.highlight, '$1');
233
-
234
- // Remove comments unless keeping
235
- if (!keepComments) {
236
- text = text.replace(PATTERNS.comment, '');
237
- }
238
-
239
- // Clean up partial/orphaned markers within the loop
240
- // This handles cases where nested annotations leave behind fragments
241
-
242
- // Empty annotations (from nested stripping)
243
- text = text.replace(/\{----\}/g, '');
244
- text = text.replace(/\{\+\+\+\+\}/g, '');
245
- text = text.replace(/\{--\s*--\}/g, '');
246
- text = text.replace(/\{\+\+\s*\+\+\}/g, '');
247
-
248
- // Orphaned substitution fragments: ~>text~~} or {~~text (no proper pairs)
249
- text = text.replace(/~>[^{]*?~~\}/g, '');
250
- text = text.replace(/\{~~[^~}]*$/gm, '');
251
-
252
- // Handle malformed substitution from nested: {~~{~~old → just strip the {~~
253
- text = text.replace(/\{~~\{~~/g, '{~~');
254
- text = text.replace(/~~\}~~\}/g, '~~}');
255
-
256
- iterations++;
257
- } while (text !== prev && iterations < maxIterations);
258
-
259
- // Final cleanup of any remaining orphaned markers
260
- // Orphaned closing markers
261
- text = text.replace(/--\}(?:--\})+/g, '');
262
- text = text.replace(/\+\+\}(?:\+\+\})+/g, '');
263
- text = text.replace(/~~\}(?:~~\})+/g, '');
264
- text = text.replace(/--\}/g, '');
265
- text = text.replace(/\+\+\}/g, '');
266
- text = text.replace(/~~\}/g, '');
267
-
268
- // Orphaned opening markers
269
- text = text.replace(/\{--(?:\{--)+/g, '');
270
- text = text.replace(/\{\+\+(?:\{\+\+)+/g, '');
271
- text = text.replace(/\{~~(?:\{~~)+/g, '');
272
- text = text.replace(/\{--/g, '');
273
- text = text.replace(/\{\+\+/g, '');
274
- text = text.replace(/\{~~/g, '');
275
- text = text.replace(/~>/g, '');
276
-
277
- // Clean up multiple spaces (but preserve structure like newlines)
278
- text = text.replace(/ +/g, ' ');
279
-
280
- return text;
281
- }
282
-
283
- /**
284
- * Check if text contains any CriticMarkup annotations
285
- * @param {string} text
286
- * @returns {boolean}
287
- */
288
- export function hasAnnotations(text) {
289
- return PATTERNS.insert.test(text) ||
290
- PATTERNS.delete.test(text) ||
291
- PATTERNS.substitute.test(text) ||
292
- PATTERNS.comment.test(text) ||
293
- PATTERNS.highlight.test(text);
294
- }
295
-
296
- /**
297
- * Apply a decision to a single annotation
298
- * @param {string} text
299
- * @param {{type: string, match: string, content: string, replacement?: string}} annotation
300
- * @param {boolean} accept
301
- * @returns {string}
302
- */
303
- export function applyDecision(text, annotation, accept) {
304
- let replacement;
305
-
306
- switch (annotation.type) {
307
- case 'insert':
308
- replacement = accept ? annotation.content : '';
309
- break;
310
- case 'delete':
311
- replacement = accept ? '' : annotation.content;
312
- break;
313
- case 'substitute':
314
- replacement = accept ? annotation.replacement : annotation.content;
315
- break;
316
- default:
317
- return text;
318
- }
319
-
320
- return text.replace(annotation.match, replacement);
321
- }
322
-
323
- /**
324
- * Get track changes only (no comments)
325
- * @param {string} text
326
- * @returns {Array}
327
- */
328
- export function getTrackChanges(text) {
329
- return parseAnnotations(text).filter((a) => a.type !== 'comment');
330
- }
331
-
332
- /**
333
- * Get comments only
334
- * @param {string} text
335
- * @param {object} options
336
- * @returns {Array}
337
- */
338
- export function getComments(text, options = {}) {
339
- const { pendingOnly = false, resolvedOnly = false } = options;
340
- let comments = parseAnnotations(text).filter((a) => a.type === 'comment');
341
-
342
- // Check for resolved status marker at end of comment
343
- comments = comments.map((c) => {
344
- const resolved = c.content.endsWith('[RESOLVED]') || c.content.endsWith('[✓]');
345
- return {
346
- ...c,
347
- resolved,
348
- content: resolved
349
- ? c.content.replace(/\s*\[(RESOLVED|✓)\]$/, '').trim()
350
- : c.content,
351
- };
352
- });
353
-
354
- if (pendingOnly) {
355
- comments = comments.filter((c) => !c.resolved);
356
- }
357
- if (resolvedOnly) {
358
- comments = comments.filter((c) => c.resolved);
359
- }
360
-
361
- return comments;
362
- }
363
-
364
- /**
365
- * Mark a comment as resolved or pending
366
- * @param {string} text - Document text
367
- * @param {object} comment - Comment object with position and match
368
- * @param {boolean} resolved - Whether to mark as resolved
369
- * @returns {string} Updated text
370
- */
371
- export function setCommentStatus(text, comment, resolved) {
372
- // Find the comment in the text
373
- const originalMatch = comment.match;
374
-
375
- if (resolved) {
376
- // Add [RESOLVED] marker before the closing <<
377
- const newMatch = originalMatch.replace(/<<\}$/, ' [RESOLVED]<<}');
378
- return text.replace(originalMatch, newMatch);
379
- } else {
380
- // Remove resolved markers
381
- const newMatch = originalMatch.replace(/\s*\[(RESOLVED|✓)\]<<\}$/, '<<}');
382
- return text.replace(originalMatch, newMatch);
383
- }
384
- }
385
-
386
- /**
387
- * Count annotations by type
388
- * @param {string} text
389
- * @returns {{inserts: number, deletes: number, substitutes: number, comments: number, total: number}}
390
- */
391
- export function countAnnotations(text) {
392
- const annotations = parseAnnotations(text);
393
- const counts = { inserts: 0, deletes: 0, substitutes: 0, comments: 0, total: 0 };
394
-
395
- for (const a of annotations) {
396
- counts.total++;
397
- switch (a.type) {
398
- case 'insert':
399
- counts.inserts++;
400
- break;
401
- case 'delete':
402
- counts.deletes++;
403
- break;
404
- case 'substitute':
405
- counts.substitutes++;
406
- break;
407
- case 'comment':
408
- counts.comments++;
409
- break;
410
- }
411
- }
412
-
413
- return counts;
414
- }