react-native-marcus 0.1.1 → 0.2.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 +220 -63
- package/RNMarcus.podspec +6 -0
- package/android/src/main/java/app/getbullet/marcus/MarcusPackage.kt +2 -1
- package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorView.kt +168 -0
- package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorViewManager.kt +38 -0
- package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorViewManagerSpec.kt +19 -0
- package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorView.kt +23 -1
- package/android/src/main/java/app/getbullet/marcus/MarkdownBackgroundPainter.kt +147 -0
- package/android/src/main/java/app/getbullet/marcus/MarkdownBackgroundPass.kt +19 -0
- package/android/src/main/java/app/getbullet/marcus/MarkdownCopy.kt +87 -0
- package/android/src/main/java/app/getbullet/marcus/MarkdownFormatter.kt +652 -113
- package/android/src/main/java/app/getbullet/marcus/MarkdownRanges.kt +47 -0
- package/android/src/main/java/app/getbullet/marcus/MarkdownStyle.kt +140 -61
- package/android/src/main/java/app/getbullet/marcus/MarkdownUtils.kt +37 -7
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownAbsoluteLineHeightSpan.kt +48 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundSpan.kt +102 -48
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBlockIndentSpan.kt +22 -4
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBulletSpan.kt +78 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownCodeBlockSpan.kt +177 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontStyleSpan.kt +32 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontWeightSpan.kt +34 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownGapSpan.kt +24 -11
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownLetterSpacingSpan.kt +32 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownTextShadowSpan.kt +32 -0
- package/android/src/main/new_arch/CMakeLists.txt +17 -0
- package/android/src/main/new_arch/RNMarcusSpec.h +1 -0
- package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorShadowNode.cpp +235 -0
- package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorShadowNode.h +62 -0
- package/android/src/reactNativeVersionPatch/CustomMountingManager/81/app/getbullet/marcus/CustomFabricUIManager.java +22 -3
- package/android/src/reactNativeVersionPatch/CustomMountingManager/latest/app/getbullet/marcus/CustomFabricUIManager.java +22 -3
- package/android/src/test/java/app/getbullet/marcus/MarkdownCopyTest.kt +69 -0
- package/android/src/test/java/app/getbullet/marcus/MarkdownFormatterTest.kt +170 -25
- package/android/src/test/java/app/getbullet/marcus/RenderModel.kt +22 -5
- package/android/src/test/java/app/getbullet/marcus/spans/MarkdownCodeBlockSpanTest.kt +91 -0
- package/android/src/test/resources/render-model.txt +221 -16
- package/apple/MarcusSwizzle.h +25 -0
- package/apple/MarcusTextDecoratorComponentView.h +22 -0
- package/apple/MarcusTextDecoratorComponentView.mm +72 -0
- package/apple/MarcusTextDecoratorShadowNode.h +62 -0
- package/apple/MarcusTextDecoratorShadowNode.mm +192 -0
- package/apple/MarcusTextDecoratorViewManager.h +4 -0
- package/apple/MarcusTextDecoratorViewManager.mm +11 -0
- package/apple/MarcusTextInputDecoratorComponentView.mm +1 -1
- package/apple/MarcusTextInputDecoratorShadowNode.mm +1 -1
- package/apple/MarcusTextLayoutManager.h +40 -0
- package/apple/MarcusTextLayoutManager.mm +173 -0
- package/apple/MarkdownAttributes.h +23 -2
- package/apple/MarkdownAttributes.m +6 -0
- package/apple/MarkdownCopy.swift +63 -0
- package/apple/MarkdownDecorator.swift +2 -2
- package/apple/MarkdownFormatter+React.swift +23 -0
- package/apple/MarkdownFormatter.swift +869 -148
- package/apple/MarkdownParagraphLayoutManager.swift +455 -0
- package/apple/MarkdownSwiftInterop.h +0 -1
- package/apple/MarkdownTextBackground.swift +59 -1
- package/apple/MarkdownTextFieldObserver.swift +2 -2
- package/apple/MarkdownTextLayoutFragment.swift +159 -33
- package/apple/MarkdownTextLayoutManagerDelegate.swift +67 -3
- package/apple/MarkdownTextStorageDelegate.swift +2 -2
- package/apple/MarkdownTextViewObserver.swift +2 -2
- package/apple/MarkdownUtils.swift +38 -0
- package/apple/MarkdownWritingDirection.swift +132 -0
- package/apple/RCTMarcusStyle+Codegen.h +115 -9
- package/apple/RCTMarcusStyle.h +128 -31
- package/apple/RCTMarcusStyle.m +48 -0
- package/apple/RCTParagraphComponentView+Marcus.h +17 -0
- package/apple/RCTParagraphComponentView+Marcus.mm +38 -0
- package/apple/RCTTextInputComponentView+Marcus.mm +1 -25
- package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorState.h +37 -0
- package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorViewComponentDescriptor.h +17 -0
- package/lib/commonjs/MarcusTextDecoratorViewNativeComponent.ts +199 -0
- package/lib/commonjs/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
- package/lib/commonjs/MarkdownText.js +330 -0
- package/lib/commonjs/MarkdownText.js.map +1 -0
- package/lib/commonjs/MarkdownText.web.js +529 -0
- package/lib/commonjs/MarkdownText.web.js.map +1 -0
- package/lib/commonjs/MarkdownTextInput.js +22 -27
- package/lib/commonjs/MarkdownTextInput.js.map +1 -1
- package/lib/commonjs/MarkdownTextInput.web.js +12 -7
- package/lib/commonjs/MarkdownTextInput.web.js.map +1 -1
- package/lib/commonjs/atomUtils.js +112 -0
- package/lib/commonjs/atomUtils.js.map +1 -0
- package/lib/commonjs/childrenUtils.js +36 -0
- package/lib/commonjs/childrenUtils.js.map +1 -0
- package/lib/commonjs/index.js +8 -7
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/math/environment.js +69 -0
- package/lib/commonjs/math/environment.js.map +1 -0
- package/lib/commonjs/math/index.js +61 -0
- package/lib/commonjs/math/index.js.map +1 -0
- package/lib/commonjs/math/mathjax.js +548 -0
- package/lib/commonjs/math/mathjax.js.map +1 -0
- package/lib/commonjs/metro.js +172 -0
- package/lib/commonjs/metro.js.map +1 -0
- package/lib/commonjs/parser/index.d.ts +3 -0
- package/lib/commonjs/parser/index.js +450 -42
- package/lib/commonjs/parser/index.js.map +4 -4
- package/lib/commonjs/stripUtils.js +743 -0
- package/lib/commonjs/stripUtils.js.map +1 -0
- package/lib/commonjs/styleUtils.js +194 -23
- package/lib/commonjs/styleUtils.js.map +1 -1
- package/lib/commonjs/web/MarkdownElements.css +67 -0
- package/lib/commonjs/web/MarkdownTextInput.css +9 -7
- package/lib/commonjs/web/utils/blockLayout.js +95 -7
- package/lib/commonjs/web/utils/blockLayout.js.map +1 -1
- package/lib/commonjs/web/utils/blockUtils.js +327 -54
- package/lib/commonjs/web/utils/blockUtils.js.map +1 -1
- package/lib/commonjs/web/utils/elementUtils.js +80 -0
- package/lib/commonjs/web/utils/elementUtils.js.map +1 -0
- package/lib/commonjs/web/utils/parserUtils.js +84 -28
- package/lib/commonjs/web/utils/parserUtils.js.map +1 -1
- package/lib/commonjs/web/utils/treeUtils.js +6 -0
- package/lib/commonjs/web/utils/treeUtils.js.map +1 -1
- package/lib/commonjs/web/utils/webStyleUtils.js +25 -5
- package/lib/commonjs/web/utils/webStyleUtils.js.map +1 -1
- package/lib/module/MarcusTextDecoratorViewNativeComponent.ts +199 -0
- package/lib/module/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
- package/lib/module/MarkdownText.js +325 -0
- package/lib/module/MarkdownText.js.map +1 -0
- package/lib/module/MarkdownText.web.js +524 -0
- package/lib/module/MarkdownText.web.js.map +1 -0
- package/lib/module/MarkdownTextInput.js +24 -29
- package/lib/module/MarkdownTextInput.js.map +1 -1
- package/lib/module/MarkdownTextInput.web.js +12 -7
- package/lib/module/MarkdownTextInput.web.js.map +1 -1
- package/lib/module/atomUtils.js +106 -0
- package/lib/module/atomUtils.js.map +1 -0
- package/lib/module/childrenUtils.js +33 -0
- package/lib/module/childrenUtils.js.map +1 -0
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/math/environment.js +67 -0
- package/lib/module/math/environment.js.map +1 -0
- package/lib/module/math/index.js +56 -0
- package/lib/module/math/index.js.map +1 -0
- package/lib/module/math/mathjax.js +541 -0
- package/lib/module/math/mathjax.js.map +1 -0
- package/lib/module/metro.js +169 -0
- package/lib/module/metro.js.map +1 -0
- package/lib/module/parser/index.d.ts +3 -0
- package/lib/module/parser/index.js +450 -42
- package/lib/module/parser/index.js.map +4 -4
- package/lib/module/stripUtils.js +739 -0
- package/lib/module/stripUtils.js.map +1 -0
- package/lib/module/styleUtils.js +196 -25
- package/lib/module/styleUtils.js.map +1 -1
- package/lib/module/web/MarkdownElements.css +67 -0
- package/lib/module/web/MarkdownTextInput.css +9 -7
- package/lib/module/web/utils/blockLayout.js +94 -8
- package/lib/module/web/utils/blockLayout.js.map +1 -1
- package/lib/module/web/utils/blockUtils.js +328 -56
- package/lib/module/web/utils/blockUtils.js.map +1 -1
- package/lib/module/web/utils/elementUtils.js +75 -0
- package/lib/module/web/utils/elementUtils.js.map +1 -0
- package/lib/module/web/utils/parserUtils.js +86 -30
- package/lib/module/web/utils/parserUtils.js.map +1 -1
- package/lib/module/web/utils/treeUtils.js +6 -0
- package/lib/module/web/utils/treeUtils.js.map +1 -1
- package/lib/module/web/utils/webStyleUtils.js +25 -5
- package/lib/module/web/utils/webStyleUtils.js.map +1 -1
- package/lib/typescript/commonjs/e2e/links.test.d.ts +2 -0
- package/lib/typescript/commonjs/e2e/links.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MarcusTextDecoratorViewNativeComponent.d.ts +93 -0
- package/lib/typescript/commonjs/src/MarcusTextDecoratorViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +64 -51
- package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/MarkdownText.d.ts +127 -0
- package/lib/typescript/commonjs/src/MarkdownText.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MarkdownText.web.d.ts +126 -0
- package/lib/typescript/commonjs/src/MarkdownText.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts +23 -1
- package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts +26 -4
- package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/atomUtils.d.ts +58 -0
- package/lib/typescript/commonjs/src/atomUtils.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/childrenUtils.d.ts +12 -0
- package/lib/typescript/commonjs/src/childrenUtils.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/commonTypes.d.ts +39 -3
- package/lib/typescript/commonjs/src/commonTypes.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/math/environment.d.ts +7 -0
- package/lib/typescript/commonjs/src/math/environment.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/math/index.d.ts +64 -0
- package/lib/typescript/commonjs/src/math/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/math/mathjax.d.ts +63 -0
- package/lib/typescript/commonjs/src/math/mathjax.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/metro.d.ts +59 -0
- package/lib/typescript/commonjs/src/metro.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/parser/index.d.ts +3 -3
- package/lib/typescript/commonjs/src/parser/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/parser/micromark-extension-mentions.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/rangeUtils.d.ts +1 -1
- package/lib/typescript/commonjs/src/stripUtils.d.ts +108 -0
- package/lib/typescript/commonjs/src/stripUtils.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/styleUtils.d.ts +62 -4
- package/lib/typescript/commonjs/src/styleUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts +32 -4
- package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts +20 -8
- package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/elementUtils.d.ts +37 -0
- package/lib/typescript/commonjs/src/web/utils/elementUtils.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts +5 -5
- package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts +6 -1
- package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts +2 -2
- package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts.map +1 -1
- package/lib/typescript/module/e2e/links.test.d.ts +2 -0
- package/lib/typescript/module/e2e/links.test.d.ts.map +1 -0
- package/lib/typescript/module/src/MarcusTextDecoratorViewNativeComponent.d.ts +93 -0
- package/lib/typescript/module/src/MarcusTextDecoratorViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +64 -51
- package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/module/src/MarkdownText.d.ts +127 -0
- package/lib/typescript/module/src/MarkdownText.d.ts.map +1 -0
- package/lib/typescript/module/src/MarkdownText.web.d.ts +126 -0
- package/lib/typescript/module/src/MarkdownText.web.d.ts.map +1 -0
- package/lib/typescript/module/src/MarkdownTextInput.d.ts +23 -1
- package/lib/typescript/module/src/MarkdownTextInput.d.ts.map +1 -1
- package/lib/typescript/module/src/MarkdownTextInput.web.d.ts +26 -4
- package/lib/typescript/module/src/MarkdownTextInput.web.d.ts.map +1 -1
- package/lib/typescript/module/src/atomUtils.d.ts +58 -0
- package/lib/typescript/module/src/atomUtils.d.ts.map +1 -0
- package/lib/typescript/module/src/childrenUtils.d.ts +12 -0
- package/lib/typescript/module/src/childrenUtils.d.ts.map +1 -0
- package/lib/typescript/module/src/commonTypes.d.ts +39 -3
- package/lib/typescript/module/src/commonTypes.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +2 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/math/environment.d.ts +7 -0
- package/lib/typescript/module/src/math/environment.d.ts.map +1 -0
- package/lib/typescript/module/src/math/index.d.ts +64 -0
- package/lib/typescript/module/src/math/index.d.ts.map +1 -0
- package/lib/typescript/module/src/math/mathjax.d.ts +63 -0
- package/lib/typescript/module/src/math/mathjax.d.ts.map +1 -0
- package/lib/typescript/module/src/metro.d.ts +59 -0
- package/lib/typescript/module/src/metro.d.ts.map +1 -0
- package/lib/typescript/module/src/parser/index.d.ts +3 -3
- package/lib/typescript/module/src/parser/index.d.ts.map +1 -1
- package/lib/typescript/module/src/parser/micromark-extension-mentions.d.ts.map +1 -1
- package/lib/typescript/module/src/rangeUtils.d.ts +1 -1
- package/lib/typescript/module/src/stripUtils.d.ts +108 -0
- package/lib/typescript/module/src/stripUtils.d.ts.map +1 -0
- package/lib/typescript/module/src/styleUtils.d.ts +62 -4
- package/lib/typescript/module/src/styleUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/blockLayout.d.ts +32 -4
- package/lib/typescript/module/src/web/utils/blockLayout.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/blockUtils.d.ts +20 -8
- package/lib/typescript/module/src/web/utils/blockUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/elementUtils.d.ts +37 -0
- package/lib/typescript/module/src/web/utils/elementUtils.d.ts.map +1 -0
- package/lib/typescript/module/src/web/utils/parserUtils.d.ts +5 -5
- package/lib/typescript/module/src/web/utils/parserUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/treeUtils.d.ts +6 -1
- package/lib/typescript/module/src/web/utils/treeUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts +2 -2
- package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts.map +1 -1
- package/package.json +29 -2
- package/react-native.config.js +8 -1
- package/src/MarcusTextDecoratorViewNativeComponent.ts +199 -0
- package/src/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
- package/src/MarkdownText.tsx +472 -0
- package/src/MarkdownText.web.tsx +668 -0
- package/src/MarkdownTextInput.tsx +42 -38
- package/src/MarkdownTextInput.web.tsx +36 -13
- package/src/atomUtils.ts +112 -0
- package/src/childrenUtils.ts +39 -0
- package/src/commonTypes.ts +69 -9
- package/src/index.tsx +2 -1
- package/src/math/environment.ts +68 -0
- package/src/math/index.ts +78 -0
- package/src/math/mathjax.ts +604 -0
- package/src/metro.ts +238 -0
- package/src/parser/index.ts +175 -10
- package/src/parser/micromark-extension-mentions.ts +104 -11
- package/src/stripUtils.ts +919 -0
- package/src/styleUtils.ts +290 -26
- package/src/web/MarkdownElements.css +67 -0
- package/src/web/MarkdownTextInput.css +9 -7
- package/src/web/utils/blockLayout.ts +124 -14
- package/src/web/utils/blockUtils.ts +365 -59
- package/src/web/utils/elementUtils.ts +87 -0
- package/src/web/utils/parserUtils.ts +119 -31
- package/src/web/utils/treeUtils.ts +6 -1
- package/src/web/utils/webStyleUtils.ts +32 -8
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundColorSpan.kt +0 -6
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBoldSpan.kt +0 -6
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownItalicSpan.kt +0 -6
- package/apple/RCTMarcusStyle+Codegen.mm +0 -64
- package/apple/RCTMarcusUtils.h +0 -26
- package/apple/RCTMarcusUtils.mm +0 -26
package/src/metro.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metro configuration for drawing maths.
|
|
3
|
+
*
|
|
4
|
+
* Two things stand between a Metro bundle and a working `MarkdownText` with
|
|
5
|
+
* `math` on, and `withMath` is both of them:
|
|
6
|
+
*
|
|
7
|
+
* 1. **The maths backend is swapped in.** `src/math/index.ts` is a stub that
|
|
8
|
+
* draws nothing, and nothing in the library ever imports the real one --
|
|
9
|
+
* otherwise every application that installed this package would carry
|
|
10
|
+
* MathJax, whether or not it has an equation in it. This resolver points
|
|
11
|
+
* that import at `src/math/mathjax.ts` instead, which is what opting in
|
|
12
|
+
* means.
|
|
13
|
+
* 2. **MathJax's own imports resolve.** It addresses its modules through
|
|
14
|
+
* Node's *subpath imports* -- the `imports` field in its `package.json`,
|
|
15
|
+
* which maps `#js/*`, `#default-font/*` and four others onto real files.
|
|
16
|
+
* Metro does not implement that field, so every internal import fails and
|
|
17
|
+
* the bundle never builds. There is nothing to fix in MathJax: the field is
|
|
18
|
+
* standard and Metro has not caught up. The mapping is read out of MathJax's
|
|
19
|
+
* own manifest rather than written down here, because a list copied into a
|
|
20
|
+
* second place is a list that goes stale.
|
|
21
|
+
*
|
|
22
|
+
* const { getDefaultConfig } = require("expo/metro-config")
|
|
23
|
+
* const { withMath } = require("react-native-marcus/metro")
|
|
24
|
+
*
|
|
25
|
+
* module.exports = withMath(getDefaultConfig(__dirname))
|
|
26
|
+
*
|
|
27
|
+
* It does one thing more, which is to *remove* rather than add: the speech
|
|
28
|
+
* engine's rules for every language but the ones named in `{ locales }` are
|
|
29
|
+
* resolved away to nothing, so a display that reads its maths in English does
|
|
30
|
+
* not carry the twelve other languages it will never speak.
|
|
31
|
+
*/
|
|
32
|
+
import { dirname } from "path"
|
|
33
|
+
|
|
34
|
+
/** As much of a Metro config as this touches. */
|
|
35
|
+
interface MetroConfig {
|
|
36
|
+
resolver?: {
|
|
37
|
+
resolveRequest?: ResolveRequest
|
|
38
|
+
[key: string]: unknown
|
|
39
|
+
}
|
|
40
|
+
[key: string]: unknown
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type Resolution = { type?: string; filePath?: string } | null | undefined
|
|
44
|
+
|
|
45
|
+
type ResolveRequest = (
|
|
46
|
+
context: unknown,
|
|
47
|
+
moduleName: string,
|
|
48
|
+
platform: string | null,
|
|
49
|
+
) => Resolution
|
|
50
|
+
|
|
51
|
+
interface ResolutionContext {
|
|
52
|
+
resolveRequest: ResolveRequest
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface WithMathOptions {
|
|
56
|
+
/**
|
|
57
|
+
* The package whose `imports` field is read, in case a fork or a pinned copy
|
|
58
|
+
* is installed under another name.
|
|
59
|
+
*
|
|
60
|
+
* @default "@mathjax/src"
|
|
61
|
+
*/
|
|
62
|
+
packageName?: string
|
|
63
|
+
/**
|
|
64
|
+
* This library, in case it is installed under another name. Its own directory
|
|
65
|
+
* is what bounds the backend swap below.
|
|
66
|
+
*
|
|
67
|
+
* @default "react-native-marcus"
|
|
68
|
+
*/
|
|
69
|
+
libraryName?: string
|
|
70
|
+
/**
|
|
71
|
+
* The languages maths can be read out in, as the speech engine names them:
|
|
72
|
+
* `af`, `ca`, `da`, `de`, `en`, `es`, `fr`, `hi`, `it`, `ko`, `nb`, `nn` and
|
|
73
|
+
* `sv`. Everything else is left out of the bundle.
|
|
74
|
+
*
|
|
75
|
+
* This is what `MarkdownText`'s `mathAccessibilityLocale` can choose between.
|
|
76
|
+
* It is a build-time list rather than a runtime one for the same reason
|
|
77
|
+
* MathJax itself is: the rules for one language are around 300KB, and an
|
|
78
|
+
* application that reads its maths in English has no business carrying the
|
|
79
|
+
* four megabytes of rules for the twelve languages it does not. Naming the
|
|
80
|
+
* ones you support is the same thing you already do for your own
|
|
81
|
+
* translations.
|
|
82
|
+
*
|
|
83
|
+
* A locale asked for and not bundled is read in English instead, with a
|
|
84
|
+
* warning in the console saying so.
|
|
85
|
+
*
|
|
86
|
+
* @default ["en"]
|
|
87
|
+
*/
|
|
88
|
+
locales?: string[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* MathJax's `#…` specifiers, as prefix pairs.
|
|
93
|
+
*
|
|
94
|
+
* Its manifest maps `"#js/*": "@mathjax/src/mjs/*"` and so on, and every entry
|
|
95
|
+
* is a single trailing wildcard, so a prefix swap is the whole of it. Anything
|
|
96
|
+
* shaped differently is skipped rather than guessed at.
|
|
97
|
+
*/
|
|
98
|
+
function importPrefixes(packageName: string): [string, string][] {
|
|
99
|
+
let manifest: { imports?: Record<string, unknown> }
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
103
|
+
manifest = require(`${packageName}/package.json`) as { imports?: Record<string, unknown> }
|
|
104
|
+
} catch {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`[react-native-marcus] withMath() could not read "${packageName}/package.json". ` +
|
|
107
|
+
`Install it (npm install ${packageName}) or drop withMath() from your Metro config.`,
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const prefixes: [string, string][] = []
|
|
112
|
+
|
|
113
|
+
for (const [specifier, target] of Object.entries(manifest.imports ?? {})) {
|
|
114
|
+
if (
|
|
115
|
+
typeof target === "string" &&
|
|
116
|
+
specifier.endsWith("/*") &&
|
|
117
|
+
target.endsWith("/*") &&
|
|
118
|
+
specifier.startsWith("#")
|
|
119
|
+
) {
|
|
120
|
+
prefixes.push([specifier.slice(0, -1), target.slice(0, -1)])
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return prefixes
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The stub backend, whichever build of the library Metro picked: `lib/module`,
|
|
129
|
+
* `lib/commonjs`, or `src` itself when the library is being worked on.
|
|
130
|
+
*/
|
|
131
|
+
const STUB = /([\\/])math([\\/])index\.(js|jsx|ts|tsx|mjs|cjs)$/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* One of the speech engine's rule tables, by the locale it holds.
|
|
135
|
+
*
|
|
136
|
+
* They are all named in `math/mathjax.ts` -- a `require` has to be written down
|
|
137
|
+
* for a bundler to find it -- and all but the ones the application asked for
|
|
138
|
+
* are resolved away here.
|
|
139
|
+
*/
|
|
140
|
+
const MATHMAPS = /[\\/]mathmaps[\\/]([a-z]+)\.json$/
|
|
141
|
+
|
|
142
|
+
/** The table every locale shares, which is never the one to leave out. */
|
|
143
|
+
const BASE_LOCALE = "base"
|
|
144
|
+
|
|
145
|
+
/** Where this library lives, so that nothing else's `math/index.js` is touched. */
|
|
146
|
+
function libraryRoot(libraryName: string): string {
|
|
147
|
+
try {
|
|
148
|
+
return dirname(require.resolve(`${libraryName}/package.json`))
|
|
149
|
+
} catch {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`[react-native-marcus] withMath() could not locate "${libraryName}". ` +
|
|
152
|
+
`Pass { libraryName } if it is installed under another name.`,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Where the speech engine lives, when it is installed at all.
|
|
159
|
+
*
|
|
160
|
+
* It bounds the locale trimming the way the library root bounds the backend
|
|
161
|
+
* swap: only files under this directory are considered rule tables. Missing is
|
|
162
|
+
* not an error -- an application can draw maths without ever reading it out --
|
|
163
|
+
* and then there is nothing to trim.
|
|
164
|
+
*/
|
|
165
|
+
function speechRoot(): string | null {
|
|
166
|
+
try {
|
|
167
|
+
return dirname(require.resolve("speech-rule-engine/package.json"))
|
|
168
|
+
} catch {
|
|
169
|
+
return null
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Teaches Metro to resolve MathJax, and points the library's maths backend at
|
|
175
|
+
* it.
|
|
176
|
+
*
|
|
177
|
+
* Any `resolveRequest` already on the config is kept and called for everything
|
|
178
|
+
* else, so this composes with whatever else the application is doing.
|
|
179
|
+
*/
|
|
180
|
+
function withMath(config: MetroConfig, options: WithMathOptions = {}): MetroConfig {
|
|
181
|
+
const prefixes = importPrefixes(options.packageName ?? "@mathjax/src")
|
|
182
|
+
const root = libraryRoot(options.libraryName ?? "react-native-marcus")
|
|
183
|
+
const speech = speechRoot()
|
|
184
|
+
const locales = new Set(options.locales ?? ["en"])
|
|
185
|
+
const existing = config.resolver?.resolveRequest
|
|
186
|
+
|
|
187
|
+
const resolveRequest: ResolveRequest = (context, moduleName, platform) => {
|
|
188
|
+
let name = moduleName
|
|
189
|
+
|
|
190
|
+
if (name.charCodeAt(0) === 35 /* # */) {
|
|
191
|
+
for (const [from, to] of prefixes) {
|
|
192
|
+
if (name.startsWith(from)) {
|
|
193
|
+
name = to + name.slice(from.length)
|
|
194
|
+
break
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const resolution = existing
|
|
200
|
+
? existing(context, name, platform)
|
|
201
|
+
: (context as ResolutionContext).resolveRequest(context, name, platform)
|
|
202
|
+
|
|
203
|
+
// Matched on what it resolved to rather than on what was asked for: the
|
|
204
|
+
// import is written `"./math"`, relative to whichever of the three builds
|
|
205
|
+
// of this library the application ended up with, and a relative specifier
|
|
206
|
+
// says nothing about which package it is in.
|
|
207
|
+
const filePath = resolution?.filePath
|
|
208
|
+
|
|
209
|
+
if (filePath && STUB.test(filePath) && isInside(root, filePath)) {
|
|
210
|
+
return { ...resolution, filePath: filePath.replace(STUB, "$1math$2mathjax.$3") }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (filePath && speech !== null && isInside(speech, filePath)) {
|
|
214
|
+
const locale = MATHMAPS.exec(filePath)?.[1]
|
|
215
|
+
|
|
216
|
+
// An empty module rather than a missing one: the backend requires every
|
|
217
|
+
// locale it knows of, unconditionally, and reads the empty object back as
|
|
218
|
+
// "this one was not bundled" -- which is what it warns about and falls
|
|
219
|
+
// back from. A resolution error here would be a bundle that does not
|
|
220
|
+
// build.
|
|
221
|
+
if (locale !== undefined && locale !== BASE_LOCALE && !locales.has(locale)) {
|
|
222
|
+
return { type: "empty" }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return resolution
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return { ...config, resolver: { ...config.resolver, resolveRequest } }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function isInside(root: string, filePath: string) {
|
|
233
|
+
return filePath.length > root.length && filePath.startsWith(root)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export type { MetroConfig, WithMathOptions }
|
|
237
|
+
|
|
238
|
+
export { withMath }
|
package/src/parser/index.ts
CHANGED
|
@@ -2,13 +2,16 @@ import emojiRegex from "emoji-regex"
|
|
|
2
2
|
import { parse, postprocess, preprocess } from "micromark"
|
|
3
3
|
import { gfmAutolinkLiteral } from "micromark-extension-gfm-autolink-literal"
|
|
4
4
|
import { gfmStrikethrough } from "micromark-extension-gfm-strikethrough"
|
|
5
|
+
import { math } from "micromark-extension-math"
|
|
5
6
|
import type { Token, TokenType } from "micromark-util-types"
|
|
6
7
|
|
|
7
|
-
import type { MarkdownRange, MarkdownType } from "../commonTypes"
|
|
8
|
+
import type { MarkdownParser, MarkdownRange, MarkdownType, ParserOptions } from "../commonTypes"
|
|
8
9
|
import { mentions } from "./micromark-extension-mentions"
|
|
9
10
|
|
|
10
11
|
const EMOJI_TYPE = "emoji"
|
|
11
12
|
const SYNTAX_TYPE = "syntax"
|
|
13
|
+
const LABEL_TYPE = "label"
|
|
14
|
+
const ALT_TYPE = "alt"
|
|
12
15
|
|
|
13
16
|
const MAX_DEPTH = 6
|
|
14
17
|
|
|
@@ -72,6 +75,20 @@ const TOKENS: {
|
|
|
72
75
|
"codeTextSequence",
|
|
73
76
|
"codeFencedFenceSequence",
|
|
74
77
|
|
|
78
|
+
// Maths. The padding is the space `$ x $` is written with, which is part of
|
|
79
|
+
// the markup rather than of the TeX.
|
|
80
|
+
"mathTextSequence",
|
|
81
|
+
"mathTextPadding",
|
|
82
|
+
"mathFlowFenceSequence",
|
|
83
|
+
|
|
84
|
+
// Whatever follows the opening `$$`, which nothing reads.
|
|
85
|
+
"mathFlowFenceMeta",
|
|
86
|
+
|
|
87
|
+
// Whatever follows a fenced block's language. The language itself is
|
|
88
|
+
// `language` -- something will want to read it -- but the rest of
|
|
89
|
+
// that line is markup like any other fence.
|
|
90
|
+
"codeFencedFenceMeta",
|
|
91
|
+
|
|
75
92
|
// Character references.
|
|
76
93
|
"characterReferenceMarker",
|
|
77
94
|
"characterReferenceMarkerNumeric",
|
|
@@ -89,22 +106,45 @@ const TOKENS: {
|
|
|
89
106
|
special: {
|
|
90
107
|
codeFenced: "codeblock",
|
|
91
108
|
codeIndented: "codeblock",
|
|
92
|
-
image: "
|
|
109
|
+
image: "image",
|
|
110
|
+
|
|
111
|
+
// One range over the whole span, delimiters included, the way an image is
|
|
112
|
+
// one range over ``. What is inside is TeX: a display strips it
|
|
113
|
+
// whole and hands it to a renderer, and an input leaves it alone. Nothing
|
|
114
|
+
// is emitted for the content itself -- there is nothing markdown-shaped in
|
|
115
|
+
// there to style.
|
|
116
|
+
mathText: "math",
|
|
117
|
+
mathFlow: "math-block",
|
|
93
118
|
},
|
|
94
119
|
scope: {
|
|
95
|
-
|
|
96
|
-
|
|
120
|
+
// Identities: these carry CommonMark's own names, so what micromark calls
|
|
121
|
+
// a token and what a range is typed as are the same word.
|
|
122
|
+
emphasis: "emphasis",
|
|
123
|
+
strong: "strong",
|
|
97
124
|
strikethrough: "strikethrough",
|
|
98
125
|
|
|
99
126
|
// Headings are absent: their level is only known once the marker token
|
|
100
127
|
// arrives, so they are emitted from emitHeading() instead.
|
|
101
128
|
},
|
|
102
129
|
content: {
|
|
103
|
-
mention: "mention
|
|
130
|
+
mention: "mention",
|
|
104
131
|
|
|
105
132
|
codeTextData: "code",
|
|
106
133
|
codeFlowValue: "pre",
|
|
107
134
|
|
|
135
|
+
// The language a fenced block declares. Its own type rather than `syntax`
|
|
136
|
+
// because it is metadata something will want to read -- highlighting, at
|
|
137
|
+
// least -- and no formatter styles it, so it stays plain text in an input
|
|
138
|
+
// the way the rest of the fence line does not.
|
|
139
|
+
codeFencedFenceInfo: "language",
|
|
140
|
+
|
|
141
|
+
// The title after a destination -- `[a](b "title")`, on a link as much as on
|
|
142
|
+
// an image. Metadata for the same reasons the language above is: a display
|
|
143
|
+
// renderer removes it with the rest of the resource, and something that
|
|
144
|
+
// renders an image wants to read it. Left unstyled in an input, where it is
|
|
145
|
+
// text between two quotes.
|
|
146
|
+
resourceTitleString: "title",
|
|
147
|
+
|
|
108
148
|
// TODO:
|
|
109
149
|
// htmlFlow: 'html',
|
|
110
150
|
// htmlText: 'html',
|
|
@@ -155,22 +195,31 @@ const EMOJI: {
|
|
|
155
195
|
"autolinkProtocol",
|
|
156
196
|
"autolinkEmail",
|
|
157
197
|
]),
|
|
158
|
-
exclude: new Set(["
|
|
198
|
+
exclude: new Set(["strong", "emphasis", "strikethrough"]),
|
|
159
199
|
pattern: emojiRegex(),
|
|
160
200
|
}
|
|
161
201
|
|
|
162
202
|
// Bundler shenanigans: when bundling esm export ends up in wrapper function body
|
|
163
203
|
// which is a syntax error so don't export anything at all.
|
|
164
204
|
// Instead declare export so typing still works.
|
|
205
|
+
//
|
|
206
|
+
// The factory is synthesized by `scripts/bundle-parser.mjs`: the whole bundle
|
|
207
|
+
// below ends up inside the worklet it returns, with the options baked into that
|
|
208
|
+
// worklet's closure. Nothing native ever passes options -- the C++ bridge calls
|
|
209
|
+
// a registered worklet with a string and nothing else -- so the only place they
|
|
210
|
+
// can be chosen is where the worklet is made.
|
|
165
211
|
declare module "./index" {
|
|
166
|
-
export function
|
|
212
|
+
export function createParser(options?: ParserOptions): MarkdownParser
|
|
167
213
|
}
|
|
168
214
|
|
|
169
215
|
declare global {
|
|
170
|
-
function __parse__micromark(markdown: string): MarkdownRange[]
|
|
216
|
+
function __parse__micromark(markdown: string, options?: ParserOptions): MarkdownRange[]
|
|
171
217
|
}
|
|
172
218
|
|
|
173
|
-
globalThis.__parse__micromark = function (
|
|
219
|
+
globalThis.__parse__micromark = function (
|
|
220
|
+
markdown: string,
|
|
221
|
+
options?: ParserOptions,
|
|
222
|
+
): MarkdownRange[] {
|
|
174
223
|
// micromark's preprocessor drops a leading BOM, so every offset it reports is
|
|
175
224
|
// one short of the caller's string -- a document that opens with one had every
|
|
176
225
|
// range shifted left by a character, leaving the last one uncovered. Parse the
|
|
@@ -182,7 +231,30 @@ globalThis.__parse__micromark = function (markdown: string): MarkdownRange[] {
|
|
|
182
231
|
markdown = markdown.slice(bomLength)
|
|
183
232
|
}
|
|
184
233
|
|
|
185
|
-
|
|
234
|
+
// Every extension is off unless it is asked for: a parser made with no
|
|
235
|
+
// options at all is CommonMark and nothing else. Each of these changes what
|
|
236
|
+
// some perfectly ordinary character means -- a tilde, an `@`, a dollar sign --
|
|
237
|
+
// and that is not a thing to do to a document on the way past.
|
|
238
|
+
const extensions = []
|
|
239
|
+
|
|
240
|
+
if (options?.gfm === true) {
|
|
241
|
+
extensions.push(gfmAutolinkLiteral(), gfmStrikethrough())
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (options?.mention === true) {
|
|
245
|
+
extensions.push(mentions())
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (options?.math === true) {
|
|
249
|
+
// Two dollars, never one. A single `$` is a currency sign far more often
|
|
250
|
+
// than it is a delimiter, and with the single form on, `$5 and $6` is one
|
|
251
|
+
// maths span whose TeX is "5 and ": prose silently turned into a picture,
|
|
252
|
+
// in the one place -- a document written by someone else -- where that is
|
|
253
|
+
// least recoverable. `$$x^2$$` costs a keystroke and can never be an
|
|
254
|
+
// accident.
|
|
255
|
+
extensions.push(math({ singleDollarTextMath: false }))
|
|
256
|
+
}
|
|
257
|
+
|
|
186
258
|
const ranges: MarkdownRange[] = []
|
|
187
259
|
// Counted block containers, innermost last. Never exceeds MAX_DEPTH per type.
|
|
188
260
|
const openBlocks: MarkdownType[] = []
|
|
@@ -190,6 +262,16 @@ globalThis.__parse__micromark = function (markdown: string): MarkdownRange[] {
|
|
|
190
262
|
const countedBlocks: boolean[] = []
|
|
191
263
|
// Heading awaiting the marker that states its level.
|
|
192
264
|
let pendingHeading: Token | null = null
|
|
265
|
+
// Open images. A link's label is a link; an image's label is alt text, and
|
|
266
|
+
// the two are the same token type distinguished only by what encloses them.
|
|
267
|
+
let openImages = 0
|
|
268
|
+
// Open labels inside an image's, so that the outermost one is the alt text
|
|
269
|
+
// and everything under it is suppressed.
|
|
270
|
+
let altDepth = 0
|
|
271
|
+
// Open link labels. A label carries everything else it is written with --
|
|
272
|
+
// emphasis, code, mentions, an image -- but not a link: a link inside a link
|
|
273
|
+
// is text, and the one wrapped around it is the one that is pressed.
|
|
274
|
+
let labelDepth = 0
|
|
193
275
|
// The container markers on the line being built, in the order they appear.
|
|
194
276
|
// Positional rather than keyed by type: one type can open more than once on a
|
|
195
277
|
// line, and the occurrences need not be adjacent (`> - > `). Cleared per
|
|
@@ -220,6 +302,9 @@ globalThis.__parse__micromark = function (markdown: string): MarkdownRange[] {
|
|
|
220
302
|
return ranges
|
|
221
303
|
|
|
222
304
|
function enter(token: Token) {
|
|
305
|
+
// Ahead of the alt-text gate below, and deliberately: an emoji is not
|
|
306
|
+
// markup but a property of a character that survives, and alt text shown as
|
|
307
|
+
// prose has to draw it in the emoji font like any other.
|
|
223
308
|
if (EMOJI.scan.has(token.type)) {
|
|
224
309
|
emitEmoji(token)
|
|
225
310
|
}
|
|
@@ -289,6 +374,16 @@ globalThis.__parse__micromark = function (markdown: string): MarkdownRange[] {
|
|
|
289
374
|
}
|
|
290
375
|
|
|
291
376
|
if (TOKENS.special[token.type]) {
|
|
377
|
+
if (token.type === "image") {
|
|
378
|
+
openImages++
|
|
379
|
+
|
|
380
|
+
// An image written inside another one's label is not an image. It is
|
|
381
|
+
// some of the characters that label is spelled with.
|
|
382
|
+
if (altDepth > 0) {
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
292
387
|
emit(token, TOKENS.special[token.type]!)
|
|
293
388
|
return
|
|
294
389
|
}
|
|
@@ -298,6 +393,47 @@ globalThis.__parse__micromark = function (markdown: string): MarkdownRange[] {
|
|
|
298
393
|
return
|
|
299
394
|
}
|
|
300
395
|
|
|
396
|
+
// The visible half of a link, emitted so that a renderer which removes the
|
|
397
|
+
// markup around it has something left to style. Deliberately not `link`:
|
|
398
|
+
// while it is being edited the label is text you are typing, and only a
|
|
399
|
+
// renderer that has taken the brackets away has any business making it look
|
|
400
|
+
// like one.
|
|
401
|
+
if (token.type === "labelText") {
|
|
402
|
+
// A label nested inside an image's is part of that alt text, not a second
|
|
403
|
+
// one: ` d](e)` has exactly one.
|
|
404
|
+
if (altDepth > 0) {
|
|
405
|
+
altDepth++
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// An image's label is alt text, not a link label. The two are the same
|
|
410
|
+
// token, distinguished only by what encloses them -- and they part ways
|
|
411
|
+
// downstream: a label is the link once the brackets are gone, while alt
|
|
412
|
+
// text is what stands in for an image nobody rendered.
|
|
413
|
+
if (openImages > 0) {
|
|
414
|
+
emit(token, ALT_TYPE)
|
|
415
|
+
altDepth = 1
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
emit(token, LABEL_TYPE)
|
|
420
|
+
labelDepth++
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Alt text is a string. CommonMark says as much -- it flattens a label to
|
|
425
|
+
// plain text to render the `alt` attribute -- but micromark reports the
|
|
426
|
+
// structure it found, so `](d)` arrives carrying emphasis, a
|
|
427
|
+
// whole link and that link's own destination. None of it means anything:
|
|
428
|
+
// nothing can draw a link inside an image. Only the syntax handled above
|
|
429
|
+
// survives, so that whatever removes markup can still remove these
|
|
430
|
+
// characters, and everything downstream sees one `alt` range over one
|
|
431
|
+
// flat string. Emoji are the exception, emitted above: they are not markup
|
|
432
|
+
// and the characters they cover are still there to be drawn.
|
|
433
|
+
if (altDepth > 0) {
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
|
|
301
437
|
if (TOKENS.scope[token.type]) {
|
|
302
438
|
emit(token, TOKENS.scope[token.type]!)
|
|
303
439
|
return
|
|
@@ -314,12 +450,40 @@ globalThis.__parse__micromark = function (markdown: string): MarkdownRange[] {
|
|
|
314
450
|
}
|
|
315
451
|
|
|
316
452
|
if (TOKENS.link[token.type]) {
|
|
453
|
+
// A link written inside a label is not one: `[<https://x.com>](y)` reads
|
|
454
|
+
// as its own URL and points somewhere else. Its markers are still markup
|
|
455
|
+
// and still go, but what they leave behind is prose, so no range is
|
|
456
|
+
// emitted over it -- a second `link` here would draw it as a link of its
|
|
457
|
+
// own in an input and, once the display has taken the brackets away,
|
|
458
|
+
// would cover the same characters twice.
|
|
459
|
+
//
|
|
460
|
+
// An image's destination is not that: it is inside the label but it is
|
|
461
|
+
// the image's, and something has to render the image.
|
|
462
|
+
if (labelDepth > 0 && openImages === 0) {
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
|
|
317
466
|
emit(token, TOKENS.link[token.type]!)
|
|
318
467
|
return
|
|
319
468
|
}
|
|
320
469
|
}
|
|
321
470
|
|
|
322
471
|
function exit(token: Token) {
|
|
472
|
+
if (token.type === "labelText") {
|
|
473
|
+
if (altDepth > 0) {
|
|
474
|
+
altDepth--
|
|
475
|
+
} else if (labelDepth > 0) {
|
|
476
|
+
labelDepth--
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (token.type === "image") {
|
|
483
|
+
openImages--
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
323
487
|
if (!TOKENS.block[token.type]) {
|
|
324
488
|
return
|
|
325
489
|
}
|
|
@@ -554,3 +718,4 @@ globalThis.__parse__micromark = function (markdown: string): MarkdownRange[] {
|
|
|
554
718
|
ranges.push(...result)
|
|
555
719
|
}
|
|
556
720
|
}
|
|
721
|
+
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Code,
|
|
3
|
+
Construct,
|
|
4
|
+
Effects,
|
|
5
|
+
Extension,
|
|
6
|
+
State,
|
|
7
|
+
TokenizeContext,
|
|
8
|
+
} from "micromark-util-types"
|
|
2
9
|
|
|
3
10
|
const LETTER = /^\p{L}$/u
|
|
4
|
-
const WORD = /^[\p{L}\p{M}\p{N}_]$/u
|
|
5
11
|
const ALPHANUMERIC = /^[\p{L}\p{M}\p{N}]$/u
|
|
6
12
|
|
|
13
|
+
const AT = 64
|
|
14
|
+
const DOT = 46
|
|
15
|
+
const HYPHEN = 45
|
|
16
|
+
const UNDERSCORE = 95
|
|
17
|
+
|
|
7
18
|
declare module "micromark-util-types" {
|
|
8
19
|
interface TokenTypeMap {
|
|
9
20
|
mention: "mention"
|
|
@@ -13,8 +24,20 @@ declare module "micromark-util-types" {
|
|
|
13
24
|
const mention = {
|
|
14
25
|
name: "mention",
|
|
15
26
|
tokenize,
|
|
27
|
+
previous,
|
|
16
28
|
} as const
|
|
17
29
|
|
|
30
|
+
/**
|
|
31
|
+
* A `-`, `_` or `@` and the part of the name after it.
|
|
32
|
+
*
|
|
33
|
+
* Attempted rather than consumed outright, because micromark cannot give a
|
|
34
|
+
* character back: a name that ends in front of one -- `@user@ ` -- has to leave
|
|
35
|
+
* the separator outside the token, and an attempt that fails rewinds to it.
|
|
36
|
+
* That is also what lets `_@someone_` be a mention in italics: the closing `_`
|
|
37
|
+
* has no name after it, so it stays markup.
|
|
38
|
+
*/
|
|
39
|
+
const join: Construct = { tokenize: tokenizeJoin, partial: true }
|
|
40
|
+
|
|
18
41
|
export function mentions(): Extension {
|
|
19
42
|
return {
|
|
20
43
|
text: {
|
|
@@ -23,6 +46,20 @@ export function mentions(): Extension {
|
|
|
23
46
|
}
|
|
24
47
|
}
|
|
25
48
|
|
|
49
|
+
/**
|
|
50
|
+
* A mention starts where a name starts: not inside a word, and not straight
|
|
51
|
+
* after another `@`.
|
|
52
|
+
*
|
|
53
|
+
* That is what keeps `user@example.com` an address rather than an address with
|
|
54
|
+
* a mention buried in it, and `@@user` a stray `@` rather than a name beginning
|
|
55
|
+
* with one. Everything else may sit against it -- `**@someone**`, `[@user](/u)`
|
|
56
|
+
* and `(@user)` all hold one -- because the markup is gone by the time a
|
|
57
|
+
* display draws it, and punctuation was never part of a name to begin with.
|
|
58
|
+
*/
|
|
59
|
+
function previous(this: TokenizeContext, code: Code): boolean {
|
|
60
|
+
return code === null || (code !== AT && !test(ALPHANUMERIC, code))
|
|
61
|
+
}
|
|
62
|
+
|
|
26
63
|
function tokenize(effects: Effects, ok: State, nok: State): State {
|
|
27
64
|
return start
|
|
28
65
|
|
|
@@ -44,32 +81,88 @@ function tokenize(effects: Effects, ok: State, nok: State): State {
|
|
|
44
81
|
|
|
45
82
|
effects.consume(code)
|
|
46
83
|
|
|
47
|
-
return
|
|
84
|
+
return part
|
|
48
85
|
}
|
|
49
86
|
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
/** Inside a part of the name: letters, marks and digits. */
|
|
88
|
+
function part(code: Code): State {
|
|
89
|
+
if (code !== null && test(ALPHANUMERIC, code)) {
|
|
90
|
+
effects.consume(code)
|
|
91
|
+
return part
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (code === DOT) {
|
|
52
95
|
effects.consume(code)
|
|
53
|
-
return
|
|
96
|
+
return afterDot
|
|
54
97
|
}
|
|
55
98
|
|
|
56
|
-
if (code ===
|
|
57
|
-
|
|
58
|
-
return hyphen
|
|
99
|
+
if (code === HYPHEN || code === UNDERSCORE || code === AT) {
|
|
100
|
+
return effects.attempt(join, part, done)
|
|
59
101
|
}
|
|
60
102
|
|
|
103
|
+
return done(code)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Just after a dot, which belongs to the name wherever it falls -- `@bullet.`
|
|
108
|
+
* is one mention rather than a mention and a full stop.
|
|
109
|
+
*
|
|
110
|
+
* Only the first of several does, though: a second dot in a row ends the name
|
|
111
|
+
* here, in front of it, so `@bullet..` is `@bullet.` and a full stop that
|
|
112
|
+
* still reads as one.
|
|
113
|
+
*/
|
|
114
|
+
function afterDot(code: Code): State {
|
|
115
|
+
if (code === DOT) {
|
|
116
|
+
return done(code)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return part(code)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The name is finished.
|
|
124
|
+
*
|
|
125
|
+
* Nothing is checked here: `part` consumes every character a name is made of,
|
|
126
|
+
* so whatever stopped it is not one -- whitespace, punctuation, or the markup
|
|
127
|
+
* the mention was written inside.
|
|
128
|
+
*/
|
|
129
|
+
function done(code: Code): State {
|
|
61
130
|
effects.exit("mention")
|
|
131
|
+
|
|
62
132
|
return ok(code)!
|
|
63
133
|
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function tokenizeJoin(effects: Effects, ok: State, nok: State): State {
|
|
137
|
+
return separator
|
|
138
|
+
|
|
139
|
+
function separator(code: Code): State {
|
|
140
|
+
// A `-`, `_` or `@`: `part` only attempts this construct on one of the
|
|
141
|
+
// three. An `@` opens a new name and so wants a letter; the other two join
|
|
142
|
+
// two parts of one and take whatever a part is made of.
|
|
143
|
+
effects.consume(code)
|
|
64
144
|
|
|
65
|
-
|
|
145
|
+
return code === AT ? afterAt : afterJoin
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function afterJoin(code: Code): State {
|
|
66
149
|
if (code === null || !test(ALPHANUMERIC, code)) {
|
|
67
150
|
return nok(code)!
|
|
68
151
|
}
|
|
69
152
|
|
|
70
153
|
effects.consume(code)
|
|
71
154
|
|
|
72
|
-
return
|
|
155
|
+
return ok
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function afterAt(code: Code): State {
|
|
159
|
+
if (code === null || !test(LETTER, code)) {
|
|
160
|
+
return nok(code)!
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
effects.consume(code)
|
|
164
|
+
|
|
165
|
+
return ok
|
|
73
166
|
}
|
|
74
167
|
}
|
|
75
168
|
|