@peaceroad/markdown-imgattr-to-pcaption 0.1.0 → 0.2.1
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 +93 -4
- package/index.js +279 -127
- package/package.json +11 -2
- package/script/set-img-figure-caption.js +305 -0
- package/test/examples-img-title-attr.txt +0 -87
- package/test/examples-label-lang.txt +0 -16
- package/test/examples.txt +0 -88
- package/test/test.js +0 -77
package/README.md
CHANGED
|
@@ -64,7 +64,9 @@ setMarkdownImgAttrToPCaption(markdownCont)
|
|
|
64
64
|
|
|
65
65
|
## Option
|
|
66
66
|
|
|
67
|
-
### imgTitleCaption
|
|
67
|
+
### imgTitleCaption
|
|
68
|
+
|
|
69
|
+
Default: false.
|
|
68
70
|
|
|
69
71
|
```
|
|
70
72
|
[Input]
|
|
@@ -85,7 +87,9 @@ setMarkdownImgAttrToPCaption(markdownCont)
|
|
|
85
87
|
段落。段落。段落。
|
|
86
88
|
```
|
|
87
89
|
|
|
88
|
-
### labelLang
|
|
90
|
+
### labelLang
|
|
91
|
+
|
|
92
|
+
Default: 'en'.
|
|
89
93
|
|
|
90
94
|
```
|
|
91
95
|
[Input]
|
|
@@ -99,9 +103,94 @@ setMarkdownImgAttrToPCaption(markdownCont)
|
|
|
99
103
|
[Output]
|
|
100
104
|
段落。段落。段落。
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
図 キャプション
|
|
103
107
|
|
|
104
108
|

|
|
105
109
|
|
|
106
110
|
段落。段落。段落。
|
|
107
|
-
```
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### autoLangDetection
|
|
114
|
+
|
|
115
|
+
Default: true. To force a specific `labelLang`, set `autoLangDetection: false`.
|
|
116
|
+
When `autoLangDetection` is true, it can override an explicitly set `labelLang` (it is treated as the fallback when detection cannot decide).
|
|
117
|
+
|
|
118
|
+
Detect `labelLang` from the first image caption line. If the caption text contains Japanese characters, it sets `labelLang: 'ja'`. Otherwise, if the caption contains ASCII letters, it sets `labelLang: 'en'` (symbols/emoji are ignored). If the caption contains non-ASCII letters such as accents, the existing `labelLang` is left unchanged.
|
|
119
|
+
|
|
120
|
+
Example (non-ASCII letters keep the current `labelLang`):
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
[Input]
|
|
124
|
+
段落。
|
|
125
|
+
|
|
126
|
+

|
|
127
|
+
|
|
128
|
+
段落。
|
|
129
|
+
|
|
130
|
+
[Output]
|
|
131
|
+
段落。
|
|
132
|
+
|
|
133
|
+
Figure. Café
|
|
134
|
+
|
|
135
|
+

