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.
- package/.claude/settings.local.json +9 -0
- package/PLAN-tables-and-postprocess.md +850 -0
- package/README.md +33 -0
- package/bin/rev.js +12 -131
- package/bin/rev.ts +145 -0
- package/dist/bin/rev.d.ts +9 -0
- package/dist/bin/rev.d.ts.map +1 -0
- package/dist/bin/rev.js +118 -0
- package/dist/bin/rev.js.map +1 -0
- package/dist/lib/annotations.d.ts +91 -0
- package/dist/lib/annotations.d.ts.map +1 -0
- package/dist/lib/annotations.js +554 -0
- package/dist/lib/annotations.js.map +1 -0
- package/dist/lib/build.d.ts +171 -0
- package/dist/lib/build.d.ts.map +1 -0
- package/dist/lib/build.js +755 -0
- package/dist/lib/build.js.map +1 -0
- package/dist/lib/citations.d.ts +34 -0
- package/dist/lib/citations.d.ts.map +1 -0
- package/dist/lib/citations.js +140 -0
- package/dist/lib/citations.js.map +1 -0
- package/dist/lib/commands/build.d.ts +13 -0
- package/dist/lib/commands/build.d.ts.map +1 -0
- package/dist/lib/commands/build.js +678 -0
- package/dist/lib/commands/build.js.map +1 -0
- package/dist/lib/commands/citations.d.ts +11 -0
- package/dist/lib/commands/citations.d.ts.map +1 -0
- package/dist/lib/commands/citations.js +428 -0
- package/dist/lib/commands/citations.js.map +1 -0
- package/dist/lib/commands/comments.d.ts +11 -0
- package/dist/lib/commands/comments.d.ts.map +1 -0
- package/dist/lib/commands/comments.js +883 -0
- package/dist/lib/commands/comments.js.map +1 -0
- package/dist/lib/commands/context.d.ts +35 -0
- package/dist/lib/commands/context.d.ts.map +1 -0
- package/dist/lib/commands/context.js +59 -0
- package/dist/lib/commands/context.js.map +1 -0
- package/dist/lib/commands/core.d.ts +11 -0
- package/dist/lib/commands/core.d.ts.map +1 -0
- package/dist/lib/commands/core.js +246 -0
- package/dist/lib/commands/core.js.map +1 -0
- package/dist/lib/commands/doi.d.ts +11 -0
- package/dist/lib/commands/doi.d.ts.map +1 -0
- package/dist/lib/commands/doi.js +373 -0
- package/dist/lib/commands/doi.js.map +1 -0
- package/dist/lib/commands/history.d.ts +11 -0
- package/dist/lib/commands/history.d.ts.map +1 -0
- package/dist/lib/commands/history.js +245 -0
- package/dist/lib/commands/history.js.map +1 -0
- package/dist/lib/commands/index.d.ts +28 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/index.js +35 -0
- package/dist/lib/commands/index.js.map +1 -0
- package/dist/lib/commands/init.d.ts +11 -0
- package/dist/lib/commands/init.d.ts.map +1 -0
- package/dist/lib/commands/init.js +209 -0
- package/dist/lib/commands/init.js.map +1 -0
- package/dist/lib/commands/response.d.ts +11 -0
- package/dist/lib/commands/response.d.ts.map +1 -0
- package/dist/lib/commands/response.js +317 -0
- package/dist/lib/commands/response.js.map +1 -0
- package/dist/lib/commands/sections.d.ts +11 -0
- package/dist/lib/commands/sections.d.ts.map +1 -0
- package/dist/lib/commands/sections.js +1071 -0
- package/dist/lib/commands/sections.js.map +1 -0
- package/dist/lib/commands/utilities.d.ts +19 -0
- package/dist/lib/commands/utilities.d.ts.map +1 -0
- package/dist/lib/commands/utilities.js +2009 -0
- package/dist/lib/commands/utilities.js.map +1 -0
- package/dist/lib/comment-realign.d.ts +50 -0
- package/dist/lib/comment-realign.d.ts.map +1 -0
- package/dist/lib/comment-realign.js +372 -0
- package/dist/lib/comment-realign.js.map +1 -0
- package/dist/lib/config.d.ts +41 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +76 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/crossref.d.ts +108 -0
- package/dist/lib/crossref.d.ts.map +1 -0
- package/dist/lib/crossref.js +597 -0
- package/dist/lib/crossref.js.map +1 -0
- package/dist/lib/dependencies.d.ts +30 -0
- package/dist/lib/dependencies.d.ts.map +1 -0
- package/dist/lib/dependencies.js +95 -0
- package/dist/lib/dependencies.js.map +1 -0
- package/dist/lib/doi-cache.d.ts +29 -0
- package/dist/lib/doi-cache.d.ts.map +1 -0
- package/dist/lib/doi-cache.js +104 -0
- package/dist/lib/doi-cache.js.map +1 -0
- package/dist/lib/doi.d.ts +65 -0
- package/dist/lib/doi.d.ts.map +1 -0
- package/dist/lib/doi.js +710 -0
- package/dist/lib/doi.js.map +1 -0
- package/dist/lib/equations.d.ts +61 -0
- package/dist/lib/equations.d.ts.map +1 -0
- package/dist/lib/equations.js +445 -0
- package/dist/lib/equations.js.map +1 -0
- package/dist/lib/errors.d.ts +60 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +303 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/format.d.ts +104 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +416 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/git.d.ts +88 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +304 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/grammar.d.ts +62 -0
- package/dist/lib/grammar.d.ts.map +1 -0
- package/dist/lib/grammar.js +244 -0
- package/dist/lib/grammar.js.map +1 -0
- package/dist/lib/image-registry.d.ts +68 -0
- package/dist/lib/image-registry.d.ts.map +1 -0
- package/dist/lib/image-registry.js +112 -0
- package/dist/lib/image-registry.js.map +1 -0
- package/dist/lib/import.d.ts +184 -0
- package/dist/lib/import.d.ts.map +1 -0
- package/dist/lib/import.js +1581 -0
- package/dist/lib/import.js.map +1 -0
- package/dist/lib/journals.d.ts +55 -0
- package/dist/lib/journals.d.ts.map +1 -0
- package/dist/lib/journals.js +417 -0
- package/dist/lib/journals.js.map +1 -0
- package/dist/lib/merge.d.ts +138 -0
- package/dist/lib/merge.d.ts.map +1 -0
- package/dist/lib/merge.js +603 -0
- package/dist/lib/merge.js.map +1 -0
- package/dist/lib/orcid.d.ts +36 -0
- package/dist/lib/orcid.d.ts.map +1 -0
- package/dist/lib/orcid.js +117 -0
- package/dist/lib/orcid.js.map +1 -0
- package/dist/lib/pdf-comments.d.ts +95 -0
- package/dist/lib/pdf-comments.d.ts.map +1 -0
- package/dist/lib/pdf-comments.js +192 -0
- package/dist/lib/pdf-comments.js.map +1 -0
- package/dist/lib/pdf-import.d.ts +118 -0
- package/dist/lib/pdf-import.d.ts.map +1 -0
- package/dist/lib/pdf-import.js +397 -0
- package/dist/lib/pdf-import.js.map +1 -0
- package/dist/lib/plugins.d.ts +76 -0
- package/dist/lib/plugins.d.ts.map +1 -0
- package/dist/lib/plugins.js +235 -0
- package/dist/lib/plugins.js.map +1 -0
- package/dist/lib/postprocess.d.ts +42 -0
- package/dist/lib/postprocess.d.ts.map +1 -0
- package/dist/lib/postprocess.js +138 -0
- package/dist/lib/postprocess.js.map +1 -0
- package/dist/lib/pptx-template.d.ts +59 -0
- package/dist/lib/pptx-template.d.ts.map +1 -0
- package/dist/lib/pptx-template.js +613 -0
- package/dist/lib/pptx-template.js.map +1 -0
- package/dist/lib/pptx-themes.d.ts +80 -0
- package/dist/lib/pptx-themes.d.ts.map +1 -0
- package/dist/lib/pptx-themes.js +818 -0
- package/dist/lib/pptx-themes.js.map +1 -0
- package/dist/lib/protect-restore.d.ts +137 -0
- package/dist/lib/protect-restore.d.ts.map +1 -0
- package/dist/lib/protect-restore.js +394 -0
- package/dist/lib/protect-restore.js.map +1 -0
- package/dist/lib/rate-limiter.d.ts +27 -0
- package/dist/lib/rate-limiter.d.ts.map +1 -0
- package/dist/lib/rate-limiter.js +79 -0
- package/dist/lib/rate-limiter.js.map +1 -0
- package/dist/lib/response.d.ts +41 -0
- package/dist/lib/response.d.ts.map +1 -0
- package/dist/lib/response.js +150 -0
- package/dist/lib/response.js.map +1 -0
- package/dist/lib/review.d.ts +35 -0
- package/dist/lib/review.d.ts.map +1 -0
- package/dist/lib/review.js +263 -0
- package/dist/lib/review.js.map +1 -0
- package/dist/lib/schema.d.ts +66 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +339 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/scientific-words.d.ts +6 -0
- package/dist/lib/scientific-words.d.ts.map +1 -0
- package/dist/lib/scientific-words.js +66 -0
- package/dist/lib/scientific-words.js.map +1 -0
- package/dist/lib/sections.d.ts +40 -0
- package/dist/lib/sections.d.ts.map +1 -0
- package/dist/lib/sections.js +288 -0
- package/dist/lib/sections.js.map +1 -0
- package/dist/lib/slides.d.ts +86 -0
- package/dist/lib/slides.d.ts.map +1 -0
- package/dist/lib/slides.js +676 -0
- package/dist/lib/slides.js.map +1 -0
- package/dist/lib/spelling.d.ts +76 -0
- package/dist/lib/spelling.d.ts.map +1 -0
- package/dist/lib/spelling.js +272 -0
- package/dist/lib/spelling.js.map +1 -0
- package/dist/lib/templates.d.ts +30 -0
- package/dist/lib/templates.d.ts.map +1 -0
- package/dist/lib/templates.js +504 -0
- package/dist/lib/templates.js.map +1 -0
- package/dist/lib/themes.d.ts +85 -0
- package/dist/lib/themes.d.ts.map +1 -0
- package/dist/lib/themes.js +652 -0
- package/dist/lib/themes.js.map +1 -0
- package/dist/lib/trackchanges.d.ts +51 -0
- package/dist/lib/trackchanges.d.ts.map +1 -0
- package/dist/lib/trackchanges.js +202 -0
- package/dist/lib/trackchanges.js.map +1 -0
- package/dist/lib/tui.d.ts +76 -0
- package/dist/lib/tui.d.ts.map +1 -0
- package/dist/lib/tui.js +377 -0
- package/dist/lib/tui.js.map +1 -0
- package/dist/lib/types.d.ts +447 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +6 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/undo.d.ts +57 -0
- package/dist/lib/undo.d.ts.map +1 -0
- package/dist/lib/undo.js +185 -0
- package/dist/lib/undo.js.map +1 -0
- package/dist/lib/utils.d.ts +16 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/variables.d.ts +42 -0
- package/dist/lib/variables.d.ts.map +1 -0
- package/dist/lib/variables.js +141 -0
- package/dist/lib/variables.js.map +1 -0
- package/dist/lib/word.d.ts +80 -0
- package/dist/lib/word.d.ts.map +1 -0
- package/dist/lib/word.js +360 -0
- package/dist/lib/word.js.map +1 -0
- package/dist/lib/wordcomments.d.ts +51 -0
- package/dist/lib/wordcomments.d.ts.map +1 -0
- package/dist/lib/wordcomments.js +587 -0
- package/dist/lib/wordcomments.js.map +1 -0
- package/eslint.config.js +27 -0
- package/lib/annotations.ts +622 -0
- package/lib/apply-buildup-colors.py +88 -0
- package/lib/build.ts +1013 -0
- package/lib/{citations.js → citations.ts} +38 -27
- package/lib/commands/{build.js → build.ts} +80 -27
- package/lib/commands/{citations.js → citations.ts} +36 -18
- package/lib/commands/{comments.js → comments.ts} +187 -54
- package/lib/commands/{context.js → context.ts} +18 -8
- package/lib/commands/{core.js → core.ts} +34 -20
- package/lib/commands/{doi.js → doi.ts} +32 -16
- package/lib/commands/{history.js → history.ts} +25 -12
- package/lib/commands/{index.js → index.ts} +9 -5
- package/lib/commands/{init.js → init.ts} +20 -8
- package/lib/commands/{response.js → response.ts} +47 -20
- package/lib/commands/{sections.js → sections.ts} +273 -68
- package/lib/commands/{utilities.js → utilities.ts} +338 -158
- package/lib/{comment-realign.js → comment-realign.ts} +117 -45
- package/lib/config.ts +84 -0
- package/lib/{crossref.js → crossref.ts} +213 -138
- package/lib/dependencies.ts +106 -0
- package/lib/doi-cache.ts +115 -0
- package/lib/{doi.js → doi.ts} +115 -281
- package/lib/{equations.js → equations.ts} +60 -64
- package/lib/{errors.js → errors.ts} +56 -48
- package/lib/{format.js → format.ts} +137 -63
- package/lib/{git.js → git.ts} +66 -63
- package/lib/{grammar.js → grammar.ts} +45 -32
- package/lib/image-registry.ts +180 -0
- package/lib/import.ts +2060 -0
- package/lib/journals.ts +505 -0
- package/lib/{merge.js → merge.ts} +185 -135
- package/lib/{orcid.js → orcid.ts} +17 -22
- package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
- package/lib/{pdf-import.js → pdf-import.ts} +148 -70
- package/lib/{plugins.js → plugins.ts} +82 -39
- package/lib/postprocess.ts +188 -0
- package/lib/pptx-color-filter.lua +37 -0
- package/lib/pptx-template.ts +625 -0
- package/lib/pptx-themes/academic.pptx +0 -0
- package/lib/pptx-themes/corporate.pptx +0 -0
- package/lib/pptx-themes/dark.pptx +0 -0
- package/lib/pptx-themes/default.pptx +0 -0
- package/lib/pptx-themes/minimal.pptx +0 -0
- package/lib/pptx-themes/plant.pptx +0 -0
- package/lib/pptx-themes.ts +896 -0
- package/lib/protect-restore.ts +516 -0
- package/lib/rate-limiter.ts +94 -0
- package/lib/{response.js → response.ts} +36 -21
- package/lib/{review.js → review.ts} +53 -43
- package/lib/{schema.js → schema.ts} +70 -25
- package/lib/{sections.js → sections.ts} +71 -76
- package/lib/slides.ts +793 -0
- package/lib/{spelling.js → spelling.ts} +43 -59
- package/lib/{templates.js → templates.ts} +20 -17
- package/lib/themes.ts +742 -0
- package/lib/{trackchanges.js → trackchanges.ts} +52 -23
- package/lib/types.ts +509 -0
- package/lib/{undo.js → undo.ts} +75 -52
- package/lib/utils.ts +41 -0
- package/lib/{variables.js → variables.ts} +60 -54
- package/lib/word.ts +428 -0
- package/lib/{wordcomments.js → wordcomments.ts} +94 -40
- package/package.json +15 -5
- package/skill/REFERENCE.md +67 -0
- package/tsconfig.json +26 -0
- package/lib/annotations.js +0 -414
- package/lib/build.js +0 -639
- package/lib/config.js +0 -79
- package/lib/import.js +0 -1145
- package/lib/journals.js +0 -629
- package/lib/word.js +0 -225
- /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
|
@@ -10,26 +10,81 @@
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import YAML from 'yaml';
|
|
13
|
+
import type {
|
|
14
|
+
RefNumber,
|
|
15
|
+
HardcodedRef,
|
|
16
|
+
DynamicRef,
|
|
17
|
+
FigureInfo,
|
|
18
|
+
Registry,
|
|
19
|
+
RefStatus,
|
|
20
|
+
ConversionResult,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Constants
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
/** Characters of context to check before a reference for deduplication */
|
|
28
|
+
const REF_CONTEXT_WINDOW = 100;
|
|
29
|
+
|
|
30
|
+
/** Minimum word length for similarity calculations */
|
|
31
|
+
const MIN_WORD_LENGTH = 2;
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Type Definitions (Internal)
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Reference info (internal use in registry building)
|
|
39
|
+
*/
|
|
40
|
+
interface RefInfo {
|
|
41
|
+
label: string;
|
|
42
|
+
num: number;
|
|
43
|
+
isSupp: boolean;
|
|
44
|
+
file: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parsed reference number components
|
|
49
|
+
*/
|
|
50
|
+
interface ParsedRefNumber {
|
|
51
|
+
isSupp: boolean;
|
|
52
|
+
num: number;
|
|
53
|
+
suffix: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Detected reference with parsed numbers
|
|
58
|
+
*/
|
|
59
|
+
interface DetectedRef {
|
|
60
|
+
type: 'fig' | 'tbl' | 'eq';
|
|
61
|
+
match: string;
|
|
62
|
+
numbers: ParsedRefNumber[];
|
|
63
|
+
position: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// =============================================================================
|
|
67
|
+
// Internal Helpers
|
|
68
|
+
// =============================================================================
|
|
13
69
|
|
|
14
70
|
/**
|
|
15
71
|
* Discover section files from a directory by reading config files
|
|
16
72
|
* Only returns files explicitly defined in rev.yaml or sections.yaml
|
|
17
73
|
* Returns empty array if no config found (caller should handle this)
|
|
18
|
-
*
|
|
19
|
-
* @param {string} directory
|
|
20
|
-
* @returns {string[]} Ordered list of section filenames, or empty if no config
|
|
21
74
|
*/
|
|
22
|
-
function discoverSectionFiles(directory) {
|
|
75
|
+
function discoverSectionFiles(directory: string): string[] {
|
|
23
76
|
// Try rev.yaml first
|
|
24
77
|
const revYamlPath = path.join(directory, 'rev.yaml');
|
|
25
78
|
if (fs.existsSync(revYamlPath)) {
|
|
26
79
|
try {
|
|
27
80
|
const config = YAML.parse(fs.readFileSync(revYamlPath, 'utf-8'));
|
|
28
81
|
if (config.sections && Array.isArray(config.sections) && config.sections.length > 0) {
|
|
29
|
-
return config.sections.filter(f => fs.existsSync(path.join(directory, f)));
|
|
82
|
+
return config.sections.filter((f: string) => fs.existsSync(path.join(directory, f)));
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
if (process.env.DEBUG) {
|
|
86
|
+
console.warn('crossref: YAML parse error in rev.yaml:', (e as Error).message);
|
|
30
87
|
}
|
|
31
|
-
} catch {
|
|
32
|
-
// Ignore yaml errors, try next option
|
|
33
88
|
}
|
|
34
89
|
}
|
|
35
90
|
|
|
@@ -40,12 +95,14 @@ function discoverSectionFiles(directory) {
|
|
|
40
95
|
const config = YAML.parse(fs.readFileSync(sectionsPath, 'utf-8'));
|
|
41
96
|
if (config.sections) {
|
|
42
97
|
const sectionOrder = Object.entries(config.sections)
|
|
43
|
-
.sort((a, b) => (a[1].order ?? 999) - (b[1].order ?? 999))
|
|
98
|
+
.sort((a, b) => ((a[1] as any).order ?? 999) - ((b[1] as any).order ?? 999))
|
|
44
99
|
.map(([file]) => file);
|
|
45
|
-
return sectionOrder.filter(f => fs.existsSync(path.join(directory, f)));
|
|
100
|
+
return sectionOrder.filter((f) => fs.existsSync(path.join(directory, f)));
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
if (process.env.DEBUG) {
|
|
104
|
+
console.warn('crossref: YAML parse error in sections.yaml:', (e as Error).message);
|
|
46
105
|
}
|
|
47
|
-
} catch {
|
|
48
|
-
// Ignore yaml errors
|
|
49
106
|
}
|
|
50
107
|
}
|
|
51
108
|
|
|
@@ -54,6 +111,10 @@ function discoverSectionFiles(directory) {
|
|
|
54
111
|
return [];
|
|
55
112
|
}
|
|
56
113
|
|
|
114
|
+
// =============================================================================
|
|
115
|
+
// Detection Patterns
|
|
116
|
+
// =============================================================================
|
|
117
|
+
|
|
57
118
|
/**
|
|
58
119
|
* Patterns for detecting hardcoded references
|
|
59
120
|
* Matches complex patterns including:
|
|
@@ -64,7 +125,7 @@ function discoverSectionFiles(directory) {
|
|
|
64
125
|
*
|
|
65
126
|
* Uses a simpler base pattern and parses the full match for lists
|
|
66
127
|
*/
|
|
67
|
-
const DETECTION_PATTERNS = {
|
|
128
|
+
const DETECTION_PATTERNS: Record<string, RegExp> = {
|
|
68
129
|
// Captures the full reference including lists with "and"
|
|
69
130
|
// Group 1: type prefix (Figure, Fig., etc.)
|
|
70
131
|
// Group 2: reference list (parsed by parseReferenceList())
|
|
@@ -103,12 +164,17 @@ const ANCHOR_PATTERN = /\{#(fig|tbl|eq):([^}]+)\}/gi;
|
|
|
103
164
|
*/
|
|
104
165
|
const REF_PATTERN = /@(fig|tbl|eq):([a-zA-Z0-9_-]+)/gi;
|
|
105
166
|
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// Public API
|
|
169
|
+
// =============================================================================
|
|
170
|
+
|
|
106
171
|
/**
|
|
107
172
|
* Normalize a reference type to standard form
|
|
108
|
-
* @param {string} typeStr - e.g., "Figure", "Fig.", "Figs", "table"
|
|
109
|
-
* @returns {string} - "fig", "tbl", or "eq"
|
|
110
173
|
*/
|
|
111
|
-
export function normalizeType(typeStr) {
|
|
174
|
+
export function normalizeType(typeStr: string): 'fig' | 'tbl' | 'eq' | string {
|
|
175
|
+
if (typeof typeStr !== 'string') {
|
|
176
|
+
throw new TypeError(`typeStr must be a string, got ${typeof typeStr}`);
|
|
177
|
+
}
|
|
112
178
|
const lower = typeStr.toLowerCase().replace(/\.$/, '');
|
|
113
179
|
if (lower.startsWith('fig')) return 'fig';
|
|
114
180
|
if (lower.startsWith('tab')) return 'tbl';
|
|
@@ -118,17 +184,16 @@ export function normalizeType(typeStr) {
|
|
|
118
184
|
|
|
119
185
|
/**
|
|
120
186
|
* Parse a reference number, handling supplementary (S1, S2) and letter suffixes (1a, 1b)
|
|
121
|
-
* @param {string} numStr - e.g., "1", "S1", "S2", "1a", "S1b"
|
|
122
|
-
* @param {string} [suffix] - optional letter suffix already extracted
|
|
123
|
-
* @returns {{isSupp: boolean, num: number, suffix: string|null}}
|
|
124
187
|
*/
|
|
125
|
-
export function parseRefNumber(numStr, suffix = null) {
|
|
126
|
-
if (!numStr
|
|
188
|
+
export function parseRefNumber(numStr: string, suffix: string | null = null): ParsedRefNumber {
|
|
189
|
+
if (!numStr || typeof numStr !== 'string') {
|
|
190
|
+
return { isSupp: false, num: 0, suffix: suffix || null };
|
|
191
|
+
}
|
|
127
192
|
const isSupp = numStr.toUpperCase().startsWith('S');
|
|
128
193
|
const numPart = isSupp ? numStr.slice(1) : numStr;
|
|
129
194
|
// Extract suffix if embedded in numStr (e.g., "1a")
|
|
130
195
|
const match = numPart.match(/^(\d+)([a-z])?$/i);
|
|
131
|
-
const num = match ? parseInt(match[1], 10) : parseInt(numPart, 10);
|
|
196
|
+
const num = match && match[1] ? parseInt(match[1], 10) : parseInt(numPart, 10);
|
|
132
197
|
const extractedSuffix = suffix || (match && match[2]) || null;
|
|
133
198
|
return { isSupp, num, suffix: extractedSuffix ? extractedSuffix.toLowerCase() : null };
|
|
134
199
|
}
|
|
@@ -136,24 +201,21 @@ export function parseRefNumber(numStr, suffix = null) {
|
|
|
136
201
|
/**
|
|
137
202
|
* Parse a reference list string like "1, 2, and 3" or "1a-c" or "1a-3b"
|
|
138
203
|
* Returns an array of {num, isSupp, suffix} objects
|
|
139
|
-
*
|
|
140
|
-
* @param {string} listStr - e.g., "1, 2, and 3", "1a-c", "1a-3b", "1a, b, c"
|
|
141
|
-
* @returns {Array<{num: number, isSupp: boolean, suffix: string|null}>}
|
|
142
204
|
*/
|
|
143
|
-
export function parseReferenceList(listStr) {
|
|
144
|
-
const results = [];
|
|
145
|
-
if (!listStr) return results;
|
|
205
|
+
export function parseReferenceList(listStr: string): ParsedRefNumber[] {
|
|
206
|
+
const results: ParsedRefNumber[] = [];
|
|
207
|
+
if (!listStr || typeof listStr !== 'string') return results;
|
|
146
208
|
|
|
147
209
|
// Normalize: replace "and" with comma, normalize dashes
|
|
148
210
|
let normalized = listStr
|
|
149
211
|
.replace(/\s+and\s+/gi, ', ')
|
|
150
|
-
.replace(/[–—]/g, '-')
|
|
151
|
-
.replace(/&/g, ', ');
|
|
212
|
+
.replace(/[–—]/g, '-') // en-dash, em-dash → hyphen
|
|
213
|
+
.replace(/&/g, ', '); // & → comma
|
|
152
214
|
|
|
153
215
|
// Split by comma (but not by dash, which indicates ranges)
|
|
154
|
-
const parts = normalized.split(/\s*,\s*/).filter(p => p.trim());
|
|
216
|
+
const parts = normalized.split(/\s*,\s*/).filter((p) => p.trim());
|
|
155
217
|
|
|
156
|
-
let lastFullRef = null; // Track the last full reference for implicit prefixes
|
|
218
|
+
let lastFullRef: { num: number; isSupp: boolean } | null = null; // Track the last full reference for implicit prefixes
|
|
157
219
|
|
|
158
220
|
for (const part of parts) {
|
|
159
221
|
const trimmed = part.trim();
|
|
@@ -161,7 +223,9 @@ export function parseReferenceList(listStr) {
|
|
|
161
223
|
|
|
162
224
|
// Check if this is a range (contains -)
|
|
163
225
|
if (trimmed.includes('-')) {
|
|
164
|
-
const
|
|
226
|
+
const parts = trimmed.split('-').map((s) => s.trim());
|
|
227
|
+
const start = parts[0] || '';
|
|
228
|
+
const end = parts[1] || '';
|
|
165
229
|
|
|
166
230
|
// Check if end is just a letter (e.g., "1a-c" where end is "c")
|
|
167
231
|
const endIsLetterOnly = /^[a-z]$/i.test(end);
|
|
@@ -187,22 +251,23 @@ export function parseReferenceList(listStr) {
|
|
|
187
251
|
);
|
|
188
252
|
|
|
189
253
|
for (let n = startRef.num; n <= endRef.num; n++) {
|
|
190
|
-
const suffixStart =
|
|
191
|
-
|
|
254
|
+
const suffixStart =
|
|
255
|
+
n === startRef.num ? startRef.suffix.charCodeAt(0) : 'a'.charCodeAt(0);
|
|
256
|
+
const suffixEnd = n === endRef.num ? endRef.suffix.charCodeAt(0) : maxSuffix;
|
|
192
257
|
|
|
193
258
|
for (let s = suffixStart; s <= suffixEnd; s++) {
|
|
194
259
|
results.push({
|
|
195
260
|
num: n,
|
|
196
261
|
isSupp: startRef.isSupp,
|
|
197
|
-
suffix: String.fromCharCode(s)
|
|
262
|
+
suffix: String.fromCharCode(s),
|
|
198
263
|
});
|
|
199
264
|
}
|
|
200
265
|
}
|
|
201
266
|
lastFullRef = { num: endRef.num, isSupp: startRef.isSupp };
|
|
202
267
|
} else if (startRef.suffix || endRef.suffix) {
|
|
203
268
|
// Suffix range on same number: "1a-c"
|
|
204
|
-
const num = startRef.num
|
|
205
|
-
const isSupp = startRef.isSupp
|
|
269
|
+
const num: number = startRef.num !== 0 ? startRef.num : (lastFullRef ? lastFullRef.num : 1);
|
|
270
|
+
const isSupp: boolean = startRef.isSupp ? startRef.isSupp : (lastFullRef ? lastFullRef.isSupp : false);
|
|
206
271
|
const startCode = (startRef.suffix || 'a').charCodeAt(0);
|
|
207
272
|
const endCode = (endRef.suffix || 'a').charCodeAt(0);
|
|
208
273
|
|
|
@@ -210,7 +275,7 @@ export function parseReferenceList(listStr) {
|
|
|
210
275
|
results.push({
|
|
211
276
|
num,
|
|
212
277
|
isSupp,
|
|
213
|
-
suffix: String.fromCharCode(code)
|
|
278
|
+
suffix: String.fromCharCode(code),
|
|
214
279
|
});
|
|
215
280
|
}
|
|
216
281
|
lastFullRef = { num, isSupp };
|
|
@@ -220,7 +285,7 @@ export function parseReferenceList(listStr) {
|
|
|
220
285
|
results.push({
|
|
221
286
|
num: n,
|
|
222
287
|
isSupp: startRef.isSupp,
|
|
223
|
-
suffix: null
|
|
288
|
+
suffix: null,
|
|
224
289
|
});
|
|
225
290
|
}
|
|
226
291
|
lastFullRef = { num: endRef.num, isSupp: startRef.isSupp };
|
|
@@ -233,7 +298,7 @@ export function parseReferenceList(listStr) {
|
|
|
233
298
|
results.push({
|
|
234
299
|
num: lastFullRef.num,
|
|
235
300
|
isSupp: lastFullRef.isSupp,
|
|
236
|
-
suffix: trimmed.toLowerCase()
|
|
301
|
+
suffix: trimmed.toLowerCase(),
|
|
237
302
|
});
|
|
238
303
|
} else {
|
|
239
304
|
// Full reference: "1", "1a", "S1", "S1a"
|
|
@@ -254,22 +319,15 @@ export function parseReferenceList(listStr) {
|
|
|
254
319
|
* IMPORTANT: This function requires either explicit sections or a rev.yaml/sections.yaml config.
|
|
255
320
|
* It will NOT guess by scanning all .md files, as this leads to incorrect numbering
|
|
256
321
|
* when temporary files (paper_clean.md, etc.) exist in the directory.
|
|
257
|
-
*
|
|
258
|
-
* @param {string} directory - Directory containing .md files
|
|
259
|
-
* @param {string[]} [sections] - Array of section filenames to scan (recommended).
|
|
260
|
-
* If not provided, reads from rev.yaml or sections.yaml.
|
|
261
|
-
* Returns empty registry if no sections can be determined.
|
|
262
|
-
* @returns {{
|
|
263
|
-
* figures: Map<string, {label: string, num: number, isSupp: boolean, file: string}>,
|
|
264
|
-
* tables: Map<string, {label: string, num: number, isSupp: boolean, file: string}>,
|
|
265
|
-
* equations: Map<string, {label: string, num: number, file: string}>,
|
|
266
|
-
* byNumber: {fig: Map<string, string>, tbl: Map<string, string>, eq: Map<string, string>}
|
|
267
|
-
* }}
|
|
268
322
|
*/
|
|
269
|
-
export function buildRegistry(directory, sections) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
323
|
+
export function buildRegistry(directory: string, sections?: string[]): Registry {
|
|
324
|
+
if (typeof directory !== 'string') {
|
|
325
|
+
throw new TypeError(`directory must be a string, got ${typeof directory}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const figures = new Map<string, FigureInfo>();
|
|
329
|
+
const tables = new Map<string, FigureInfo>();
|
|
330
|
+
const equations = new Map<string, FigureInfo>();
|
|
273
331
|
|
|
274
332
|
// Counters for numbering (separate for main and supplementary)
|
|
275
333
|
let figNum = 0;
|
|
@@ -278,11 +336,11 @@ export function buildRegistry(directory, sections) {
|
|
|
278
336
|
let tblSuppNum = 0;
|
|
279
337
|
let eqNum = 0;
|
|
280
338
|
|
|
281
|
-
let orderedFiles;
|
|
339
|
+
let orderedFiles: string[];
|
|
282
340
|
|
|
283
341
|
if (Array.isArray(sections) && sections.length > 0) {
|
|
284
342
|
// Use explicitly provided section files - most reliable
|
|
285
|
-
orderedFiles = sections.filter(f => fs.existsSync(path.join(directory, f)));
|
|
343
|
+
orderedFiles = sections.filter((f) => fs.existsSync(path.join(directory, f)));
|
|
286
344
|
} else {
|
|
287
345
|
// Try to determine sections from config files (rev.yaml or sections.yaml)
|
|
288
346
|
orderedFiles = discoverSectionFiles(directory);
|
|
@@ -291,7 +349,7 @@ export function buildRegistry(directory, sections) {
|
|
|
291
349
|
}
|
|
292
350
|
|
|
293
351
|
// Determine if a file is supplementary
|
|
294
|
-
const isSupplementary = (filename) =>
|
|
352
|
+
const isSupplementary = (filename: string): boolean =>
|
|
295
353
|
filename.toLowerCase().includes('supp') || filename.toLowerCase().includes('appendix');
|
|
296
354
|
|
|
297
355
|
// Process each file in order
|
|
@@ -301,11 +359,15 @@ export function buildRegistry(directory, sections) {
|
|
|
301
359
|
const isSupp = isSupplementary(file);
|
|
302
360
|
|
|
303
361
|
// Find all anchors
|
|
304
|
-
let match;
|
|
362
|
+
let match: RegExpExecArray | null;
|
|
305
363
|
ANCHOR_PATTERN.lastIndex = 0;
|
|
306
364
|
while ((match = ANCHOR_PATTERN.exec(content)) !== null) {
|
|
307
|
-
const
|
|
308
|
-
const
|
|
365
|
+
const typeRaw = match[1];
|
|
366
|
+
const labelRaw = match[2];
|
|
367
|
+
if (!typeRaw || !labelRaw) continue;
|
|
368
|
+
|
|
369
|
+
const type = typeRaw.toLowerCase();
|
|
370
|
+
const label = labelRaw;
|
|
309
371
|
|
|
310
372
|
if (type === 'fig') {
|
|
311
373
|
if (isSupp) {
|
|
@@ -325,13 +387,13 @@ export function buildRegistry(directory, sections) {
|
|
|
325
387
|
}
|
|
326
388
|
} else if (type === 'eq') {
|
|
327
389
|
eqNum++;
|
|
328
|
-
equations.set(label, { label, num: eqNum, file });
|
|
390
|
+
equations.set(label, { label, num: eqNum, isSupp: false, file });
|
|
329
391
|
}
|
|
330
392
|
}
|
|
331
393
|
}
|
|
332
394
|
|
|
333
395
|
// Build reverse lookup: number → label
|
|
334
|
-
const byNumber = {
|
|
396
|
+
const byNumber: Registry['byNumber'] = {
|
|
335
397
|
fig: new Map(),
|
|
336
398
|
figS: new Map(),
|
|
337
399
|
tbl: new Map(),
|
|
@@ -356,12 +418,14 @@ export function buildRegistry(directory, sections) {
|
|
|
356
418
|
|
|
357
419
|
/**
|
|
358
420
|
* Get the display string for a label (e.g., "Figure 1", "Table S2")
|
|
359
|
-
* @param {string} type - "fig", "tbl", "eq"
|
|
360
|
-
* @param {string} label
|
|
361
|
-
* @param {object} registry
|
|
362
|
-
* @returns {string|null}
|
|
363
421
|
*/
|
|
364
|
-
export function labelToDisplay(
|
|
422
|
+
export function labelToDisplay(
|
|
423
|
+
type: 'fig' | 'tbl' | 'eq',
|
|
424
|
+
label: string,
|
|
425
|
+
registry: Registry
|
|
426
|
+
): string | null {
|
|
427
|
+
if (!registry || !registry.figures) return null;
|
|
428
|
+
|
|
365
429
|
const collection =
|
|
366
430
|
type === 'fig' ? registry.figures : type === 'tbl' ? registry.tables : registry.equations;
|
|
367
431
|
|
|
@@ -376,28 +440,32 @@ export function labelToDisplay(type, label, registry) {
|
|
|
376
440
|
|
|
377
441
|
/**
|
|
378
442
|
* Get the label for a display number (e.g., "fig:heatmap" from Figure 1)
|
|
379
|
-
* @param {string} type - "fig", "tbl", "eq"
|
|
380
|
-
* @param {number} num
|
|
381
|
-
* @param {boolean} isSupp
|
|
382
|
-
* @param {object} registry
|
|
383
|
-
* @returns {string|null}
|
|
384
443
|
*/
|
|
385
|
-
export function numberToLabel(
|
|
386
|
-
|
|
444
|
+
export function numberToLabel(
|
|
445
|
+
type: 'fig' | 'tbl' | 'eq',
|
|
446
|
+
num: number,
|
|
447
|
+
isSupp: boolean,
|
|
448
|
+
registry: Registry
|
|
449
|
+
): string | null {
|
|
450
|
+
if (!registry || !registry.byNumber) return null;
|
|
451
|
+
|
|
452
|
+
const key = isSupp ? (`${type}S` as keyof Registry['byNumber']) : type;
|
|
387
453
|
return registry.byNumber[key]?.get(num) || null;
|
|
388
454
|
}
|
|
389
455
|
|
|
390
456
|
/**
|
|
391
457
|
* Detect all hardcoded references in text
|
|
392
|
-
* @param {string} text
|
|
393
|
-
* @returns {Array<{type: string, match: string, numbers: Array<{num: number, isSupp: boolean, suffix: string|null}>, position: number}>}
|
|
394
458
|
*/
|
|
395
|
-
export function detectHardcodedRefs(text) {
|
|
396
|
-
|
|
459
|
+
export function detectHardcodedRefs(text: string): DetectedRef[] {
|
|
460
|
+
if (typeof text !== 'string') {
|
|
461
|
+
throw new TypeError(`text must be a string, got ${typeof text}`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const refs: DetectedRef[] = [];
|
|
397
465
|
|
|
398
466
|
for (const [type, pattern] of Object.entries(DETECTION_PATTERNS)) {
|
|
399
467
|
pattern.lastIndex = 0;
|
|
400
|
-
let match;
|
|
468
|
+
let match: RegExpExecArray | null;
|
|
401
469
|
|
|
402
470
|
while ((match = pattern.exec(text)) !== null) {
|
|
403
471
|
// Pattern groups:
|
|
@@ -405,13 +473,14 @@ export function detectHardcodedRefs(text) {
|
|
|
405
473
|
// [2] = reference list string (e.g., "1, 2, and 3" or "1a-3b")
|
|
406
474
|
|
|
407
475
|
const listStr = match[2];
|
|
476
|
+
if (!listStr) continue;
|
|
408
477
|
const numbers = parseReferenceList(listStr);
|
|
409
478
|
|
|
410
479
|
// Skip if no valid numbers were parsed
|
|
411
480
|
if (numbers.length === 0) continue;
|
|
412
481
|
|
|
413
482
|
refs.push({
|
|
414
|
-
type: normalizeType(type),
|
|
483
|
+
type: normalizeType(type) as 'fig' | 'tbl' | 'eq',
|
|
415
484
|
match: match[0],
|
|
416
485
|
numbers,
|
|
417
486
|
position: match.index,
|
|
@@ -426,22 +495,21 @@ export function detectHardcodedRefs(text) {
|
|
|
426
495
|
|
|
427
496
|
/**
|
|
428
497
|
* Convert hardcoded references to @-style references
|
|
429
|
-
* @param {string} text
|
|
430
|
-
* @param {object} registry
|
|
431
|
-
* @returns {{converted: string, conversions: Array<{from: string, to: string}>, warnings: string[]}}
|
|
432
498
|
*/
|
|
433
|
-
export function convertHardcodedRefs(text, registry) {
|
|
499
|
+
export function convertHardcodedRefs(text: string, registry: Registry): ConversionResult {
|
|
500
|
+
// Input validation delegated to detectHardcodedRefs
|
|
434
501
|
const refs = detectHardcodedRefs(text);
|
|
435
|
-
const conversions = [];
|
|
436
|
-
const warnings = [];
|
|
502
|
+
const conversions: Array<{ from: string; to: string }> = [];
|
|
503
|
+
const warnings: string[] = [];
|
|
437
504
|
|
|
438
505
|
// Process in reverse order to preserve positions
|
|
439
506
|
let result = text;
|
|
440
507
|
for (let i = refs.length - 1; i >= 0; i--) {
|
|
441
508
|
const ref = refs[i];
|
|
509
|
+
if (!ref) continue;
|
|
442
510
|
|
|
443
511
|
// Build replacement
|
|
444
|
-
const labels = [];
|
|
512
|
+
const labels: string[] = [];
|
|
445
513
|
for (const { num, isSupp } of ref.numbers) {
|
|
446
514
|
const label = numberToLabel(ref.type, num, isSupp, registry);
|
|
447
515
|
if (label) {
|
|
@@ -455,7 +523,18 @@ export function convertHardcodedRefs(text, registry) {
|
|
|
455
523
|
|
|
456
524
|
if (labels.length > 0 && !labels.includes(ref.match)) {
|
|
457
525
|
const replacement = labels.join('; ');
|
|
458
|
-
|
|
526
|
+
|
|
527
|
+
// Skip if the @-syntax already appears in the preceding text
|
|
528
|
+
// This prevents duplication when import restores @fig:x and then we see "Fig. 1"
|
|
529
|
+
// e.g., "@fig:map@fig:map{++@fig:map++}" or "@fig:mapFigure 1" patterns
|
|
530
|
+
const textBefore = result.slice(Math.max(0, ref.position - REF_CONTEXT_WINDOW), ref.position);
|
|
531
|
+
const alreadyHasRef = labels.some((label) => textBefore.includes(label));
|
|
532
|
+
if (alreadyHasRef) {
|
|
533
|
+
continue; // Skip - ref already present nearby
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
result =
|
|
537
|
+
result.slice(0, ref.position) + replacement + result.slice(ref.position + ref.match.length);
|
|
459
538
|
|
|
460
539
|
conversions.push({
|
|
461
540
|
from: ref.match,
|
|
@@ -469,18 +548,23 @@ export function convertHardcodedRefs(text, registry) {
|
|
|
469
548
|
|
|
470
549
|
/**
|
|
471
550
|
* Detect @-style references in text
|
|
472
|
-
* @param {string} text
|
|
473
|
-
* @returns {Array<{type: string, label: string, match: string, position: number}>}
|
|
474
551
|
*/
|
|
475
|
-
export function detectDynamicRefs(text) {
|
|
476
|
-
|
|
552
|
+
export function detectDynamicRefs(text: string): DynamicRef[] {
|
|
553
|
+
if (typeof text !== 'string') {
|
|
554
|
+
throw new TypeError(`text must be a string, got ${typeof text}`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const refs: DynamicRef[] = [];
|
|
477
558
|
REF_PATTERN.lastIndex = 0;
|
|
478
|
-
let match;
|
|
559
|
+
let match: RegExpExecArray | null;
|
|
479
560
|
|
|
480
561
|
while ((match = REF_PATTERN.exec(text)) !== null) {
|
|
562
|
+
const type = match[1];
|
|
563
|
+
const label = match[2];
|
|
564
|
+
if (!type || !label) continue;
|
|
481
565
|
refs.push({
|
|
482
|
-
type:
|
|
483
|
-
label:
|
|
566
|
+
type: type as 'fig' | 'tbl' | 'eq',
|
|
567
|
+
label: label,
|
|
484
568
|
match: match[0],
|
|
485
569
|
position: match.index,
|
|
486
570
|
});
|
|
@@ -491,28 +575,23 @@ export function detectDynamicRefs(text) {
|
|
|
491
575
|
|
|
492
576
|
/**
|
|
493
577
|
* Get reference status for a file/text
|
|
494
|
-
* @param {string} text
|
|
495
|
-
* @param {object} registry
|
|
496
|
-
* @returns {{
|
|
497
|
-
* dynamic: Array,
|
|
498
|
-
* hardcoded: Array,
|
|
499
|
-
* anchors: {figures: number, tables: number, equations: number}
|
|
500
|
-
* }}
|
|
501
578
|
*/
|
|
502
|
-
export function getRefStatus(text, registry) {
|
|
579
|
+
export function getRefStatus(text: string, registry: Registry): RefStatus {
|
|
503
580
|
const dynamic = detectDynamicRefs(text);
|
|
504
|
-
const hardcoded = detectHardcodedRefs(text);
|
|
581
|
+
const hardcoded = detectHardcodedRefs(text) as HardcodedRef[];
|
|
505
582
|
|
|
506
583
|
// Count anchors in this text
|
|
507
584
|
ANCHOR_PATTERN.lastIndex = 0;
|
|
508
585
|
let figCount = 0,
|
|
509
586
|
tblCount = 0,
|
|
510
587
|
eqCount = 0;
|
|
511
|
-
let match;
|
|
588
|
+
let match: RegExpExecArray | null;
|
|
512
589
|
while ((match = ANCHOR_PATTERN.exec(text)) !== null) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
590
|
+
const type = match[1];
|
|
591
|
+
if (!type) continue;
|
|
592
|
+
if (type === 'fig') figCount++;
|
|
593
|
+
else if (type === 'tbl') tblCount++;
|
|
594
|
+
else if (type === 'eq') eqCount++;
|
|
516
595
|
}
|
|
517
596
|
|
|
518
597
|
return {
|
|
@@ -525,20 +604,20 @@ export function getRefStatus(text, registry) {
|
|
|
525
604
|
/**
|
|
526
605
|
* Detect forward references in combined text
|
|
527
606
|
* A forward reference is a @ref that appears before its {#anchor} definition
|
|
528
|
-
*
|
|
529
|
-
* @param {string} text - Combined document text
|
|
530
|
-
* @returns {{
|
|
531
|
-
* forwardRefs: Array<{type: string, label: string, match: string, position: number}>,
|
|
532
|
-
* anchorPositions: Map<string, number>
|
|
533
|
-
* }}
|
|
534
607
|
*/
|
|
535
|
-
export function detectForwardRefs(text) {
|
|
608
|
+
export function detectForwardRefs(text: string): {
|
|
609
|
+
forwardRefs: Array<{ type: string; label: string; match: string; position: number }>;
|
|
610
|
+
anchorPositions: Map<string, number>;
|
|
611
|
+
} {
|
|
536
612
|
// Build map of anchor positions: "fig:label" -> position
|
|
537
|
-
const anchorPositions = new Map();
|
|
613
|
+
const anchorPositions = new Map<string, number>();
|
|
538
614
|
ANCHOR_PATTERN.lastIndex = 0;
|
|
539
|
-
let match;
|
|
615
|
+
let match: RegExpExecArray | null;
|
|
540
616
|
while ((match = ANCHOR_PATTERN.exec(text)) !== null) {
|
|
541
|
-
const
|
|
617
|
+
const type = match[1];
|
|
618
|
+
const label = match[2];
|
|
619
|
+
if (!type || !label) continue;
|
|
620
|
+
const key = `${type}:${label}`;
|
|
542
621
|
// Only store first occurrence (in case of duplicates)
|
|
543
622
|
if (!anchorPositions.has(key)) {
|
|
544
623
|
anchorPositions.set(key, match.index);
|
|
@@ -563,31 +642,29 @@ export function detectForwardRefs(text) {
|
|
|
563
642
|
* Resolve forward references to display format
|
|
564
643
|
* Only resolves refs that appear before their anchor definition
|
|
565
644
|
* Leaves other refs for pandoc-crossref to handle (preserves clickable links)
|
|
566
|
-
*
|
|
567
|
-
* @param {string} text - Combined document text
|
|
568
|
-
* @param {object} registry - Registry from buildRegistry()
|
|
569
|
-
* @returns {{
|
|
570
|
-
* text: string,
|
|
571
|
-
* resolved: Array<{from: string, to: string, position: number}>,
|
|
572
|
-
* unresolved: Array<{ref: string, position: number}>
|
|
573
|
-
* }}
|
|
574
645
|
*/
|
|
575
|
-
export function resolveForwardRefs(
|
|
646
|
+
export function resolveForwardRefs(
|
|
647
|
+
text: string,
|
|
648
|
+
registry: Registry
|
|
649
|
+
): {
|
|
650
|
+
text: string;
|
|
651
|
+
resolved: Array<{ from: string; to: string; position: number }>;
|
|
652
|
+
unresolved: Array<{ ref: string; position: number }>;
|
|
653
|
+
} {
|
|
576
654
|
const { forwardRefs } = detectForwardRefs(text);
|
|
577
|
-
const resolved = [];
|
|
578
|
-
const unresolved = [];
|
|
655
|
+
const resolved: Array<{ from: string; to: string; position: number }> = [];
|
|
656
|
+
const unresolved: Array<{ ref: string; position: number }> = [];
|
|
579
657
|
|
|
580
658
|
// Process in reverse order to preserve positions
|
|
581
659
|
let result = text;
|
|
582
660
|
for (let i = forwardRefs.length - 1; i >= 0; i--) {
|
|
583
661
|
const ref = forwardRefs[i];
|
|
584
|
-
|
|
662
|
+
if (!ref) continue;
|
|
663
|
+
const display = labelToDisplay(ref.type as 'fig' | 'tbl' | 'eq', ref.label, registry);
|
|
585
664
|
|
|
586
665
|
if (display) {
|
|
587
666
|
result =
|
|
588
|
-
result.slice(0, ref.position) +
|
|
589
|
-
display +
|
|
590
|
-
result.slice(ref.position + ref.match.length);
|
|
667
|
+
result.slice(0, ref.position) + display + result.slice(ref.position + ref.match.length);
|
|
591
668
|
resolved.push({
|
|
592
669
|
from: ref.match,
|
|
593
670
|
to: display,
|
|
@@ -606,11 +683,9 @@ export function resolveForwardRefs(text, registry) {
|
|
|
606
683
|
|
|
607
684
|
/**
|
|
608
685
|
* Format registry for display
|
|
609
|
-
* @param {object} registry
|
|
610
|
-
* @returns {string}
|
|
611
686
|
*/
|
|
612
|
-
export function formatRegistry(registry) {
|
|
613
|
-
const lines = [];
|
|
687
|
+
export function formatRegistry(registry: Registry): string {
|
|
688
|
+
const lines: string[] = [];
|
|
614
689
|
|
|
615
690
|
if (registry.figures.size > 0) {
|
|
616
691
|
lines.push('Figures:');
|