@peaceroad/markdown-it-strong-ja 0.7.1 → 0.8.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/index.js CHANGED
@@ -1,62 +1,32 @@
1
- import { hasCjkBreaksRule, normalizeCoreRulesBeforePostprocess, ensureCoreRuleOrder, resolveMode } from './src/token-utils.js'
1
+ import { hasCjkBreaksRule, normalizeCoreRulesBeforePostprocess, ensureCoreRuleOrder, deriveModeInfo } from './src/token-utils.js'
2
2
  import { patchScanDelims } from './src/token-core.js'
3
3
  import { registerTokenCompat } from './src/token-compat.js'
4
4
  import { registerTokenPostprocess } from './src/token-postprocess.js'
5
5
 
6
- const buildNoLinkCacheKey = (opt) => {
7
- const mode = resolveMode(opt)
8
- const mditAttrs = opt && opt.mditAttrs === false ? '0' : '1'
9
- const mdBreaks = opt && opt.mdBreaks === true ? '1' : '0'
10
- return `${mode}|${mditAttrs}|${mdBreaks}`
11
- }
12
-
13
- const getNoLinkMdInstance = (md, opt) => {
14
- const baseOpt = opt || md.__strongJaTokenOpt || { mode: 'japanese' }
15
- const key = buildNoLinkCacheKey(baseOpt)
16
- if (!md.__strongJaTokenNoLinkCache) {
17
- md.__strongJaTokenNoLinkCache = new Map()
18
- }
19
- const cache = md.__strongJaTokenNoLinkCache
20
- if (cache.has(key)) return cache.get(key)
21
- const noLink = new md.constructor(md.options)
22
- mditStrongJa(noLink, { ...baseOpt, _skipPostprocess: true })
23
- noLink.inline.ruler.disable(['link'])
24
- cache.set(key, noLink)
25
- return noLink
26
- }
27
-
28
6
  const mditStrongJa = (md, option) => {
29
- if (option && typeof option.engine === 'string' && option.engine !== 'token') {
30
- throw new Error('mditStrongJa: legacy engine was removed; use token (default)')
31
- }
7
+ if (option && typeof option.engine === 'string' && option.engine !== 'token') {
8
+ throw new Error('mditStrongJa: legacy engine was removed; use token (default)')
9
+ }
32
10
  const opt = {
33
11
  mditAttrs: true, // assume markdown-it-attrs integration by default
34
- mdBreaks: md.options.breaks, // inherit md.options.breaks for compat handling
35
- mode: 'japanese', // 'japanese' | 'aggressive' | 'compatible' (pairing behavior)
36
- coreRulesBeforePostprocess: [], // e.g. ['cjk_breaks'] to keep rules ahead of postprocess
37
- postprocess: true, // enable link/ref reconstruction pass
38
- patchCorePush: true // keep restore-softbreaks after late cjk_breaks
39
- }
40
- if (option) Object.assign(opt, option)
41
- opt.hasCjkBreaks = hasCjkBreaksRule(md)
42
-
12
+ mode: 'japanese', // 'japanese'(->japanese-boundary-guard) | 'japanese-boundary' | 'japanese-boundary-guard' | 'aggressive' | 'compatible'
13
+ coreRulesBeforePostprocess: [], // e.g. ['cjk_breaks'] to keep rules ahead of postprocess
14
+ postprocess: true, // enable link/ref reconstruction pass
15
+ patchCorePush: true // keep restore-softbreaks after late cjk_breaks
16
+ }
17
+ if (option) Object.assign(opt, option)
18
+ opt.hasCjkBreaks = hasCjkBreaksRule(md)
19
+ deriveModeInfo(opt)
20
+
43
21
  md.__strongJaTokenOpt = opt
44
22
  patchScanDelims(md)
45
23
  registerTokenCompat(md, opt)
46
24
 
47
- if (!opt._skipPostprocess) {
48
- registerTokenPostprocess(md, opt, getNoLinkMdInstance)
49
- const rawCoreRules = opt.coreRulesBeforePostprocess
50
- const hasCoreRuleConfig = Array.isArray(rawCoreRules)
51
- ? rawCoreRules.length > 0
52
- : !!rawCoreRules
53
- const coreRulesBeforePostprocess = hasCoreRuleConfig
54
- ? normalizeCoreRulesBeforePostprocess(rawCoreRules)
55
- : []
56
- ensureCoreRuleOrder(md, coreRulesBeforePostprocess, 'strong_ja_token_postprocess')
57
- }
25
+ registerTokenPostprocess(md, opt)
26
+ const coreRulesBeforePostprocess = normalizeCoreRulesBeforePostprocess(opt.coreRulesBeforePostprocess)
27
+ ensureCoreRuleOrder(md, coreRulesBeforePostprocess, 'strong_ja_token_postprocess')
58
28
 