|
|
136
|
+
|
|
137
|
+
段落。
|
|
138
|
+
```
|
|
139
|
+
Only `ja` and `en` are auto-detected. For other languages, set `labelLang` explicitly (and use `labelSet` as needed) or leave auto-detection off.
|
|
140
|
+
Detection runs only once on the first eligible image line; subsequent images do not affect the language choice.
|
|
141
|
+
|
|
142
|
+
### labelSet
|
|
143
|
+
|
|
144
|
+
Override the auto-inserted label, joint, and space for captions without labels (useful for other languages).
|
|
145
|
+
`labelSet` accepts either a single config for the current `labelLang` or a per-language map.
|
|
146
|
+
If a matching language entry is not found, the default (English) label config is used.
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
setMarkdownImgAttrToPCaption(markdownCont, {
|
|
150
|
+
labelSet: { label: '図', joint: ':', space: ' ' }
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
setMarkdownImgAttrToPCaption(markdownCont, {
|
|
156
|
+
labelSet: {
|
|
157
|
+
en: { label: 'Figure', joint: '.', space: ' ' },
|
|
158
|
+
ja: { label: '図', joint: ' ', space: '' },
|
|
159
|
+
fr: { label: 'Fig', joint: '.', space: ' ' },
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Notes
|
|
165
|
+
|
|
166
|
+
- Only converts images that are on a single line and surrounded by blank lines. Inline images or list items are not changed.
|
|
167
|
+
- Skips fenced code blocks (``` or ~~~).
|
|
168
|
+
- Label detection uses `p7d-markdown-it-p-captions` label patterns (en/ja by default). `labelSet` only affects auto-inserted labels when no label is detected.
|
|
169
|
+
- `autoLangDetection` inspects the first eligible image line and uses the caption text (title when `imgTitleCaption: true`, otherwise alt). If the caption text is empty, it falls back to alt.
|
|
170
|
+
|
|
171
|
+
## Browser DOM helper (live preview)
|
|
172
|
+
|
|
173
|
+
This package also provides a DOM helper to turn image alt/title into `<figure><figcaption>` on the fly.
|
|
174
|
+
It is useful for live preview environments that do not re-run markdown-it on each edit.
|
|
175
|
+
This helper does not insert label prefixes; it uses the raw alt/title text as the caption.
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<script type="module">
|
|
179
|
+
import setImgFigureCaption from '@peaceroad/markdown-imgattr-to-pcaption/script/set-img-figure-caption.js'
|
|
180
|
+
|
|
181
|
+
await setImgFigureCaption({
|
|
182
|
+
imgAltCaption: true,
|
|
183
|
+
imgTitleCaption: false,
|
|
184
|
+
observe: true
|
|
185
|
+
})
|
|
186
|
+
</script>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### DOM helper options
|
|
190
|
+
|
|
191
|
+
- `imgAltCaption` (boolean|string): use `alt` text as caption (strings are treated as true)
|
|
192
|
+
- `imgTitleCaption` (boolean|string): use `title` text as caption (strings are treated as true)
|
|
193
|
+
- `preferAlt` (boolean): when both are enabled, prefer alt (default true)
|
|
194
|
+
- `figureClass` (string): class name for created figures (default `f-img`)
|
|
195
|
+
- `readMeta` (boolean): read `<meta name="markdown-frontmatter">` and apply `imgAltCaption` / `imgTitleCaption`
|
|
196
|
+
- `observe` (boolean): re-run on DOM changes (MutationObserver)
|
package/index.js
CHANGED
|
@@ -1,139 +1,305 @@
|
|
|
1
|
+
import { markAfterNum, markReg, joint } from 'p7d-markdown-it-p-captions'
|
|
2
|
+
import langSets from 'p7d-markdown-it-p-captions/lang.js'
|
|
3
|
+
|
|
4
|
+
const imageLineReg = /^([ \t]*?)!\[ *?(.*?) *?\]\(([^ ]*?)( +"(.*?)")?\)( *(?:{.*?})?)$/
|
|
5
|
+
const backquoteFenceReg = /^[ \t]*```/
|
|
6
|
+
const tildeFenceReg = /^[ \t]*~~~/
|
|
7
|
+
const blankLineReg = /^[ \t]*$/
|
|
8
|
+
const asciiOnlyReg = /^[\x00-\x7F]*$/
|
|
9
|
+
const whitespaceOnlyReg = /^[\s\u3000]+$/
|
|
10
|
+
const unicodeLetterReg = (() => {
|
|
11
|
+
try {
|
|
12
|
+
return new RegExp('\\p{L}', 'u')
|
|
13
|
+
} catch {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
})()
|
|
17
|
+
|
|
18
|
+
const DEFAULT_LABEL_CONFIG_MAP = {
|
|
19
|
+
en: { label: 'Figure', joint: '.', space: ' ' },
|
|
20
|
+
ja: { label: '図', joint: ' ', space: '' },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const buildLabelOnlyReg = () => {
|
|
24
|
+
const langs = Object.keys(langSets)
|
|
25
|
+
if (langs.length === 0) return null
|
|
26
|
+
|
|
27
|
+
const patterns = []
|
|
28
|
+
for (const lang of langs) {
|
|
29
|
+
const data = langSets[lang]
|
|
30
|
+
if (!data || !data.markReg || !data.markReg.img) continue
|
|
31
|
+
let pattern = data.markReg.img
|
|
32
|
+
if (data.type && data.type['inter-word-space']) {
|
|
33
|
+
pattern = pattern.replace(/([a-z])/g, (match) => '[' + match + match.toUpperCase() + ']')
|
|
34
|
+
}
|
|
35
|
+
patterns.push(pattern)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (patterns.length === 0) return null
|
|
39
|
+
return new RegExp('^(' + patterns.join('|') + ')([ .]?' + markAfterNum + ')?$')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const labelOnlyReg = buildLabelOnlyReg()
|
|
43
|
+
const jointSuffixReg = new RegExp(joint + '$')
|
|
44
|
+
|
|
45
|
+
const getCaptionText = (imgLine, opt) => {
|
|
46
|
+
if (opt.imgTitleCaption) {
|
|
47
|
+
return imgLine[5] || ''
|
|
48
|
+
}
|
|
49
|
+
return imgLine[2] || ''
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const getCaptionTextForDetection = (imgLine, opt) => {
|
|
53
|
+
const captionText = getCaptionText(imgLine, opt)
|
|
54
|
+
if (captionText) {
|
|
55
|
+
return captionText
|
|
56
|
+
}
|
|
57
|
+
return imgLine[2] || ''
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const isAsciiOnly = (value) => asciiOnlyReg.test(value)
|
|
61
|
+
const isAsciiLetterCode = (code) => (
|
|
62
|
+
(code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const isJapaneseCodePoint = (code) => {
|
|
66
|
+
return (
|
|
67
|
+
(code >= 0x3040 && code <= 0x30ff) || // Hiragana + Katakana
|
|
68
|
+
(code >= 0x31f0 && code <= 0x31ff) || // Katakana extensions
|
|
69
|
+
(code >= 0x4e00 && code <= 0x9fff) || // CJK Unified Ideographs
|
|
70
|
+
(code >= 0xff66 && code <= 0xff9f) // Half-width Katakana
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const containsJapanese = (value) => {
|
|
75
|
+
for (const ch of value) {
|
|
76
|
+
const code = ch.codePointAt(0)
|
|
77
|
+
if (code !== undefined && isJapaneseCodePoint(code)) {
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const detectAutoLang = (value) => {
|
|
85
|
+
let hasAsciiLetter = false
|
|
86
|
+
for (const ch of value) {
|
|
87
|
+
const code = ch.codePointAt(0)
|
|
88
|
+
if (code === undefined) continue
|
|
89
|
+
if (isJapaneseCodePoint(code)) return 'ja'
|
|
90
|
+
if (code <= 0x7f) {
|
|
91
|
+
if (isAsciiLetterCode(code)) {
|
|
92
|
+
hasAsciiLetter = true
|
|
93
|
+
}
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if (unicodeLetterReg) {
|
|
97
|
+
if (unicodeLetterReg.test(ch)) return null
|
|
98
|
+
} else if (ch.toLowerCase() !== ch.toUpperCase()) {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return hasAsciiLetter ? 'en' : null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const getInterWordSpace = (labelLang, labelText) => {
|
|
106
|
+
const lang = langSets[labelLang]
|
|
107
|
+
if (lang && lang.type) {
|
|
108
|
+
return Boolean(lang.type['inter-word-space'])
|
|
109
|
+
}
|
|
110
|
+
return isAsciiOnly(labelText)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const normalizeLabelConfig = (value) => {
|
|
114
|
+
if (!value || typeof value !== 'object') return null
|
|
115
|
+
const config = {}
|
|
116
|
+
const labelValue = (typeof value.label === 'string')
|
|
117
|
+
? value.label
|
|
118
|
+
: (typeof value.lable === 'string' ? value.lable : undefined)
|
|
119
|
+
if (labelValue !== undefined) {
|
|
120
|
+
config.label = labelValue
|
|
121
|
+
}
|
|
122
|
+
if (Object.prototype.hasOwnProperty.call(value, 'joint')) {
|
|
123
|
+
config.joint = String(value.joint)
|
|
124
|
+
}
|
|
125
|
+
if (Object.prototype.hasOwnProperty.call(value, 'space')) {
|
|
126
|
+
config.space = String(value.space)
|
|
127
|
+
}
|
|
128
|
+
if (Object.keys(config).length === 0) return null
|
|
129
|
+
return config
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const mergeLabelConfig = (base, override) => {
|
|
133
|
+
if (!override) return base
|
|
134
|
+
const merged = { ...base }
|
|
135
|
+
if (Object.prototype.hasOwnProperty.call(override, 'label')) {
|
|
136
|
+
merged.label = override.label
|
|
137
|
+
}
|
|
138
|
+
if (Object.prototype.hasOwnProperty.call(override, 'joint')) {
|
|
139
|
+
merged.joint = override.joint
|
|
140
|
+
}
|
|
141
|
+
if (Object.prototype.hasOwnProperty.call(override, 'space')) {
|
|
142
|
+
merged.space = override.space
|
|
143
|
+
}
|
|
144
|
+
return merged
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const getDefaultLabelConfig = (labelLang) => {
|
|
148
|
+
const base = DEFAULT_LABEL_CONFIG_MAP[labelLang] || DEFAULT_LABEL_CONFIG_MAP.en
|
|
149
|
+
return { label: base.label, joint: base.joint, space: base.space }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const resolveLabelConfig = (opt) => {
|
|
153
|
+
let config = getDefaultLabelConfig(opt.labelLang)
|
|
154
|
+
let mapConfig = null
|
|
155
|
+
let singleConfig = null
|
|
156
|
+
if (opt.labelSet && typeof opt.labelSet === 'object') {
|
|
157
|
+
singleConfig = normalizeLabelConfig(opt.labelSet)
|
|
158
|
+
if (!singleConfig) {
|
|
159
|
+
mapConfig = normalizeLabelConfig(opt.labelSet[opt.labelLang])
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
config = mergeLabelConfig(config, mapConfig)
|
|
163
|
+
config = mergeLabelConfig(config, singleConfig)
|
|
164
|
+
|
|
165
|
+
if (!config.label) {
|
|
166
|
+
config.label = DEFAULT_LABEL_CONFIG_MAP.en.label
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (config.joint === undefined || config.space === undefined) {
|
|
170
|
+
const interWordSpace = getInterWordSpace(opt.labelLang, config.label)
|
|
171
|
+
if (config.joint === undefined) {
|
|
172
|
+
config.joint = interWordSpace ? '.' : ' '
|
|
173
|
+
}
|
|
174
|
+
if (config.space === undefined) {
|
|
175
|
+
config.space = interWordSpace ? ' ' : ' '
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return config
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const buildLabelPrefix = (labelConfig, hasCaption) => {
|
|
182
|
+
const labelText = labelConfig.label || ''
|
|
183
|
+
const joint = labelConfig.joint || ''
|
|
184
|
+
const space = labelConfig.space || ''
|
|
185
|
+
let prefix = labelText
|
|
186
|
+
|
|
187
|
+
if (joint) {
|
|
188
|
+
const jointIsWhitespace = whitespaceOnlyReg.test(joint)
|
|
189
|
+
if (hasCaption || !jointIsWhitespace) {
|
|
190
|
+
if (!prefix.endsWith(joint)) {
|
|
191
|
+
prefix += joint
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (hasCaption && space) {
|
|
197
|
+
if (!prefix.endsWith(space)) {
|
|
198
|
+
if (!(space === joint && prefix.endsWith(joint))) {
|
|
199
|
+
prefix += space
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return prefix
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const buildLabelMeta = (opt) => {
|
|
207
|
+
return resolveLabelConfig(opt)
|
|
208
|
+
}
|
|
209
|
+
|
|
1
210
|
const setMarkdownImgAttrToPCaption = (markdown, option) => {
|
|
2
211
|
|
|
3
212
|
const opt = {
|
|
4
213
|
imgAltCaption : true,
|
|
5
214
|
imgTitleCaption: false,
|
|
6
|
-
labelLang: '
|
|
215
|
+
labelLang: 'en',
|
|
216
|
+
autoLangDetection: true,
|
|
217
|
+
labelSet: null, // { label: '図', joint: ':', space: ' ' } or { ja: { label: '図', joint: ' ', space: '' }, en: { label: 'Figure', joint: '.', space: ' ' } }
|
|
7
218
|
}
|
|
8
|
-
if (option
|
|
219
|
+
if (option && typeof option === 'object') {
|
|
9
220
|
if (option.imgTitleCaption) {
|
|
10
221
|
opt.imgTitleCaption = option.imgTitleCaption
|
|
11
222
|
}
|
|
12
223
|
if (option.labelLang) {
|
|
13
224
|
opt.labelLang = option.labelLang
|
|
14
225
|
}
|
|
226
|
+
if (option.labelSet && typeof option.labelSet === 'object') {
|
|
227
|
+
opt.labelSet = option.labelSet
|
|
228
|
+
}
|
|
229
|
+
if (Object.prototype.hasOwnProperty.call(option, 'autoLangDetection')) {
|
|
230
|
+
opt.autoLangDetection = Boolean(option.autoLangDetection)
|
|
231
|
+
}
|
|
15
232
|
}
|
|
16
233
|
if (opt.imgTitleCaption) opt.imgAltCaption = false
|
|
17
234
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let lineBreaks = markdown.match(/\r\n|\n/g);
|
|
235
|
+
const lines = markdown.split(/\r\n|\n/)
|
|
236
|
+
const lineBreaks = markdown.match(/\r\n|\n/g) || []
|
|
21
237
|
let isBackquoteCodeBlock = false
|
|
22
238
|
let isTildeCodeBlock = false
|
|
23
|
-
const br = lineBreaks
|
|
239
|
+
const br = lineBreaks[0] || '\n'
|
|
240
|
+
|
|
241
|
+
let labelMeta = null
|
|
242
|
+
let autoLangChecked = !opt.autoLangDetection
|
|
24
243
|
|
|
25
244
|
if(lines.length === 0) return markdown
|
|
26
245
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (isBackquoteCodeBlock) {
|
|
32
|
-
isBackquoteCodeBlock = false
|
|
33
|
-
} else {
|
|
34
|
-
isBackquoteCodeBlock = true
|
|
35
|
-
}
|
|
246
|
+
for (let n = 0; n < lines.length; n++) {
|
|
247
|
+
const line = lines[n]
|
|
248
|
+
if (backquoteFenceReg.test(line)) {
|
|
249
|
+
isBackquoteCodeBlock = !isBackquoteCodeBlock
|
|
36
250
|
}
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
isTildeCodeBlock = false
|
|
40
|
-
} else {
|
|
41
|
-
isTildeCodeBlock = true
|
|
42
|
-
}
|
|
251
|
+
if (tildeFenceReg.test(line)) {
|
|
252
|
+
isTildeCodeBlock = !isTildeCodeBlock
|
|
43
253
|
}
|
|
44
254
|
if (isBackquoteCodeBlock || isTildeCodeBlock) {
|
|
45
|
-
n++
|
|
46
255
|
continue
|
|
47
256
|
}
|
|
48
257
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
} else {
|
|
52
|
-
isPrevBreakLine = /^[ \t]*$/.test(lines[n-1])
|
|
53
|
-
}
|
|
54
|
-
if (n === lines.length -1) {
|
|
55
|
-
isNextBreakLine = true
|
|
56
|
-
} else {
|
|
57
|
-
isNextBreakLine = /^[ \t]*$/.test(lines[n+1])
|
|
58
|
-
}
|
|
258
|
+
const isPrevBreakLine = (n === 0) ? true : blankLineReg.test(lines[n-1])
|
|
259
|
+
const isNextBreakLine = (n === lines.length -1) ? true : blankLineReg.test(lines[n+1])
|
|
59
260
|
if (isPrevBreakLine && isNextBreakLine) {
|
|
60
|
-
|
|
261
|
+
if (line.indexOf(' !== -1) {
|
|
262
|
+
if (!autoLangChecked) {
|
|
263
|
+
const imgLine = line.match(imageLineReg)
|
|
264
|
+
if (imgLine) {
|
|
265
|
+
const rawText = getCaptionTextForDetection(imgLine, opt).trim()
|
|
266
|
+
if (rawText) {
|
|
267
|
+
const detected = detectAutoLang(rawText)
|
|
268
|
+
if (detected) {
|
|
269
|
+
opt.labelLang = detected
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
autoLangChecked = true
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (!labelMeta && (!opt.autoLangDetection || autoLangChecked)) {
|
|
276
|
+
labelMeta = buildLabelMeta(opt)
|
|
277
|
+
}
|
|
278
|
+
if (labelMeta) {
|
|
279
|
+
lines[n] = modLines(n ,lines, br, opt, labelMeta)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
61
282
|
}
|
|
62
|
-
n++
|
|
63
283
|
}
|
|
64
284
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (n
|
|
69
|
-
|
|
70
|
-
break
|
|
285
|
+
const output = []
|
|
286
|
+
for (let n = 0; n < lines.length; n++) {
|
|
287
|
+
output.push(lines[n])
|
|
288
|
+
if (n < lines.length - 1) {
|
|
289
|
+
output.push(lineBreaks[n] || br)
|
|
71
290
|
}
|
|
72
|
-
markdown += lines[n] + lineBreaks[n]
|
|
73
|
-
n++
|
|
74
291
|
}
|
|
75
|
-
return
|
|
292
|
+
return output.join('')
|
|
76
293
|
}
|
|
77
294
|
|
|
78
|
-
const modLines = (n, lines, br, opt) => {
|
|
79
|
-
|
|
80
|
-
const markAfterNum = '[A-Z0-9]{1,6}(?:[.-][A-Z0-9]{1,6}){0,5}';
|
|
81
|
-
const joint = '[.:.。: ]';
|
|
82
|
-
const jointFullWidth = '[.。: ]';
|
|
83
|
-
const jointHalfWidth = '[.:]';
|
|
84
|
-
|
|
85
|
-
const markAfterEn = '(?:' +
|
|
86
|
-
' *(?:' +
|
|
87
|
-
jointHalfWidth + '(?:(?=[ ]+)|$)|' +
|
|
88
|
-
jointFullWidth + '|' +
|
|
89
|
-
'(?=[ ]+[^0-9a-zA-Z])' +
|
|
90
|
-
')|' +
|
|
91
|
-
' *' + '(' + markAfterNum + ')(?:' +
|
|
92
|
-
jointHalfWidth + '(?:(?=[ ]+)|$)|' +
|
|
93
|
-
jointFullWidth + '|' +
|
|
94
|
-
'(?=[ ]+[^a-z])|$' +
|
|
95
|
-
')|' +
|
|
96
|
-
'[.](' + markAfterNum + ')(?:' +
|
|
97
|
-
joint + '|(?=[ ]+[^a-z])|$' +
|
|
98
|
-
')' +
|
|
99
|
-
')';
|
|
100
|
-
const markAfterJa = '(?:' +
|
|
101
|
-
' *(?:' +
|
|
102
|
-
jointHalfWidth + '(?:(?=[ ]+)|$)|' +
|
|
103
|
-
jointFullWidth + '|' +
|
|
104
|
-
'(?=[ ]+)' +
|
|
105
|
-
')|' +
|
|
106
|
-
' *' + '(' + markAfterNum + ')(?:' +
|
|
107
|
-
jointHalfWidth + '(?:(?=[ ]+)|$)|' +
|
|
108
|
-
jointFullWidth + '|' +
|
|
109
|
-
'(?=[ ]+)|$' +
|
|
110
|
-
')' +
|
|
111
|
-
')';
|
|
112
|
-
|
|
113
|
-
const labelEn = '(?:[fF][iI][gG](?:[uU][rR][eE])?|[iI][lL]{2}[uU][sS][tT]|[pP][hH][oO][tT][oO])';
|
|
114
|
-
const labelJa = '(?:図|イラスト|写真)';
|
|
115
|
-
|
|
116
|
-
const markReg = {
|
|
117
|
-
//fig(ure)?, illust, photo
|
|
118
|
-
"img": new RegExp('^(?:' + labelEn + markAfterEn + '|' + labelJa + markAfterJa +
|
|
119
|
-
')'),
|
|
120
|
-
}
|
|
121
|
-
const markRegWithNoJoint = {
|
|
122
|
-
"img": new RegExp('^(' + labelEn + '|' + labelJa + ')([ .]?' + markAfterNum + ')?$'),
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
let reg = /^([ \t]*?)!\[ *?(.*?) *?\]\(([^ ]*?)( +"(.*?)")?\)( *(?:{.*?})?)$/
|
|
126
|
-
|
|
127
|
-
const imgLine = lines[n].match(reg)
|
|
295
|
+
const modLines = (n, lines, br, opt, labelMeta) => {
|
|
296
|
+
const imgLine = lines[n].match(imageLineReg)
|
|
128
297
|
if (!imgLine) return lines[n]
|
|
129
298
|
//console.log(imgLine)
|
|
130
299
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
let hasLabelWithNoJoint
|
|
135
|
-
if (opt.imgAltCaption) hasLabelWithNoJoint = imgLine[2].match(new RegExp(markRegWithNoJoint.img))
|
|
136
|
-
if (opt.imgTitleCaption) hasLabelWithNoJoint = imgLine[5].match(new RegExp(markRegWithNoJoint.img))
|
|
300
|
+
const captionText = getCaptionText(imgLine, opt)
|
|
301
|
+
const hasLabel = captionText ? captionText.match(markReg.img) : null
|
|
302
|
+
const hasLabelWithNoJoint = captionText && labelOnlyReg ? captionText.match(labelOnlyReg) : null
|
|
137
303
|
|
|
138
304
|
lines[n] = imgLine[1]
|
|
139
305
|
if (hasLabel) {
|
|
@@ -154,36 +320,22 @@ const setMarkdownImgAttrToPCaption = (markdown, option) => {
|
|
|
154
320
|
} else if (opt.imgTitleCaption) {
|
|
155
321
|
lines[n] += imgLine[5]
|
|
156
322
|
}
|
|
157
|
-
lines[n] += br + br + imgLine[1] + '![' + hasLabelWithNoJoint[0].replace(
|
|
323
|
+
lines[n] += br + br + imgLine[1] + '![' + hasLabelWithNoJoint[0].replace(jointSuffixReg, '') + ']'
|
|
158
324
|
} else {
|
|
159
325
|
//console.log('No label::')
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
} else if (opt.imgTitleCaption) {
|
|
168
|
-
if (imgLine[5]) {
|
|
169
|
-
lines[n] += '図 ' + imgLine[5] + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
170
|
-
} else {
|
|
171
|
-
lines[n] += '図' + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
172
|
-
}
|
|
326
|
+
const hasCaption = captionText !== ''
|
|
327
|
+
const labelPrefix = buildLabelPrefix(labelMeta, hasCaption)
|
|
328
|
+
if (opt.imgAltCaption) {
|
|
329
|
+
if (hasCaption) {
|
|
330
|
+
lines[n] += labelPrefix + captionText + br + br + imgLine[1] + '![]'
|
|
331
|
+
} else {
|
|
332
|
+
lines[n] += labelPrefix + br + br + imgLine[1] + '![]'
|
|
173
333
|
}
|
|
174
|
-
} else if (opt.
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
lines[n] += 'Figure.' + br + br + imgLine[1] +'![]'
|
|
180
|
-
}
|
|
181
|
-
} else if (opt.imgTitleCaption) {
|
|
182
|
-
if (imgLine[5]) {
|
|
183
|
-
lines[n] += 'Figure. ' + imgLine[5] + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
184
|
-
} else {
|
|
185
|
-
lines[n] += 'Figure. ' + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
186
|
-
}
|
|
334
|
+
} else if (opt.imgTitleCaption) {
|
|
335
|
+
if (hasCaption) {
|
|
336
|
+
lines[n] += labelPrefix + captionText + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
337
|
+
} else {
|
|
338
|
+
lines[n] += labelPrefix + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
187
339
|
}
|
|
188
340
|
}
|
|
189
341
|
}
|
|
@@ -191,4 +343,4 @@ const setMarkdownImgAttrToPCaption = (markdown, option) => {
|
|
|
191
343
|
return lines[n]
|
|
192
344
|
}
|
|
193
345
|
|
|
194
|
-
export default setMarkdownImgAttrToPCaption
|
|
346
|
+
export default setMarkdownImgAttrToPCaption
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-imgattr-to-pcaption",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Change img alt attribute to figure caption paragraph for p7d-markdown-it-p-captions.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"script/"
|
|
12
|
+
],
|
|
7
13
|
"scripts": {
|
|
8
14
|
"test": "node test/test.js"
|
|
9
15
|
},
|
|
@@ -16,5 +22,8 @@
|
|
|
16
22
|
"bugs": {
|
|
17
23
|
"url": "https://github.com/peaceroad/markdown-imgattr-to-pcaption/issues"
|
|
18
24
|
},
|
|
19
|
-
"homepage": "https://github.com/peaceroad/markdown-imgattr-to-pcaption#readme"
|
|
25
|
+
"homepage": "https://github.com/peaceroad/markdown-imgattr-to-pcaption#readme",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"p7d-markdown-it-p-captions": "^0.20.0"
|
|
28
|
+
}
|
|
20
29
|
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
const whitespaceOnlyReg = /^[\s\u3000]+$/
|
|
2
|
+
|
|
3
|
+
const normalizeBoolean = (value) => {
|
|
4
|
+
if (value === true) return true
|
|
5
|
+
if (value === false) return false
|
|
6
|
+
if (typeof value === 'string') {
|
|
7
|
+
const trimmed = value.trim()
|
|
8
|
+
if (!trimmed) return null
|
|
9
|
+
if (trimmed.toLowerCase() === 'true') return true
|
|
10
|
+
if (trimmed.toLowerCase() === 'false') return false
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const isBlank = (value) => {
|
|
17
|
+
if (!value) return true
|
|
18
|
+
return whitespaceOnlyReg.test(value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const getAttr = (element, name) => {
|
|
22
|
+
const value = element.getAttribute(name)
|
|
23
|
+
return value == null ? '' : value
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const setTextIfChanged = (element, value) => {
|
|
27
|
+
if (!element) return
|
|
28
|
+
const nextValue = value == null ? '' : String(value)
|
|
29
|
+
if (element.textContent === nextValue) return
|
|
30
|
+
element.textContent = nextValue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const createCaption = (documentRef, text) => {
|
|
34
|
+
const caption = documentRef.createElement('figcaption')
|
|
35
|
+
caption.textContent = text
|
|
36
|
+
return caption
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const readMetaFrontmatter = (readMeta) => {
|
|
40
|
+
if (!readMeta) return null
|
|
41
|
+
if (typeof document === 'undefined' || typeof document.querySelector !== 'function') return null
|
|
42
|
+
const metaTag = document.querySelector('meta[name="markdown-frontmatter"]')
|
|
43
|
+
if (!metaTag) return null
|
|
44
|
+
const content = metaTag.getAttribute('content')
|
|
45
|
+
if (!content) return null
|
|
46
|
+
const parseJson = (value) => {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(value)
|
|
49
|
+
} catch {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
let parsed = parseJson(content)
|
|
54
|
+
if (!parsed && content.includes('"')) {
|
|
55
|
+
parsed = parseJson(content.replace(/"/g, '"'))
|
|
56
|
+
}
|
|
57
|
+
return parsed && typeof parsed === 'object' ? parsed : null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const applyMetaOptions = (targetOpt, meta, optionOverrides) => {
|
|
61
|
+
if (!meta || typeof meta !== 'object') return
|
|
62
|
+
const extensionSettings = meta._extensionSettings && typeof meta._extensionSettings === 'object'
|
|
63
|
+
? meta._extensionSettings
|
|
64
|
+
: null
|
|
65
|
+
|
|
66
|
+
const setFlag = (key) => {
|
|
67
|
+
if (optionOverrides.has(key)) return
|
|
68
|
+
const directValue = Object.prototype.hasOwnProperty.call(meta, key)
|
|
69
|
+
? normalizeBoolean(meta[key])
|
|
70
|
+
: null
|
|
71
|
+
if (directValue !== null) {
|
|
72
|
+
targetOpt[key] = directValue
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
if (!extensionSettings || !Object.prototype.hasOwnProperty.call(extensionSettings, key)) return
|
|
76
|
+
const extValue = normalizeBoolean(extensionSettings[key])
|
|
77
|
+
if (extValue !== null) {
|
|
78
|
+
targetOpt[key] = extValue
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setFlag('imgAltCaption')
|
|
83
|
+
setFlag('imgTitleCaption')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const getCaptionText = (img, opt) => {
|
|
87
|
+
if (!opt.imgAltCaption && !opt.imgTitleCaption) return ''
|
|
88
|
+
const alt = getAttr(img, 'alt')
|
|
89
|
+
const title = getAttr(img, 'title')
|
|
90
|
+
if (opt.imgAltCaption && opt.imgTitleCaption) {
|
|
91
|
+
if (opt.preferAlt) return alt || title
|
|
92
|
+
return title || alt
|
|
93
|
+
}
|
|
94
|
+
if (opt.imgAltCaption) return alt
|
|
95
|
+
if (opt.imgTitleCaption) return title
|
|
96
|
+
return ''
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const updateFigure = (img, captionText, opt) => {
|
|
100
|
+
const figure = img.closest('figure')
|
|
101
|
+
if (figure) {
|
|
102
|
+
const figcaption = figure.querySelector('figcaption')
|
|
103
|
+
if (!captionText || isBlank(captionText)) {
|
|
104
|
+
if (figcaption && figcaption.parentNode) {
|
|
105
|
+
figcaption.parentNode.removeChild(figcaption)
|
|
106
|
+
}
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
if (figcaption) {
|
|
110
|
+
setTextIfChanged(figcaption, captionText)
|
|
111
|
+
} else {
|
|
112
|
+
figure.appendChild(createCaption(figure.ownerDocument, captionText))
|
|
113
|
+
}
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!captionText || isBlank(captionText)) return
|
|
118
|
+
const parent = img.parentNode
|
|
119
|
+
if (!parent) return
|
|
120
|
+
const figureEl = img.ownerDocument.createElement('figure')
|
|
121
|
+
if (opt.figureClass) {
|
|
122
|
+
figureEl.className = opt.figureClass
|
|
123
|
+
}
|
|
124
|
+
parent.insertBefore(figureEl, img)
|
|
125
|
+
figureEl.appendChild(img)
|
|
126
|
+
figureEl.appendChild(createCaption(figureEl.ownerDocument, captionText))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const processImages = (images, opt) => {
|
|
130
|
+
if (!images) return []
|
|
131
|
+
const processed = []
|
|
132
|
+
for (const img of images) {
|
|
133
|
+
if (!img || img.nodeType !== 1 || img.tagName !== 'IMG') continue
|
|
134
|
+
const captionText = getCaptionText(img, opt)
|
|
135
|
+
updateFigure(img, captionText, opt)
|
|
136
|
+
processed.push(img)
|
|
137
|
+
}
|
|
138
|
+
return processed
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default async function setImgFigureCaption(option = {}) {
|
|
142
|
+
if (typeof document === 'undefined' || typeof document.querySelectorAll !== 'function') return []
|
|
143
|
+
|
|
144
|
+
const opt = {
|
|
145
|
+
imgAltCaption: false,
|
|
146
|
+
imgTitleCaption: false,
|
|
147
|
+
preferAlt: true,
|
|
148
|
+
figureClass: 'f-img',
|
|
149
|
+
readMeta: false,
|
|
150
|
+
observe: false,
|
|
151
|
+
}
|
|
152
|
+
Object.assign(opt, option)
|
|
153
|
+
const optionOverrides = new Set(Object.keys(option || {}))
|
|
154
|
+
|
|
155
|
+
const buildContext = () => {
|
|
156
|
+
const currentOpt = { ...opt }
|
|
157
|
+
const meta = readMetaFrontmatter(currentOpt.readMeta)
|
|
158
|
+
if (meta) {
|
|
159
|
+
applyMetaOptions(currentOpt, meta, optionOverrides)
|
|
160
|
+
}
|
|
161
|
+
return { opt: currentOpt }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const runProcess = (targets = null) => {
|
|
165
|
+
const { opt: currentOpt } = buildContext()
|
|
166
|
+
if (!currentOpt.imgAltCaption && !currentOpt.imgTitleCaption) return []
|
|
167
|
+
const images = targets
|
|
168
|
+
? Array.from(targets)
|
|
169
|
+
: Array.from(document.querySelectorAll('img'))
|
|
170
|
+
return processImages(images, currentOpt)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!opt.observe || typeof MutationObserver !== 'function') {
|
|
174
|
+
return runProcess()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let scheduled = false
|
|
178
|
+
let running = false
|
|
179
|
+
let pending = false
|
|
180
|
+
let pendingAll = false
|
|
181
|
+
const pendingImages = new Set()
|
|
182
|
+
let observer = null
|
|
183
|
+
|
|
184
|
+
const scheduleProcess = () => {
|
|
185
|
+
if (scheduled) return
|
|
186
|
+
scheduled = true
|
|
187
|
+
const run = () => {
|
|
188
|
+
scheduled = false
|
|
189
|
+
runProcessLoop()
|
|
190
|
+
}
|
|
191
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
192
|
+
requestAnimationFrame(run)
|
|
193
|
+
} else {
|
|
194
|
+
setTimeout(run, 50)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const runProcessLoop = async () => {
|
|
199
|
+
if (running) {
|
|
200
|
+
pending = true
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
running = true
|
|
204
|
+
do {
|
|
205
|
+
pending = false
|
|
206
|
+
const targets = pendingAll ? null : Array.from(pendingImages)
|
|
207
|
+
pendingAll = false
|
|
208
|
+
pendingImages.clear()
|
|
209
|
+
runProcess(targets)
|
|
210
|
+
} while (pending)
|
|
211
|
+
running = false
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const isElementNode = (node) => node && node.nodeType === 1
|
|
215
|
+
const isMetaNode = (node) => {
|
|
216
|
+
if (!opt.readMeta || !isElementNode(node)) return false
|
|
217
|
+
return node.tagName === 'META'
|
|
218
|
+
&& node.getAttribute('name') === 'markdown-frontmatter'
|
|
219
|
+
}
|
|
220
|
+
const isImageNode = (node) => isElementNode(node) && node.tagName === 'IMG'
|
|
221
|
+
|
|
222
|
+
const collectImagesFromNodes = (nodes) => {
|
|
223
|
+
if (!nodes) return
|
|
224
|
+
for (const node of nodes) {
|
|
225
|
+
if (!isElementNode(node)) continue
|
|
226
|
+
if (node.tagName === 'FIGCAPTION') continue
|
|
227
|
+
if (isImageNode(node)) {
|
|
228
|
+
pendingImages.add(node)
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
231
|
+
if (node.querySelectorAll) {
|
|
232
|
+
const images = node.querySelectorAll('img')
|
|
233
|
+
for (const image of images) pendingImages.add(image)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const hasMetaInNodes = (nodes) => {
|
|
239
|
+
if (!opt.readMeta || !nodes) return false
|
|
240
|
+
for (const node of nodes) {
|
|
241
|
+
if (!isElementNode(node)) continue
|
|
242
|
+
if (isMetaNode(node)) return true
|
|
243
|
+
if (node.querySelector && node.querySelector('meta[name="markdown-frontmatter"]')) return true
|
|
244
|
+
}
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const attributeFilter = ['alt', 'title']
|
|
249
|
+
if (opt.readMeta) attributeFilter.push('content')
|
|
250
|
+
|
|
251
|
+
if (!observer) {
|
|
252
|
+
const root = document.documentElement || document.body
|
|
253
|
+
if (root) {
|
|
254
|
+
observer = new MutationObserver((mutations) => {
|
|
255
|
+
let shouldSchedule = false
|
|
256
|
+
let metaChanged = false
|
|
257
|
+
for (const mutation of mutations) {
|
|
258
|
+
if (!mutation) continue
|
|
259
|
+
if (mutation.type === 'attributes') {
|
|
260
|
+
const target = mutation.target
|
|
261
|
+
if (isImageNode(target) && ['alt', 'title'].includes(mutation.attributeName)) {
|
|
262
|
+
pendingImages.add(target)
|
|
263
|
+
shouldSchedule = true
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
if (isMetaNode(target) && mutation.attributeName === 'content') {
|
|
267
|
+
metaChanged = true
|
|
268
|
+
shouldSchedule = true
|
|
269
|
+
continue
|
|
270
|
+
}
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
if (mutation.type !== 'childList') continue
|
|
274
|
+
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
|
|
275
|
+
collectImagesFromNodes(mutation.addedNodes)
|
|
276
|
+
if (pendingImages.size > 0) shouldSchedule = true
|
|
277
|
+
if (hasMetaInNodes(mutation.addedNodes)) {
|
|
278
|
+
metaChanged = true
|
|
279
|
+
shouldSchedule = true
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (mutation.removedNodes && mutation.removedNodes.length > 0) {
|
|
283
|
+
if (hasMetaInNodes(mutation.removedNodes)) {
|
|
284
|
+
metaChanged = true
|
|
285
|
+
shouldSchedule = true
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (metaChanged) {
|
|
290
|
+
pendingAll = true
|
|
291
|
+
pendingImages.clear()
|
|
292
|
+
}
|
|
293
|
+
if (shouldSchedule) scheduleProcess()
|
|
294
|
+
})
|
|
295
|
+
observer.observe(root, {
|
|
296
|
+
childList: true,
|
|
297
|
+
subtree: true,
|
|
298
|
+
attributes: true,
|
|
299
|
+
attributeFilter,
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return runProcess()
|
|
305
|
+
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
[Input]
|
|
2
|
-
段落。段落。段落。
|
|
3
|
-
|
|
4
|
-

|
|
5
|
-
|
|
6
|
-
段落。段落。段落。
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[Output]
|
|
10
|
-
段落。段落。段落。
|
|
11
|
-
|
|
12
|
-
図 キャプション
|
|
13
|
-
|
|
14
|
-

|
|
15
|
-
|
|
16
|
-
段落。段落。段落。
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
[Input]
|
|
20
|
-
段落。段落。段落。
|
|
21
|
-
|
|
22
|
-

|
|
23
|
-
|
|
24
|
-
段落。段落。段落。
|
|
25
|
-
|
|
26
|
-
[Output]
|
|
27
|
-
段落。段落。段落。
|
|
28
|
-
|
|
29
|
-
図 キャプション
|
|
30
|
-
|
|
31
|
-

|
|
32
|
-
|
|
33
|
-
段落。段落。段落。
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
[Input]
|
|
38
|
-
段落。段落。段落。
|
|
39
|
-
|
|
40
|
-

|
|
41
|
-
|
|
42
|
-
段落。段落。段落。
|
|
43
|
-
|
|
44
|
-
[Output]
|
|
45
|
-
段落。段落。段落。
|
|
46
|
-
|
|
47
|
-
図1 キャプション
|
|
48
|
-
|
|
49
|
-

|
|
50
|
-
|
|
51
|
-
段落。段落。段落。
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[Input]
|
|
56
|
-
段落。段落。段落。
|
|
57
|
-
|
|
58
|
-

|
|
59
|
-
|
|
60
|
-
段落。段落。段落。
|
|
61
|
-
|
|
62
|
-
[Output]
|
|
63
|
-
段落。段落。段落。
|
|
64
|
-
|
|
65
|
-
図1 キャプション
|
|
66
|
-
|
|
67
|
-

|
|
68
|
-
|
|
69
|
-
段落。段落。段落。
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
[Input]
|
|
74
|
-
段落。段落。段落。
|
|
75
|
-
|
|
76
|
-

|
|
77
|
-
|
|
78
|
-
段落。段落。段落。
|
|
79
|
-
|
|
80
|
-
[Output]
|
|
81
|
-
段落。段落。段落。
|
|
82
|
-
|
|
83
|
-
図1:キャプション
|
|
84
|
-
|
|
85
|
-

|
|
86
|
-
|
|
87
|
-
段落。段落。段落。
|
package/test/examples.txt
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
[Input]
|
|
2
|
-
段落。段落。段落。
|
|
3
|
-
|
|
4
|
-

|
|
5
|
-
|
|
6
|
-
段落。段落。段落。
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[Output]
|
|
10
|
-
段落。段落。段落。
|
|
11
|
-
|
|
12
|
-
図 キャプション
|
|
13
|
-
|
|
14
|
-

|
|
15
|
-
|
|
16
|
-
段落。段落。段落。
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
[Input]
|
|
20
|
-
段落。段落。段落。
|
|
21
|
-
|
|
22
|
-

|
|
23
|
-
|
|
24
|
-
段落。段落。段落。
|
|
25
|
-
|
|
26
|
-
[Output]
|
|
27
|
-
段落。段落。段落。
|
|
28
|
-
|
|
29
|
-
図 キャプション
|
|
30
|
-
|
|
31
|
-

|
|
32
|
-
|
|
33
|
-
段落。段落。段落。
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
[Input]
|
|
38
|
-
段落。段落。段落。
|
|
39
|
-
|
|
40
|
-

|
|
41
|
-
|
|
42
|
-
段落。段落。段落。
|
|
43
|
-
|
|
44
|
-
[Output]
|
|
45
|
-
段落。段落。段落。
|
|
46
|
-
|
|
47
|
-
図1 キャプション
|
|
48
|
-
|
|
49
|
-

|
|
50
|
-
|
|
51
|
-
段落。段落。段落。
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[Input]
|
|
56
|
-
段落。段落。段落。
|
|
57
|
-
|
|
58
|
-

|
|
59
|
-
|
|
60
|
-
段落。段落。段落。
|
|
61
|
-
|
|
62
|
-
[Output]
|
|
63
|
-
段落。段落。段落。
|
|
64
|
-
|
|
65
|
-
図1 キャプション
|
|
66
|
-
|
|
67
|
-

|
|
68
|
-
|
|
69
|
-
段落。段落。段落。
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
[Input]
|
|
73
|
-
段落。段落。段落。
|
|
74
|
-
|
|
75
|
-

|
|
76
|
-
|
|
77
|
-
段落。段落。段落。
|
|
78
|
-
|
|
79
|
-
[Output]
|
|
80
|
-
段落。段落。段落。
|
|
81
|
-
|
|
82
|
-
図1:キャプション
|
|
83
|
-
|
|
84
|
-

|
|
85
|
-
|
|
86
|
-
段落。段落。段落。
|
|
87
|
-
|
|
88
|
-
|
package/test/test.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import assert from 'assert'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import setMarkdownImgAttrToPCaption from '../index.js'
|
|
5
|
-
|
|
6
|
-
let __dirname = path.dirname(new URL(import.meta.url).pathname)
|
|
7
|
-
const isWindows = (process.platform === 'win32')
|
|
8
|
-
if (isWindows) {
|
|
9
|
-
__dirname = __dirname.replace(/^\/+/, '').replace(/\//g, '\\')
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const check = (name, ex) => {
|
|
13
|
-
const exCont = fs.readFileSync(ex, 'utf-8').trim()
|
|
14
|
-
let ms = [];
|
|
15
|
-
let ms0 = exCont.split(/\n*\[Input\]\n/)
|
|
16
|
-
let n = 1;
|
|
17
|
-
while(n < ms0.length) {
|
|
18
|
-
let mhs = ms0[n].split(/\n+\[Output[^\]]*?\]\n/)
|
|
19
|
-
let i = 1
|
|
20
|
-
while (i < 2) {
|
|
21
|
-
if (mhs[i] === undefined) {
|
|
22
|
-
mhs[i] = ''
|
|
23
|
-
} else {
|
|
24
|
-
mhs[i] = mhs[i].replace(/$/,'\n')
|
|
25
|
-
}
|
|
26
|
-
i++
|
|
27
|
-
}
|
|
28
|
-
ms[n] = {
|
|
29
|
-
inputMarkdown: mhs[0].trim(),
|
|
30
|
-
outputMarkdown: mhs[1].trim(),
|
|
31
|
-
};
|
|
32
|
-
n++
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
n = 1
|
|
36
|
-
while(n < ms.length) {
|
|
37
|
-
//if (n !== 10) { n++; continue }
|
|
38
|
-
console.log('Test: ' + n + ' >>>')
|
|
39
|
-
const m = ms[n].inputMarkdown
|
|
40
|
-
let h
|
|
41
|
-
let option = {}
|
|
42
|
-
if (name === 'default') {
|
|
43
|
-
h = setMarkdownImgAttrToPCaption(m)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (name === 'imgTitleAttr') {
|
|
48
|
-
h = setMarkdownImgAttrToPCaption(m, {imgTitleCaption: true})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (name === 'labelLang') {
|
|
52
|
-
h = setMarkdownImgAttrToPCaption(m, {labelLang: 'en'})
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
assert.strictEqual(h, ms[n].outputMarkdown)
|
|
58
|
-
} catch(e) {
|
|
59
|
-
console.log('incorrect: ')
|
|
60
|
-
//console.log(m)
|
|
61
|
-
//console.log('::convert ->')
|
|
62
|
-
console.log('H: ' + h +'\n\nC: ' + ms[n].outputMarkdown)
|
|
63
|
-
}
|
|
64
|
-
n++
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const example = {
|
|
70
|
-
default: __dirname + path.sep + 'examples.txt',
|
|
71
|
-
imgTitleAttr: __dirname + path.sep + 'examples-img-title-attr.txt',
|
|
72
|
-
labelLang: __dirname + path.sep + 'examples-label-lang.txt',
|
|
73
|
-
}
|
|
74
|
-
for (let ex in example) {
|
|
75
|
-
console.log('[Test] ' + ex)
|
|
76
|
-
check(ex, example[ex])
|
|
77
|
-
}
|