react-native-simple-rich-text-editor 0.1.0-beta.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.
Files changed (122) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +69 -0
  3. package/lib/commonjs/index.js +166 -0
  4. package/lib/commonjs/index.js.map +1 -0
  5. package/lib/commonjs/internal/rendering/components/Bold.js +23 -0
  6. package/lib/commonjs/internal/rendering/components/Bold.js.map +1 -0
  7. package/lib/commonjs/internal/rendering/components/BulletPoint.js +30 -0
  8. package/lib/commonjs/internal/rendering/components/BulletPoint.js.map +1 -0
  9. package/lib/commonjs/internal/rendering/components/Headline.js +37 -0
  10. package/lib/commonjs/internal/rendering/components/Headline.js.map +1 -0
  11. package/lib/commonjs/internal/rendering/components/Italic.js +23 -0
  12. package/lib/commonjs/internal/rendering/components/Italic.js.map +1 -0
  13. package/lib/commonjs/internal/rendering/components/Render.js +99 -0
  14. package/lib/commonjs/internal/rendering/components/Render.js.map +1 -0
  15. package/lib/commonjs/internal/rendering/utils.js +19 -0
  16. package/lib/commonjs/internal/rendering/utils.js.map +1 -0
  17. package/lib/commonjs/internal/text-formats/conversions.js +47 -0
  18. package/lib/commonjs/internal/text-formats/conversions.js.map +1 -0
  19. package/lib/commonjs/internal/text-formats/markdown-format.js +2 -0
  20. package/lib/commonjs/internal/text-formats/markdown-format.js.map +1 -0
  21. package/lib/commonjs/internal/text-formats/unicode-markers-format/constants.js +48 -0
  22. package/lib/commonjs/internal/text-formats/unicode-markers-format/constants.js.map +1 -0
  23. package/lib/commonjs/internal/text-formats/unicode-markers-format/encode-decode.js +65 -0
  24. package/lib/commonjs/internal/text-formats/unicode-markers-format/encode-decode.js.map +1 -0
  25. package/lib/commonjs/internal/text-formats/unicode-markers-format/markers.js +19 -0
  26. package/lib/commonjs/internal/text-formats/unicode-markers-format/markers.js.map +1 -0
  27. package/lib/commonjs/internal/text-formats/unicode-markers-format/text-manipulation.js +124 -0
  28. package/lib/commonjs/internal/text-formats/unicode-markers-format/text-manipulation.js.map +1 -0
  29. package/lib/module/index.js +162 -0
  30. package/lib/module/index.js.map +1 -0
  31. package/lib/module/internal/rendering/components/Bold.js +18 -0
  32. package/lib/module/internal/rendering/components/Bold.js.map +1 -0
  33. package/lib/module/internal/rendering/components/BulletPoint.js +25 -0
  34. package/lib/module/internal/rendering/components/BulletPoint.js.map +1 -0
  35. package/lib/module/internal/rendering/components/Headline.js +32 -0
  36. package/lib/module/internal/rendering/components/Headline.js.map +1 -0
  37. package/lib/module/internal/rendering/components/Italic.js +18 -0
  38. package/lib/module/internal/rendering/components/Italic.js.map +1 -0
  39. package/lib/module/internal/rendering/components/Render.js +94 -0
  40. package/lib/module/internal/rendering/components/Render.js.map +1 -0
  41. package/lib/module/internal/rendering/utils.js +16 -0
  42. package/lib/module/internal/rendering/utils.js.map +1 -0
  43. package/lib/module/internal/text-formats/conversions.js +43 -0
  44. package/lib/module/internal/text-formats/conversions.js.map +1 -0
  45. package/lib/module/internal/text-formats/markdown-format.js +2 -0
  46. package/lib/module/internal/text-formats/markdown-format.js.map +1 -0
  47. package/lib/module/internal/text-formats/unicode-markers-format/constants.js +45 -0
  48. package/lib/module/internal/text-formats/unicode-markers-format/constants.js.map +1 -0
  49. package/lib/module/internal/text-formats/unicode-markers-format/encode-decode.js +61 -0
  50. package/lib/module/internal/text-formats/unicode-markers-format/encode-decode.js.map +1 -0
  51. package/lib/module/internal/text-formats/unicode-markers-format/markers.js +15 -0
  52. package/lib/module/internal/text-formats/unicode-markers-format/markers.js.map +1 -0
  53. package/lib/module/internal/text-formats/unicode-markers-format/text-manipulation.js +117 -0
  54. package/lib/module/internal/text-formats/unicode-markers-format/text-manipulation.js.map +1 -0
  55. package/lib/typescript/commonjs/package.json +1 -0
  56. package/lib/typescript/commonjs/src/index.d.ts +38 -0
  57. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  58. package/lib/typescript/commonjs/src/internal/rendering/components/Bold.d.ts +3 -0
  59. package/lib/typescript/commonjs/src/internal/rendering/components/Bold.d.ts.map +1 -0
  60. package/lib/typescript/commonjs/src/internal/rendering/components/BulletPoint.d.ts +3 -0
  61. package/lib/typescript/commonjs/src/internal/rendering/components/BulletPoint.d.ts.map +1 -0
  62. package/lib/typescript/commonjs/src/internal/rendering/components/Headline.d.ts +24 -0
  63. package/lib/typescript/commonjs/src/internal/rendering/components/Headline.d.ts.map +1 -0
  64. package/lib/typescript/commonjs/src/internal/rendering/components/Italic.d.ts +3 -0
  65. package/lib/typescript/commonjs/src/internal/rendering/components/Italic.d.ts.map +1 -0
  66. package/lib/typescript/commonjs/src/internal/rendering/components/Render.d.ts +4 -0
  67. package/lib/typescript/commonjs/src/internal/rendering/components/Render.d.ts.map +1 -0
  68. package/lib/typescript/commonjs/src/internal/rendering/utils.d.ts +3 -0
  69. package/lib/typescript/commonjs/src/internal/rendering/utils.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/src/internal/text-formats/conversions.d.ts +9 -0
  71. package/lib/typescript/commonjs/src/internal/text-formats/conversions.d.ts.map +1 -0
  72. package/lib/typescript/commonjs/src/internal/text-formats/markdown-format.d.ts +1 -0
  73. package/lib/typescript/commonjs/src/internal/text-formats/markdown-format.d.ts.map +1 -0
  74. package/lib/typescript/commonjs/src/internal/text-formats/unicode-markers-format/constants.d.ts +26 -0
  75. package/lib/typescript/commonjs/src/internal/text-formats/unicode-markers-format/constants.d.ts.map +1 -0
  76. package/lib/typescript/commonjs/src/internal/text-formats/unicode-markers-format/encode-decode.d.ts +15 -0
  77. package/lib/typescript/commonjs/src/internal/text-formats/unicode-markers-format/encode-decode.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/src/internal/text-formats/unicode-markers-format/markers.d.ts +11 -0
  79. package/lib/typescript/commonjs/src/internal/text-formats/unicode-markers-format/markers.d.ts.map +1 -0
  80. package/lib/typescript/commonjs/src/internal/text-formats/unicode-markers-format/text-manipulation.d.ts +52 -0
  81. package/lib/typescript/commonjs/src/internal/text-formats/unicode-markers-format/text-manipulation.d.ts.map +1 -0
  82. package/lib/typescript/module/package.json +1 -0
  83. package/lib/typescript/module/src/index.d.ts +38 -0
  84. package/lib/typescript/module/src/index.d.ts.map +1 -0
  85. package/lib/typescript/module/src/internal/rendering/components/Bold.d.ts +3 -0
  86. package/lib/typescript/module/src/internal/rendering/components/Bold.d.ts.map +1 -0
  87. package/lib/typescript/module/src/internal/rendering/components/BulletPoint.d.ts +3 -0
  88. package/lib/typescript/module/src/internal/rendering/components/BulletPoint.d.ts.map +1 -0
  89. package/lib/typescript/module/src/internal/rendering/components/Headline.d.ts +24 -0
  90. package/lib/typescript/module/src/internal/rendering/components/Headline.d.ts.map +1 -0
  91. package/lib/typescript/module/src/internal/rendering/components/Italic.d.ts +3 -0
  92. package/lib/typescript/module/src/internal/rendering/components/Italic.d.ts.map +1 -0
  93. package/lib/typescript/module/src/internal/rendering/components/Render.d.ts +4 -0
  94. package/lib/typescript/module/src/internal/rendering/components/Render.d.ts.map +1 -0
  95. package/lib/typescript/module/src/internal/rendering/utils.d.ts +3 -0
  96. package/lib/typescript/module/src/internal/rendering/utils.d.ts.map +1 -0
  97. package/lib/typescript/module/src/internal/text-formats/conversions.d.ts +9 -0
  98. package/lib/typescript/module/src/internal/text-formats/conversions.d.ts.map +1 -0
  99. package/lib/typescript/module/src/internal/text-formats/markdown-format.d.ts +1 -0
  100. package/lib/typescript/module/src/internal/text-formats/markdown-format.d.ts.map +1 -0
  101. package/lib/typescript/module/src/internal/text-formats/unicode-markers-format/constants.d.ts +26 -0
  102. package/lib/typescript/module/src/internal/text-formats/unicode-markers-format/constants.d.ts.map +1 -0
  103. package/lib/typescript/module/src/internal/text-formats/unicode-markers-format/encode-decode.d.ts +15 -0
  104. package/lib/typescript/module/src/internal/text-formats/unicode-markers-format/encode-decode.d.ts.map +1 -0
  105. package/lib/typescript/module/src/internal/text-formats/unicode-markers-format/markers.d.ts +11 -0
  106. package/lib/typescript/module/src/internal/text-formats/unicode-markers-format/markers.d.ts.map +1 -0
  107. package/lib/typescript/module/src/internal/text-formats/unicode-markers-format/text-manipulation.d.ts +52 -0
  108. package/lib/typescript/module/src/internal/text-formats/unicode-markers-format/text-manipulation.d.ts.map +1 -0
  109. package/package.json +194 -0
  110. package/src/index.tsx +178 -0
  111. package/src/internal/rendering/components/Bold.tsx +12 -0
  112. package/src/internal/rendering/components/BulletPoint.tsx +19 -0
  113. package/src/internal/rendering/components/Headline.tsx +32 -0
  114. package/src/internal/rendering/components/Italic.tsx +12 -0
  115. package/src/internal/rendering/components/Render.tsx +105 -0
  116. package/src/internal/rendering/utils.ts +15 -0
  117. package/src/internal/text-formats/conversions.ts +62 -0
  118. package/src/internal/text-formats/markdown-format.ts +0 -0
  119. package/src/internal/text-formats/unicode-markers-format/constants.ts +64 -0
  120. package/src/internal/text-formats/unicode-markers-format/encode-decode.ts +60 -0
  121. package/src/internal/text-formats/unicode-markers-format/markers.ts +19 -0
  122. package/src/internal/text-formats/unicode-markers-format/text-manipulation.ts +135 -0
