react-native-jet-markdown 0.2.0-beta.7

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.
Files changed (157) hide show
  1. package/JetMarkdown.podspec +42 -0
  2. package/LICENSE +20 -0
  3. package/README.md +247 -0
  4. package/android/CMakeLists.txt +52 -0
  5. package/android/build.gradle +55 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/JniBindings.cpp +243 -0
  8. package/android/src/main/java/com/jetmarkdown/JetMarkdownNative.kt +114 -0
  9. package/android/src/main/java/com/jetmarkdown/JetMarkdownPackage.kt +22 -0
  10. package/android/src/main/java/com/jetmarkdown/JetMarkdownView.kt +235 -0
  11. package/android/src/main/java/com/jetmarkdown/JetMarkdownViewManager.kt +75 -0
  12. package/android/src/main/java/com/jetmarkdown/editor/EditorSpans.kt +217 -0
  13. package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorManager.kt +224 -0
  14. package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorView.kt +1736 -0
  15. package/android/src/main/java/com/jetmarkdown/measure/MarkdownMeasurer.kt +54 -0
  16. package/android/src/main/java/com/jetmarkdown/parser/AstDecoder.kt +59 -0
  17. package/android/src/main/java/com/jetmarkdown/parser/MdNode.kt +43 -0
  18. package/android/src/main/java/com/jetmarkdown/render/ContentCache.kt +53 -0
  19. package/android/src/main/java/com/jetmarkdown/render/RenderedContent.kt +318 -0
  20. package/android/src/main/java/com/jetmarkdown/render/SpannableRenderer.kt +658 -0
  21. package/android/src/main/java/com/jetmarkdown/render/spans/ChipSpan.kt +17 -0
  22. package/android/src/main/java/com/jetmarkdown/render/spans/InteractiveSpans.kt +11 -0
  23. package/android/src/main/java/com/jetmarkdown/render/spans/MarkdownLineHeightSpan.kt +59 -0
  24. package/android/src/main/java/com/jetmarkdown/render/spans/RunSpan.kt +40 -0
  25. package/android/src/main/java/com/jetmarkdown/style/Fonts.kt +34 -0
  26. package/android/src/main/java/com/jetmarkdown/style/LayoutStyleSpec.kt +71 -0
  27. package/android/src/main/java/com/jetmarkdown/style/PlatformColorResolver.kt +77 -0
  28. package/android/src/main/java/com/jetmarkdown/style/StyleConfig.kt +80 -0
  29. package/android/src/main/java/com/jetmarkdown/style/TextStyleSpec.kt +61 -0
  30. package/android/src/main/java/com/jetmarkdown/views/BlockStackView.kt +292 -0
  31. package/android/src/main/java/com/jetmarkdown/views/BlockTextView.kt +378 -0
  32. package/android/src/main/java/com/jetmarkdown/views/BoxDrawing.kt +50 -0
  33. package/android/src/main/java/com/jetmarkdown/views/MarkdownHost.kt +12 -0
  34. package/android/src/main/java/com/jetmarkdown/views/MarkdownImageView.kt +113 -0
  35. package/android/src/main/java/com/jetmarkdown/views/NestedHorizontalScrollView.kt +87 -0
  36. package/android/src/main/java/com/jetmarkdown/views/TableBlockView.kt +139 -0
  37. package/cpp/core/Ast.h +85 -0
  38. package/cpp/core/AstJson.cpp +129 -0
  39. package/cpp/core/AstJson.h +12 -0
  40. package/cpp/core/AstSerializer.cpp +40 -0
  41. package/cpp/core/AstSerializer.h +21 -0
  42. package/cpp/core/AstToMarkdown.cpp +549 -0
  43. package/cpp/core/AstToMarkdown.h +16 -0
  44. package/cpp/core/EditorRuns.cpp +640 -0
  45. package/cpp/core/EditorRuns.h +94 -0
  46. package/cpp/core/EditorText.cpp +15 -0
  47. package/cpp/core/EditorText.h +18 -0
  48. package/cpp/core/InlineExtensions.cpp +305 -0
  49. package/cpp/core/InlineExtensions.h +17 -0
  50. package/cpp/core/Parser.cpp +470 -0
  51. package/cpp/core/Parser.h +16 -0
  52. package/cpp/core/Preprocess.cpp +181 -0
  53. package/cpp/core/Preprocess.h +14 -0
  54. package/cpp/md4c/LICENSE +22 -0
  55. package/cpp/md4c/VERSION +1 -0
  56. package/cpp/md4c/md4c.c +6462 -0
  57. package/cpp/md4c/md4c.h +407 -0
  58. package/cpp/react/JetMarkdownEditorShadowNode.cpp +54 -0
  59. package/cpp/react/JetMarkdownEditorShadowNode.h +55 -0
  60. package/cpp/react/JetMarkdownEditorState.h +35 -0
  61. package/cpp/react/JetMarkdownMeasurer.cpp +32 -0
  62. package/cpp/react/JetMarkdownMeasurer.h +41 -0
  63. package/cpp/react/JetMarkdownShadowNode.cpp +100 -0
  64. package/cpp/react/JetMarkdownShadowNode.h +53 -0
  65. package/cpp/react/JetMarkdownState.h +52 -0
  66. package/cpp/react/override/react/renderer/components/JetMarkdownViewSpec/ComponentDescriptors.h +21 -0
  67. package/cpp/tests/parser_tests.cpp +662 -0
  68. package/cpp/tests/run_tests.sh +25 -0
  69. package/ios/JetMarkdownView.h +14 -0
  70. package/ios/JetMarkdownView.mm +468 -0
  71. package/ios/editor/JetMarkdownEditor.h +12 -0
  72. package/ios/editor/JetMarkdownEditor.mm +2002 -0
  73. package/ios/measure/JMDMarkdownMeasurer.h +22 -0
  74. package/ios/measure/JMDMarkdownMeasurer.mm +38 -0
  75. package/ios/render/JMDBlock.h +97 -0
  76. package/ios/render/JMDBlock.m +28 -0
  77. package/ios/render/JMDBlockRenderer.h +17 -0
  78. package/ios/render/JMDBlockRenderer.mm +852 -0
  79. package/ios/render/JMDContentCache.h +17 -0
  80. package/ios/render/JMDContentCache.mm +42 -0
  81. package/ios/render/JMDRenderedContent.h +35 -0
  82. package/ios/render/JMDRenderedContent.mm +274 -0
  83. package/ios/style/JMDFontScale.h +33 -0
  84. package/ios/style/JMDLayoutStyle.h +37 -0
  85. package/ios/style/JMDLayoutStyle.mm +68 -0
  86. package/ios/style/JMDStyleConfig.h +39 -0
  87. package/ios/style/JMDStyleConfig.mm +111 -0
  88. package/ios/style/JMDTextStyle.h +25 -0
  89. package/ios/style/JMDTextStyle.mm +68 -0
  90. package/ios/views/JMDBlockStackView.h +18 -0
  91. package/ios/views/JMDBlockStackView.m +97 -0
  92. package/ios/views/JMDBlockTextView.h +24 -0
  93. package/ios/views/JMDBlockTextView.m +340 -0
  94. package/ios/views/JMDBoxViews.h +33 -0
  95. package/ios/views/JMDBoxViews.m +292 -0
  96. package/ios/views/JMDImageView.h +19 -0
  97. package/ios/views/JMDImageView.m +68 -0
  98. package/ios/views/JMDMarkdownHost.h +16 -0
  99. package/ios/views/JMDTableView.h +16 -0
  100. package/ios/views/JMDTableView.m +172 -0
  101. package/lib/module/JetMarkdownEditor.js +85 -0
  102. package/lib/module/JetMarkdownEditor.js.map +1 -0
  103. package/lib/module/JetMarkdownEditor.native.js +244 -0
  104. package/lib/module/JetMarkdownEditor.native.js.map +1 -0
  105. package/lib/module/JetMarkdownEditorNativeComponent.ts +173 -0
  106. package/lib/module/JetMarkdownView.js +19 -0
  107. package/lib/module/JetMarkdownView.js.map +1 -0
  108. package/lib/module/JetMarkdownView.native.js +44 -0
  109. package/lib/module/JetMarkdownView.native.js.map +1 -0
  110. package/lib/module/JetMarkdownViewNativeComponent.ts +35 -0
  111. package/lib/module/defaultStyles.js +122 -0
  112. package/lib/module/defaultStyles.js.map +1 -0
  113. package/lib/module/index.js +9 -0
  114. package/lib/module/index.js.map +1 -0
  115. package/lib/module/package.json +1 -0
  116. package/lib/module/serializeStyles.js +259 -0
  117. package/lib/module/serializeStyles.js.map +1 -0
  118. package/lib/module/types.js +4 -0
  119. package/lib/module/types.js.map +1 -0
  120. package/lib/module/useJetMarkdownEditor.js +41 -0
  121. package/lib/module/useJetMarkdownEditor.js.map +1 -0
  122. package/lib/typescript/package.json +1 -0
  123. package/lib/typescript/src/JetMarkdownEditor.d.ts +6 -0
  124. package/lib/typescript/src/JetMarkdownEditor.d.ts.map +1 -0
  125. package/lib/typescript/src/JetMarkdownEditor.native.d.ts +6 -0
  126. package/lib/typescript/src/JetMarkdownEditor.native.d.ts.map +1 -0
  127. package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts +105 -0
  128. package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts.map +1 -0
  129. package/lib/typescript/src/JetMarkdownView.d.ts +3 -0
  130. package/lib/typescript/src/JetMarkdownView.d.ts.map +1 -0
  131. package/lib/typescript/src/JetMarkdownView.native.d.ts +3 -0
  132. package/lib/typescript/src/JetMarkdownView.native.d.ts.map +1 -0
  133. package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts +28 -0
  134. package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts.map +1 -0
  135. package/lib/typescript/src/defaultStyles.d.ts +20 -0
  136. package/lib/typescript/src/defaultStyles.d.ts.map +1 -0
  137. package/lib/typescript/src/index.d.ts +7 -0
  138. package/lib/typescript/src/index.d.ts.map +1 -0
  139. package/lib/typescript/src/serializeStyles.d.ts +34 -0
  140. package/lib/typescript/src/serializeStyles.d.ts.map +1 -0
  141. package/lib/typescript/src/types.d.ts +352 -0
  142. package/lib/typescript/src/types.d.ts.map +1 -0
  143. package/lib/typescript/src/useJetMarkdownEditor.d.ts +36 -0
  144. package/lib/typescript/src/useJetMarkdownEditor.d.ts.map +1 -0
  145. package/package.json +150 -0
  146. package/react-native.config.js +11 -0
  147. package/src/JetMarkdownEditor.native.tsx +301 -0
  148. package/src/JetMarkdownEditor.tsx +90 -0
  149. package/src/JetMarkdownEditorNativeComponent.ts +173 -0
  150. package/src/JetMarkdownView.native.tsx +67 -0
  151. package/src/JetMarkdownView.tsx +16 -0
  152. package/src/JetMarkdownViewNativeComponent.ts +35 -0
  153. package/src/defaultStyles.ts +102 -0
  154. package/src/index.tsx +34 -0
  155. package/src/serializeStyles.ts +369 -0
  156. package/src/types.ts +438 -0
  157. package/src/useJetMarkdownEditor.ts +50 -0
