@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,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMarkRegStateForLanguages,
|
|
3
|
+
isCaptionLabelForMark,
|
|
4
|
+
} from 'p7d-markdown-it-p-captions'
|
|
5
|
+
|
|
6
|
+
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key)
|
|
7
|
+
|
|
8
|
+
const validateCaptionDecision = (captionDecision) => {
|
|
9
|
+
if (!captionDecision || typeof captionDecision !== 'object' || Array.isArray(captionDecision)) {
|
|
10
|
+
throw new TypeError('captionDecision must be an object returned by p-captions.')
|
|
11
|
+
}
|
|
12
|
+
if (typeof captionDecision.mark !== 'string' || captionDecision.mark.length === 0) {
|
|
13
|
+
throw new TypeError('captionDecision.mark must be a non-empty canonical mark.')
|
|
14
|
+
}
|
|
15
|
+
if (captionDecision.mark === 'code' || captionDecision.mark === 'samp') {
|
|
16
|
+
throw new TypeError('captionDecision.mark must use the canonical pre-code or pre-samp mark.')
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const createFigureCaptionCounterKeyResolverFromMarkRegState = (markRegState) => {
|
|
21
|
+
const resolveCounterKey = (captionDecision) => {
|
|
22
|
+
const mark = captionDecision.mark
|
|
23
|
+
if (mark === 'img') return 'figure'
|
|
24
|
+
if (mark === 'pre-code') return 'listing'
|
|
25
|
+
if (mark === 'pre-samp') {
|
|
26
|
+
const labelText = captionDecision.labelText
|
|
27
|
+
if (isCaptionLabelForMark(labelText, 'img', markRegState)) return 'figure'
|
|
28
|
+
if (isCaptionLabelForMark(labelText, 'pre-code', markRegState)) return 'listing'
|
|
29
|
+
return 'samp'
|
|
30
|
+
}
|
|
31
|
+
return mark
|
|
32
|
+
}
|
|
33
|
+
return Object.freeze(resolveCounterKey)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const createFigureCaptionCounterKeyResolver = (options) => {
|
|
37
|
+
if (options !== undefined && (
|
|
38
|
+
!options ||
|
|
39
|
+
typeof options !== 'object' ||
|
|
40
|
+
Array.isArray(options)
|
|
41
|
+
)) {
|
|
42
|
+
throw new TypeError('counter-key resolver options must be an object or undefined.')
|
|
43
|
+
}
|
|
44
|
+
const normalizedOptions = options || {}
|
|
45
|
+
const keys = Object.keys(normalizedOptions)
|
|
46
|
+
for (let index = 0; index < keys.length; index++) {
|
|
47
|
+
if (keys[index] !== 'languages') {
|
|
48
|
+
throw new TypeError(`counter-key resolver option "${keys[index]}" is not supported.`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const languages = hasOwn(normalizedOptions, 'languages')
|
|
52
|
+
? normalizedOptions.languages
|
|
53
|
+
: undefined
|
|
54
|
+
const resolveCounterKey = createFigureCaptionCounterKeyResolverFromMarkRegState(
|
|
55
|
+
getMarkRegStateForLanguages(languages),
|
|
56
|
+
)
|
|
57
|
+
const resolveValidatedCounterKey = (captionDecision) => {
|
|
58
|
+
validateCaptionDecision(captionDecision)
|
|
59
|
+
return resolveCounterKey(captionDecision)
|
|
60
|
+
}
|
|
61
|
+
return Object.freeze(resolveValidatedCounterKey)
|
|
62
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { markAfterNum } from 'p7d-markdown-it-p-captions'
|
|
2
|
+
|
|
3
|
+
const captionNumberReg = new RegExp('^(?:' + markAfterNum + ')$')
|
|
4
|
+
const numberingContexts = new WeakSet()
|
|
5
|
+
|
|
6
|
+
const parseAsciiPositiveIntegerOrNull = (text) => {
|
|
7
|
+
if (typeof text !== 'string' || text.length === 0) return null
|
|
8
|
+
let value = 0
|
|
9
|
+
for (let index = 0; index < text.length; index++) {
|
|
10
|
+
const code = text.charCodeAt(index)
|
|
11
|
+
if (code < 0x30 || code > 0x39) return null
|
|
12
|
+
value = value * 10 + code - 0x30
|
|
13
|
+
}
|
|
14
|
+
return Number.isSafeInteger(value) && value > 0 ? value : null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const freezeNumberingContext = (context) => {
|
|
18
|
+
const frozen = Object.freeze(context)
|
|
19
|
+
numberingContexts.add(frozen)
|
|
20
|
+
return frozen
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const createUnscopedNumberingContext = (separator) => freezeNumberingContext({
|
|
24
|
+
scoped: false,
|
|
25
|
+
scopeKey: null,
|
|
26
|
+
sequenceKey: null,
|
|
27
|
+
displayPrefix: '',
|
|
28
|
+
separator,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const createScopedNumberingContext = (
|
|
32
|
+
scopeKey,
|
|
33
|
+
sequenceKey,
|
|
34
|
+
displayPrefix,
|
|
35
|
+
separator,
|
|
36
|
+
) => freezeNumberingContext({
|
|
37
|
+
scoped: true,
|
|
38
|
+
scopeKey,
|
|
39
|
+
sequenceKey,
|
|
40
|
+
displayPrefix,
|
|
41
|
+
separator,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export const getFigureCaptionNumberingContext = (value) => {
|
|
45
|
+
if (!value || !numberingContexts.has(value)) {
|
|
46
|
+
throw new TypeError('numberingContext must be created by the figure caption-numbering API.')
|
|
47
|
+
}
|
|
48
|
+
return value
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const getCaptionNumberingContext = (captionContext) => {
|
|
52
|
+
return getFigureCaptionNumberingContext(captionContext && captionContext.numbering)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const parseFigureCaptionExplicitNumberUnchecked = (number, numbering) => {
|
|
56
|
+
if (!numbering.scoped) return parseAsciiPositiveIntegerOrNull(number)
|
|
57
|
+
const prefix = numbering.displayPrefix + numbering.separator
|
|
58
|
+
if (!number.startsWith(prefix)) return null
|
|
59
|
+
return parseAsciiPositiveIntegerOrNull(number.slice(prefix.length))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const formatFigureCaptionGeneratedNumberUnchecked = (sequence, numbering) => {
|
|
63
|
+
return numbering.scoped
|
|
64
|
+
? numbering.displayPrefix + numbering.separator + sequence
|
|
65
|
+
: String(sequence)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const parseFigureCaptionExplicitNumber = (number, context) => {
|
|
69
|
+
if (typeof number !== 'string') {
|
|
70
|
+
throw new TypeError('number must be a string.')
|
|
71
|
+
}
|
|
72
|
+
return parseFigureCaptionExplicitNumberUnchecked(
|
|
73
|
+
number,
|
|
74
|
+
getFigureCaptionNumberingContext(context),
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const formatFigureCaptionGeneratedNumber = (sequence, context) => {
|
|
79
|
+
if (!Number.isSafeInteger(sequence) || sequence < 1) {
|
|
80
|
+
throw new RangeError('sequence must be a positive safe integer.')
|
|
81
|
+
}
|
|
82
|
+
return formatFigureCaptionGeneratedNumberUnchecked(
|
|
83
|
+
sequence,
|
|
84
|
+
getFigureCaptionNumberingContext(context),
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const numberCodec = Object.freeze({
|
|
89
|
+
parseExplicit(number, context) {
|
|
90
|
+
return parseFigureCaptionExplicitNumber(number, context)
|
|
91
|
+
},
|
|
92
|
+
format(sequence, context) {
|
|
93
|
+
const number = formatFigureCaptionGeneratedNumber(sequence, context)
|
|
94
|
+
if (!captionNumberReg.test(number)) {
|
|
95
|
+
throw new RangeError('The generated figure caption number exceeds the p-captions number grammar.')
|
|
96
|
+
}
|
|
97
|
+
return number
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
export const createFigureCaptionNumberCodec = () => numberCodec
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const policyStateByPolicy = new WeakMap()
|
|
2
|
+
|
|
3
|
+
export const normalizeNumberingSeparator = (value, optionName) => {
|
|
4
|
+
if (value !== '-' && value !== '.') {
|
|
5
|
+
throw new TypeError(`${optionName} must be "-" or ".".`)
|
|
6
|
+
}
|
|
7
|
+
return value
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const normalizeScopeSources = (value) => {
|
|
11
|
+
const source = value === undefined ? ['frontmatter', 'heading'] : value
|
|
12
|
+
if (!Array.isArray(source)) {
|
|
13
|
+
throw new TypeError('autoLabelNumberPolicy.scope.sources must be an array.')
|
|
14
|
+
}
|
|
15
|
+
const sources = []
|
|
16
|
+
for (let index = 0; index < source.length; index++) {
|
|
17
|
+
const entry = source[index]
|
|
18
|
+
if (entry !== 'frontmatter' && entry !== 'heading') {
|
|
19
|
+
throw new TypeError('autoLabelNumberPolicy.scope.sources entries must be "frontmatter" or "heading".')
|
|
20
|
+
}
|
|
21
|
+
if (sources.indexOf(entry) === -1) sources.push(entry)
|
|
22
|
+
}
|
|
23
|
+
return sources
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const normalizeHeadingLevels = (value) => {
|
|
27
|
+
const source = value === undefined ? [1] : value
|
|
28
|
+
if (!Array.isArray(source)) {
|
|
29
|
+
throw new TypeError('autoLabelNumberPolicy.scope.headingLevels must be an array.')
|
|
30
|
+
}
|
|
31
|
+
const levels = []
|
|
32
|
+
for (let index = 0; index < source.length; index++) {
|
|
33
|
+
const level = source[index]
|
|
34
|
+
if (!Number.isInteger(level) || level < 1 || level > 6) {
|
|
35
|
+
throw new RangeError('autoLabelNumberPolicy.scope.headingLevels entries must be integers from 1 through 6.')
|
|
36
|
+
}
|
|
37
|
+
if (levels.indexOf(level) === -1) levels.push(level)
|
|
38
|
+
}
|
|
39
|
+
return levels
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const normalizeNumberingScopeMode = (value, optionName) => {
|
|
43
|
+
if (value !== 'auto' && value !== 'document') {
|
|
44
|
+
throw new TypeError(`${optionName} must be "auto" or "document".`)
|
|
45
|
+
}
|
|
46
|
+
return value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const normalizeFigureCaptionNumberingPolicy = (value) => {
|
|
50
|
+
if (value === null) return null
|
|
51
|
+
const source = value === undefined ? {} : value
|
|
52
|
+
if (typeof source !== 'object' || Array.isArray(source)) {
|
|
53
|
+
throw new TypeError('autoLabelNumberPolicy must be an object or null.')
|
|
54
|
+
}
|
|
55
|
+
const separator = normalizeNumberingSeparator(
|
|
56
|
+
source.separator === undefined ? '.' : source.separator,
|
|
57
|
+
'autoLabelNumberPolicy.separator',
|
|
58
|
+
)
|
|
59
|
+
let scope = null
|
|
60
|
+
const requestedScope = source.scope
|
|
61
|
+
if (requestedScope !== null && requestedScope !== 'document') {
|
|
62
|
+
const scopeValue = requestedScope === undefined || requestedScope === 'auto'
|
|
63
|
+
? {}
|
|
64
|
+
: requestedScope
|
|
65
|
+
if (typeof scopeValue !== 'object' || Array.isArray(scopeValue)) {
|
|
66
|
+
throw new TypeError('autoLabelNumberPolicy.scope must be "auto", "document", an object, or null.')
|
|
67
|
+
}
|
|
68
|
+
const sources = normalizeScopeSources(scopeValue.sources)
|
|
69
|
+
const headingLevels = normalizeHeadingLevels(scopeValue.headingLevels)
|
|
70
|
+
const repeatScope = scopeValue.repeatScope === undefined ? 'continue' : scopeValue.repeatScope
|
|
71
|
+
if (repeatScope !== 'continue' && repeatScope !== 'reset') {
|
|
72
|
+
throw new TypeError('autoLabelNumberPolicy.scope.repeatScope must be "continue" or "reset".')
|
|
73
|
+
}
|
|
74
|
+
const resolveFrontmatterTitle = scopeValue.resolveFrontmatterTitle
|
|
75
|
+
if (resolveFrontmatterTitle !== undefined && resolveFrontmatterTitle !== null && typeof resolveFrontmatterTitle !== 'function') {
|
|
76
|
+
throw new TypeError('autoLabelNumberPolicy.scope.resolveFrontmatterTitle must be a function or null.')
|
|
77
|
+
}
|
|
78
|
+
scope = Object.freeze({
|
|
79
|
+
headingLevelLookup: Object.freeze(headingLevels.reduce((lookup, level) => {
|
|
80
|
+
lookup[level] = true
|
|
81
|
+
return lookup
|
|
82
|
+
}, {})),
|
|
83
|
+
repeatScope,
|
|
84
|
+
resolveFrontmatterTitle: resolveFrontmatterTitle || null,
|
|
85
|
+
usesFrontmatter: sources.indexOf('frontmatter') !== -1,
|
|
86
|
+
usesHeading: sources.indexOf('heading') !== -1,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
const state = Object.freeze({ separator, scope })
|
|
90
|
+
const policy = Object.freeze({})
|
|
91
|
+
policyStateByPolicy.set(policy, state)
|
|
92
|
+
return policy
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const getFigureCaptionNumberingPolicyState = (policy) => {
|
|
96
|
+
const state = policy && policyStateByPolicy.get(policy)
|
|
97
|
+
if (!state) {
|
|
98
|
+
throw new TypeError('numberingPolicy must be created by normalizeFigureCaptionNumberingPolicy().')
|
|
99
|
+
}
|
|
100
|
+
return state
|
|
101
|
+
}
|