@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/README.md +42 -13
- package/index.js +44 -2488
- package/package.json +4 -3
- package/src/token-compat.js +226 -0
- package/src/token-core.js +439 -0
- package/src/token-link-utils.js +774 -0
- package/src/token-postprocess.js +340 -0
- package/src/token-utils.js +166 -0
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.
|
|
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.
|
|
23
|
-
"@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.8.
|
|
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
|
+
}
|