@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.
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@peaceroad/markdown-it-strong-ja",
3
3
  "description": "This is a plugin for markdown-it. It is an alternative to the standard `**` (strong) and `*` (em) processing. It also processes strings that cannot be converted by the standard.",
4
- "version": "0.6.2",
4
+ "version": "0.7.1",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "files": [
8
8
  "index.js",
9
+ "src",
9
10
  "README.md",
10
11
  "LICENSE"
11
12
  ],
@@ -19,8 +20,8 @@
19
20
  "markdown-it": "^14.1.0"
20
21
  },
21
22
  "devDependencies": {
22
- "@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.4",
23
- "@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.8.0",
23
+ "@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.5",
24
+ "@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.8.2",
24
25
  "markdown-it-attrs": "^4.3.1",
25
26
  "markdown-it-sub": "^2.0.0",
26
27
  "markdown-it-sup": "^2.0.0"
@@ -0,0 +1,226 @@
1
+ import Token from 'markdown-it/lib/token.mjs'
2
+ import {
3
+ REG_ATTRS,
4
+ isJapaneseChar,
5
+ hasCjkBreaksRule,
6
+ getRuntimeOpt,
7
+ moveRuleAfter
8
+ } from './token-utils.js'
9
+
10
+ const registerTokenCompat = (md, baseOpt) => {
11
+ const hasTextJoinRule = Array.isArray(md.core?.ruler?.__rules__)
12
+ ? md.core.ruler.__rules__.some((rule) => rule && rule.name === 'text_join')
13
+ : false
14
+
15
+ if (!md.__strongJaTokenTrimTrailingRegistered) {
16
+ md.__strongJaTokenTrimTrailingRegistered = true
17
+ const trimInlineTrailingSpaces = (state) => {
18
+ if (!state || !state.tokens) return
19
+ for (let i = 0; i < state.tokens.length; i++) {
20
+ const token = state.tokens[i]
21
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
22
+ let idx = token.children.length - 1
23
+ while (idx >= 0 && (!token.children[idx] || (token.children[idx].type === 'text' && token.children[idx].content === ''))) {
24
+ idx--
25
+ }
26
+ if (idx < 0) continue
27
+ const tail = token.children[idx]
28
+ if (!tail || tail.type !== 'text' || !tail.content) continue
29
+ const trimmed = tail.content.replace(/[ \t]+$/, '')
30
+ if (trimmed !== tail.content) {
31
+ tail.content = trimmed
32
+ }
33
+ }
34
+ }
35
+ if (hasTextJoinRule) {
36
+ md.core.ruler.after('text_join', 'strong_ja_trim_trailing_spaces', trimInlineTrailingSpaces)
37
+ } else {
38
+ md.core.ruler.after('inline', 'strong_ja_trim_trailing_spaces', trimInlineTrailingSpaces)
39
+ }
40
+ }
41
+
42
+ if (!md.__strongJaTokenSoftbreakSpacingRegistered) {
43
+ md.__strongJaTokenSoftbreakSpacingRegistered = true
44
+ const normalizeSoftbreakSpacing = (state) => {
45
+ if (!state) return
46
+ if (baseOpt.hasCjkBreaks !== true && state.md) {
47
+ baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
48
+ }
49
+ if (baseOpt.hasCjkBreaks !== true) return
50
+ if (!state.tokens || state.tokens.length === 0) return
51
+ for (let i = 0; i < state.tokens.length; i++) {
52
+ const token = state.tokens[i]
53
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
54
+ let hasEmphasis = false
55
+ for (let j = 0; j < token.children.length; j++) {
56
+ const child = token.children[j]
57
+ if (!child) continue
58
+ if (child.type === 'strong_open' || child.type === 'strong_close' || child.type === 'em_open' || child.type === 'em_close') {
59
+ hasEmphasis = true
60
+ break
61
+ }
62
+ }
63
+ for (let j = 0; j < token.children.length; j++) {
64
+ const child = token.children[j]
65
+ if (!child) continue
66
+ if (child.type === 'softbreak') {
67
+ if (!hasEmphasis) continue
68
+ const prevToken = token.children[j - 1]
69
+ const nextToken = token.children[j + 1]
70
+ if (!prevToken || !nextToken) continue
71
+ if (prevToken.type !== 'text' || !prevToken.content) continue
72
+ if (nextToken.type !== 'text' || !nextToken.content) continue
73
+ const prevChar = prevToken.content.slice(-1)
74
+ const nextChar = nextToken.content.charAt(0)
75
+ const isAsciiWord = nextChar >= '0' && nextChar <= 'z' && /[A-Za-z0-9]/.test(nextChar)
76
+ const shouldReplace = isAsciiWord && nextChar !== '{' && nextChar !== '\\' && isJapaneseChar(prevChar) && !isJapaneseChar(nextChar)
77
+ if (!shouldReplace) continue
78
+ child.type = 'text'
79
+ child.tag = ''
80
+ child.content = ' '
81
+ child.markup = ''
82
+ child.info = ''
83
+ continue
84
+ }
85
+ if (child.type !== 'text' || !child.content) continue
86
+ if (!hasEmphasis) continue
87
+ if (child.content.indexOf('\n') === -1) continue
88
+ let normalized = ''
89
+ for (let idx = 0; idx < child.content.length; idx++) {
90
+ const ch = child.content[idx]
91
+ if (ch === '\n') {
92
+ const prevChar = idx > 0 ? child.content[idx - 1] : ''
93
+ const nextChar = idx + 1 < child.content.length ? child.content[idx + 1] : ''
94
+ const isAsciiWord = nextChar && nextChar >= '0' && nextChar <= 'z' && /[A-Za-z0-9]/.test(nextChar)
95
+ const shouldReplace = isAsciiWord && nextChar !== '{' && nextChar !== '\\' && isJapaneseChar(prevChar) && !isJapaneseChar(nextChar)
96
+ if (shouldReplace) {
97
+ normalized += ' '
98
+ continue
99
+ }
100
+ }
101
+ normalized += ch
102
+ }
103
+ if (normalized !== child.content) {
104
+ child.content = normalized
105
+ }
106
+ }
107
+ }
108
+ }
109
+ if (hasTextJoinRule) {
110
+ md.core.ruler.after('text_join', 'strong_ja_softbreak_spacing', normalizeSoftbreakSpacing)
111
+ } else {
112
+ md.core.ruler.after('inline', 'strong_ja_softbreak_spacing', normalizeSoftbreakSpacing)
113
+ }
114
+ }
115
+
116
+ const restoreSoftbreaksAfterCjk = (state) => {
117
+ if (!state) return
118
+ const opt = getRuntimeOpt(state, baseOpt)
119
+ if (opt.mditAttrs !== false) return
120
+ if (!state.md || state.md.__strongJaRestoreSoftbreaksForAttrs !== true) return
121
+ if (baseOpt.hasCjkBreaks !== true && state.md) {
122
+ baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
123
+ }
124
+ if (baseOpt.hasCjkBreaks !== true) return
125
+ if (!state.tokens || state.tokens.length === 0) return
126
+ for (let i = 0; i < state.tokens.length; i++) {
127
+ const token = state.tokens[i]
128
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
129
+ const children = token.children
130
+ for (let j = 0; j < children.length; j++) {
131
+ const child = children[j]
132
+ if (!child || child.type !== 'text' || child.content !== '') continue
133
+ let prevChar = ''
134
+ for (let k = j - 1; k >= 0; k--) {
135
+ const prev = children[k]
136
+ if (prev && prev.type === 'text' && prev.content) {
137
+ prevChar = prev.content.charAt(prev.content.length - 1)
138
+ break
139
+ }
140
+ }
141
+ if (!prevChar || !isJapaneseChar(prevChar)) continue
142
+ const next = children[j + 1]
143
+ if (!next || next.type !== 'text' || !next.content) continue
144
+ const nextChar = next.content.charAt(0)
145
+ if (nextChar !== '{') continue
146
+ child.type = 'softbreak'
147
+ child.tag = ''
148
+ child.content = '\n'
149
+ child.markup = ''
150
+ child.info = ''
151
+ }
152
+ }
153
+ }
154
+
155
+ const registerRestoreSoftbreaks = () => {
156
+ if (baseOpt.mditAttrs !== false) return
157
+ if (md.__strongJaTokenRestoreRegistered) return
158
+ const anchorRule = hasTextJoinRule ? 'text_join' : 'inline'
159
+ const added = md.core.ruler.after(anchorRule, 'strong_ja_restore_softbreaks', restoreSoftbreaksAfterCjk)
160
+ if (added !== false) {
161
+ md.__strongJaTokenRestoreRegistered = true
162
+ md.__strongJaRestoreSoftbreaksForAttrs = baseOpt.mditAttrs === false
163
+ if (baseOpt.hasCjkBreaks) {
164
+ moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', 'cjk_breaks')
165
+ }
166
+ if (baseOpt.patchCorePush !== false && !md.__strongJaTokenPatchCorePush) {
167
+ md.__strongJaTokenPatchCorePush = true
168
+ const originalPush = md.core.ruler.push.bind(md.core.ruler)
169
+ md.core.ruler.push = (name, fn, options) => {
170
+ const res = originalPush(name, fn, options)
171
+ if (name && name.indexOf && name.indexOf('cjk_breaks') !== -1) {
172
+ baseOpt.hasCjkBreaks = true
173
+ moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', name)
174
+ }
175
+ return res
176
+ }
177
+ }
178
+ if (baseOpt.hasCjkBreaks) {
179
+ moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', 'cjk_breaks')
180
+ }
181
+ }
182
+ }
183
+ registerRestoreSoftbreaks()
184
+
185
+ if (baseOpt.mditAttrs !== false && !md.__strongJaTokenPreAttrsRegistered) {
186
+ md.__strongJaTokenPreAttrsRegistered = true
187
+ md.core.ruler.before('linkify', 'strong_ja_token_pre_attrs', (state) => {
188
+ if (!state || !state.tokens) return
189
+ const opt = getRuntimeOpt(state, baseOpt)
190
+ if (opt.mditAttrs === false) return
191
+ for (let i = 0; i < state.tokens.length; i++) {
192
+ const token = state.tokens[i]
193
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
194
+ const children = token.children
195
+ let lastMeaningful = children.length - 1
196
+ while (lastMeaningful >= 0) {
197
+ const child = children[lastMeaningful]
198
+ if (!child) {
199
+ lastMeaningful--
200
+ continue
201
+ }
202
+ if (child.type === 'text' && child.content === '') {
203
+ lastMeaningful--
204
+ continue
205
+ }
206
+ break
207
+ }
208
+ for (let j = 0; j <= lastMeaningful; j++) {
209
+ const child = children[j]
210
+ if (!child || child.type !== 'text' || !child.content) continue
211
+ const content = child.content
212
+ if (content.charCodeAt(0) !== 0x7B || content.charCodeAt(content.length - 1) !== 0x7D) continue
213
+ if (REG_ATTRS.test(content)) continue
214
+ if (j !== lastMeaningful) continue
215
+ const placeholder = new Token('text', '', 0)
216
+ placeholder.content = ''
217
+ children.splice(j + 1, 0, placeholder)
218
+ lastMeaningful = j
219
+ j++
220
+ }
221
+ }
222
+ })
223
+ }
224
+ }
225
+
226
+ export { registerTokenCompat }
@@ -0,0 +1,439 @@
1
+ import { isWhiteSpace } from 'markdown-it/lib/common/utils.mjs'
2
+ import Token from 'markdown-it/lib/token.mjs'
3
+ import {
4
+ CHAR_ASTERISK,
5
+ CHAR_SPACE,
6
+ CHAR_TAB,
7
+ findPrevNonSpace,
8
+ findNextNonSpace,
9
+ resolveMode,
10
+ shouldUseJapaneseRule,
11
+ getRuntimeOpt
12
+ } from './token-utils.js'
13
+
14
+ const findMatchingEmOpen = (tokens, closeIdx) => {
15
+ let depth = 0
16
+ for (let i = closeIdx; i >= 0; i--) {
17
+ const t = tokens[i]
18
+ if (!t) continue
19
+ if (t.type === 'em_close') depth++
20
+ if (t.type === 'em_open') {
21
+ depth--
22
+ if (depth === 0) return i
23
+ }
24
+ }
25
+ return -1
26
+ }
27
+
28
+ const rebuildInlineLevels = (tokens) => {
29
+ let level = 0
30
+ for (let i = 0; i < tokens.length; i++) {
31
+ const t = tokens[i]
32
+ if (!t) continue
33
+ t.level = level
34
+ if (t.nesting === 1) level++
35
+ else if (t.nesting === -1) level--
36
+ }
37
+ }
38
+
39
+ const findLinkClose = (tokens, startIdx) => {
40
+ let depth = 0
41
+ for (let i = startIdx; i < tokens.length; i++) {
42
+ const t = tokens[i]
43
+ if (!t) continue
44
+ if (t.type === 'link_open') depth++
45
+ if (t.type === 'link_close') {
46
+ depth--
47
+ if (depth === 0) return i
48
+ }
49
+ }
50
+ return -1
51
+ }
52
+
53
+ const findLinkOpen = (tokens, closeIdx) => {
54
+ let depth = 0
55
+ for (let i = closeIdx; i >= 0; i--) {
56
+ const t = tokens[i]
57
+ if (!t) continue
58
+ if (t.type === 'link_close') depth++
59
+ if (t.type === 'link_open') {
60
+ depth--
61
+ if (depth === 0) return i
62
+ }
63
+ }
64
+ return -1
65
+ }
66
+
67
+ const nextNonEmptyIndex = (tokens, startIdx) => {
68
+ for (let i = startIdx; i < tokens.length; i++) {
69
+ const t = tokens[i]
70
+ if (!t) continue
71
+ if (t.type !== 'text') return i
72
+ if (t.content) return i
73
+ }
74
+ return -1
75
+ }
76
+
77
+ const fixTrailingStrong = (tokens) => {
78
+ let changed = false
79
+ for (let i = 1; i < tokens.length; i++) {
80
+ const token = tokens[i]
81
+ if (!token || token.type !== 'text' || !token.content) continue
82
+ const starIdx = token.content.indexOf('**')
83
+ if (starIdx <= 0) continue
84
+ if (!tokens[i - 1] || tokens[i - 1].type !== 'em_close') continue
85
+
86
+ const closeIdx = i - 1
87
+ const openIdx = findMatchingEmOpen(tokens, closeIdx)
88
+ if (openIdx === -1) continue
89
+
90
+ let hasInnerEm = false
91
+ let emDepth = 0
92
+ let hasStrongBetween = false
93
+ for (let j = openIdx + 1; j < closeIdx; j++) {
94
+ const t = tokens[j]
95
+ if (!t) continue
96
+ if (t.type === 'strong_open' || t.type === 'strong_close') {
97
+ hasStrongBetween = true
98
+ break
99
+ }
100
+ if (t.type === 'em_open') {
101
+ if (emDepth === 0) hasInnerEm = true
102
+ emDepth++
103
+ } else if (t.type === 'em_close') {
104
+ if (emDepth > 0) emDepth--
105
+ }
106
+ }
107
+ if (hasStrongBetween || !hasInnerEm) continue
108
+
109
+ const innerOpenIdx = openIdx + 1
110
+ if (innerOpenIdx + 3 < closeIdx &&
111
+ tokens[innerOpenIdx] && tokens[innerOpenIdx].type === 'em_open' &&
112
+ tokens[innerOpenIdx + 1] && tokens[innerOpenIdx + 1].type === 'text' &&
113
+ tokens[innerOpenIdx + 2] && tokens[innerOpenIdx + 2].type === 'em_close' &&
114
+ tokens[innerOpenIdx + 3] && tokens[innerOpenIdx + 3].type === 'text' &&
115
+ closeIdx === innerOpenIdx + 4) {
116
+ tokens.splice(innerOpenIdx + 2, 1)
117
+ tokens.splice(innerOpenIdx, 1)
118
+ const movedOpen = new Token('em_open', 'em', 1)
119
+ movedOpen.markup = '*'
120
+ const movedClose = new Token('em_close', 'em', -1)
121
+ movedClose.markup = '*'
122
+ tokens.splice(innerOpenIdx + 1, 0, movedOpen)
123
+ tokens.splice(innerOpenIdx + 3, 0, movedClose)
124
+ }
125
+
126
+ const before = token.content.slice(0, starIdx)
127
+ const after = token.content.slice(starIdx + 2)
128
+
129
+ tokens.splice(closeIdx, 1)
130
+ if (closeIdx < i) i--
131
+
132
+ const openToken = tokens[openIdx]
133
+ if (!openToken) continue
134
+ openToken.type = 'strong_open'
135
+ openToken.tag = 'strong'
136
+ openToken.markup = '**'
137
+ openToken.nesting = 1
138
+
139
+ if (before) {
140
+ token.content = before
141
+ } else {
142
+ tokens.splice(i, 1)
143
+ i--
144
+ }
145
+
146
+ const insertAt = i + 1
147
+ const strongClose = new Token('strong_close', 'strong', -1)
148
+ strongClose.markup = '**'
149
+ tokens.splice(insertAt, 0, strongClose)
150
+ if (after) {
151
+ const tail = new Token('text', '', 0)
152
+ tail.content = after
153
+ tokens.splice(insertAt + 1, 0, tail)
154
+ }
155
+ changed = true
156
+ }
157
+ return changed
158
+ }
159
+
160
+ function fixEmOuterStrongSequence(tokens) {
161
+ let changed = false
162
+ let i = 0
163
+ while (i < tokens.length) {
164
+ const idx0 = nextNonEmptyIndex(tokens, i)
165
+ if (idx0 === -1) break
166
+ const t0 = tokens[idx0]
167
+ if (!t0 || t0.type !== 'em_open') {
168
+ i = idx0 + 1
169
+ continue
170
+ }
171
+ const idx1 = nextNonEmptyIndex(tokens, idx0 + 1)
172
+ const idx2 = idx1 === -1 ? -1 : nextNonEmptyIndex(tokens, idx1 + 1)
173
+ const idx3 = idx2 === -1 ? -1 : nextNonEmptyIndex(tokens, idx2 + 1)
174
+ const idx4 = idx3 === -1 ? -1 : nextNonEmptyIndex(tokens, idx3 + 1)
175
+ const idx5 = idx4 === -1 ? -1 : nextNonEmptyIndex(tokens, idx4 + 1)
176
+ const idx6 = idx5 === -1 ? -1 : nextNonEmptyIndex(tokens, idx5 + 1)
177
+ const idx7 = idx6 === -1 ? -1 : nextNonEmptyIndex(tokens, idx6 + 1)
178
+ if (idx7 === -1) break
179
+
180
+ const t1 = tokens[idx1]
181
+ const t2 = tokens[idx2]
182
+ const t3 = tokens[idx3]
183
+ const t4 = tokens[idx4]
184
+ const t5 = tokens[idx5]
185
+ const t6 = tokens[idx6]
186
+ const t7 = tokens[idx7]
187
+
188
+ if (!t1 || !t2 || !t3 || !t4 || !t5 || !t6 || !t7) {
189
+ i = idx0 + 1
190
+ continue
191
+ }
192
+ if (t1.type !== 'em_open') {
193
+ i = idx0 + 1
194
+ continue
195
+ }
196
+ if (t3.type !== 'em_close' || t5.type !== 'em_close') {
197
+ i = idx0 + 1
198
+ continue
199
+ }
200
+ if (t7.type !== 'strong_open') {
201
+ i = idx0 + 1
202
+ continue
203
+ }
204
+ if (t2.type !== 'text' || !t2.content) {
205
+ i = idx0 + 1
206
+ continue
207
+ }
208
+ if (t4.type !== 'text' || !t4.content) {
209
+ i = idx0 + 1
210
+ continue
211
+ }
212
+ if (t6.type !== 'text' || !t6.content) {
213
+ i = idx0 + 1
214
+ continue
215
+ }
216
+
217
+ t0.type = 'strong_open'
218
+ t0.tag = 'strong'
219
+ t0.markup = '**'
220
+ t0.nesting = 1
221
+
222
+ const emOpen = new Token('em_open', 'em', 1)
223
+ emOpen.markup = '*'
224
+ const emClose = new Token('em_close', 'em', -1)
225
+ emClose.markup = '*'
226
+ const strongClose = new Token('strong_close', 'strong', -1)
227
+ strongClose.markup = '**'
228
+
229
+ const removeIndices = [idx7, idx5, idx3, idx1].sort((a, b) => b - a)
230
+ for (const removeIdx of removeIndices) {
231
+ if (removeIdx >= 0 && removeIdx < tokens.length) {
232
+ tokens.splice(removeIdx, 1)
233
+ }
234
+ }
235
+
236
+ const idxT4 = tokens.indexOf(t4)
237
+ if (idxT4 === -1) {
238
+ changed = true
239
+ i = idx0 + 1
240
+ continue
241
+ }
242
+ tokens.splice(idxT4, 0, emOpen)
243
+
244
+ let idxT6 = tokens.indexOf(t6)
245
+ if (idxT6 === -1) {
246
+ changed = true
247
+ i = idx0 + 1
248
+ continue
249
+ }
250
+ tokens.splice(idxT6, 0, emClose)
251
+
252
+ idxT6 = tokens.indexOf(t6)
253
+ if (idxT6 === -1) {
254
+ changed = true
255
+ i = idx0 + 1
256
+ continue
257
+ }
258
+ tokens.splice(idxT6 + 1, 0, strongClose)
259
+
260
+ changed = true
261
+ i = idxT6 + 2
262
+ }
263
+ return changed
264
+ }
265
+
266
+ const shiftEmWithLeadingStar = (tokens, rangeStart, rangeEnd, closeIdx) => {
267
+ let openIdx = findMatchingEmOpen(tokens, closeIdx)
268
+ if (openIdx === -1 || openIdx < rangeStart || openIdx >= rangeEnd) return false
269
+
270
+ for (let j = openIdx + 1; j < closeIdx; j++) {
271
+ if (!tokens[j]) continue
272
+ if (tokens[j].type === 'em_open' || tokens[j].type === 'em_close') return false
273
+ }
274
+
275
+ let starTokenIdx = -1
276
+ let starPos = -1
277
+ for (let i = openIdx - 1; i >= rangeStart; i--) {
278
+ const t = tokens[i]
279
+ if (!t || t.type !== 'text' || !t.content) continue
280
+ const pos = t.content.lastIndexOf('*')
281
+ if (pos <= 0) continue
282
+ const prevChar = t.content.charAt(pos - 1)
283
+ const nextChar = pos + 1 < t.content.length ? t.content.charAt(pos + 1) : ''
284
+ if (!/\s/.test(prevChar)) continue
285
+ if (!nextChar || /\s/.test(nextChar) || nextChar === '*') continue
286
+ starTokenIdx = i
287
+ starPos = pos
288
+ break
289
+ }
290
+ if (starTokenIdx === -1) return false
291
+
292
+ const starToken = tokens[starTokenIdx]
293
+ const before = starToken.content.slice(0, starPos)
294
+ const after = starToken.content.slice(starPos + 1)
295
+ let insertAt = starTokenIdx
296
+ if (before) {
297
+ starToken.content = before
298
+ insertAt = starTokenIdx + 1
299
+ } else {
300
+ tokens.splice(starTokenIdx, 1)
301
+ if (starTokenIdx < openIdx) {
302
+ openIdx--
303
+ closeIdx--
304
+ }
305
+ }
306
+
307
+ const emOpen = new Token('em_open', 'em', 1)
308
+ emOpen.markup = '*'
309
+ tokens.splice(insertAt, 0, emOpen)
310
+ if (insertAt <= openIdx) {
311
+ openIdx++
312
+ closeIdx++
313
+ }
314
+ if (after) {
315
+ const afterToken = new Token('text', '', 0)
316
+ afterToken.content = after
317
+ tokens.splice(insertAt + 1, 0, afterToken)
318
+ if (insertAt + 1 <= openIdx) {
319
+ openIdx++
320
+ closeIdx++
321
+ }
322
+ }
323
+
324
+ const openToken = tokens[openIdx]
325
+ if (!openToken) return false
326
+ openToken.type = 'em_close'
327
+ openToken.tag = 'em'
328
+ openToken.markup = '*'
329
+ openToken.nesting = -1
330
+
331
+ tokens.splice(closeIdx, 1)
332
+ const tailIdx = closeIdx - 1
333
+ if (tailIdx >= 0 && tokens[tailIdx] && tokens[tailIdx].type === 'text') {
334
+ tokens[tailIdx].content += '*'
335
+ } else {
336
+ const tail = new Token('text', '', 0)
337
+ tail.content = '*'
338
+ tokens.splice(closeIdx, 0, tail)
339
+ }
340
+ return true
341
+ }
342
+
343
+ const fixLeadingAsteriskEm = (tokens) => {
344
+ let changed = false
345
+ for (let i = 0; i < tokens.length; i++) {
346
+ const t = tokens[i]
347
+ if (!t || t.type !== 'em_close') continue
348
+ const nextIdx = nextNonEmptyIndex(tokens, i + 1)
349
+ if (nextIdx === -1 || tokens[nextIdx].type !== 'link_close') continue
350
+ const linkCloseIdx = nextIdx
351
+ const linkOpenIdx = findLinkOpen(tokens, linkCloseIdx)
352
+ if (linkOpenIdx === -1) continue
353
+ if (shiftEmWithLeadingStar(tokens, linkOpenIdx + 1, linkCloseIdx, i)) {
354
+ changed = true
355
+ i = linkCloseIdx
356
+ }
357
+ }
358
+ return changed
359
+ }
360
+
361
+ const patchScanDelims = (md) => {
362
+ if (md.__strongJaTokenScanDelimsPatched) return
363
+ md.__strongJaTokenScanDelimsPatched = true
364
+
365
+ const original = md.inline.State.prototype.scanDelims
366
+ md.inline.State.prototype.scanDelims = function strongJaTokenScanDelims(start, canSplitWord) {
367
+ const marker = this.src.charCodeAt(start)
368
+ if (marker !== CHAR_ASTERISK) {
369
+ return original.call(this, start, canSplitWord)
370
+ }
371
+
372
+ const baseOpt = this.md && this.md.__strongJaTokenOpt ? this.md.__strongJaTokenOpt : null
373
+ const opt = getRuntimeOpt(this, baseOpt)
374
+ if (!opt) {
375
+ return original.call(this, start, canSplitWord)
376
+ }
377
+ const mode = resolveMode(opt)
378
+ const useJapaneseRule = shouldUseJapaneseRule(this, opt, mode)
379
+ if (!useJapaneseRule) {
380
+ return original.call(this, start, canSplitWord)
381
+ }
382
+
383
+ const max = this.posMax
384
+ const lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20
385
+
386
+ let pos = start
387
+ while (pos < max && this.src.charCodeAt(pos) === marker) { pos++ }
388
+ const count = pos - start
389
+
390
+ const nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20
391
+
392
+ const isLastPunctChar = false
393
+ const isNextPunctChar = false
394
+
395
+ let isLastWhiteSpace = isWhiteSpace(lastChar)
396
+ let isNextWhiteSpace = isWhiteSpace(nextChar)
397
+ if (useJapaneseRule) {
398
+ if (isLastWhiteSpace && (lastChar === CHAR_SPACE || lastChar === CHAR_TAB)) {
399
+ const prevNonSpace = findPrevNonSpace(this.src, start - 2)
400
+ if (prevNonSpace && prevNonSpace !== CHAR_ASTERISK) {
401
+ isLastWhiteSpace = false
402
+ }
403
+ }
404
+ if (isNextWhiteSpace && (nextChar === CHAR_SPACE || nextChar === CHAR_TAB)) {
405
+ const nextNonSpace = findNextNonSpace(this.src, pos, max)
406
+ if (nextNonSpace && nextNonSpace !== CHAR_ASTERISK) {
407
+ isNextWhiteSpace = false
408
+ }
409
+ }
410
+ }
411
+
412
+ const left_flanking =
413
+ !isNextWhiteSpace && (!isNextPunctChar || isLastWhiteSpace || isLastPunctChar)
414
+ const right_flanking =
415
+ !isLastWhiteSpace && (!isLastPunctChar || isNextWhiteSpace || isNextPunctChar)
416
+
417
+ const can_open = left_flanking && (canSplitWord || !right_flanking || isLastPunctChar)
418
+ const can_close = right_flanking && (canSplitWord || !left_flanking || isNextPunctChar)
419
+
420
+ const forbidClose = lastChar === 0x5B || lastChar === 0x28
421
+ const forbidOpen = nextChar === 0x5D || nextChar === 0x29
422
+ return {
423
+ can_open: forbidOpen ? false : can_open,
424
+ can_close: forbidClose ? false : can_close,
425
+ length: count
426
+ }
427
+ }
428
+ }
429
+
430
+ export {
431
+ rebuildInlineLevels,
432
+ findLinkClose,
433
+ findLinkOpen,
434
+ nextNonEmptyIndex,
435
+ fixTrailingStrong,
436
+ fixEmOuterStrongSequence,
437
+ fixLeadingAsteriskEm,
438
+ patchScanDelims
439
+ }