@peaceroad/markdown-it-figure-with-p-caption 0.19.0 → 0.20.0
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/README.md +259 -475
- package/caption-numbering/counter-series.js +62 -0
- package/caption-numbering/number-codec.js +101 -0
- package/caption-numbering/options.js +101 -0
- package/caption-numbering/scope.js +466 -0
- package/caption-numbering.js +12 -0
- package/docs/examples.md +571 -0
- package/docs/numbering.md +215 -0
- package/docs/reference.md +148 -0
- package/embeds/detect.js +85 -63
- package/index.js +387 -305
- package/package.json +15 -7
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { isCaptionLabelBoundary } from 'p7d-markdown-it-p-captions'
|
|
2
|
+
import {
|
|
3
|
+
createScopedNumberingContext,
|
|
4
|
+
createUnscopedNumberingContext,
|
|
5
|
+
} from './number-codec.js'
|
|
6
|
+
import {
|
|
7
|
+
getFigureCaptionNumberingPolicyState,
|
|
8
|
+
normalizeNumberingScopeMode,
|
|
9
|
+
normalizeNumberingSeparator,
|
|
10
|
+
} from './options.js'
|
|
11
|
+
|
|
12
|
+
const captionNumberSegmentReg = /^[A-Z0-9]{1,6}$/
|
|
13
|
+
const maxScopeKeyLength = 256
|
|
14
|
+
const maxScopeVisiblePrefixLength = 128
|
|
15
|
+
const frontmatterNumberingKey = 'figure-caption-numbering'
|
|
16
|
+
const frontmatterNumberingScopeKey = frontmatterNumberingKey + '.scope'
|
|
17
|
+
const frontmatterNumberingSeparatorKey = frontmatterNumberingKey + '.separator'
|
|
18
|
+
const scopeTransparentInlineTokenTypes = new Set([
|
|
19
|
+
'em_open', 'em_close',
|
|
20
|
+
'strong_open', 'strong_close',
|
|
21
|
+
's_open', 's_close',
|
|
22
|
+
'link_open', 'link_close',
|
|
23
|
+
])
|
|
24
|
+
const emptyScopeBoundaries = Object.freeze([])
|
|
25
|
+
|
|
26
|
+
const hasOwnOption = (option, name) => !!(
|
|
27
|
+
option && Object.prototype.hasOwnProperty.call(option, name)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
export const getFigureCaptionScopeNestedContainerType = (token) => {
|
|
31
|
+
if (!token) return null
|
|
32
|
+
switch (token.type) {
|
|
33
|
+
case 'blockquote_open':
|
|
34
|
+
return 'blockquote'
|
|
35
|
+
case 'list_item_open':
|
|
36
|
+
return 'list_item'
|
|
37
|
+
case 'dd_open':
|
|
38
|
+
return 'dd'
|
|
39
|
+
default:
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const startsWithAsciiCaseInsensitive = (text, expectedLowerCase) => {
|
|
45
|
+
if (text.length < expectedLowerCase.length) return false
|
|
46
|
+
for (let index = 0; index < expectedLowerCase.length; index++) {
|
|
47
|
+
let code = text.charCodeAt(index)
|
|
48
|
+
if (code >= 0x41 && code <= 0x5a) code += 0x20
|
|
49
|
+
if (code !== expectedLowerCase.charCodeAt(index)) return false
|
|
50
|
+
}
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const readAsciiDigits = (text, start) => {
|
|
55
|
+
let end = start
|
|
56
|
+
while (end < text.length && end - start < 6) {
|
|
57
|
+
const code = text.charCodeAt(end)
|
|
58
|
+
if (code < 0x30 || code > 0x39) break
|
|
59
|
+
end++
|
|
60
|
+
}
|
|
61
|
+
return end === start ? null : { id: text.slice(start, end), end }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const readAppendixIdentifier = (text, start) => {
|
|
65
|
+
const digits = readAsciiDigits(text, start)
|
|
66
|
+
if (digits) return digits
|
|
67
|
+
const code = text.charCodeAt(start)
|
|
68
|
+
if (code >= 0x41 && code <= 0x5a) {
|
|
69
|
+
return { id: text.charAt(start), end: start + 1 }
|
|
70
|
+
}
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const matchScopeMarker = (text) => {
|
|
75
|
+
if (typeof text !== 'string' || !text) return null
|
|
76
|
+
const first = text.charAt(0)
|
|
77
|
+
if (first === 'C' || first === 'c') {
|
|
78
|
+
if (!startsWithAsciiCaseInsensitive(text, 'chapter ')) return null
|
|
79
|
+
const identifier = readAsciiDigits(text, 8)
|
|
80
|
+
return identifier && {
|
|
81
|
+
kind: 'chapter',
|
|
82
|
+
id: identifier.id,
|
|
83
|
+
markerEnd: identifier.end,
|
|
84
|
+
layout: 'spaced',
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (first === 'A' || first === 'a') {
|
|
88
|
+
if (!startsWithAsciiCaseInsensitive(text, 'appendix ')) return null
|
|
89
|
+
const identifier = readAppendixIdentifier(text, 9)
|
|
90
|
+
return identifier && {
|
|
91
|
+
kind: 'appendix',
|
|
92
|
+
id: identifier.id,
|
|
93
|
+
markerEnd: identifier.end,
|
|
94
|
+
layout: 'spaced',
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (first === '第') {
|
|
98
|
+
const identifier = readAsciiDigits(text, 1)
|
|
99
|
+
if (!identifier || text.charAt(identifier.end) !== '章') return null
|
|
100
|
+
return {
|
|
101
|
+
kind: 'chapter',
|
|
102
|
+
id: identifier.id,
|
|
103
|
+
markerEnd: identifier.end + 1,
|
|
104
|
+
layout: 'compact',
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const firstCode = text.charCodeAt(0)
|
|
108
|
+
if (firstCode >= 0x30 && firstCode <= 0x39) {
|
|
109
|
+
const identifier = readAsciiDigits(text, 0)
|
|
110
|
+
if (!identifier || text.charAt(identifier.end) !== '章') return null
|
|
111
|
+
return {
|
|
112
|
+
kind: 'chapter',
|
|
113
|
+
id: identifier.id,
|
|
114
|
+
markerEnd: identifier.end + 1,
|
|
115
|
+
layout: 'compact',
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
let prefix = ''
|
|
119
|
+
if (text.startsWith('付録')) prefix = '付録'
|
|
120
|
+
else if (text.startsWith('付属')) prefix = '付属'
|
|
121
|
+
else if (text.startsWith('附属')) prefix = '附属'
|
|
122
|
+
if (!prefix) return null
|
|
123
|
+
const identifier = readAppendixIdentifier(text, prefix.length)
|
|
124
|
+
return identifier && {
|
|
125
|
+
kind: 'appendix',
|
|
126
|
+
id: identifier.id,
|
|
127
|
+
markerEnd: identifier.end,
|
|
128
|
+
layout: 'compact',
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const buildScopeSemanticResult = (candidate) => ({
|
|
133
|
+
scopeKey: candidate.kind + ':' + candidate.id,
|
|
134
|
+
displayPrefix: candidate.id,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const recognizeScopeText = (text, requireVisibleTail) => {
|
|
138
|
+
const candidate = matchScopeMarker(text)
|
|
139
|
+
if (!candidate) return null
|
|
140
|
+
if (requireVisibleTail && candidate.markerEnd === text.length) return null
|
|
141
|
+
if (!isCaptionLabelBoundary(text, candidate.markerEnd, {
|
|
142
|
+
layout: candidate.layout,
|
|
143
|
+
hasNumber: true,
|
|
144
|
+
})) return null
|
|
145
|
+
return buildScopeSemanticResult(candidate)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const recognizeScopeFromInline = (inlineToken) => {
|
|
149
|
+
if (!inlineToken || inlineToken.type !== 'inline' || !Array.isArray(inlineToken.children)) return null
|
|
150
|
+
const children = inlineToken.children
|
|
151
|
+
if (children.length === 0 || !children[0] || children[0].type !== 'text') return null
|
|
152
|
+
if (typeof children[0].content !== 'string' || children[0].content.length === 0) return null
|
|
153
|
+
let visiblePrefix = ''
|
|
154
|
+
for (let index = 0; index < children.length; index++) {
|
|
155
|
+
const child = children[index]
|
|
156
|
+
if (!child) return null
|
|
157
|
+
if (child.type === 'text') {
|
|
158
|
+
const content = typeof child.content === 'string' ? child.content : ''
|
|
159
|
+
const remaining = maxScopeVisiblePrefixLength - visiblePrefix.length
|
|
160
|
+
if (remaining <= 0) return null
|
|
161
|
+
visiblePrefix += content.slice(0, remaining)
|
|
162
|
+
const result = recognizeScopeText(visiblePrefix, true)
|
|
163
|
+
// A half-width joint at this token boundary is not conclusive because
|
|
164
|
+
// later visible text can turn `Chapter 1:` into `Chapter 1:st`.
|
|
165
|
+
const lastCode = visiblePrefix.charCodeAt(visiblePrefix.length - 1)
|
|
166
|
+
if (result && lastCode !== 0x2e && lastCode !== 0x3a) return result
|
|
167
|
+
if (content.length > remaining) return null
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
if (scopeTransparentInlineTokenTypes.has(child.type)) continue
|
|
171
|
+
return null
|
|
172
|
+
}
|
|
173
|
+
return recognizeScopeText(visiblePrefix, false)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const normalizeFigureSequenceKey = (value, optionName) => {
|
|
177
|
+
if (typeof value === 'string') {
|
|
178
|
+
if (!value || value.length > maxScopeKeyLength) {
|
|
179
|
+
throw new RangeError(`${optionName} must be a non-empty string of at most ${maxScopeKeyLength} UTF-16 code units.`)
|
|
180
|
+
}
|
|
181
|
+
return value
|
|
182
|
+
}
|
|
183
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
184
|
+
throw new TypeError(`${optionName} must be a non-empty string or a finite number.`)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const getFrontmatterNumberingOverride = (env) => {
|
|
188
|
+
const frontmatter = env && env.frontmatter
|
|
189
|
+
if (!frontmatter || typeof frontmatter !== 'object' || Array.isArray(frontmatter)) return null
|
|
190
|
+
const hasNested = hasOwnOption(frontmatter, frontmatterNumberingKey)
|
|
191
|
+
const hasDottedScope = hasOwnOption(frontmatter, frontmatterNumberingScopeKey)
|
|
192
|
+
const hasDottedSeparator = hasOwnOption(frontmatter, frontmatterNumberingSeparatorKey)
|
|
193
|
+
if (!hasNested && !hasDottedScope && !hasDottedSeparator) return null
|
|
194
|
+
const value = hasNested ? frontmatter[frontmatterNumberingKey] : null
|
|
195
|
+
const optionName = `env.frontmatter["${frontmatterNumberingKey}"]`
|
|
196
|
+
if (hasNested && (!value || typeof value !== 'object' || Array.isArray(value))) {
|
|
197
|
+
throw new TypeError(`${optionName} must be an object.`)
|
|
198
|
+
}
|
|
199
|
+
if (hasNested) {
|
|
200
|
+
const keys = Object.keys(value)
|
|
201
|
+
for (let index = 0; index < keys.length; index++) {
|
|
202
|
+
const key = keys[index]
|
|
203
|
+
if (key !== 'scope' && key !== 'separator') {
|
|
204
|
+
throw new TypeError(`${optionName}.${key} is not supported.`)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const normalized = {}
|
|
209
|
+
if (hasNested && hasOwnOption(value, 'scope')) {
|
|
210
|
+
if (hasDottedScope) {
|
|
211
|
+
throw new TypeError(`${optionName}.scope is defined more than once.`)
|
|
212
|
+
}
|
|
213
|
+
normalized.scope = normalizeNumberingScopeMode(value.scope, `${optionName}.scope`)
|
|
214
|
+
} else if (hasDottedScope) {
|
|
215
|
+
normalized.scope = normalizeNumberingScopeMode(
|
|
216
|
+
frontmatter[frontmatterNumberingScopeKey],
|
|
217
|
+
`env.frontmatter["${frontmatterNumberingScopeKey}"]`,
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
if (hasNested && hasOwnOption(value, 'separator')) {
|
|
221
|
+
if (hasDottedSeparator) {
|
|
222
|
+
throw new TypeError(`${optionName}.separator is defined more than once.`)
|
|
223
|
+
}
|
|
224
|
+
normalized.separator = normalizeNumberingSeparator(
|
|
225
|
+
value.separator,
|
|
226
|
+
`${optionName}.separator`,
|
|
227
|
+
)
|
|
228
|
+
} else if (hasDottedSeparator) {
|
|
229
|
+
normalized.separator = normalizeNumberingSeparator(
|
|
230
|
+
frontmatter[frontmatterNumberingSeparatorKey],
|
|
231
|
+
`env.frontmatter["${frontmatterNumberingSeparatorKey}"]`,
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
return normalized
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const normalizeExplicitScopeOverride = (value, separator) => {
|
|
238
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
239
|
+
throw new TypeError('env.figureCaptionNumbering.scope must be an object.')
|
|
240
|
+
}
|
|
241
|
+
const scopeKey = normalizeFigureSequenceKey(value.scopeKey, 'env.figureCaptionNumbering.scope.scopeKey')
|
|
242
|
+
if (typeof scopeKey !== 'string') {
|
|
243
|
+
throw new TypeError('env.figureCaptionNumbering.scope.scopeKey must be a non-empty string.')
|
|
244
|
+
}
|
|
245
|
+
if (typeof value.displayPrefix !== 'string' || !captionNumberSegmentReg.test(value.displayPrefix)) {
|
|
246
|
+
throw new TypeError('env.figureCaptionNumbering.scope.displayPrefix must match the caption number segment grammar.')
|
|
247
|
+
}
|
|
248
|
+
const sequenceKey = value.sequenceKey === undefined
|
|
249
|
+
? scopeKey
|
|
250
|
+
: normalizeFigureSequenceKey(value.sequenceKey, 'env.figureCaptionNumbering.scope.sequenceKey')
|
|
251
|
+
return createScopedNumberingContext(
|
|
252
|
+
scopeKey,
|
|
253
|
+
sequenceKey,
|
|
254
|
+
value.displayPrefix,
|
|
255
|
+
separator,
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const applySemanticScope = (scopeState, semanticScope) => {
|
|
260
|
+
const sequenceKey = scopeState.repeatScope === 'reset'
|
|
261
|
+
? scopeState.nextResetSequenceKey++
|
|
262
|
+
: semanticScope.scopeKey
|
|
263
|
+
scopeState.currentContext = createScopedNumberingContext(
|
|
264
|
+
semanticScope.scopeKey,
|
|
265
|
+
sequenceKey,
|
|
266
|
+
semanticScope.displayPrefix,
|
|
267
|
+
scopeState.separator,
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const findRawFrontmatterToken = (tokens) => {
|
|
272
|
+
const firstToken = tokens && tokens[0]
|
|
273
|
+
return firstToken && firstToken.type === 'front_matter' ? firstToken : null
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const resolveInitialFrontmatterScope = (state, scopeConfig) => {
|
|
277
|
+
const env = state.env && typeof state.env === 'object' ? state.env : null
|
|
278
|
+
const parsedTitle = env && env.frontmatter && typeof env.frontmatter === 'object'
|
|
279
|
+
? env.frontmatter.title
|
|
280
|
+
: null
|
|
281
|
+
if (typeof parsedTitle === 'string') {
|
|
282
|
+
const prefix = parsedTitle.slice(0, maxScopeVisiblePrefixLength)
|
|
283
|
+
const recognized = recognizeScopeText(prefix, parsedTitle.length > prefix.length)
|
|
284
|
+
if (recognized) return recognized
|
|
285
|
+
}
|
|
286
|
+
if (!scopeConfig.resolveFrontmatterTitle) return null
|
|
287
|
+
const token = findRawFrontmatterToken(state.tokens)
|
|
288
|
+
if (!token) return null
|
|
289
|
+
let title
|
|
290
|
+
try {
|
|
291
|
+
const raw = typeof token.meta === 'string'
|
|
292
|
+
? token.meta
|
|
293
|
+
: (typeof token.content === 'string' ? token.content : '')
|
|
294
|
+
title = scopeConfig.resolveFrontmatterTitle(raw, state)
|
|
295
|
+
} catch (_err) {
|
|
296
|
+
return null
|
|
297
|
+
}
|
|
298
|
+
if (typeof title !== 'string') return null
|
|
299
|
+
const prefix = title.slice(0, maxScopeVisiblePrefixLength)
|
|
300
|
+
return recognizeScopeText(prefix, title.length > prefix.length)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const createFigureCaptionScopeRuntimeFromPolicyState = (
|
|
304
|
+
state,
|
|
305
|
+
advancedPolicy,
|
|
306
|
+
unscopedContext,
|
|
307
|
+
) => {
|
|
308
|
+
const scopeConfig = advancedPolicy.scope
|
|
309
|
+
if (!scopeConfig) return null
|
|
310
|
+
const env = state.env && typeof state.env === 'object' ? state.env : null
|
|
311
|
+
const frontmatterOverride = getFrontmatterNumberingOverride(env)
|
|
312
|
+
let separator = hasOwnOption(frontmatterOverride, 'separator')
|
|
313
|
+
? frontmatterOverride.separator
|
|
314
|
+
: advancedPolicy.separator
|
|
315
|
+
let scopeMode = hasOwnOption(frontmatterOverride, 'scope')
|
|
316
|
+
? frontmatterOverride.scope
|
|
317
|
+
: 'auto'
|
|
318
|
+
let explicitScope = null
|
|
319
|
+
let hasExplicitScope = false
|
|
320
|
+
if (env && hasOwnOption(env, 'figureCaptionNumbering')) {
|
|
321
|
+
const namespace = env.figureCaptionNumbering
|
|
322
|
+
if (!namespace || typeof namespace !== 'object' || Array.isArray(namespace)) {
|
|
323
|
+
throw new TypeError('env.figureCaptionNumbering must be an object.')
|
|
324
|
+
}
|
|
325
|
+
if (hasOwnOption(namespace, 'separator')) {
|
|
326
|
+
separator = normalizeNumberingSeparator(
|
|
327
|
+
namespace.separator,
|
|
328
|
+
'env.figureCaptionNumbering.separator',
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
if (hasOwnOption(namespace, 'scope')) {
|
|
332
|
+
if (typeof namespace.scope === 'string') {
|
|
333
|
+
scopeMode = normalizeNumberingScopeMode(
|
|
334
|
+
namespace.scope,
|
|
335
|
+
'env.figureCaptionNumbering.scope',
|
|
336
|
+
)
|
|
337
|
+
} else {
|
|
338
|
+
explicitScope = namespace.scope
|
|
339
|
+
hasExplicitScope = true
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const currentUnscopedContext = separator === advancedPolicy.separator && unscopedContext
|
|
344
|
+
? unscopedContext
|
|
345
|
+
: createUnscopedNumberingContext(separator)
|
|
346
|
+
const scopeState = {
|
|
347
|
+
separator,
|
|
348
|
+
repeatScope: scopeConfig.repeatScope,
|
|
349
|
+
nextResetSequenceKey: 1,
|
|
350
|
+
currentContext: currentUnscopedContext,
|
|
351
|
+
fixed: false,
|
|
352
|
+
scopeConfig,
|
|
353
|
+
}
|
|
354
|
+
if (hasExplicitScope) {
|
|
355
|
+
scopeState.currentContext = normalizeExplicitScopeOverride(explicitScope, separator)
|
|
356
|
+
scopeState.fixed = true
|
|
357
|
+
return scopeState
|
|
358
|
+
}
|
|
359
|
+
if (scopeMode === 'document') {
|
|
360
|
+
scopeState.fixed = true
|
|
361
|
+
return scopeState
|
|
362
|
+
}
|
|
363
|
+
if (scopeConfig.usesFrontmatter) {
|
|
364
|
+
const initialScope = resolveInitialFrontmatterScope(state, scopeConfig)
|
|
365
|
+
if (initialScope) applySemanticScope(scopeState, initialScope)
|
|
366
|
+
}
|
|
367
|
+
return scopeState
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export const createFigureCaptionScopeRuntime = (
|
|
371
|
+
state,
|
|
372
|
+
numberingPolicy,
|
|
373
|
+
unscopedContext,
|
|
374
|
+
) => {
|
|
375
|
+
if (!numberingPolicy) return null
|
|
376
|
+
return createFigureCaptionScopeRuntimeFromPolicyState(
|
|
377
|
+
state,
|
|
378
|
+
getFigureCaptionNumberingPolicyState(numberingPolicy),
|
|
379
|
+
unscopedContext,
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export const updateFigureCaptionScopeFromHeading = (tokens, index, scopeState) => {
|
|
384
|
+
const token = tokens[index]
|
|
385
|
+
if (!token || typeof token.tag !== 'string') return false
|
|
386
|
+
const level = token.tag.length === 2 && token.tag.charCodeAt(0) === 0x68
|
|
387
|
+
? token.tag.charCodeAt(1) - 0x30
|
|
388
|
+
: 0
|
|
389
|
+
if (!scopeState.scopeConfig.headingLevelLookup[level]) return false
|
|
390
|
+
const semanticScope = recognizeScopeFromInline(tokens[index + 1])
|
|
391
|
+
if (!semanticScope) return false
|
|
392
|
+
applySemanticScope(scopeState, semanticScope)
|
|
393
|
+
return true
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const isValidSourceMap = (map) => (
|
|
397
|
+
Array.isArray(map) &&
|
|
398
|
+
Number.isSafeInteger(map[0]) &&
|
|
399
|
+
Number.isSafeInteger(map[1]) &&
|
|
400
|
+
map[0] >= 0 &&
|
|
401
|
+
map[1] > map[0]
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
export const createFigureCaptionScopeTimeline = (state, numberingPolicy) => {
|
|
405
|
+
if (!state || typeof state !== 'object' || !Array.isArray(state.tokens)) {
|
|
406
|
+
throw new TypeError('state must be a markdown-it StateCore with a tokens array.')
|
|
407
|
+
}
|
|
408
|
+
if (!numberingPolicy) return null
|
|
409
|
+
const advancedPolicy = getFigureCaptionNumberingPolicyState(numberingPolicy)
|
|
410
|
+
if (!advancedPolicy.scope) {
|
|
411
|
+
return Object.freeze({
|
|
412
|
+
initialContext: createUnscopedNumberingContext(advancedPolicy.separator),
|
|
413
|
+
boundaries: emptyScopeBoundaries,
|
|
414
|
+
hasUnmappableBoundaries: false,
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
const scopeState = createFigureCaptionScopeRuntimeFromPolicyState(
|
|
418
|
+
state,
|
|
419
|
+
advancedPolicy,
|
|
420
|
+
null,
|
|
421
|
+
)
|
|
422
|
+
const initialContext = scopeState.currentContext
|
|
423
|
+
let boundaries = null
|
|
424
|
+
let hasUnmappableBoundaries = false
|
|
425
|
+
if (!scopeState.fixed && scopeState.scopeConfig.usesHeading) {
|
|
426
|
+
const nestedContainerStack = []
|
|
427
|
+
for (let index = 0; index < state.tokens.length; index++) {
|
|
428
|
+
const token = state.tokens[index]
|
|
429
|
+
const containerType = getFigureCaptionScopeNestedContainerType(token)
|
|
430
|
+
if (containerType) {
|
|
431
|
+
nestedContainerStack.push(containerType + '_close')
|
|
432
|
+
continue
|
|
433
|
+
}
|
|
434
|
+
if (nestedContainerStack.length > 0) {
|
|
435
|
+
const parentCloseType = nestedContainerStack[nestedContainerStack.length - 1]
|
|
436
|
+
if (token && token.type === parentCloseType) {
|
|
437
|
+
nestedContainerStack.pop()
|
|
438
|
+
}
|
|
439
|
+
continue
|
|
440
|
+
}
|
|
441
|
+
if (
|
|
442
|
+
!token ||
|
|
443
|
+
token.type !== 'heading_open' ||
|
|
444
|
+
!updateFigureCaptionScopeFromHeading(state.tokens, index, scopeState)
|
|
445
|
+
) {
|
|
446
|
+
continue
|
|
447
|
+
}
|
|
448
|
+
if (!isValidSourceMap(token.map)) {
|
|
449
|
+
hasUnmappableBoundaries = true
|
|
450
|
+
continue
|
|
451
|
+
}
|
|
452
|
+
if (!boundaries) boundaries = []
|
|
453
|
+
boundaries.push(Object.freeze({
|
|
454
|
+
tokenIndex: index,
|
|
455
|
+
sourceStartLine: token.map[0],
|
|
456
|
+
sourceEndLine: token.map[1],
|
|
457
|
+
context: scopeState.currentContext,
|
|
458
|
+
}))
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return Object.freeze({
|
|
462
|
+
initialContext,
|
|
463
|
+
boundaries: boundaries ? Object.freeze(boundaries) : emptyScopeBoundaries,
|
|
464
|
+
hasUnmappableBoundaries,
|
|
465
|
+
})
|
|
466
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
normalizeFigureCaptionNumberingPolicy,
|
|
3
|
+
} from './caption-numbering/options.js'
|
|
4
|
+
export {
|
|
5
|
+
createFigureCaptionNumberCodec,
|
|
6
|
+
} from './caption-numbering/number-codec.js'
|
|
7
|
+
export {
|
|
8
|
+
createFigureCaptionCounterKeyResolver,
|
|
9
|
+
} from './caption-numbering/counter-series.js'
|
|
10
|
+
export {
|
|
11
|
+
createFigureCaptionScopeTimeline,
|
|
12
|
+
} from './caption-numbering/scope.js'
|