@peaceroad/markdown-it-strong-ja 0.6.1 → 0.7.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/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.1",
4
+ "version": "0.7.0",
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,227 @@
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
+ const opt = getRuntimeOpt(state, baseOpt)
47
+ if (baseOpt.hasCjkBreaks !== true && state.md) {
48
+ baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
49
+ }
50
+ if (baseOpt.hasCjkBreaks !== true) return
51
+ if (!state.tokens || state.tokens.length === 0) return
52
+ for (let i = 0; i < state.tokens.length; i++) {
53
+ const token = state.tokens[i]
54
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
55
+ let hasEmphasis = false
56
+ for (let j = 0; j < token.children.length; j++) {
57
+ const child = token.children[j]
58
+ if (!child) continue
59
+ if (child.type === 'strong_open' || child.type === 'strong_close' || child.type === 'em_open' || child.type === 'em_close') {
60
+ hasEmphasis = true
61
+ break
62
+ }
63
+ }
64
+ for (let j = 0; j < token.children.length; j++) {
65
+ const child = token.children[j]
66
+ if (!child) continue
67
+ if (child.type === 'softbreak') {
68
+ if (!hasEmphasis) continue
69
+ const prevToken = token.children[j - 1]
70
+ const nextToken = token.children[j + 1]
71
+ if (!prevToken || !nextToken) continue
72
+ if (prevToken.type !== 'text' || !prevToken.content) continue
73
+ if (nextToken.type !== 'text' || !nextToken.content) continue
74
+ const prevChar = prevToken.content.slice(-1)
75
+ const nextChar = nextToken.content.charAt(0)
76
+ const isAsciiWord = nextChar >= '0' && nextChar <= 'z' && /[A-Za-z0-9]/.test(nextChar)
77
+ const shouldReplace = isAsciiWord && nextChar !== '{' && nextChar !== '\\' && isJapaneseChar(prevChar) && !isJapaneseChar(nextChar)
78
+ if (!shouldReplace) continue
79
+ child.type = 'text'
80
+ child.tag = ''
81
+ child.content = ' '
82
+ child.markup = ''
83
+ child.info = ''
84
+ continue
85
+ }
86
+ if (child.type !== 'text' || !child.content) continue
87
+ if (!hasEmphasis) continue
88
+ if (child.content.indexOf('\n') === -1) continue
89
+ let normalized = ''
90
+ for (let idx = 0; idx < child.content.length; idx++) {
91
+ const ch = child.content[idx]
92
+ if (ch === '\n') {
93
+ const prevChar = idx > 0 ? child.content[idx - 1] : ''
94
+ const nextChar = idx + 1 < child.content.length ? child.content[idx + 1] : ''
95
+ const isAsciiWord = nextChar && nextChar >= '0' && nextChar <= 'z' && /[A-Za-z0-9]/.test(nextChar)
96
+ const shouldReplace = isAsciiWord && nextChar !== '{' && nextChar !== '\\' && isJapaneseChar(prevChar) && !isJapaneseChar(nextChar)
97
+ if (shouldReplace) {
98
+ normalized += ' '
99
+ continue
100
+ }
101
+ }
102
+ normalized += ch
103
+ }
104
+ if (normalized !== child.content) {
105
+ child.content = normalized
106
+ }
107
+ }
108
+ }
109
+ }
110
+ if (hasTextJoinRule) {
111
+ md.core.ruler.after('text_join', 'strong_ja_softbreak_spacing', normalizeSoftbreakSpacing)
112
+ } else {
113
+ md.core.ruler.after('inline', 'strong_ja_softbreak_spacing', normalizeSoftbreakSpacing)
114
+ }
115
+ }
116
+
117
+ const restoreSoftbreaksAfterCjk = (state) => {
118
+ if (!state) return
119
+ const opt = getRuntimeOpt(state, baseOpt)
120
+ if (opt.mditAttrs !== false) return
121
+ if (!state.md || state.md.__strongJaRestoreSoftbreaksForAttrs !== true) return
122
+ if (baseOpt.hasCjkBreaks !== true && state.md) {
123
+ baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
124
+ }
125
+ if (baseOpt.hasCjkBreaks !== true) return
126
+ if (!state.tokens || state.tokens.length === 0) return
127
+ for (let i = 0; i < state.tokens.length; i++) {
128
+ const token = state.tokens[i]
129
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
130
+ const children = token.children
131
+ for (let j = 0; j < children.length; j++) {
132
+ const child = children[j]
133
+ if (!child || child.type !== 'text' || child.content !== '') continue
134
+ let prevChar = ''
135
+ for (let k = j - 1; k >= 0; k--) {
136
+ const prev = children[k]
137
+ if (prev && prev.type === 'text' && prev.content) {
138
+ prevChar = prev.content.charAt(prev.content.length - 1)
139
+ break
140
+ }
141
+ }
142
+ if (!prevChar || !isJapaneseChar(prevChar)) continue
143
+ const next = children[j + 1]
144
+ if (!next || next.type !== 'text' || !next.content) continue
145
+ const nextChar = next.content.charAt(0)
146
+ if (nextChar !== '{') continue
147
+ child.type = 'softbreak'
148
+ child.tag = ''
149
+ child.content = '\n'
150
+ child.markup = ''
151
+ child.info = ''
152
+ }
153
+ }
154
+ }
155
+
156
+ const registerRestoreSoftbreaks = () => {
157
+ if (baseOpt.mditAttrs !== false) return
158
+ if (md.__strongJaTokenRestoreRegistered) return
159
+ const anchorRule = hasTextJoinRule ? 'text_join' : 'inline'
160
+ const added = md.core.ruler.after(anchorRule, 'strong_ja_restore_softbreaks', restoreSoftbreaksAfterCjk)
161
+ if (added !== false) {
162
+ md.__strongJaTokenRestoreRegistered = true
163
+ md.__strongJaRestoreSoftbreaksForAttrs = baseOpt.mditAttrs === false
164
+ if (baseOpt.hasCjkBreaks) {
165
+ moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', 'cjk_breaks')
166
+ }
167
+ if (baseOpt.patchCorePush !== false && !md.__strongJaTokenPatchCorePush) {
168
+ md.__strongJaTokenPatchCorePush = true
169
+ const originalPush = md.core.ruler.push.bind(md.core.ruler)
170
+ md.core.ruler.push = (name, fn, options) => {
171
+ const res = originalPush(name, fn, options)
172
+ if (name && name.indexOf && name.indexOf('cjk_breaks') !== -1) {
173
+ baseOpt.hasCjkBreaks = true
174
+ moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', name)
175
+ }
176
+ return res
177
+ }
178
+ }
179
+ if (baseOpt.hasCjkBreaks) {
180
+ moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', 'cjk_breaks')
181
+ }
182
+ }
183
+ }
184
+ registerRestoreSoftbreaks()
185
+
186
+ if (baseOpt.mditAttrs !== false && !md.__strongJaTokenPreAttrsRegistered) {
187
+ md.__strongJaTokenPreAttrsRegistered = true
188
+ md.core.ruler.before('linkify', 'strong_ja_token_pre_attrs', (state) => {
189
+ if (!state || !state.tokens) return
190
+ const opt = getRuntimeOpt(state, baseOpt)
191
+ if (opt.mditAttrs === false) return
192
+ for (let i = 0; i < state.tokens.length; i++) {
193
+ const token = state.tokens[i]
194
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
195
+ const children = token.children
196
+ let lastMeaningful = children.length - 1
197
+ while (lastMeaningful >= 0) {
198
+ const child = children[lastMeaningful]
199
+ if (!child) {
200
+ lastMeaningful--
201
+ continue
202
+ }
203
+ if (child.type === 'text' && child.content === '') {
204
+ lastMeaningful--
205
+ continue
206
+ }
207
+ break
208
+ }
209
+ for (let j = 0; j <= lastMeaningful; j++) {
210
+ const child = children[j]
211
+ if (!child || child.type !== 'text' || !child.content) continue
212
+ const content = child.content
213
+ if (content.charCodeAt(0) !== 0x7B || content.charCodeAt(content.length - 1) !== 0x7D) continue
214
+ if (REG_ATTRS.test(content)) continue
215
+ if (j !== lastMeaningful) continue
216
+ const placeholder = new Token('text', '', 0)
217
+ placeholder.content = ''
218
+ children.splice(j + 1, 0, placeholder)
219
+ lastMeaningful = j
220
+ j++
221
+ }
222
+ }
223
+ })
224
+ }
225
+ }
226
+
227
+ 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
+ }