docrev 0.9.13 → 0.9.15
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.
- package/.claude/settings.local.json +9 -9
- package/.gitattributes +1 -1
- package/CHANGELOG.md +149 -149
- package/PLAN-tables-and-postprocess.md +850 -850
- package/README.md +411 -391
- package/bin/rev.js +11 -11
- package/bin/rev.ts +145 -145
- package/completions/rev.bash +127 -127
- package/completions/rev.ps1 +210 -210
- package/completions/rev.zsh +207 -207
- package/dev_notes/stress2/build_adversarial.ts +186 -186
- package/dev_notes/stress2/drift_matcher.ts +62 -62
- package/dev_notes/stress2/probe_anchors.ts +35 -35
- package/dev_notes/stress2/project/discussion.before.md +3 -3
- package/dev_notes/stress2/project/discussion.md +3 -3
- package/dev_notes/stress2/project/methods.before.md +20 -20
- package/dev_notes/stress2/project/methods.md +20 -20
- package/dev_notes/stress2/project/rev.yaml +5 -5
- package/dev_notes/stress2/project/sections.yaml +4 -4
- package/dev_notes/stress2/sections.yaml +5 -5
- package/dev_notes/stress2/trace_placement.ts +50 -50
- package/dev_notes/stresstest_boundaries.ts +27 -27
- package/dev_notes/stresstest_drift_apply.ts +43 -43
- package/dev_notes/stresstest_drift_compare.ts +43 -43
- package/dev_notes/stresstest_drift_v2.ts +54 -54
- package/dev_notes/stresstest_inspect.ts +54 -54
- package/dev_notes/stresstest_pstyle.ts +55 -55
- package/dev_notes/stresstest_section_debug.ts +23 -23
- package/dev_notes/stresstest_split.ts +70 -70
- package/dev_notes/stresstest_trace.ts +19 -19
- package/dev_notes/stresstest_verify_no_overwrite.ts +40 -40
- package/dist/lib/build.d.ts +38 -1
- package/dist/lib/build.d.ts.map +1 -1
- package/dist/lib/build.js +68 -30
- package/dist/lib/build.js.map +1 -1
- package/dist/lib/commands/build.d.ts.map +1 -1
- package/dist/lib/commands/build.js +38 -5
- package/dist/lib/commands/build.js.map +1 -1
- package/dist/lib/commands/utilities.js +164 -164
- package/dist/lib/commands/word-tools.js +8 -8
- package/dist/lib/grammar.js +3 -3
- package/dist/lib/pdf-comments.js +44 -44
- package/dist/lib/plugins.js +57 -57
- package/dist/lib/pptx-themes.js +115 -115
- package/dist/lib/spelling.js +2 -2
- package/dist/lib/templates.js +387 -387
- package/dist/lib/themes.js +51 -51
- package/eslint.config.js +27 -27
- package/lib/anchor-match.ts +276 -276
- package/lib/annotations.ts +644 -644
- package/lib/build.ts +1300 -1251
- package/lib/citations.ts +160 -160
- package/lib/commands/build.ts +833 -801
- package/lib/commands/citations.ts +515 -515
- package/lib/commands/comments.ts +1050 -1050
- package/lib/commands/context.ts +174 -174
- package/lib/commands/core.ts +309 -309
- package/lib/commands/doi.ts +435 -435
- package/lib/commands/file-ops.ts +372 -372
- package/lib/commands/history.ts +320 -320
- package/lib/commands/index.ts +87 -87
- package/lib/commands/init.ts +259 -259
- package/lib/commands/merge-resolve.ts +378 -378
- package/lib/commands/preview.ts +178 -178
- package/lib/commands/project-info.ts +244 -244
- package/lib/commands/quality.ts +517 -517
- package/lib/commands/response.ts +454 -454
- package/lib/commands/section-boundaries.ts +82 -82
- package/lib/commands/sections.ts +451 -451
- package/lib/commands/sync.ts +706 -706
- package/lib/commands/text-ops.ts +449 -449
- package/lib/commands/utilities.ts +448 -448
- package/lib/commands/verify-anchors.ts +272 -272
- package/lib/commands/word-tools.ts +340 -340
- package/lib/comment-realign.ts +517 -517
- package/lib/config.ts +84 -84
- package/lib/crossref.ts +781 -781
- package/lib/csl.ts +191 -191
- package/lib/dependencies.ts +98 -98
- package/lib/diff-engine.ts +465 -465
- package/lib/doi-cache.ts +115 -115
- package/lib/doi.ts +897 -897
- package/lib/equations.ts +506 -506
- package/lib/errors.ts +346 -346
- package/lib/format.ts +541 -541
- package/lib/git.ts +326 -326
- package/lib/grammar.ts +303 -303
- package/lib/image-registry.ts +180 -180
- package/lib/import.ts +911 -911
- package/lib/journals.ts +543 -543
- package/lib/merge.ts +633 -633
- package/lib/orcid.ts +144 -144
- package/lib/pdf-comments.ts +263 -263
- package/lib/pdf-import.ts +524 -524
- package/lib/plugins.ts +362 -362
- package/lib/postprocess.ts +188 -188
- package/lib/pptx-color-filter.lua +37 -37
- package/lib/pptx-template.ts +469 -469
- package/lib/pptx-themes.ts +483 -483
- package/lib/protect-restore.ts +520 -520
- package/lib/rate-limiter.ts +94 -94
- package/lib/response.ts +197 -197
- package/lib/restore-references.ts +240 -240
- package/lib/review.ts +327 -327
- package/lib/schema.ts +417 -417
- package/lib/scientific-words.ts +73 -73
- package/lib/sections.ts +335 -335
- package/lib/slides.ts +756 -756
- package/lib/spelling.ts +334 -334
- package/lib/templates.ts +526 -526
- package/lib/themes.ts +742 -742
- package/lib/trackchanges.ts +247 -247
- package/lib/tui.ts +450 -450
- package/lib/types.ts +550 -550
- package/lib/undo.ts +250 -250
- package/lib/utils.ts +69 -69
- package/lib/variables.ts +179 -179
- package/lib/word-extraction.ts +806 -806
- package/lib/word.ts +643 -643
- package/lib/wordcomments.ts +817 -817
- package/package.json +137 -137
- package/scripts/postbuild.js +28 -28
- package/skill/REFERENCE.md +473 -431
- package/skill/SKILL.md +274 -258
- package/tsconfig.json +26 -26
- package/types/index.d.ts +525 -525
package/lib/pdf-comments.ts
CHANGED
|
@@ -1,263 +1,263 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PDF comment rendering for dual export
|
|
3
|
-
*
|
|
4
|
-
* Converts CriticMarkup comments to LaTeX margin notes for PDF output
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { escapeLatex } from './utils.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* LaTeX preamble for margin comments
|
|
11
|
-
* Uses todonotes package with custom styling
|
|
12
|
-
*/
|
|
13
|
-
export const MARGIN_NOTES_PREAMBLE = `
|
|
14
|
-
% Margin notes for comments
|
|
15
|
-
\\usepackage[colorinlistoftodos,textsize=scriptsize]{todonotes}
|
|
16
|
-
\\usepackage{xcolor}
|
|
17
|
-
|
|
18
|
-
% Define comment colors by author
|
|
19
|
-
\\definecolor{commentblue}{RGB}{59, 130, 246}
|
|
20
|
-
\\definecolor{commentgreen}{RGB}{34, 197, 94}
|
|
21
|
-
\\definecolor{commentorange}{RGB}{249, 115, 22}
|
|
22
|
-
\\definecolor{commentpurple}{RGB}{168, 85, 247}
|
|
23
|
-
\\definecolor{commentgray}{RGB}{107, 114, 128}
|
|
24
|
-
|
|
25
|
-
% Custom margin note command
|
|
26
|
-
\\newcommand{\\margincomment}[2][]{%
|
|
27
|
-
\\todo[linecolor=commentblue,backgroundcolor=commentblue!10,bordercolor=commentblue,size=\\scriptsize,#1]{#2}%
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
% Author-specific commands
|
|
31
|
-
\\newcommand{\\reviewercomment}[2]{%
|
|
32
|
-
\\todo[linecolor=commentgreen,backgroundcolor=commentgreen!10,bordercolor=commentgreen,size=\\scriptsize]{\\textbf{#1:} #2}%
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
% Increase margin for notes (if needed)
|
|
36
|
-
% \\setlength{\\marginparwidth}{2.5cm}
|
|
37
|
-
`;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Simpler preamble using marginpar (no extra packages needed)
|
|
41
|
-
*/
|
|
42
|
-
export const SIMPLE_MARGIN_PREAMBLE = `
|
|
43
|
-
% Simple margin notes for comments
|
|
44
|
-
\\usepackage{xcolor}
|
|
45
|
-
\\definecolor{commentcolor}{RGB}{59, 130, 246}
|
|
46
|
-
|
|
47
|
-
\\newcommand{\\margincomment}[1]{%
|
|
48
|
-
\\marginpar{\\raggedright\\scriptsize\\textcolor{commentcolor}{#1}}%
|
|
49
|
-
}
|
|
50
|
-
`;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Options for converting comments to margin notes
|
|
54
|
-
*/
|
|
55
|
-
export interface CommentConversionOptions {
|
|
56
|
-
useTodonotes?: boolean;
|
|
57
|
-
stripResolved?: boolean;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Result of comment conversion
|
|
62
|
-
*/
|
|
63
|
-
export interface CommentConversionResult {
|
|
64
|
-
markdown: string;
|
|
65
|
-
commentCount: number;
|
|
66
|
-
preamble: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Convert CriticMarkup comments to LaTeX margin notes
|
|
71
|
-
* {>>Author: comment text<<} -> \margincomment{Author: comment text}
|
|
72
|
-
*
|
|
73
|
-
* @param markdown - Markdown with CriticMarkup comments
|
|
74
|
-
* @param options - { useTodonotes: boolean, stripResolved: boolean }
|
|
75
|
-
* @returns Converted markdown with comment count and preamble
|
|
76
|
-
*/
|
|
77
|
-
export function convertCommentsToMarginNotes(
|
|
78
|
-
markdown: string,
|
|
79
|
-
options: CommentConversionOptions = {}
|
|
80
|
-
): CommentConversionResult {
|
|
81
|
-
const { useTodonotes = true, stripResolved = true } = options;
|
|
82
|
-
|
|
83
|
-
let commentCount = 0;
|
|
84
|
-
|
|
85
|
-
// Pattern for CriticMarkup comments: {>>author: text<<} or {>>text<<}
|
|
86
|
-
// Also handle resolved comments: {>>✓ author: text<<}
|
|
87
|
-
const commentPattern = /\{>>(✓\s*)?([^<]+)<<\}/g;
|
|
88
|
-
|
|
89
|
-
const converted = markdown.replace(commentPattern, (match, resolved, content) => {
|
|
90
|
-
// Skip resolved comments if requested
|
|
91
|
-
if (resolved && stripResolved) {
|
|
92
|
-
return '';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
commentCount++;
|
|
96
|
-
|
|
97
|
-
// Escape LaTeX special characters
|
|
98
|
-
const escaped = escapeLatex(content.trim());
|
|
99
|
-
|
|
100
|
-
if (useTodonotes) {
|
|
101
|
-
// Check if content has author prefix (Author: text)
|
|
102
|
-
const authorMatch = escaped.match(/^([^:]+):\s*([\s\S]+)$/);
|
|
103
|
-
if (authorMatch) {
|
|
104
|
-
const [, author, text] = authorMatch;
|
|
105
|
-
return `\\reviewercomment{${author}}{${text}}`;
|
|
106
|
-
}
|
|
107
|
-
return `\\margincomment{${escaped}}`;
|
|
108
|
-
} else {
|
|
109
|
-
return `\\margincomment{${escaped}}`;
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
const preamble = useTodonotes ? MARGIN_NOTES_PREAMBLE : SIMPLE_MARGIN_PREAMBLE;
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
markdown: converted,
|
|
117
|
-
commentCount,
|
|
118
|
-
preamble,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Result of track changes conversion
|
|
124
|
-
*/
|
|
125
|
-
export interface TrackChangesResult {
|
|
126
|
-
markdown: string;
|
|
127
|
-
preamble: string;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Convert track changes to visible LaTeX formatting
|
|
132
|
-
* {++inserted++} -> \textcolor{green}{inserted}
|
|
133
|
-
* {--deleted--} -> \textcolor{red}{\sout{deleted}}
|
|
134
|
-
* {~~old~>new~~} -> \textcolor{red}{\sout{old}}\textcolor{green}{new}
|
|
135
|
-
*
|
|
136
|
-
* @param markdown - Markdown with track changes
|
|
137
|
-
* @returns Converted markdown and preamble
|
|
138
|
-
*/
|
|
139
|
-
export function convertTrackChangesToLatex(markdown: string): TrackChangesResult {
|
|
140
|
-
let result = markdown;
|
|
141
|
-
|
|
142
|
-
// Insertions: {++text++} -> green text
|
|
143
|
-
result = result.replace(/\{\+\+([^+]+)\+\+\}/g, (match, text) => {
|
|
144
|
-
return `\\textcolor{green}{${escapeLatex(text)}}`;
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// Deletions: {--text--} -> red strikethrough
|
|
148
|
-
result = result.replace(/\{--([^-]+)--\}/g, (match, text) => {
|
|
149
|
-
return `\\textcolor{red}{\\sout{${escapeLatex(text)}}}`;
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Substitutions: {~~old~>new~~} -> red strikethrough + green new
|
|
153
|
-
result = result.replace(/\{~~([^~]+)~>([^~]+)~~\}/g, (match, oldText, newText) => {
|
|
154
|
-
return `\\textcolor{red}{\\sout{${escapeLatex(oldText)}}}\\textcolor{green}{${escapeLatex(newText)}}`;
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const preamble = `
|
|
158
|
-
% Track changes visualization
|
|
159
|
-
\\usepackage{xcolor}
|
|
160
|
-
\\usepackage[normalem]{ulem}
|
|
161
|
-
\\definecolor{green}{RGB}{34, 197, 94}
|
|
162
|
-
\\definecolor{red}{RGB}{239, 68, 68}
|
|
163
|
-
`;
|
|
164
|
-
|
|
165
|
-
return { markdown: result, preamble };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Options for combined preamble
|
|
170
|
-
*/
|
|
171
|
-
export interface PreambleOptions {
|
|
172
|
-
comments?: boolean;
|
|
173
|
-
trackChanges?: boolean;
|
|
174
|
-
useTodonotes?: boolean;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Get combined preamble for comments and track changes
|
|
179
|
-
* @param options - { comments: boolean, trackChanges: boolean, useTodonotes: boolean }
|
|
180
|
-
* @returns Combined LaTeX preamble
|
|
181
|
-
*/
|
|
182
|
-
export function getCombinedPreamble(options: PreambleOptions = {}): string {
|
|
183
|
-
const { comments = true, trackChanges = false, useTodonotes = true } = options;
|
|
184
|
-
|
|
185
|
-
let preamble = '';
|
|
186
|
-
|
|
187
|
-
if (comments) {
|
|
188
|
-
preamble += useTodonotes ? MARGIN_NOTES_PREAMBLE : SIMPLE_MARGIN_PREAMBLE;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (trackChanges) {
|
|
192
|
-
preamble += `
|
|
193
|
-
% Track changes visualization
|
|
194
|
-
\\usepackage[normalem]{ulem}
|
|
195
|
-
`;
|
|
196
|
-
if (!comments) {
|
|
197
|
-
preamble += `\\usepackage{xcolor}\n`;
|
|
198
|
-
}
|
|
199
|
-
preamble += `
|
|
200
|
-
\\definecolor{insertgreen}{RGB}{34, 197, 94}
|
|
201
|
-
\\definecolor{deletered}{RGB}{239, 68, 68}
|
|
202
|
-
`;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return preamble;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Options for preparing markdown for annotated PDF
|
|
210
|
-
*/
|
|
211
|
-
export interface AnnotatedPdfOptions {
|
|
212
|
-
showTrackChanges?: boolean;
|
|
213
|
-
useTodonotes?: boolean;
|
|
214
|
-
stripResolved?: boolean;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Result of preparing markdown for annotated PDF
|
|
219
|
-
*/
|
|
220
|
-
export interface AnnotatedPdfResult {
|
|
221
|
-
markdown: string;
|
|
222
|
-
preamble: string;
|
|
223
|
-
commentCount: number;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Prepare markdown for PDF with visible comments
|
|
228
|
-
* Converts comments to margin notes and optionally shows track changes
|
|
229
|
-
*
|
|
230
|
-
* @param markdown - Markdown content
|
|
231
|
-
* @param options - { showTrackChanges: boolean, useTodonotes: boolean }
|
|
232
|
-
* @returns Converted markdown with preamble and comment count
|
|
233
|
-
*/
|
|
234
|
-
export function prepareMarkdownForAnnotatedPdf(
|
|
235
|
-
markdown: string,
|
|
236
|
-
options: AnnotatedPdfOptions = {}
|
|
237
|
-
): AnnotatedPdfResult {
|
|
238
|
-
const { showTrackChanges = false, useTodonotes = true, stripResolved = true } = options;
|
|
239
|
-
|
|
240
|
-
let result = markdown;
|
|
241
|
-
let preamble = '';
|
|
242
|
-
let commentCount = 0;
|
|
243
|
-
|
|
244
|
-
// Convert comments to margin notes
|
|
245
|
-
const commentResult = convertCommentsToMarginNotes(result, { useTodonotes, stripResolved });
|
|
246
|
-
result = commentResult.markdown;
|
|
247
|
-
commentCount = commentResult.commentCount;
|
|
248
|
-
preamble += commentResult.preamble;
|
|
249
|
-
|
|
250
|
-
// Optionally show track changes
|
|
251
|
-
if (showTrackChanges) {
|
|
252
|
-
const trackResult = convertTrackChangesToLatex(result);
|
|
253
|
-
result = trackResult.markdown;
|
|
254
|
-
// Add ulem package if not already in todonotes preamble
|
|
255
|
-
if (!useTodonotes) {
|
|
256
|
-
preamble += trackResult.preamble;
|
|
257
|
-
} else {
|
|
258
|
-
preamble += `\\usepackage[normalem]{ulem}\n`;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return { markdown: result, preamble, commentCount };
|
|
263
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* PDF comment rendering for dual export
|
|
3
|
+
*
|
|
4
|
+
* Converts CriticMarkup comments to LaTeX margin notes for PDF output
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { escapeLatex } from './utils.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* LaTeX preamble for margin comments
|
|
11
|
+
* Uses todonotes package with custom styling
|
|
12
|
+
*/
|
|
13
|
+
export const MARGIN_NOTES_PREAMBLE = `
|
|
14
|
+
% Margin notes for comments
|
|
15
|
+
\\usepackage[colorinlistoftodos,textsize=scriptsize]{todonotes}
|
|
16
|
+
\\usepackage{xcolor}
|
|
17
|
+
|
|
18
|
+
% Define comment colors by author
|
|
19
|
+
\\definecolor{commentblue}{RGB}{59, 130, 246}
|
|
20
|
+
\\definecolor{commentgreen}{RGB}{34, 197, 94}
|
|
21
|
+
\\definecolor{commentorange}{RGB}{249, 115, 22}
|
|
22
|
+
\\definecolor{commentpurple}{RGB}{168, 85, 247}
|
|
23
|
+
\\definecolor{commentgray}{RGB}{107, 114, 128}
|
|
24
|
+
|
|
25
|
+
% Custom margin note command
|
|
26
|
+
\\newcommand{\\margincomment}[2][]{%
|
|
27
|
+
\\todo[linecolor=commentblue,backgroundcolor=commentblue!10,bordercolor=commentblue,size=\\scriptsize,#1]{#2}%
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
% Author-specific commands
|
|
31
|
+
\\newcommand{\\reviewercomment}[2]{%
|
|
32
|
+
\\todo[linecolor=commentgreen,backgroundcolor=commentgreen!10,bordercolor=commentgreen,size=\\scriptsize]{\\textbf{#1:} #2}%
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
% Increase margin for notes (if needed)
|
|
36
|
+
% \\setlength{\\marginparwidth}{2.5cm}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Simpler preamble using marginpar (no extra packages needed)
|
|
41
|
+
*/
|
|
42
|
+
export const SIMPLE_MARGIN_PREAMBLE = `
|
|
43
|
+
% Simple margin notes for comments
|
|
44
|
+
\\usepackage{xcolor}
|
|
45
|
+
\\definecolor{commentcolor}{RGB}{59, 130, 246}
|
|
46
|
+
|
|
47
|
+
\\newcommand{\\margincomment}[1]{%
|
|
48
|
+
\\marginpar{\\raggedright\\scriptsize\\textcolor{commentcolor}{#1}}%
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Options for converting comments to margin notes
|
|
54
|
+
*/
|
|
55
|
+
export interface CommentConversionOptions {
|
|
56
|
+
useTodonotes?: boolean;
|
|
57
|
+
stripResolved?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Result of comment conversion
|
|
62
|
+
*/
|
|
63
|
+
export interface CommentConversionResult {
|
|
64
|
+
markdown: string;
|
|
65
|
+
commentCount: number;
|
|
66
|
+
preamble: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Convert CriticMarkup comments to LaTeX margin notes
|
|
71
|
+
* {>>Author: comment text<<} -> \margincomment{Author: comment text}
|
|
72
|
+
*
|
|
73
|
+
* @param markdown - Markdown with CriticMarkup comments
|
|
74
|
+
* @param options - { useTodonotes: boolean, stripResolved: boolean }
|
|
75
|
+
* @returns Converted markdown with comment count and preamble
|
|
76
|
+
*/
|
|
77
|
+
export function convertCommentsToMarginNotes(
|
|
78
|
+
markdown: string,
|
|
79
|
+
options: CommentConversionOptions = {}
|
|
80
|
+
): CommentConversionResult {
|
|
81
|
+
const { useTodonotes = true, stripResolved = true } = options;
|
|
82
|
+
|
|
83
|
+
let commentCount = 0;
|
|
84
|
+
|
|
85
|
+
// Pattern for CriticMarkup comments: {>>author: text<<} or {>>text<<}
|
|
86
|
+
// Also handle resolved comments: {>>✓ author: text<<}
|
|
87
|
+
const commentPattern = /\{>>(✓\s*)?([^<]+)<<\}/g;
|
|
88
|
+
|
|
89
|
+
const converted = markdown.replace(commentPattern, (match, resolved, content) => {
|
|
90
|
+
// Skip resolved comments if requested
|
|
91
|
+
if (resolved && stripResolved) {
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
commentCount++;
|
|
96
|
+
|
|
97
|
+
// Escape LaTeX special characters
|
|
98
|
+
const escaped = escapeLatex(content.trim());
|
|
99
|
+
|
|
100
|
+
if (useTodonotes) {
|
|
101
|
+
// Check if content has author prefix (Author: text)
|
|
102
|
+
const authorMatch = escaped.match(/^([^:]+):\s*([\s\S]+)$/);
|
|
103
|
+
if (authorMatch) {
|
|
104
|
+
const [, author, text] = authorMatch;
|
|
105
|
+
return `\\reviewercomment{${author}}{${text}}`;
|
|
106
|
+
}
|
|
107
|
+
return `\\margincomment{${escaped}}`;
|
|
108
|
+
} else {
|
|
109
|
+
return `\\margincomment{${escaped}}`;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const preamble = useTodonotes ? MARGIN_NOTES_PREAMBLE : SIMPLE_MARGIN_PREAMBLE;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
markdown: converted,
|
|
117
|
+
commentCount,
|
|
118
|
+
preamble,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Result of track changes conversion
|
|
124
|
+
*/
|
|
125
|
+
export interface TrackChangesResult {
|
|
126
|
+
markdown: string;
|
|
127
|
+
preamble: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Convert track changes to visible LaTeX formatting
|
|
132
|
+
* {++inserted++} -> \textcolor{green}{inserted}
|
|
133
|
+
* {--deleted--} -> \textcolor{red}{\sout{deleted}}
|
|
134
|
+
* {~~old~>new~~} -> \textcolor{red}{\sout{old}}\textcolor{green}{new}
|
|
135
|
+
*
|
|
136
|
+
* @param markdown - Markdown with track changes
|
|
137
|
+
* @returns Converted markdown and preamble
|
|
138
|
+
*/
|
|
139
|
+
export function convertTrackChangesToLatex(markdown: string): TrackChangesResult {
|
|
140
|
+
let result = markdown;
|
|
141
|
+
|
|
142
|
+
// Insertions: {++text++} -> green text
|
|
143
|
+
result = result.replace(/\{\+\+([^+]+)\+\+\}/g, (match, text) => {
|
|
144
|
+
return `\\textcolor{green}{${escapeLatex(text)}}`;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Deletions: {--text--} -> red strikethrough
|
|
148
|
+
result = result.replace(/\{--([^-]+)--\}/g, (match, text) => {
|
|
149
|
+
return `\\textcolor{red}{\\sout{${escapeLatex(text)}}}`;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Substitutions: {~~old~>new~~} -> red strikethrough + green new
|
|
153
|
+
result = result.replace(/\{~~([^~]+)~>([^~]+)~~\}/g, (match, oldText, newText) => {
|
|
154
|
+
return `\\textcolor{red}{\\sout{${escapeLatex(oldText)}}}\\textcolor{green}{${escapeLatex(newText)}}`;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const preamble = `
|
|
158
|
+
% Track changes visualization
|
|
159
|
+
\\usepackage{xcolor}
|
|
160
|
+
\\usepackage[normalem]{ulem}
|
|
161
|
+
\\definecolor{green}{RGB}{34, 197, 94}
|
|
162
|
+
\\definecolor{red}{RGB}{239, 68, 68}
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
return { markdown: result, preamble };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Options for combined preamble
|
|
170
|
+
*/
|
|
171
|
+
export interface PreambleOptions {
|
|
172
|
+
comments?: boolean;
|
|
173
|
+
trackChanges?: boolean;
|
|
174
|
+
useTodonotes?: boolean;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get combined preamble for comments and track changes
|
|
179
|
+
* @param options - { comments: boolean, trackChanges: boolean, useTodonotes: boolean }
|
|
180
|
+
* @returns Combined LaTeX preamble
|
|
181
|
+
*/
|
|
182
|
+
export function getCombinedPreamble(options: PreambleOptions = {}): string {
|
|
183
|
+
const { comments = true, trackChanges = false, useTodonotes = true } = options;
|
|
184
|
+
|
|
185
|
+
let preamble = '';
|
|
186
|
+
|
|
187
|
+
if (comments) {
|
|
188
|
+
preamble += useTodonotes ? MARGIN_NOTES_PREAMBLE : SIMPLE_MARGIN_PREAMBLE;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (trackChanges) {
|
|
192
|
+
preamble += `
|
|
193
|
+
% Track changes visualization
|
|
194
|
+
\\usepackage[normalem]{ulem}
|
|
195
|
+
`;
|
|
196
|
+
if (!comments) {
|
|
197
|
+
preamble += `\\usepackage{xcolor}\n`;
|
|
198
|
+
}
|
|
199
|
+
preamble += `
|
|
200
|
+
\\definecolor{insertgreen}{RGB}{34, 197, 94}
|
|
201
|
+
\\definecolor{deletered}{RGB}{239, 68, 68}
|
|
202
|
+
`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return preamble;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Options for preparing markdown for annotated PDF
|
|
210
|
+
*/
|
|
211
|
+
export interface AnnotatedPdfOptions {
|
|
212
|
+
showTrackChanges?: boolean;
|
|
213
|
+
useTodonotes?: boolean;
|
|
214
|
+
stripResolved?: boolean;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Result of preparing markdown for annotated PDF
|
|
219
|
+
*/
|
|
220
|
+
export interface AnnotatedPdfResult {
|
|
221
|
+
markdown: string;
|
|
222
|
+
preamble: string;
|
|
223
|
+
commentCount: number;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Prepare markdown for PDF with visible comments
|
|
228
|
+
* Converts comments to margin notes and optionally shows track changes
|
|
229
|
+
*
|
|
230
|
+
* @param markdown - Markdown content
|
|
231
|
+
* @param options - { showTrackChanges: boolean, useTodonotes: boolean }
|
|
232
|
+
* @returns Converted markdown with preamble and comment count
|
|
233
|
+
*/
|
|
234
|
+
export function prepareMarkdownForAnnotatedPdf(
|
|
235
|
+
markdown: string,
|
|
236
|
+
options: AnnotatedPdfOptions = {}
|
|
237
|
+
): AnnotatedPdfResult {
|
|
238
|
+
const { showTrackChanges = false, useTodonotes = true, stripResolved = true } = options;
|
|
239
|
+
|
|
240
|
+
let result = markdown;
|
|
241
|
+
let preamble = '';
|
|
242
|
+
let commentCount = 0;
|
|
243
|
+
|
|
244
|
+
// Convert comments to margin notes
|
|
245
|
+
const commentResult = convertCommentsToMarginNotes(result, { useTodonotes, stripResolved });
|
|
246
|
+
result = commentResult.markdown;
|
|
247
|
+
commentCount = commentResult.commentCount;
|
|
248
|
+
preamble += commentResult.preamble;
|
|
249
|
+
|
|
250
|
+
// Optionally show track changes
|
|
251
|
+
if (showTrackChanges) {
|
|
252
|
+
const trackResult = convertTrackChangesToLatex(result);
|
|
253
|
+
result = trackResult.markdown;
|
|
254
|
+
// Add ulem package if not already in todonotes preamble
|
|
255
|
+
if (!useTodonotes) {
|
|
256
|
+
preamble += trackResult.preamble;
|
|
257
|
+
} else {
|
|
258
|
+
preamble += `\\usepackage[normalem]{ulem}\n`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return { markdown: result, preamble, commentCount };
|
|
263
|
+
}
|