@peaceroad/markdown-it-strong-ja 0.6.2 → 0.7.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.
@@ -0,0 +1,340 @@
1
+ import Token from 'markdown-it/lib/token.mjs'
2
+ import { convertCollapsedReferenceLinks, mergeBrokenMarksAroundLinks, getMapFromTokenRange } from './token-link-utils.js'
3
+ import {
4
+ rebuildInlineLevels,
5
+ findLinkClose,
6
+ fixEmOuterStrongSequence,
7
+ fixLeadingAsteriskEm,
8
+ fixTrailingStrong
9
+ } from './token-core.js'
10
+ import { getRuntimeOpt } from './token-utils.js'
11
+
12
+ const scanBrokenRefState = (text, out) => {
13
+ if (!text || text.indexOf('][') === -1) {
14
+ out.depth = 0
15
+ out.brokenEnd = false
16
+ return out
17
+ }
18
+ let depth = 0
19
+ let lastOpen = -1
20
+ let lastClose = -1
21
+ for (let i = 0; i < text.length; i++) {
22
+ const ch = text.charCodeAt(i)
23
+ if (ch === 0x5B) {
24
+ depth++
25
+ lastOpen = i
26
+ } else if (ch === 0x5D) {
27
+ if (depth > 0) depth--
28
+ lastClose = i
29
+ }
30
+ }
31
+ out.depth = depth
32
+ out.brokenEnd = depth > 0 && lastOpen > lastClose
33
+ return out
34
+ }
35
+
36
+ const updateBracketDepth = (text, depth) => {
37
+ if (!text || depth <= 0) return depth
38
+ for (let i = 0; i < text.length; i++) {
39
+ const ch = text.charCodeAt(i)
40
+ if (ch === 0x5B) {
41
+ depth++
42
+ } else if (ch === 0x5D) {
43
+ if (depth > 0) {
44
+ depth--
45
+ if (depth === 0) return 0
46
+ }
47
+ }
48
+ }
49
+ return depth
50
+ }
51
+
52
+ const getAttr = (token, name) => {
53
+ if (!token || !token.attrs) return ''
54
+ for (let i = 0; i < token.attrs.length; i++) {
55
+ if (token.attrs[i][0] === name) return token.attrs[i][1]
56
+ }
57
+ return ''
58
+ }
59
+
60
+ const buildLabelText = (tokens, startIdx, endIdx) => {
61
+ let label = ''
62
+ for (let i = startIdx; i <= endIdx; i++) {
63
+ const t = tokens[i]
64
+ if (!t) continue
65
+ if (t.type === 'text') {
66
+ label += t.content
67
+ } else if (t.type === 'code_inline') {
68
+ const fence = t.markup || '`'
69
+ label += fence + t.content + fence
70
+ } else if (t.markup) {
71
+ label += t.markup
72
+ }
73
+ }
74
+ return label
75
+ }
76
+
77
+ const buildRawFromTokens = (tokens, startIdx, endIdx) => {
78
+ let raw = ''
79
+ for (let i = startIdx; i <= endIdx; i++) {
80
+ const t = tokens[i]
81
+ if (!t) continue
82
+ if (t.type === 'link_open') {
83
+ const closeIdx = findLinkClose(tokens, i)
84
+ if (closeIdx === -1 || closeIdx > endIdx) break
85
+ const label = buildLabelText(tokens, i + 1, closeIdx - 1)
86
+ const href = getAttr(t, 'href')
87
+ raw += `[${label}](${href || ''})`
88
+ i = closeIdx
89
+ continue
90
+ }
91
+ if (t.type === 'text') {
92
+ raw += t.content
93
+ continue
94
+ }
95
+ if (t.type === 'code_inline') {
96
+ const fence = t.markup || '`'
97
+ raw += fence + t.content + fence
98
+ continue
99
+ }
100
+ if (t.markup) {
101
+ raw += t.markup
102
+ }
103
+ }
104
+ return raw
105
+ }
106
+
107
+ const parseInlineWithFixes = (md, raw, env) => {
108
+ const parsed = md.parseInline(raw, env)
109
+ const inline = parsed && parsed.length > 0 ? parsed[0] : null
110
+ if (!inline || !inline.children) return null
111
+ const children = inline.children
112
+ let changed = false
113
+ if (fixEmOuterStrongSequence(children)) changed = true
114
+ if (fixLeadingAsteriskEm(children)) changed = true
115
+ if (fixTrailingStrong(children)) changed = true
116
+ if (changed) rebuildInlineLevels(children)
117
+ return children
118
+ }
119
+
120
+ const hasUnsafeAttrs = (token) => {
121
+ if (!token) return false
122
+ if (token.meta && Object.keys(token.meta).length > 0) return true
123
+ if (!token.attrs || token.attrs.length === 0) return false
124
+ if (token.type !== 'link_open') return true
125
+ for (let i = 0; i < token.attrs.length; i++) {
126
+ const name = token.attrs[i][0]
127
+ if (name !== 'href' && name !== 'title') return true
128
+ }
129
+ return false
130
+ }
131
+
132
+ const REPARSE_ALLOWED_TYPES = new Set([
133
+ 'text',
134
+ 'strong_open',
135
+ 'strong_close',
136
+ 'em_open',
137
+ 'em_close',
138
+ 'code_inline',
139
+ 'link_open',
140
+ 'link_close',
141
+ 'softbreak',
142
+ 'hardbreak'
143
+ ])
144
+
145
+ const shouldReparseSegment = (tokens, startIdx, endIdx) => {
146
+ for (let i = startIdx; i <= endIdx && i < tokens.length; i++) {
147
+ const t = tokens[i]
148
+ if (!t) continue
149
+ if (hasUnsafeAttrs(t)) return false
150
+ if (t.type && !REPARSE_ALLOWED_TYPES.has(t.type)) return false
151
+ }
152
+ return true
153
+ }
154
+
155
+ const fixTailAfterLinkStrongClose = (tokens, md, env) => {
156
+ let strongDepth = 0
157
+ for (let i = 0; i < tokens.length; i++) {
158
+ const t = tokens[i]
159
+ if (!t) continue
160
+ if (t.type === 'strong_open') strongDepth++
161
+ if (t.type === 'strong_close') {
162
+ if (strongDepth > 0) strongDepth--
163
+ }
164
+ if (t.type !== 'link_close') continue
165
+ if (strongDepth !== 0) continue
166
+ let startIdx = -1
167
+ let foundStrongClose = -1
168
+ let foundStrongOpen = -1
169
+ for (let j = i + 1; j < tokens.length; j++) {
170
+ const node = tokens[j]
171
+ if (!node) continue
172
+ if (node.type === 'strong_open') {
173
+ foundStrongOpen = j
174
+ break
175
+ }
176
+ if (node.type === 'strong_close') {
177
+ foundStrongClose = j
178
+ break
179
+ }
180
+ if (node.type === 'text' && node.content && startIdx === -1) {
181
+ startIdx = j
182
+ }
183
+ }
184
+ if (foundStrongClose === -1 || foundStrongOpen !== -1) continue
185
+ if (startIdx === -1) startIdx = foundStrongClose
186
+ const raw = buildRawFromTokens(tokens, startIdx, tokens.length - 1)
187
+ const children = parseInlineWithFixes(md, raw, env)
188
+ if (children && children.length > 0) {
189
+ tokens.splice(startIdx, tokens.length - startIdx, ...children)
190
+ return true
191
+ }
192
+ }
193
+ return false
194
+ }
195
+
196
+ const registerTokenPostprocess = (md, baseOpt, getNoLinkMdInstance) => {
197
+ if (md.__strongJaTokenPostprocessRegistered) return
198
+ md.__strongJaTokenPostprocessRegistered = true
199
+ md.core.ruler.after('inline', 'strong_ja_token_postprocess', (state) => {
200
+ if (!state || !state.tokens) return
201
+ const opt = getRuntimeOpt(state, baseOpt)
202
+ if (opt.postprocess === false) return
203
+ if (state.__strongJaReferenceCount === undefined) {
204
+ const references = state.env && state.env.references
205
+ state.__strongJaReferenceCount = references ? Object.keys(references).length : 0
206
+ }
207
+ for (let i = 0; i < state.tokens.length; i++) {
208
+ const token = state.tokens[i]
209
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
210
+ const children = token.children
211
+ let changed = false
212
+ let hasBracketText = false
213
+ let hasEmphasis = false
214
+ let hasLinkClose = false
215
+ let reparseCount = 0
216
+ let maxReparse = 0
217
+ const scanState = { depth: 0, brokenEnd: false }
218
+ for (let j = 0; j < children.length; j++) {
219
+ const child = children[j]
220
+ if (!child || child.type !== 'text' || !child.content) continue
221
+ if (scanBrokenRefState(child.content, scanState).brokenEnd) {
222
+ maxReparse++
223
+ }
224
+ }
225
+ if (maxReparse === 0) {
226
+ for (let j = 0; j < children.length; j++) {
227
+ const child = children[j]
228
+ if (!child) continue
229
+ if (child.type === 'text' && child.content) {
230
+ if (!hasBracketText && (child.content.indexOf('[') !== -1 || child.content.indexOf(']') !== -1)) {
231
+ hasBracketText = true
232
+ }
233
+ }
234
+ if (!hasEmphasis &&
235
+ (child.type === 'strong_open' || child.type === 'strong_close' || child.type === 'em_open' || child.type === 'em_close')) {
236
+ hasEmphasis = true
237
+ }
238
+ if (!hasLinkClose && child.type === 'link_close') {
239
+ hasLinkClose = true
240
+ }
241
+ }
242
+ } else {
243
+ let allowReparse = true
244
+ while (true) {
245
+ let didReparse = false
246
+ let brokenRefStart = -1
247
+ let brokenRefDepth = 0
248
+ hasBracketText = false
249
+ hasEmphasis = false
250
+ hasLinkClose = false
251
+ for (let j = 0; j < children.length; j++) {
252
+ const child = children[j]
253
+ if (!child) continue
254
+ if (child.type === 'text' && child.content) {
255
+ if (!hasBracketText && (child.content.indexOf('[') !== -1 || child.content.indexOf(']') !== -1)) {
256
+ hasBracketText = true
257
+ }
258
+ if (brokenRefStart === -1) {
259
+ const scan = scanBrokenRefState(child.content, scanState)
260
+ if (scan.brokenEnd) {
261
+ brokenRefStart = j
262
+ brokenRefDepth = scan.depth
263
+ continue
264
+ }
265
+ } else {
266
+ brokenRefDepth = updateBracketDepth(child.content, brokenRefDepth)
267
+ if (brokenRefDepth <= 0) {
268
+ brokenRefStart = -1
269
+ brokenRefDepth = 0
270
+ }
271
+ }
272
+ }
273
+ if (!hasEmphasis &&
274
+ (child.type === 'strong_open' || child.type === 'strong_close' || child.type === 'em_open' || child.type === 'em_close')) {
275
+ hasEmphasis = true
276
+ }
277
+ if (!hasLinkClose && child.type === 'link_close') {
278
+ hasLinkClose = true
279
+ }
280
+ if (allowReparse && brokenRefStart !== -1 && child.type === 'link_open') {
281
+ if (brokenRefDepth <= 0) {
282
+ brokenRefStart = -1
283
+ brokenRefDepth = 0
284
+ continue
285
+ }
286
+ const closeIdx = findLinkClose(children, j)
287
+ if (closeIdx !== -1) {
288
+ if (shouldReparseSegment(children, brokenRefStart, closeIdx)) {
289
+ const originalMap = getMapFromTokenRange(children, brokenRefStart, closeIdx)
290
+ const raw = buildRawFromTokens(children, brokenRefStart, closeIdx)
291
+ const noLink = getNoLinkMdInstance(md, opt)
292
+ const parsed = parseInlineWithFixes(noLink, raw, state.env)
293
+ if (parsed && parsed.length > 0) {
294
+ if (originalMap) {
295
+ for (let k = 0; k < parsed.length; k++) {
296
+ const childToken = parsed[k]
297
+ if (childToken && !childToken.map) childToken.map = [originalMap[0], originalMap[1]]
298
+ }
299
+ }
300
+ children.splice(brokenRefStart, closeIdx - brokenRefStart + 1, ...parsed)
301
+ } else {
302
+ const text = new Token('text', '', 0)
303
+ text.content = raw
304
+ if (originalMap) text.map = [originalMap[0], originalMap[1]]
305
+ children.splice(brokenRefStart, closeIdx - brokenRefStart + 1, text)
306
+ }
307
+ brokenRefStart = -1
308
+ changed = true
309
+ didReparse = true
310
+ break
311
+ }
312
+ brokenRefStart = -1
313
+ }
314
+ }
315
+ }
316
+ if (!didReparse) break
317
+ reparseCount++
318
+ if (reparseCount >= maxReparse) {
319
+ allowReparse = false
320
+ }
321
+ if (!allowReparse) {
322
+ continue
323
+ }
324
+ }
325
+ }
326
+ if (hasEmphasis) {
327
+ if (fixEmOuterStrongSequence(children)) changed = true
328
+ if (hasLinkClose && fixTailAfterLinkStrongClose(children, md, state.env)) changed = true
329
+ if (hasLinkClose && fixLeadingAsteriskEm(children)) changed = true
330
+ if (fixTrailingStrong(children)) changed = true
331
+ }
332
+ if (changed) rebuildInlineLevels(children)
333
+ if (!hasBracketText) continue
334
+ convertCollapsedReferenceLinks(children, state)
335
+ mergeBrokenMarksAroundLinks(children)
336
+ }
337
+ })
338
+ }
339
+
340
+ export { registerTokenPostprocess }
@@ -0,0 +1,166 @@
1
+ const CHAR_ASTERISK = 0x2A // *
2
+ const CHAR_OPEN_BRACKET = 0x5B // [
3
+ const CHAR_CLOSE_BRACKET = 0x5D // ]
4
+ const CHAR_SPACE = 0x20 // ' '
5
+ const CHAR_TAB = 0x09 // '\t'
6
+ const CHAR_NEWLINE = 0x0A // '\n'
7
+ const REG_JAPANESE = /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}\u3000-\u303F\uFF00-\uFFEF]/u
8
+ const REG_ATTRS = /{[^{}\n!@#%^&*()]+?}$/
9
+
10
+ const hasJapaneseText = (str) => {
11
+ if (!str) return false
12
+ return REG_JAPANESE.test(str)
13
+ }
14
+
15
+ const isJapaneseChar = (ch) => {
16
+ if (!ch) return false
17
+ const code = typeof ch === 'string' ? ch.charCodeAt(0) : ch
18
+ if (code < 128) return false
19
+ if (code >= 0x3040 && code <= 0x309F) return true
20
+ if (code >= 0x30A0 && code <= 0x30FF) return true
21
+ if (code >= 0x4E00 && code <= 0x9FAF) return true
22
+ return REG_JAPANESE.test(String.fromCharCode(code))
23
+ }
24
+
25
+ const hasCjkBreaksRule = (md) => {
26
+ if (!md || !md.core || !md.core.ruler || !Array.isArray(md.core.ruler.__rules__)) return false
27
+ if (md.__strongJaHasCjkBreaks === true) return true
28
+ const found = md.core.ruler.__rules__.some((rule) => rule && typeof rule.name === 'string' && rule.name.indexOf('cjk_breaks') !== -1)
29
+ if (found) md.__strongJaHasCjkBreaks = true
30
+ return found
31
+ }
32
+
33
+ const findPrevNonSpace = (src, start) => {
34
+ for (let i = start; i >= 0; i--) {
35
+ const ch = src.charCodeAt(i)
36
+ if (ch === CHAR_NEWLINE) return 0
37
+ if (ch === CHAR_SPACE || ch === CHAR_TAB) continue
38
+ return ch
39
+ }
40
+ return 0
41
+ }
42
+
43
+ const findNextNonSpace = (src, start, max) => {
44
+ for (let i = start; i < max; i++) {
45
+ const ch = src.charCodeAt(i)
46
+ if (ch === CHAR_NEWLINE) return 0
47
+ if (ch === CHAR_SPACE || ch === CHAR_TAB) continue
48
+ return ch
49
+ }
50
+ return 0
51
+ }
52
+
53
+ const resolveMode = (opt) => {
54
+ const raw = opt && typeof opt.mode === 'string' ? opt.mode : 'japanese'
55
+ const mode = raw.toLowerCase()
56
+ if (mode === 'japanese-only') return 'japanese'
57
+ return mode
58
+ }
59
+
60
+ const shouldUseJapaneseRule = (state, opt, mode) => {
61
+ if (mode === 'aggressive') return true
62
+ if (mode === 'compatible') return false
63
+ let hasJapanese = state.__strongJaTokenHasJapanese
64
+ if (hasJapanese === undefined) {
65
+ hasJapanese = hasJapaneseText(state.src)
66
+ state.__strongJaTokenHasJapanese = hasJapanese
67
+ }
68
+ return hasJapanese
69
+ }
70
+
71
+ const getRuntimeOpt = (state, baseOpt) => {
72
+ if (!state || !state.env || !state.env.__strongJaTokenOpt) return baseOpt
73
+ const override = state.env.__strongJaTokenOpt
74
+ if (state.__strongJaTokenRuntimeOpt &&
75
+ state.__strongJaTokenRuntimeBase === baseOpt &&
76
+ state.__strongJaTokenRuntimeOverride === override) {
77
+ return state.__strongJaTokenRuntimeOpt
78
+ }
79
+ const merged = { ...baseOpt, ...override }
80
+ state.__strongJaTokenRuntimeOpt = merged
81
+ state.__strongJaTokenRuntimeBase = baseOpt
82
+ state.__strongJaTokenRuntimeOverride = override
83
+ return merged
84
+ }
85
+
86
+ function normalizeCoreRulesBeforePostprocess(value) {
87
+ if (!value) return []
88
+ const list = Array.isArray(value) ? value : [value]
89
+ const normalized = []
90
+ const seen = new Set()
91
+ for (let idx = 0; idx < list.length; idx++) {
92
+ const raw = list[idx]
93
+ if (typeof raw !== 'string') continue
94
+ const trimmed = raw.trim()
95
+ if (!trimmed || seen.has(trimmed)) continue
96
+ seen.add(trimmed)
97
+ normalized.push(trimmed)
98
+ }
99
+ return normalized
100
+ }
101
+
102
+ function ensureCoreRuleOrder(md, ruleNames, targetRuleName) {
103
+ if (!md || !md.core || !md.core.ruler) return
104
+ if (!ruleNames || ruleNames.length === 0) return
105
+ for (let idx = 0; idx < ruleNames.length; idx++) {
106
+ moveRuleBefore(md.core.ruler, ruleNames[idx], targetRuleName)
107
+ }
108
+ }
109
+
110
+ function moveRuleBefore(ruler, ruleName, beforeName) {
111
+ if (!ruler || !ruler.__rules__) return
112
+ const rules = ruler.__rules__
113
+ let fromIdx = -1
114
+ let beforeIdx = -1
115
+ for (let idx = 0; idx < rules.length; idx++) {
116
+ if (rules[idx].name === ruleName) fromIdx = idx
117
+ if (rules[idx].name === beforeName) beforeIdx = idx
118
+ if (fromIdx !== -1 && beforeIdx !== -1) break
119
+ }
120
+ // Ensure ruleName is before beforeName; keep existing order if already earlier.
121
+ if (fromIdx === -1 || beforeIdx === -1 || fromIdx < beforeIdx) return
122
+
123
+ const rule = rules.splice(fromIdx, 1)[0]
124
+ rules.splice(beforeIdx, 0, rule)
125
+ ruler.__cache__ = null
126
+ }
127
+
128
+ function moveRuleAfter(ruler, ruleName, afterName) {
129
+ if (!ruler || !ruler.__rules__) return
130
+ const rules = ruler.__rules__
131
+ let fromIdx = -1
132
+ let afterIdx = -1
133
+ for (let idx = 0; idx < rules.length; idx++) {
134
+ if (rules[idx].name === ruleName) fromIdx = idx
135
+ if (rules[idx].name === afterName) afterIdx = idx
136
+ if (fromIdx !== -1 && afterIdx !== -1) break
137
+ }
138
+ if (fromIdx === -1 || afterIdx === -1 || fromIdx === afterIdx + 1) return
139
+
140
+ const rule = rules.splice(fromIdx, 1)[0]
141
+ const targetIdx = fromIdx < afterIdx ? afterIdx - 1 : afterIdx
142
+ rules.splice(targetIdx + 1, 0, rule)
143
+ ruler.__cache__ = null
144
+ }
145
+
146
+ export {
147
+ CHAR_ASTERISK,
148
+ CHAR_OPEN_BRACKET,
149
+ CHAR_CLOSE_BRACKET,
150
+ CHAR_SPACE,
151
+ CHAR_TAB,
152
+ CHAR_NEWLINE,
153
+ REG_ATTRS,
154
+ hasJapaneseText,
155
+ isJapaneseChar,
156
+ hasCjkBreaksRule,
157
+ findPrevNonSpace,
158
+ findNextNonSpace,
159
+ resolveMode,
160
+ shouldUseJapaneseRule,
161
+ getRuntimeOpt,
162
+ normalizeCoreRulesBeforePostprocess,
163
+ ensureCoreRuleOrder,
164
+ moveRuleBefore,
165
+ moveRuleAfter
166
+ }