@@ -0,0 +1,12 @@
1
+ import type { PropsWithChildren } from 'react';
2
+ import { Text, StyleSheet } from 'react-native';
3
+
4
+ export const Italic = ({ children }: PropsWithChildren) => {
5
+ return <Text style={styles.italic}>{children}</Text>;
6
+ };
7
+
8
+ const styles = StyleSheet.create({
9
+ italic: {
10
+ fontStyle: 'italic',
11
+ },
12
+ });
@@ -0,0 +1,105 @@
1
+ import { Text } from 'react-native';
2
+ import {
3
+ ZWS,
4
+ MARKER_STRING_LENGTH,
5
+ BULLET_POINT_STRING_REPRESENTATION,
6
+ } from '../../text-formats/unicode-markers-format/constants.ts';
7
+ import { Headline } from './Headline.tsx';
8
+ import { Bold } from './Bold.tsx';
9
+ import { Markers } from '../../text-formats/unicode-markers-format/markers.ts';
10
+ import { Fragment } from 'react/jsx-runtime';
11
+ import { Italic } from './Italic.tsx';
12
+ import { BulletPoint } from './BulletPoint.tsx';
13
+ import { intersperse } from '../utils.ts';
14
+
15
+ export const Render = ({ encodedText }: { encodedText: string }) => {
16
+ const lines = splitPreservingSeparator(
17
+ encodedText.replaceAll(BULLET_POINT_STRING_REPRESENTATION, ''),
18
+ ZWS
19
+ ); // each BulletPoint.tsx instance adds a BULLET_POINT_STRING_REPRESENTATION, we need to remove them to avoid adding extra chars on each render
20
+
21
+ return lines.map((line, i) => {
22
+ const marker = line.substring(0, MARKER_STRING_LENGTH);
23
+ switch (marker) {
24
+ case Markers.H1:
25
+ case Markers.H2:
26
+ case Markers.H3:
27
+ const [headline, ...restOfLineWithoutMarkers] =
28
+ splitPreservingSeparator(line, '\n');
29
+ return (
30
+ <Fragment key={i + line}>
31
+ <Headline level={marker}>{headline}</Headline>
32
+ {restOfLineWithoutMarkers.join('')}
33
+ </Fragment>
34
+ );
35
+ case Markers.UL:
36
+ // NOTE: no other markers are allowed in a bullet point list for now..
37
+ // if there are other markers they will lead to a break out of the the list -> will make the list terminate early
38
+ const [bulletPoints, ...paragraphs] = splitPreservingSeparator(
39
+ line,
40
+ '\n\n'
41
+ );
42
+
43
+ return (
44
+ <Fragment key={i + line}>
45
+ {intersperse(
46
+ // adds newlines after split() operation
47
+ (bulletPoints ?? '')
48
+ .split('\n') // erases newlines
49
+ .map((bulletPointLine, j) => (
50
+ <BulletPoint key={j + bulletPointLine}>
51
+ {bulletPointLine}
52
+ </BulletPoint>
53
+ )),
54
+ '\n'
55
+ )}
56
+ {paragraphs.map((p) => (
57
+ <Text key={p}>{p}</Text>
58
+ ))}
59
+ </Fragment>
60
+ );
61
+ case Markers.BOLD_START:
62
+ // NOTE: We could check if lines[i+1] starts with a BOLD_END marker
63
+ // but for now I opt for this simpler solution.
64
+ // Because of the above: There must not be any other markers between Markers.BOLD_START and Markers.BOLD_END!
65
+ return <Bold key={i + line}>{line}</Bold>;
66
+ case Markers.BOLD_END:
67
+ break;
68
+ case Markers.ITALIC_START:
69
+ return <Italic key={i + line}>{line}</Italic>;
70
+ case Markers.ITALIC_END:
71
+ break;
72
+ default:
73
+ // remove any broken markers
74
+ // markers can be broken by erasing text via backspace key in the editor
75
+ // first line always has no marker at the start
76
+ if (i !== 0) {
77
+ // detect broken marker
78
+ if (line.length < MARKER_STRING_LENGTH) {
79
+ return '';
80
+ }
81
+ }
82
+ break;
83
+ }
84
+
85
+ return <Text key={i + line}>{line}</Text>;
86
+ });
87
+ };
88
+
89
+ /**
90
+ * Splits a string by the specified separator but preserves the separator
91
+ * in the resulting array.
92
+ *
93
+ * @param str - The string to split
94
+ * @param separator - The separator to split by (string or RegExp)
95
+ * @returns An array of strings split by the separator with special handling
96
+ */
97
+ function splitPreservingSeparator(str: string, separator: string): string[] {
98
+ return str.split(separator).map((s, i) => {
99
+ if (i === 0) {
100
+ return s;
101
+ } else {
102
+ return separator + s;
103
+ }
104
+ });
105
+ }
@@ -0,0 +1,15 @@
1
+ // add the separator after each element of arr, except for the last element
2
+ function intersperse<T>(arr: T[], separator: T | string): (T | string)[] {
3
+ if (arr.length === 0) return [];
4
+
5
+ const output = new Array(arr.length * 2 - 1);
6
+ for (let i = 0, j = 0; i < arr.length; i++, j += 2) {
7
+ output[j] = arr[i];
8
+ if (j + 1 < output.length) {
9
+ output[j + 1] = separator;
10
+ }
11
+ }
12
+ return output;
13
+ }
14
+
15
+ export { intersperse };
@@ -0,0 +1,62 @@
1
+ import {
2
+ HEADLINE_LEVELS,
3
+ HEADLINE_MARKER_TYPE,
4
+ ZWS,
5
+ MARKER_BYTE_LENGTH,
6
+ } from './unicode-markers-format/constants.ts';
7
+ import { decode } from './unicode-markers-format/encode-decode.ts';
8
+
9
+ const Markdown = {
10
+ H1: '# ',
11
+ H2: '## ',
12
+ H3: '### ',
13
+ } as const;
14
+
15
+ type MarkdownString = string;
16
+
17
+ /**
18
+ * convertInternalFormatToMarkdown converts text from the internal format to Markdown
19
+ * @param internalText rich text in the internal format
20
+ * @returns markdown formatted text
21
+ */
22
+ const convertInternalFormatToMarkdown = (
23
+ internalText: string
24
+ ): MarkdownString => {
25
+ // // 1. find all markers
26
+ // // 2. translate the markers into corresponding Markdown markers
27
+ // // 3. Replace internal markers with Markdown markers
28
+ let markdownText = '';
29
+
30
+ let i = 0;
31
+ while (i < internalText.length) {
32
+ const char = internalText[i];
33
+
34
+ if (char === ZWS) {
35
+ markdownText += decodedTokenToMarkdownToken(
36
+ decode(internalText.substring(i, i + MARKER_BYTE_LENGTH))
37
+ );
38
+ i += MARKER_BYTE_LENGTH;
39
+ } else {
40
+ markdownText += char;
41
+ i++;
42
+ }
43
+ }
44
+
45
+ return markdownText;
46
+ };
47
+
48
+ const decodedTokenToMarkdownToken = (
49
+ internalToken: number[]
50
+ ): MarkdownString => {
51
+ // the first byte declares the token type
52
+ switch (internalToken[0]) {
53
+ case HEADLINE_MARKER_TYPE:
54
+ return Markdown[
55
+ `H${internalToken[1] as (typeof HEADLINE_LEVELS)[keyof typeof HEADLINE_LEVELS]}`
56
+ ];
57
+ default:
58
+ return '';
59
+ }
60
+ };
61
+
62
+ export { convertInternalFormatToMarkdown };
File without changes
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Constants for rich text markers.
3
+ */
4
+ const ZWS = '\u200B'; // Zero-width space used as the marker (both as prefix and base).
5
+ const MARKER_BYTE_LENGTH = 6; // Define a fixed marker length enables easy decoding when iterating over a string of the internal text format
6
+ const MARKER_STRING_LENGTH = MARKER_BYTE_LENGTH + 2; // like Markers.H1.length or Markers.BOLD.length
7
+ const HEADLINE_MARKER_TYPE = 0x10; // Marker type for headline start.
8
+ // PROBLEM: HEADLINE_LEVELS is used in two semantic ways. 1) for specifying the headline level in the encoded marker (see below) 2) and at the level interface (e.g. of Headline component) --> here we should use "H1" | "H2" | "H3" in an Enum or readonly object
9
+ const HEADLINE_LEVELS = {
10
+ H1: 1,
11
+ H2: 2,
12
+ H3: 3,
13
+ } as const;
14
+ const HEADLINE_MARKERS: Record<keyof typeof HEADLINE_LEVELS, Uint8Array> = {
15
+ H1: createMarker([HEADLINE_MARKER_TYPE, HEADLINE_LEVELS.H1]),
16
+ H2: createMarker([HEADLINE_MARKER_TYPE, HEADLINE_LEVELS.H2]),
17
+ H3: createMarker([HEADLINE_MARKER_TYPE, HEADLINE_LEVELS.H3]),
18
+ } as const;
19
+
20
+ const BOLD_MARKER_TYPE = 0x20;
21
+ const BOLD_MARKER_START = 1;
22
+ const BOLD_MARKER_END = 2;
23
+
24
+ const BOLD_MARKERS = {
25
+ START: createMarker([BOLD_MARKER_TYPE, BOLD_MARKER_START]),
26
+ END: createMarker([BOLD_MARKER_TYPE, BOLD_MARKER_END]),
27
+ } as const;
28
+
29
+ const ITALIC_MARKER_TYPE = 0x30;
30
+ const ITALIC_MARKER_START = 3;
31
+ const ITALIC_MARKER_END = 4;
32
+
33
+ const ITALIC_MARKERS = {
34
+ START: createMarker([ITALIC_MARKER_TYPE, ITALIC_MARKER_START]),
35
+ END: createMarker([ITALIC_MARKER_TYPE, ITALIC_MARKER_END]),
36
+ } as const;
37
+
38
+ const BULLET_POINT_MARKER_TYPE = 0x40;
39
+ const BULLET_POINT_UNORDERED = 1;
40
+ const BULLET_POINT_UL_MARKER = createMarker([
41
+ BULLET_POINT_MARKER_TYPE,
42
+ BULLET_POINT_UNORDERED,
43
+ ]);
44
+ const BULLET_POINT_STRING_REPRESENTATION = '• ';
45
+
46
+ function createMarker(data: number[], length = MARKER_BYTE_LENGTH) {
47
+ const array = new Uint8Array(length);
48
+ array.set(data);
49
+ return array;
50
+ }
51
+
52
+ export {
53
+ ZWS,
54
+ MARKER_STRING_LENGTH,
55
+ MARKER_BYTE_LENGTH,
56
+ HEADLINE_MARKER_TYPE,
57
+ HEADLINE_LEVELS,
58
+ HEADLINE_MARKERS,
59
+ BOLD_MARKER_TYPE,
60
+ BOLD_MARKERS,
61
+ ITALIC_MARKERS,
62
+ BULLET_POINT_UL_MARKER,
63
+ BULLET_POINT_STRING_REPRESENTATION,
64
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The encode function leverages a concept from the Unicode spec - Variant Selectors - which enables arbitrary bytes to be appended invisibly to a unicode char.
3
+ * @param base string, usually a single char, to which bytes are appended. These bytes remain invisible when base is rendered
4
+ * @param bytes to added to the base string/char.
5
+ * @returns string
6
+ */
7
+ function encode(base: string, bytes: Uint8Array): string {
8
+ let result = base;
9
+ for (const byte of bytes) {
10
+ result += byteToVariationSelector(byte);
11
+ }
12
+ return result;
13
+ }
14
+
15
+ /**
16
+ * The decode function extracts data hidden inside a string masquerading as variation selectors.
17
+ * @param variationSelectors string that may contain bytes hidden as variation selectors
18
+ * @returns a list of numbers/bytes
19
+ */
20
+ function decode(variationSelectors: string): number[] {
21
+ const result: number[] = [];
22
+ let started = false;
23
+
24
+ for (const char of variationSelectors) {
25
+ const byte = variationSelectorToByte(char);
26
+ if (byte !== null) {
27
+ result.push(byte);
28
+ started = true;
29
+ } else if (started) {
30
+ break;
31
+ }
32
+ }
33
+
34
+ return result;
35
+ }
36
+
37
+ // Utility function to transform a byte into a variation selector, for appending it to a unicode char.
38
+ function byteToVariationSelector(byte: number): string {
39
+ if (byte < 16) {
40
+ return String.fromCodePoint(0xfe00 + byte);
41
+ } else {
42
+ return String.fromCodePoint(0xe0100 + (byte - 16));
43
+ }
44
+ }
45
+
46
+ // Utility function to extract hidden data from a unicode char's variation selectors.
47
+ function variationSelectorToByte(variationSelector: string): number | null {
48
+ const codePoint = variationSelector.codePointAt(0);
49
+ if (codePoint === undefined) return null;
50
+ if (codePoint >= 0xfe00 && codePoint <= 0xfe0f) {
51
+ return codePoint - 0xfe00;
52
+ } else if (codePoint >= 0xe0100 && codePoint <= 0xe01ef) {
53
+ return codePoint - 0xe0100 + 16;
54
+ } else {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ // Source of encode, decode and variation selector utilities is https://paulbutler.org/2025/smuggling-arbitrary-data-through-an-emoji/ originally written in Rust
60
+ export { encode, decode };
@@ -0,0 +1,19 @@
1
+ import { encode } from './encode-decode.ts';
2
+ import {
3
+ ZWS,
4
+ HEADLINE_MARKERS,
5
+ BOLD_MARKERS,
6
+ ITALIC_MARKERS,
7
+ BULLET_POINT_UL_MARKER,
8
+ } from './constants.ts';
9
+
10
+ export const Markers = {
11
+ H1: encode(ZWS, HEADLINE_MARKERS.H1),
12
+ H2: encode(ZWS, HEADLINE_MARKERS.H2),
13
+ H3: encode(ZWS, HEADLINE_MARKERS.H3),
14
+ BOLD_START: encode(ZWS, BOLD_MARKERS.START),
15
+ BOLD_END: encode(ZWS, BOLD_MARKERS.END),
16
+ ITALIC_START: encode(ZWS, ITALIC_MARKERS.START),
17
+ ITALIC_END: encode(ZWS, ITALIC_MARKERS.END),
18
+ UL: encode(ZWS, BULLET_POINT_UL_MARKER),
19
+ } as const;
@@ -0,0 +1,135 @@
1
+ import { Markers } from './markers';
2
+
3
+ /**
4
+ * Adds a bullet point marker to the text at the current cursor position.
5
+ * If the cursor is not at the beginning of a line, a newline is added before the marker.
6
+ * If the cursor is at the beginning of a line, the marker is added directly.
7
+ */
8
+ const addBulletPointMarker = (
9
+ text: string,
10
+ selection: { start: number; end: number }
11
+ ) => {
12
+ if (!text) {
13
+ return Markers.UL;
14
+ }
15
+
16
+ // Split text at the current selection.
17
+ const { before, selected, after } = splitBySelection(text, selection);
18
+
19
+ // Ensure that the marker begins on a new line.
20
+ let prefix = '';
21
+ if (before.length > 0 && !before.endsWith('\n')) {
22
+ prefix = '\n';
23
+ }
24
+
25
+ // Append the marker
26
+ const insertion = prefix + Markers.UL;
27
+ return before + insertion + selected + after;
28
+ };
29
+
30
+ /**
31
+ * Inserts a headline start marker into the text at the current cursor position.
32
+ * The marker encodes two bytes:
33
+ * - Byte 0: marker type (0x10 for headline)
34
+ * - Byte 1: headline level (here: 1)
35
+ *
36
+ * The marker is inserted so that it starts on a new line.
37
+ */
38
+ const addHeadlineMarker = (
39
+ marker: string, // marker already contains the headline level encoded
40
+ text: string,
41
+ selection: { start: number; end: number }
42
+ ) => {
43
+ if (!text) {
44
+ return marker;
45
+ }
46
+ // Split text at the current selection.
47
+ const { before, after } = splitBySelection(text, selection);
48
+
49
+ // Do not add multiple consecutive markers, ZWS is the basis of each marker
50
+ // if (before.endsWith(ZWS) || after.startsWith(ZWS)) {
51
+ // return before + after;
52
+ // }
53
+
54
+ // Ensure that the marker begins on a new line.
55
+ let prefix = '';
56
+ if (!before.endsWith('\n')) {
57
+ prefix = '\n';
58
+ }
59
+ // Append the marker followed by a newline so that following text starts on a new line.
60
+ const insertion = prefix + marker;
61
+ return before + insertion + after;
62
+ };
63
+
64
+ const FontStyleMarkers: Record<
65
+ 'bold' | 'italic',
66
+ { START: string; END: string }
67
+ > = {
68
+ bold: {
69
+ START: Markers.BOLD_START,
70
+ END: Markers.BOLD_END,
71
+ },
72
+ italic: { START: Markers.ITALIC_START, END: Markers.ITALIC_END },
73
+ };
74
+
75
+ const addFontStyleEndMarker = (
76
+ text: string,
77
+ selection: { start: number; end: number },
78
+ style: 'bold' | 'italic'
79
+ ) => {
80
+ const { before, selected, after } = splitBySelection(text, selection);
81
+ return {
82
+ text: before + selected + FontStyleMarkers[style].END + after,
83
+ selection,
84
+ };
85
+ };
86
+
87
+ const addFontStyleMarkers = (
88
+ text: string,
89
+ selection: { start: number; end: number },
90
+ style: 'bold' | 'italic'
91
+ ) => {
92
+ const { before, selected, after } = splitBySelection(text, selection);
93
+
94
+ // effectively no selection
95
+ if (selection.end - selection.start === 0) {
96
+ let prefix = '';
97
+ if (!before.endsWith('\n') && !before.endsWith(' ')) {
98
+ prefix += ' ';
99
+ }
100
+
101
+ return {
102
+ text: before + prefix + FontStyleMarkers[style].START,
103
+ selection,
104
+ };
105
+ }
106
+
107
+ return {
108
+ text:
109
+ before +
110
+ FontStyleMarkers[style].START +
111
+ selected +
112
+ FontStyleMarkers[style].END +
113
+ after,
114
+ selection,
115
+ };
116
+ };
117
+
118
+ function splitBySelection(
119
+ text: string,
120
+ selection: { start: number; end: number }
121
+ ): { before: string; selected: string; after: string } {
122
+ return {
123
+ before: text.substring(0, selection.start),
124
+ selected: text.substring(selection.start, selection.end),
125
+ after: text.substring(selection.end),
126
+ };
127
+ }
128
+
129
+ export {
130
+ addBulletPointMarker,
131
+ addHeadlineMarker,
132
+ addFontStyleMarkers,
133
+ addFontStyleEndMarker,
134
+ splitBySelection,
135
+ };