59
29
  return md
60
30
  }
61
-
62
- export default mditStrongJa
31
+
32
+ export default mditStrongJa
package/package.json CHANGED
@@ -1,7 +1,7 @@
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.7.1",
4
+ "version": "0.8.0",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "files": [
@@ -11,7 +11,22 @@
11
11
  "LICENSE"
12
12
  ],
13
13
  "scripts": {
14
- "test": "node test/test.js"
14
+ "test": "node test/test.js",
15
+ "test:fixtures": "node test/test-fixtures.js",
16
+ "test:edge": "node test/test-edge.js",
17
+ "test:postprocess": "node test/post-processing.test.js",
18
+ "test:postprocess-fastpath": "node test/post-processing-fastpath.test.js",
19
+ "test:postprocess-fastpath-roster": "node test/post-processing-fastpath-roster.test.js",
20
+ "test:postprocess-flow": "node test/post-processing-flow.test.js",
21
+ "test:postprocess-gate": "node test/postprocess-gate.js",
22
+ "test:tokenonly-progress": "node test/post-processing-progress.test.js",
23
+ "test:readme": "node test/test-readme.js",
24
+ "test:map": "node test/test-map.js",
25
+ "test:all": "node test/test-all.js",
26
+ "bench:scan": "node test/material/perf-scan-delims.mjs",
27
+ "bench:postprocess": "node test/material/perf-postprocess.mjs",
28
+ "analyze:postprocess-calls": "node test/material/analyze-postprocess-calls.mjs",
29
+ "analyze:fastpath": "node test/material/analyze-fastpath-hits.mjs"
15
30
  },
16
31
  "repository": "https://github.com/peaceroad/p7d-markdown-it-strong-ja.git",
17
32
  "author": "peaceroad <peaceroad@gmail.com>",
@@ -20,10 +35,13 @@
20
35
  "markdown-it": "^14.1.0"
21
36
  },
22
37
  "devDependencies": {
23
- "@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.5",
24
- "@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.8.2",
38
+ "@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.7",
39
+ "@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.8.5",
40
+ "@peaceroad/markdown-it-renderer-image": "^0.10.0",
41
+ "@peaceroad/markdown-it-renderer-inline-text": "^0.6.0",
25
42
  "markdown-it-attrs": "^4.3.1",
26
43
  "markdown-it-sub": "^2.0.0",
27
- "markdown-it-sup": "^2.0.0"
44
+ "markdown-it-sup": "^2.0.0",
45
+ "p7d-markdown-it-p-captions": "^0.21.0"
28
46
  }
29
47
  }
@@ -3,18 +3,60 @@ import {
3
3
  REG_ATTRS,
4
4
  isJapaneseChar,
5
5
  hasCjkBreaksRule,
6
+ isCjkBreaksRuleName,
7
+ deriveModeInfo,
8
+ MODE_FLAG_COMPATIBLE,
6
9
  getRuntimeOpt,
7
10
  moveRuleAfter
8
11
  } from './token-utils.js'
9
12
 
13
+ const isAsciiWordCode = (code) => {
14
+ return (code >= 0x30 && code <= 0x39) ||
15
+ (code >= 0x41 && code <= 0x5A) ||
16
+ (code >= 0x61 && code <= 0x7A)
17
+ }
18
+
19
+ const trimTrailingSpaceTab = (text) => {
20
+ if (!text) return text
21
+ let end = text.length
22
+ while (end > 0) {
23
+ const code = text.charCodeAt(end - 1)
24
+ if (code !== 0x20 && code !== 0x09) break
25
+ end--
26
+ }
27
+ return end === text.length ? text : text.slice(0, end)
28
+ }
29
+
10
30
  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
31
+ const baseModeFlags = typeof baseOpt.__strongJaModeFlags === 'number'
32
+ ? baseOpt.__strongJaModeFlags
33
+ : deriveModeInfo(baseOpt).__strongJaModeFlags
34
+ const baseCompatible = (baseModeFlags & MODE_FLAG_COMPATIBLE) !== 0
35
+ const isCompatibleMode = (state) => {
36
+ const override = state && state.env && state.env.__strongJaTokenOpt
37
+ if (!override) return baseCompatible
38
+ const opt = getRuntimeOpt(state, baseOpt)
39
+ return (opt.__strongJaModeFlags & MODE_FLAG_COMPATIBLE) !== 0
40
+ }
41
+
42
+ let hasTextJoinRule = false
43
+ const coreRules = md.core && md.core.ruler && Array.isArray(md.core.ruler.__rules__)
44
+ ? md.core.ruler.__rules__
45
+ : null
46
+ if (coreRules) {
47
+ for (let i = 0; i < coreRules.length; i++) {
48
+ const rule = coreRules[i]
49
+ if (rule && rule.name === 'text_join') {
50
+ hasTextJoinRule = true
51
+ break
52
+ }
53
+ }
54
+ }
14
55
 
