punctilio 4.3.1 → 5.0.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/README.md +36 -22
- package/dist/constants.d.ts +0 -27
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +0 -40
- package/dist/constants.js.map +1 -1
- package/dist/dashes.d.ts +11 -7
- package/dist/dashes.d.ts.map +1 -1
- package/dist/dashes.js +423 -284
- package/dist/dashes.js.map +1 -1
- package/dist/index.d.ts +14 -118
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +65 -116
- package/dist/index.js.map +1 -1
- package/dist/markdown.js +1 -1
- package/dist/markdown.js.map +1 -1
- package/dist/nbsp.d.ts +19 -13
- package/dist/nbsp.d.ts.map +1 -1
- package/dist/nbsp.js +214 -69
- package/dist/nbsp.js.map +1 -1
- package/dist/prose-view.d.ts +48 -7
- package/dist/prose-view.d.ts.map +1 -1
- package/dist/prose-view.js +163 -27
- package/dist/prose-view.js.map +1 -1
- package/dist/quote-classifier.d.ts +5 -4
- package/dist/quote-classifier.d.ts.map +1 -1
- package/dist/quote-classifier.js +601 -88
- package/dist/quote-classifier.js.map +1 -1
- package/dist/quotes.d.ts +12 -4
- package/dist/quotes.d.ts.map +1 -1
- package/dist/quotes.js +24 -27
- package/dist/quotes.js.map +1 -1
- package/dist/rehype.d.ts +53 -8
- package/dist/rehype.d.ts.map +1 -1
- package/dist/rehype.js +105 -62
- package/dist/rehype.js.map +1 -1
- package/dist/remark.d.ts +1 -1
- package/dist/remark.d.ts.map +1 -1
- package/dist/remark.js +6 -9
- package/dist/remark.js.map +1 -1
- package/dist/symbols.d.ts +25 -14
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +812 -220
- package/dist/symbols.js.map +1 -1
- package/dist/transform-options.d.ts +97 -0
- package/dist/transform-options.d.ts.map +1 -0
- package/dist/transform-options.js +34 -0
- package/dist/transform-options.js.map +1 -0
- package/dist/utils.d.ts +0 -16
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +0 -56
- package/dist/utils.js.map +1 -1
- package/package.json +6 -2
- package/src/constants.ts +0 -46
- package/src/dashes.ts +450 -269
- package/src/index.ts +70 -291
- package/src/markdown.ts +1 -1
- package/src/nbsp.ts +255 -78
- package/src/prose-view.ts +234 -39
- package/src/quote-classifier.ts +618 -88
- package/src/quotes.ts +36 -19
- package/src/rehype.ts +169 -88
- package/src/remark.ts +6 -11
- package/src/symbols.ts +814 -251
- package/src/transform-options.ts +143 -0
- package/src/utils.ts +0 -82
package/src/nbsp.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { cachedRegExp,
|
|
1
|
+
import { cachedRegExp, LATIN_LETTERS, NBSP_CHARS, SPACE_CHARS, UNICODE_SYMBOLS } from "./constants.js"
|
|
2
|
+
import { boundaryCountAt, overInput, type ProseView, replaceAllInView } from "./prose-view.js"
|
|
2
3
|
|
|
3
4
|
const {
|
|
4
5
|
NBSP,
|
|
@@ -11,11 +12,6 @@ const {
|
|
|
11
12
|
TRADEMARK,
|
|
12
13
|
} = UNICODE_SYMBOLS
|
|
13
14
|
|
|
14
|
-
export interface NbspOptions {
|
|
15
|
-
/** Boundary marker for HTML element boundaries. Default: "\uE000\uE001" */
|
|
16
|
-
separator?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
const SPACE = `[${SPACE_CHARS}]`
|
|
20
16
|
|
|
21
17
|
const UNICODE_UPPERCASE = "\\p{Lu}"
|
|
@@ -97,31 +93,104 @@ const ABBREVIATION_PATTERN = REFERENCE_ABBREVIATIONS.map((a) => `${a}\\.`).join(
|
|
|
97
93
|
const PUNCTUATION_OR_QUOTE = `[.,!?:;)(${LEFT_DOUBLE_QUOTE}${RIGHT_DOUBLE_QUOTE}\u00AB\u00BB${LEFT_SINGLE_QUOTE}${RIGHT_SINGLE_QUOTE}"]`
|
|
98
94
|
const COPYRIGHT_SYMBOLS = `[${COPYRIGHT}${REGISTERED}${TRADEMARK}]`
|
|
99
95
|
|
|
96
|
+
|
|
97
|
+
// The boundary-tolerance positions in this module reproduce the element-boundary
|
|
98
|
+
// semantics of the pre-v5 sentinel-marked pipeline; they are pinned by the golden
|
|
99
|
+
// corpus and the migration's differential fuzz.
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A tolerated slot admits exactly ONE node boundary at that position; two
|
|
103
|
+
* adjacent boundaries block the match. This enforces that: at most one boundary
|
|
104
|
+
* at `offset`, and every interior boundary in the span must sit at one of the
|
|
105
|
+
* allowed slot offsets.
|
|
106
|
+
*/
|
|
107
|
+
function boundariesOnlyAtSlots(view: ProseView, start: number, end: number, slots: number[]): boolean {
|
|
108
|
+
for (const boundary of view.boundaries) {
|
|
109
|
+
if (boundary <= start || boundary >= end) continue
|
|
110
|
+
if (!slots.includes(boundary)) return false
|
|
111
|
+
}
|
|
112
|
+
for (const slot of slots) {
|
|
113
|
+
if (boundaryCountAt(view, slot) > 1) return false
|
|
114
|
+
}
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
|
|
100
118
|
// Skips when preceded by NBSP or back-to-back with the previous match,
|
|
101
119
|
// preventing 3+ words from binding into a single line-break atom.
|
|
102
|
-
export function nbspAfterShortWords(
|
|
103
|
-
|
|
120
|
+
export function nbspAfterShortWords(input: string): string
|
|
121
|
+
export function nbspAfterShortWords(input: ProseView): void
|
|
122
|
+
export function nbspAfterShortWords(input: string | ProseView): string | void {
|
|
123
|
+
return overInput(input, nbspAfterShortWordsOverView)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function nbspAfterShortWordsOverView(view: ProseView): void {
|
|
104
127
|
const pattern = cachedRegExp(
|
|
105
|
-
`(?<=^|${SPACE}|${PUNCTUATION_OR_QUOTE}|>)(?<shortWord>[${LATIN_LETTERS}]{1,2})
|
|
128
|
+
`(?<=^|${SPACE}|${PUNCTUATION_OR_QUOTE}|>)(?<shortWord>[${LATIN_LETTERS}]{1,2})${SPACE}`,
|
|
106
129
|
"gmu"
|
|
107
130
|
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
131
|
+
{
|
|
132
|
+
const clean = view.text
|
|
133
|
+
let previousMatchEnd = -1
|
|
134
|
+
replaceAllInView(
|
|
135
|
+
view,
|
|
136
|
+
pattern,
|
|
137
|
+
(match, v) => {
|
|
138
|
+
const offset = match.index
|
|
139
|
+
if (offset === previousMatchEnd) return null
|
|
140
|
+
// A boundary directly before the short word fails the
|
|
141
|
+
// `(?<=^|space|punct|>)` lookbehind (a boundary is not one of those).
|
|
142
|
+
if (view.hasBoundary(offset)) return null
|
|
143
|
+
if (offset > 0 && NBSP_CHARS.includes(clean[offset - 1])) return null
|
|
144
|
+
previousMatchEnd = offset + match[0].length
|
|
145
|
+
// Edit only the space so any boundary keeps its side of the NBSP.
|
|
146
|
+
v.replace(offset + match[0].length - 1, offset + match[0].length, NBSP)
|
|
147
|
+
return null
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
allowBoundaries: (match, v) =>
|
|
151
|
+
boundariesOnlyAtSlots(v, match.index, match.index + match[0].length, [
|
|
152
|
+
match.index + match[0].length - 1,
|
|
153
|
+
]),
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
view.commit()
|
|
115
158
|
}
|
|
116
159
|
|
|
117
|
-
export function nbspBetweenNumberAndUnit(
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
export function nbspBetweenNumberAndUnit(input: string): string
|
|
161
|
+
export function nbspBetweenNumberAndUnit(input: ProseView): void
|
|
162
|
+
export function nbspBetweenNumberAndUnit(input: string | ProseView): string | void {
|
|
163
|
+
return overInput(input, nbspBetweenNumberAndUnitOverView)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function nbspBetweenNumberAndUnitOverView(view: ProseView): void {
|
|
120
167
|
const pattern = cachedRegExp(
|
|
121
|
-
`(?<digit>\\d)
|
|
168
|
+
`(?<digit>\\d)${SPACE}(?<unit>${UNIT_PATTERN})\\b`,
|
|
122
169
|
"gm"
|
|
123
170
|
)
|
|
124
|
-
|
|
171
|
+
{
|
|
172
|
+
replaceAllInView(
|
|
173
|
+
view,
|
|
174
|
+
pattern,
|
|
175
|
+
(match, v) => {
|
|
176
|
+
// Edit only the space (after the single digit) so any boundaries keep
|
|
177
|
+
// their sides.
|
|
178
|
+
v.replace(match.index + 1, match.index + 2, NBSP)
|
|
179
|
+
return null
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
// Tolerated slots: between digit and space (index+1) and between space
|
|
183
|
+
// and unit (index+2). The unit literal itself must be boundary-free,
|
|
184
|
+
// which the default interior-boundary skip already enforces.
|
|
185
|
+
allowBoundaries: (match, v) =>
|
|
186
|
+
boundariesOnlyAtSlots(v, match.index, match.index + match[0].length, [
|
|
187
|
+
match.index + 1,
|
|
188
|
+
match.index + 2,
|
|
189
|
+
]),
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
view.commit()
|
|
125
194
|
}
|
|
126
195
|
|
|
127
196
|
const MAX_LAST_WORD_LENGTH = 10
|
|
@@ -129,84 +198,192 @@ const MAX_LAST_WORD_LENGTH = 10
|
|
|
129
198
|
// Bounded so the lookbehind stays ReDoS-safe.
|
|
130
199
|
const MAX_MIDDLE_WORD_LENGTH = 15
|
|
131
200
|
|
|
201
|
+
const LATIN_LETTER_RE = cachedRegExp(`[${LATIN_LETTERS}]`, "u")
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The cascade lookbehind `(?<![NBSP][LATIN]{1,15})` over clean text: blocks
|
|
205
|
+
* when an NBSP/NNBSP is followed by 1..15 Latin letters ending at the space. A
|
|
206
|
+
* node boundary anywhere in that backward run breaks the run, re-enabling the
|
|
207
|
+
* match.
|
|
208
|
+
*/
|
|
209
|
+
function cascadeBlocksLastWord(view: ProseView, spaceOffset: number): boolean {
|
|
210
|
+
const text = view.text
|
|
211
|
+
let j = spaceOffset - 1
|
|
212
|
+
let run = 0
|
|
213
|
+
while (j >= 0 && run < MAX_MIDDLE_WORD_LENGTH && !view.hasBoundary(j + 1) && LATIN_LETTER_RE.test(text[j])) {
|
|
214
|
+
j--
|
|
215
|
+
run++
|
|
216
|
+
}
|
|
217
|
+
if (run === 0 || view.hasBoundary(j + 1)) return false
|
|
218
|
+
return j >= 0 && NBSP_CHARS.includes(text[j])
|
|
219
|
+
}
|
|
220
|
+
|
|
132
221
|
// Skips when the second-to-last word is already glued backwards via NBSP.
|
|
133
|
-
export function nbspBeforeLastWord(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const pattern = cachedRegExp(
|
|
138
|
-
`${cascadeBlock}(?<=\\w|${sep})${SPACE}(?<lastWord>(?:(?!\\s)(?!${sep}).){1,${MAX_LAST_WORD_LENGTH}})(?<ending>${sep}?(?:\\n\\n|$))`,
|
|
139
|
-
"g"
|
|
140
|
-
)
|
|
141
|
-
return text.replace(pattern, `${NBSP}$<lastWord>$<ending>`)
|
|
222
|
+
export function nbspBeforeLastWord(input: string): string
|
|
223
|
+
export function nbspBeforeLastWord(input: ProseView): void
|
|
224
|
+
export function nbspBeforeLastWord(input: string | ProseView): string | void {
|
|
225
|
+
return overInput(input, nbspBeforeLastWordOverView)
|
|
142
226
|
}
|
|
143
227
|
|
|
144
|
-
|
|
145
|
-
|
|
228
|
+
function nbspBeforeLastWordOverView(view: ProseView): void {
|
|
229
|
+
// The `(?<=\w|boundary)` lookbehind and the `[NBSP][LATIN]{1,15}` cascade
|
|
230
|
+
// lookbehind are enforced in the replacer, where boundaries are visible.
|
|
146
231
|
const pattern = cachedRegExp(
|
|
147
|
-
|
|
232
|
+
`${SPACE}(?<lastWord>(?:(?!\\s).){1,${MAX_LAST_WORD_LENGTH}})(?<ending>\\n\\n|$)`,
|
|
148
233
|
"g"
|
|
149
234
|
)
|
|
150
|
-
|
|
235
|
+
{
|
|
236
|
+
const clean = view.text
|
|
237
|
+
replaceAllInView(
|
|
238
|
+
view,
|
|
239
|
+
pattern,
|
|
240
|
+
(match, v) => {
|
|
241
|
+
const start = match.index
|
|
242
|
+
const end = start + match[0].length
|
|
243
|
+
// `(?<=\w|sep)`: a word char before the space, or a boundary there.
|
|
244
|
+
const precededByWord = start > 0 && /\w/.test(clean[start - 1])
|
|
245
|
+
if (!precededByWord && !view.hasBoundary(start)) return null
|
|
246
|
+
if (cascadeBlocksLastWord(v, start)) return null
|
|
247
|
+
// The `ending` group tolerates exactly one boundary at the slot before
|
|
248
|
+
// `\n\n`/end-of-text; two adjacent boundaries block the match. The
|
|
249
|
+
// end-of-text slot sits at the match end (never an interior boundary),
|
|
250
|
+
// so the cap is checked here rather than only in allowBoundaries.
|
|
251
|
+
if (boundaryCountAt(v, end - match.groups!.ending.length) > 1) return null
|
|
252
|
+
// Edit only the leading space so the lastWord/ending (and any boundary
|
|
253
|
+
// before \n\n) keep their positions.
|
|
254
|
+
v.replace(start, start + 1, NBSP)
|
|
255
|
+
return null
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
// The space-lookbehind boundary (== match.index) is not interior. The
|
|
259
|
+
// last word admits no interior boundary, so a boundary inside it blocks
|
|
260
|
+
// the match (the default skip). The only interior slot is the `ending`
|
|
261
|
+
// group's slot before a `\n\n`: one boundary tolerated.
|
|
262
|
+
allowBoundaries: (match, v) => {
|
|
263
|
+
const end = match.index + match[0].length
|
|
264
|
+
const endingSlot = end - match.groups!.ending.length
|
|
265
|
+
return boundariesOnlyAtSlots(v, match.index, end, [endingSlot])
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
view.commit()
|
|
151
271
|
}
|
|
152
272
|
|
|
153
|
-
|
|
154
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Shared driver for the abbreviation-family rules: `(thing)(SPACE)(?=context)`
|
|
275
|
+
* where the slot between thing and space and the lookahead slot (between space
|
|
276
|
+
* and context) each tolerate one boundary.
|
|
277
|
+
*/
|
|
278
|
+
function nbspBeforeContext(
|
|
279
|
+
view: ProseView,
|
|
280
|
+
thingPattern: string,
|
|
281
|
+
contextPattern: string,
|
|
282
|
+
flags: string,
|
|
283
|
+
thingGroup: string,
|
|
284
|
+
): void {
|
|
155
285
|
const pattern = cachedRegExp(
|
|
156
|
-
`(
|
|
157
|
-
|
|
286
|
+
`(?<${thingGroup}>${thingPattern})${SPACE}(?=${contextPattern})`,
|
|
287
|
+
flags
|
|
158
288
|
)
|
|
159
|
-
|
|
289
|
+
{
|
|
290
|
+
const clean = view.text
|
|
291
|
+
pattern.lastIndex = 0
|
|
292
|
+
let match: RegExpExecArray | null
|
|
293
|
+
while ((match = pattern.exec(clean)) !== null) {
|
|
294
|
+
const start = match.index
|
|
295
|
+
const end = start + match[0].length
|
|
296
|
+
// Slot between the `thing` literal and the space (offset = end - 1);
|
|
297
|
+
// lookahead slot at the match end. Each tolerates one boundary; the
|
|
298
|
+
// literal itself must be boundary-free.
|
|
299
|
+
const allowed =
|
|
300
|
+
boundariesOnlyAtSlots(view, start, end, [end - 1]) && boundaryCountAt(view, end) <= 1
|
|
301
|
+
if (!allowed) {
|
|
302
|
+
// A boundary broke the (possibly longer) literal; let the scan re-anchor
|
|
303
|
+
// one position later so a shorter sub-abbreviation can still match.
|
|
304
|
+
pattern.lastIndex = start + 1
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
// Edit only the trailing space so the slot/lookahead boundaries keep their
|
|
308
|
+
// sides.
|
|
309
|
+
view.replace(end - 1, end, NBSP)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
view.commit()
|
|
160
313
|
}
|
|
161
314
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const pattern = cachedRegExp(
|
|
165
|
-
`(?<honorific>${HONORIFIC_PATTERN})(?<marker>${sep}?)${SPACE}(?=${sep}?${UNICODE_UPPERCASE})`,
|
|
166
|
-
"gu"
|
|
167
|
-
)
|
|
168
|
-
return text.replace(pattern, `$<honorific>$<marker>${NBSP}`)
|
|
315
|
+
function nbspAfterReferenceAbbreviationsOverView(view: ProseView): void {
|
|
316
|
+
nbspBeforeContext(view, ABBREVIATION_PATTERN, "\\d", "g", "abbrev")
|
|
169
317
|
}
|
|
170
318
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const pattern = cachedRegExp(
|
|
174
|
-
`(?<symbol>${COPYRIGHT_SYMBOLS})(?<marker>${sep}?)${SPACE}(?=${sep}?[\\d${UNICODE_UPPERCASE}])`,
|
|
175
|
-
"gu"
|
|
176
|
-
)
|
|
177
|
-
return text.replace(pattern, `$<symbol>$<marker>${NBSP}`)
|
|
319
|
+
function nbspAfterSectionSymbolsOverView(view: ProseView): void {
|
|
320
|
+
nbspBeforeContext(view, "[\u00A7\u00B6]", "\\d", "g", "symbol")
|
|
178
321
|
}
|
|
179
322
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
)
|
|
186
|
-
return text.replace(pattern, `$<initial>$<marker>${NBSP}`)
|
|
323
|
+
function nbspAfterHonorificsOverView(view: ProseView): void {
|
|
324
|
+
nbspBeforeContext(view, HONORIFIC_PATTERN, UNICODE_UPPERCASE, "gu", "honorific")
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function nbspAfterCopyrightSymbolsOverView(view: ProseView): void {
|
|
328
|
+
nbspBeforeContext(view, COPYRIGHT_SYMBOLS, `[\\d${UNICODE_UPPERCASE}]`, "gu", "symbol")
|
|
187
329
|
}
|
|
188
330
|
|
|
189
|
-
|
|
331
|
+
function nbspBetweenInitialsOverView(view: ProseView): void {
|
|
332
|
+
nbspBeforeContext(view, `${UNICODE_UPPERCASE}\\.`, UNICODE_UPPERCASE, "gu", "initial")
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function nbspAfterReferenceAbbreviations(input: string): string
|
|
336
|
+
export function nbspAfterReferenceAbbreviations(input: ProseView): void
|
|
337
|
+
export function nbspAfterReferenceAbbreviations(input: string | ProseView): string | void {
|
|
338
|
+
return overInput(input, nbspAfterReferenceAbbreviationsOverView)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function nbspAfterSectionSymbols(input: string): string
|
|
342
|
+
export function nbspAfterSectionSymbols(input: ProseView): void
|
|
343
|
+
export function nbspAfterSectionSymbols(input: string | ProseView): string | void {
|
|
344
|
+
return overInput(input, nbspAfterSectionSymbolsOverView)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function nbspAfterHonorifics(input: string): string
|
|
348
|
+
export function nbspAfterHonorifics(input: ProseView): void
|
|
349
|
+
export function nbspAfterHonorifics(input: string | ProseView): string | void {
|
|
350
|
+
return overInput(input, nbspAfterHonorificsOverView)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function nbspAfterCopyrightSymbols(input: string): string
|
|
354
|
+
export function nbspAfterCopyrightSymbols(input: ProseView): void
|
|
355
|
+
export function nbspAfterCopyrightSymbols(input: string | ProseView): string | void {
|
|
356
|
+
return overInput(input, nbspAfterCopyrightSymbolsOverView)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function nbspBetweenInitials(input: string): string
|
|
360
|
+
export function nbspBetweenInitials(input: ProseView): void
|
|
361
|
+
export function nbspBetweenInitials(input: string | ProseView): string | void {
|
|
362
|
+
return overInput(input, nbspBetweenInitialsOverView)
|
|
363
|
+
}
|
|
190
364
|
|
|
191
365
|
// Specific patterns first so they claim matches before generic nbspAfterShortWords.
|
|
192
|
-
const NBSP_TRANSFORMS:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
366
|
+
const NBSP_TRANSFORMS: ((view: ProseView) => void)[] = [
|
|
367
|
+
nbspAfterHonorificsOverView,
|
|
368
|
+
nbspAfterReferenceAbbreviationsOverView,
|
|
369
|
+
nbspAfterSectionSymbolsOverView,
|
|
370
|
+
nbspAfterCopyrightSymbolsOverView,
|
|
371
|
+
nbspBetweenInitialsOverView,
|
|
372
|
+
nbspBetweenNumberAndUnitOverView,
|
|
373
|
+
nbspAfterShortWordsOverView,
|
|
374
|
+
nbspBeforeLastWordOverView,
|
|
201
375
|
]
|
|
202
376
|
|
|
203
|
-
export function nbspTransform(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
377
|
+
export function nbspTransform(input: string): string
|
|
378
|
+
export function nbspTransform(input: ProseView): void
|
|
379
|
+
export function nbspTransform(input: string | ProseView): string | void {
|
|
380
|
+
return overInput(input, (view) => {
|
|
381
|
+
// All nbsp patterns require a space, tab, or existing nbsp to match.
|
|
382
|
+
// Short-circuit if the text contains none of these.
|
|
383
|
+
if (!cachedRegExp(`[${SPACE_CHARS}]`, "").test(view.text)) return
|
|
207
384
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
385
|
+
for (const fn of NBSP_TRANSFORMS) {
|
|
386
|
+
fn(view)
|
|
387
|
+
}
|
|
388
|
+
})
|
|
212
389
|
}
|