@@ -0,0 +1,94 @@
1
+ #pragma once
2
+
3
+ #include <cstdint>
4
+ #include <string>
5
+ #include <vector>
6
+
7
+ namespace jetmarkdown {
8
+
9
+ // Inline mark bits carried by editor styled runs. Mirrored by the native
10
+ // editors (JMDEditorMarks attribute on iOS, EditorMarkSpan on Android).
11
+ enum EditorMark : uint32_t {
12
+ MarkBold = 1u << 0,
13
+ MarkItalic = 1u << 1,
14
+ MarkStrikethrough = 1u << 2,
15
+ MarkInlineCode = 1u << 3,
16
+ MarkSpoiler = 1u << 4,
17
+ MarkSuperscript = 1u << 5,
18
+ MarkSubscript = 1u << 6,
19
+ };
20
+
21
+ // Per-line block types. Mirrored by the native editors (JMDEditorBlock
22
+ // attribute on iOS, EditorBlockSpan on Android).
23
+ enum class EditorBlockType : uint8_t {
24
+ Paragraph = 0,
25
+ Heading = 1, // level = 1..6
26
+ Quote = 2,
27
+ Code = 3,
28
+ Bullet = 4,
29
+ Ordered = 5,
30
+ };
31
+
32
+ // A contiguous marked range over the editor's text. Offsets are UTF-16 code
33
+ // units (NSRange / Spannable indices), converted internally.
34
+ struct StyledRun {
35
+ uint32_t start = 0;
36
+ uint32_t end = 0;
37
+ uint32_t flags = 0;
38
+ };
39
+
40
+ struct EditorLine {
41
+ EditorBlockType type = EditorBlockType::Paragraph;
42
+ uint8_t level = 0;
43
+ };
44
+
45
+ // A linked range (mentions are links whose URL carries an app scheme, e.g.
46
+ // users://ali). Links never overlap; offsets are UTF-16 code units.
47
+ struct LinkRun {
48
+ uint32_t start = 0;
49
+ uint32_t end = 0;
50
+ std::string url;
51
+ };
52
+
53
+ struct StyledText {
54
+ std::string text;
55
+ std::vector<StyledRun> runs;
56
+ };
57
+
58
+ struct EditorDocument {
59
+ std::string text;
60
+ std::vector<StyledRun> runs;
61
+ // One entry per text line (newline-separated); missing entries mean
62
+ // Paragraph.
63
+ std::vector<EditorLine> lines;
64
+ std::vector<LinkRun> links;
65
+ // Running UTF-16 length of `text`, maintained by every append during
66
+ // extraction so run offsets never require an O(N) rescan (which made
67
+ // opening large documents quadratic).
68
+ uint32_t textUtf16Length = 0;
69
+ };
70
+
71
+ // Serializes the editor's content to markdown. Every newline in `text` is a
72
+ // line break; `lines` assigns each line its block type, and consecutive
73
+ // same-type lines merge into one block (quote, code fence, list).
74
+ // Overlapping mark runs are nested by longest extent; inline-code content is
75
+ // emitted verbatim; run edges are trimmed past whitespace so emphasis
76
+ // delimiters always re-parse. Code lines ignore inline marks.
77
+ std::string markdownFromEditor(
78
+ const std::string& text,
79
+ const std::vector<StyledRun>& runs,
80
+ const std::vector<EditorLine>& lines,
81
+ const std::vector<LinkRun>& links = {});
82
+
83
+ // Parses markdown and flattens it to editor text + mark runs + line blocks
84
+ // (the inverse of markdownFromEditor for the editor's subset; unsupported
85
+ // structure flattens to paragraph lines).
86
+ EditorDocument editorFromMarkdown(const std::string& markdown);
87
+
88
+ // Marks-only conveniences (all lines are paragraphs).
89
+ std::string markdownFromStyledText(
90
+ const std::string& text,
91
+ const std::vector<StyledRun>& runs);
92
+ StyledText styledTextFromMarkdown(const std::string& markdown);
93
+
94
+ } // namespace jetmarkdown
@@ -0,0 +1,15 @@
1
+ #include "EditorText.h"
2
+
3
+ #include "EditorRuns.h"
4
+
5
+ namespace jetmarkdown {
6
+
7
+ std::string markdownFromPlainText(const std::string& text) {
8
+ return markdownFromStyledText(text, {});
9
+ }
10
+
11
+ std::string plainTextFromMarkdown(const std::string& markdown) {
12
+ return styledTextFromMarkdown(markdown).text;
13
+ }
14
+
15
+ } // namespace jetmarkdown
@@ -0,0 +1,18 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+
5
+ namespace jetmarkdown {
6
+
7
+ // E1 editor bridge: the editor holds plain text until inline marks (E2) and
8
+ // block formatting (E3) attach structure.
9
+
10
+ // Lines become paragraphs (blank lines separate them) and text is escaped,
11
+ // so literal markdown characters the user typed survive as literals.
12
+ std::string markdownFromPlainText(const std::string& text);
13
+
14
+ // Flattens parsed markdown to the editor's plain-text representation:
15
+ // blocks become lines; inline formatting reduces to its text content.
16
+ std::string plainTextFromMarkdown(const std::string& markdown);
17
+
18
+ } // namespace jetmarkdown
@@ -0,0 +1,305 @@
1
+ #include "InlineExtensions.h"
2
+
3
+ #include <cstring>
4
+
5
+ namespace jetmarkdown {
6
+
7
+ namespace {
8
+
9
+ constexpr size_t kNpos = std::string::npos;
10
+
11
+ // Walls stop delimiter matching; inline nodes may sit between a pair.
12
+ bool isWall(NodeType type) {
13
+ switch (type) {
14
+ case NodeType::Text:
15
+ case NodeType::SoftBreak:
16
+ case NodeType::HardBreak:
17
+ case NodeType::Bold:
18
+ case NodeType::Italic:
19
+ case NodeType::Strikethrough:
20
+ case NodeType::Link:
21
+ case NodeType::InlineCode:
22
+ case NodeType::Spoiler:
23
+ case NodeType::Superscript:
24
+ case NodeType::Subscript:
25
+ case NodeType::Image:
26
+ return false;
27
+ default:
28
+ return true;
29
+ }
30
+ }
31
+
32
+ struct Pos {
33
+ size_t child = 0;
34
+ size_t off = 0;
35
+ bool valid = false;
36
+ };
37
+
38
+ // First occurrence of token in Text children, starting at (startChild,
39
+ // startOff). Search stops at walls.
40
+ Pos findToken(
41
+ const std::vector<Node*>& kids,
42
+ size_t startChild,
43
+ size_t startOff,
44
+ const char* token) {
45
+ for (size_t ci = startChild; ci < kids.size(); ci++) {
46
+ Node* child = kids[ci];
47
+ if (child->type == NodeType::Text) {
48
+ if (child->verbatim) {
49
+ // Escaped/entity characters never form delimiters.
50
+ continue;
51
+ }
52
+ size_t from = (ci == startChild) ? startOff : 0;
53
+ size_t found = child->text.find(token, from);
54
+ if (found != kNpos) {
55
+ return {ci, found, true};
56
+ }
57
+ } else if (isWall(child->type)) {
58
+ return {};
59
+ }
60
+ }
61
+ return {};
62
+ }
63
+
64
+ // Wraps [open, close) into a new `type` node, splitting the boundary Text
65
+ // nodes. closeLen may be 0 (delimiter-less end, used by bare ^word).
66
+ // Returns the child index right after the wrapper, where scanning resumes.
67
+ size_t wrapRange(
68
+ std::vector<Node*>& kids,
69
+ AstArena& arena,
70
+ Pos open,
71
+ size_t openLen,
72
+ Pos close,
73
+ size_t closeLen,
74
+ NodeType type) {
75
+ Node* openNode = kids[open.child];
76
+ Node* closeNode = kids[close.child];
77
+ Node* wrapper = arena.alloc(type);
78
+
79
+ const std::string before = openNode->text.substr(0, open.off);
80
+ std::string after;
81
+
82
+ auto pushText = [&](std::string&& value) {
83
+ if (value.empty()) {
84
+ return;
85
+ }
86
+ Node* text = arena.alloc(NodeType::Text);
87
+ text->text = std::move(value);
88
+ wrapper->children.push_back(text);
89
+ };
90
+
91
+ if (open.child == close.child) {
92
+ const size_t innerStart = open.off + openLen;
93
+ pushText(openNode->text.substr(innerStart, close.off - innerStart));
94
+ after = openNode->text.substr(close.off + closeLen);
95
+ } else {
96
+ pushText(openNode->text.substr(open.off + openLen));
97
+ for (size_t i = open.child + 1; i < close.child; i++) {
98
+ wrapper->children.push_back(kids[i]);
99
+ }
100
+ pushText(closeNode->text.substr(0, close.off));
101
+ after = closeNode->text.substr(close.off + closeLen);
102
+ }
103
+
104
+ std::vector<Node*> replacement;
105
+ if (!before.empty()) {
106
+ openNode->text = before;
107
+ replacement.push_back(openNode);
108
+ }
109
+ replacement.push_back(wrapper);
110
+ const size_t resumeIdx = open.child + replacement.size();
111
+ if (!after.empty()) {
112
+ Node* text = arena.alloc(NodeType::Text);
113
+ text->text = std::move(after);
114
+ replacement.push_back(text);
115
+ }
116
+
117
+ kids.erase(kids.begin() + open.child, kids.begin() + close.child + 1);
118
+ kids.insert(kids.begin() + open.child, replacement.begin(), replacement.end());
119
+ return resumeIdx;
120
+ }
121
+
122
+ // Cross-node symmetric-or-asymmetric pair pass (spoilers, strikethrough).
123
+ void pairPass(
124
+ Node* parent,
125
+ AstArena& arena,
126
+ const char* openTok,
127
+ const char* closeTok,
128
+ NodeType type) {
129
+ auto& kids = parent->children;
130
+ const size_t openLen = std::strlen(openTok);
131
+ const size_t closeLen = std::strlen(closeTok);
132
+
133
+ size_t ci = 0;
134
+ size_t off = 0;
135
+ while (ci < kids.size()) {
136
+ Node* child = kids[ci];
137
+ if (child->type != NodeType::Text || child->verbatim) {
138
+ ci++;
139
+ off = 0;
140
+ continue;
141
+ }
142
+ const size_t found = child->text.find(openTok, off);
143
+ if (found == kNpos) {
144
+ ci++;
145
+ off = 0;
146
+ continue;
147
+ }
148
+ const Pos close = findToken(kids, ci, found + openLen, closeTok);
149
+ if (!close.valid) {
150
+ off = found + openLen;
151
+ continue;
152
+ }
153
+ if (close.child == ci && close.off == found + openLen) {
154
+ // Empty content ("||||") stays literal.
155
+ off = close.off + closeLen;
156
+ continue;
157
+ }
158
+ ci = wrapRange(kids, arena, {ci, found, true}, openLen, close, closeLen, type);
159
+ off = 0;
160
+ }
161
+ }
162
+
163
+ bool containsWhitespace(const std::string& s, size_t start, size_t end) {
164
+ for (size_t i = start; i < end; i++) {
165
+ if (s[i] == ' ' || s[i] == '\t') {
166
+ return true;
167
+ }
168
+ }
169
+ return false;
170
+ }
171
+
172
+ // ~subscript~ : same Text run, non-empty, no whitespace inside.
173
+ void subscriptPass(Node* parent, AstArena& arena) {
174
+ auto& kids = parent->children;
175
+ size_t ci = 0;
176
+ size_t off = 0;
177
+ while (ci < kids.size()) {
178
+ Node* child = kids[ci];
179
+ if (child->type != NodeType::Text || child->verbatim) {
180
+ ci++;
181
+ off = 0;
182
+ continue;
183
+ }
184
+ const size_t open = child->text.find('~', off);
185
+ if (open == kNpos) {
186
+ ci++;
187
+ off = 0;
188
+ continue;
189
+ }
190
+ const size_t close = child->text.find('~', open + 1);
191
+ if (close == kNpos) {
192
+ ci++;
193
+ off = 0;
194
+ continue;
195
+ }
196
+ if (close == open + 1 || containsWhitespace(child->text, open + 1, close)) {
197
+ off = open + 1;
198
+ continue;
199
+ }
200
+ ci = wrapRange(
201
+ kids, arena, {ci, open, true}, 1, {ci, close, true}, 1, NodeType::Subscript);
202
+ off = 0;
203
+ }
204
+ }
205
+
206
+ // ^(multi word) | ^pandoc^ | ^word : see header for the disambiguation.
207
+ void superscriptPass(Node* parent, AstArena& arena) {
208
+ auto& kids = parent->children;
209
+ size_t ci = 0;
210
+ size_t off = 0;
211
+ while (ci < kids.size()) {
212
+ Node* child = kids[ci];
213
+ if (child->type != NodeType::Text || child->verbatim) {
214
+ ci++;
215
+ off = 0;
216
+ continue;
217
+ }
218
+ const std::string& text = child->text;
219
+ const size_t caret = text.find('^', off);
220
+ if (caret == kNpos) {
221
+ ci++;
222
+ off = 0;
223
+ continue;
224
+ }
225
+
226
+ // ^(multi word) — may span styled runs until the first ")".
227
+ if (caret + 1 < text.size() && text[caret + 1] == '(') {
228
+ const Pos close = findToken(kids, ci, caret + 2, ")");
229
+ if (close.valid && !(close.child == ci && close.off == caret + 2)) {
230
+ ci = wrapRange(
231
+ kids, arena, {ci, caret, true}, 2, close, 1, NodeType::Superscript);
232
+ off = 0;
233
+ continue;
234
+ }
235
+ off = caret + 1;
236
+ continue;
237
+ }
238
+
239
+ if (caret + 1 >= text.size() || text[caret + 1] == ' ' ||
240
+ text[caret + 1] == '\t' || text[caret + 1] == '^') {
241
+ off = caret + 1;
242
+ continue;
243
+ }
244
+
245
+ const size_t nextCaret = text.find('^', caret + 1);
246
+ size_t nextSpace = kNpos;
247
+ for (size_t i = caret + 1; i < text.size(); i++) {
248
+ if (text[i] == ' ' || text[i] == '\t') {
249
+ nextSpace = i;
250
+ break;
251
+ }
252
+ }
253
+
254
+ if (nextCaret != kNpos && (nextSpace == kNpos || nextCaret < nextSpace)) {
255
+ // Pandoc ^sup^.
256
+ ci = wrapRange(
257
+ kids,
258
+ arena,
259
+ {ci, caret, true},
260
+ 1,
261
+ {ci, nextCaret, true},
262
+ 1,
263
+ NodeType::Superscript);
264
+ off = 0;
265
+ continue;
266
+ }
267
+
268
+ // Reddit bare ^word — runs to the next whitespace (or end of run).
269
+ const size_t end = (nextSpace == kNpos) ? text.size() : nextSpace;
270
+ if (end == caret + 1) {
271
+ off = caret + 1;
272
+ continue;
273
+ }
274
+ ci = wrapRange(
275
+ kids, arena, {ci, caret, true}, 1, {ci, end, true}, 0, NodeType::Superscript);
276
+ off = 0;
277
+ }
278
+ }
279
+
280
+ void process(Node* node, AstArena& arena) {
281
+ if (node->type == NodeType::Link || node->type == NodeType::CodeBlock ||
282
+ node->type == NodeType::InlineCode || node->type == NodeType::Image) {
283
+ return;
284
+ }
285
+ if (!node->children.empty()) {
286
+ pairPass(node, arena, ">!", "!<", NodeType::Spoiler);
287
+ pairPass(node, arena, "||", "||", NodeType::Spoiler);
288
+ pairPass(node, arena, "~~", "~~", NodeType::Strikethrough);
289
+ subscriptPass(node, arena);
290
+ superscriptPass(node, arena);
291
+ for (Node* child : node->children) {
292
+ process(child, arena);
293
+ }
294
+ }
295
+ }
296
+
297
+ } // namespace
298
+
299
+ void applyInlineExtensions(MarkdownDocument& doc) {
300
+ if (doc.root != nullptr) {
301
+ process(doc.root, doc.arena);
302
+ }
303
+ }
304
+
305
+ } // namespace jetmarkdown
@@ -0,0 +1,17 @@
1
+ #pragma once
2
+
3
+ #include "Ast.h"
4
+
5
+ namespace jetmarkdown {
6
+
7
+ // Post-parse scan of inline runs adding the syntax md4c does not know:
8
+ // >!spoiler!< and ||spoiler|| -> Spoiler
9
+ // ~~strikethrough~~ -> Strikethrough
10
+ // ~subscript~ -> Subscript (same run, no spaces)
11
+ // ^sup^ ^word ^(multi word) -> Superscript
12
+ // Delimiters may span styled runs (e.g. "||bold **x**||") for spoilers and
13
+ // strikethrough. Unclosed delimiters stay literal text. Link contents are
14
+ // left untouched.
15
+ void applyInlineExtensions(MarkdownDocument& doc);
16
+
17
+ } // namespace jetmarkdown