15
56
  if (!md.__strongJaTokenTrimTrailingRegistered) {
16
57
  md.__strongJaTokenTrimTrailingRegistered = true
17
58
  const trimInlineTrailingSpaces = (state) => {
59
+ if (isCompatibleMode(state)) return
18
60
  if (!state || !state.tokens) return
19
61
  for (let i = 0; i < state.tokens.length; i++) {
20
62
  const token = state.tokens[i]
@@ -26,7 +68,9 @@ const registerTokenCompat = (md, baseOpt) => {
26
68
  if (idx < 0) continue
27
69
  const tail = token.children[idx]
28
70
  if (!tail || tail.type !== 'text' || !tail.content) continue
29
- const trimmed = tail.content.replace(/[ \t]+$/, '')
71
+ const lastCode = tail.content.charCodeAt(tail.content.length - 1)
72
+ if (lastCode !== 0x20 && lastCode !== 0x09) continue
73
+ const trimmed = trimTrailingSpaceTab(tail.content)
30
74
  if (trimmed !== tail.content) {
31
75
  tail.content = trimmed
32
76
  }
@@ -42,6 +86,7 @@ const registerTokenCompat = (md, baseOpt) => {
42
86
  if (!md.__strongJaTokenSoftbreakSpacingRegistered) {
43
87
  md.__strongJaTokenSoftbreakSpacingRegistered = true
44
88
  const normalizeSoftbreakSpacing = (state) => {
89
+ if (isCompatibleMode(state)) return
45
90
  if (!state) return
46
91
  if (baseOpt.hasCjkBreaks !== true && state.md) {
47
92
  baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
@@ -51,29 +96,38 @@ const registerTokenCompat = (md, baseOpt) => {
51
96
  for (let i = 0; i < state.tokens.length; i++) {
52
97
  const token = state.tokens[i]
53
98
  if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
99
+ const children = token.children
54
100
  let hasEmphasis = false
55
- for (let j = 0; j < token.children.length; j++) {
56
- const child = token.children[j]
101
+ let hasBreakCandidate = false
102
+ for (let j = 0; j < children.length; j++) {
103
+ const child = children[j]
57
104
  if (!child) continue
58
105
  if (child.type === 'strong_open' || child.type === 'strong_close' || child.type === 'em_open' || child.type === 'em_close') {
59
106
  hasEmphasis = true
60
- break
61
107
  }
108
+ if (!hasBreakCandidate &&
109
+ (child.type === 'softbreak' ||
110
+ (child.type === 'text' && child.content && child.content.indexOf('\n') !== -1))) {
111
+ hasBreakCandidate = true
112
+ }
113
+ if (hasEmphasis && hasBreakCandidate) break
62
114
  }
63
- for (let j = 0; j < token.children.length; j++) {
64
- const child = token.children[j]
115
+ if (!hasEmphasis) continue
116
+ if (!hasBreakCandidate) continue
117
+ for (let j = 0; j < children.length; j++) {
118
+ const child = children[j]
65
119
  if (!child) continue
66
120
  if (child.type === 'softbreak') {
67
- if (!hasEmphasis) continue
68
- const prevToken = token.children[j - 1]
69
- const nextToken = token.children[j + 1]
121
+ const prevToken = children[j - 1]
122
+ const nextToken = children[j + 1]
70
123
  if (!prevToken || !nextToken) continue
71
124
  if (prevToken.type !== 'text' || !prevToken.content) continue
72
125
  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)
126
+ const prevCharCode = prevToken.content.charCodeAt(prevToken.content.length - 1)
127
+ const nextCharCode = nextToken.content.charCodeAt(0)
128
+ const isAsciiWord = isAsciiWordCode(nextCharCode)
129
+ const shouldReplace = isAsciiWord &&
130
+ isJapaneseChar(prevCharCode) && !isJapaneseChar(nextCharCode)
77
131
  if (!shouldReplace) continue
78
132
  child.type = 'text'
79
133
  child.tag = ''
@@ -83,16 +137,16 @@ const registerTokenCompat = (md, baseOpt) => {
83
137
  continue
84
138
  }
85
139
  if (child.type !== 'text' || !child.content) continue
86
- if (!hasEmphasis) continue
87
140
  if (child.content.indexOf('\n') === -1) continue
88
141
  let normalized = ''
89
142
  for (let idx = 0; idx < child.content.length; idx++) {
90
143
  const ch = child.content[idx]
91
144
  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)
145
+ const prevCharCode = idx > 0 ? child.content.charCodeAt(idx - 1) : 0
146
+ const nextCharCode = idx + 1 < child.content.length ? child.content.charCodeAt(idx + 1) : 0
147
+ const isAsciiWord = isAsciiWordCode(nextCharCode)
148
+ const shouldReplace = isAsciiWord &&
149
+ isJapaneseChar(prevCharCode) && !isJapaneseChar(nextCharCode)
96
150
  if (shouldReplace) {
97
151
  normalized += ' '
98
152
  continue
@@ -114,9 +168,13 @@ const registerTokenCompat = (md, baseOpt) => {
114
168
  }
115
169
 
116
170
  const restoreSoftbreaksAfterCjk = (state) => {
171
+ if (isCompatibleMode(state)) return
117
172
  if (!state) return
118
- const opt = getRuntimeOpt(state, baseOpt)
119
- if (opt.mditAttrs !== false) return
173
+ const overrideOpt = state.env && state.env.__strongJaTokenOpt
174
+ if (overrideOpt) {
175
+ const opt = getRuntimeOpt(state, baseOpt)
176
+ if (opt.mditAttrs !== false) return
177
+ }
120
178
  if (!state.md || state.md.__strongJaRestoreSoftbreaksForAttrs !== true) return
121
179
  if (baseOpt.hasCjkBreaks !== true && state.md) {
122
180
  baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
@@ -127,27 +185,26 @@ const registerTokenCompat = (md, baseOpt) => {
127
185
  const token = state.tokens[i]
128
186
  if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
129
187
  const children = token.children
188
+ let prevTextCharCode = 0
130
189
  for (let j = 0; j < children.length; j++) {
131
190
  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
191
+ if (!child) continue
192
+ if (child.type === 'text') {
193
+ if (child.content === '') {
194
+ if (!prevTextCharCode || !isJapaneseChar(prevTextCharCode)) continue
195
+ const next = children[j + 1]
196
+ if (!next || next.type !== 'text' || !next.content) continue
197
+ const nextCharCode = next.content.charCodeAt(0)
198
+ if (nextCharCode !== 0x7B) continue
199
+ child.type = 'softbreak'
200
+ child.tag = ''
201
+ child.content = '\n'
202
+ child.markup = ''
203
+ child.info = ''
204
+ continue
139
205
  }
206
+ prevTextCharCode = child.content.charCodeAt(child.content.length - 1)
140
207
  }
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
208
  }
152
209
  }
153
210
  }
@@ -160,16 +217,17 @@ const registerTokenCompat = (md, baseOpt) => {
160
217
  if (added !== false) {
161
218
  md.__strongJaTokenRestoreRegistered = true
162
219
  md.__strongJaRestoreSoftbreaksForAttrs = baseOpt.mditAttrs === false
163
- if (baseOpt.hasCjkBreaks) {
164
- moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', 'cjk_breaks')
165
- }
166
220
  if (baseOpt.patchCorePush !== false && !md.__strongJaTokenPatchCorePush) {
167
221
  md.__strongJaTokenPatchCorePush = true
168
222
  const originalPush = md.core.ruler.push.bind(md.core.ruler)
169
223
  md.core.ruler.push = (name, fn, options) => {
170
224
  const res = originalPush(name, fn, options)
171
- if (name && name.indexOf && name.indexOf('cjk_breaks') !== -1) {
225
+ if (isCjkBreaksRuleName(name)) {
172
226
  baseOpt.hasCjkBreaks = true
227
+ md.__strongJaHasCjkBreaks = true
228
+ if (Array.isArray(md.core.ruler.__rules__)) {
229
+ md.__strongJaCjkBreaksRuleCount = md.core.ruler.__rules__.length
230
+ }
173
231
  moveRuleAfter(md.core.ruler, 'strong_ja_restore_softbreaks', name)
174
232
  }
175
233
  return res
@@ -185,9 +243,13 @@ const registerTokenCompat = (md, baseOpt) => {
185
243
  if (baseOpt.mditAttrs !== false && !md.__strongJaTokenPreAttrsRegistered) {
186
244
  md.__strongJaTokenPreAttrsRegistered = true
187
245
  md.core.ruler.before('linkify', 'strong_ja_token_pre_attrs', (state) => {
246
+ if (isCompatibleMode(state)) return
188
247
  if (!state || !state.tokens) return
189
- const opt = getRuntimeOpt(state, baseOpt)
190
- if (opt.mditAttrs === false) return
248
+ const overrideOpt = state.env && state.env.__strongJaTokenOpt
249
+ if (overrideOpt) {
250
+ const opt = getRuntimeOpt(state, baseOpt)
251
+ if (opt.mditAttrs === false) return
252
+ }
191
253
  for (let i = 0; i < state.tokens.length; i++) {
192
254
  const token = state.tokens[i]
193
255
  if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue