@synerise/ds-rich-text 0.1.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 (52) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +173 -0
  4. package/dist/RichText.d.ts +4 -0
  5. package/dist/RichText.js +179 -0
  6. package/dist/RichText.styles.d.ts +31 -0
  7. package/dist/RichText.styles.js +87 -0
  8. package/dist/RichText.types.d.ts +139 -0
  9. package/dist/RichText.types.js +44 -0
  10. package/dist/blockModel/blockModel.types.d.ts +130 -0
  11. package/dist/blockModel/blockModel.types.js +1 -0
  12. package/dist/blockModel/index.d.ts +2 -0
  13. package/dist/blockModel/index.js +14 -0
  14. package/dist/blockModel/serializers/documentToHtml.d.ts +2 -0
  15. package/dist/blockModel/serializers/documentToHtml.js +101 -0
  16. package/dist/blockModel/serializers/documentToMarkdown.d.ts +2 -0
  17. package/dist/blockModel/serializers/documentToMarkdown.js +84 -0
  18. package/dist/blockModel/serializers/documentToTiptap.d.ts +2 -0
  19. package/dist/blockModel/serializers/documentToTiptap.js +133 -0
  20. package/dist/blockModel/serializers/htmlToDocument.d.ts +2 -0
  21. package/dist/blockModel/serializers/htmlToDocument.js +242 -0
  22. package/dist/blockModel/serializers/index.d.ts +6 -0
  23. package/dist/blockModel/serializers/index.js +14 -0
  24. package/dist/blockModel/serializers/markdownToDocument.d.ts +10 -0
  25. package/dist/blockModel/serializers/markdownToDocument.js +11 -0
  26. package/dist/blockModel/serializers/tiptapToDocument.d.ts +2 -0
  27. package/dist/blockModel/serializers/tiptapToDocument.js +189 -0
  28. package/dist/components/ImagePopover.d.ts +3 -0
  29. package/dist/components/ImagePopover.js +89 -0
  30. package/dist/components/LinkPopover.d.ts +3 -0
  31. package/dist/components/LinkPopover.js +57 -0
  32. package/dist/components/Toolbar.d.ts +3 -0
  33. package/dist/components/Toolbar.js +436 -0
  34. package/dist/components/index.d.ts +3 -0
  35. package/dist/components/index.js +8 -0
  36. package/dist/extensions/CodeSnippet.d.ts +19 -0
  37. package/dist/extensions/CodeSnippet.js +76 -0
  38. package/dist/index.d.ts +6 -0
  39. package/dist/index.js +52 -0
  40. package/dist/modules.d.js +1 -0
  41. package/dist/modules.d.ts +4 -0
  42. package/dist/utils/computeVisibleCount.d.ts +6 -0
  43. package/dist/utils/computeVisibleCount.js +19 -0
  44. package/dist/utils/getExtensions.d.ts +13 -0
  45. package/dist/utils/getExtensions.js +80 -0
  46. package/dist/utils/index.d.ts +3 -0
  47. package/dist/utils/index.js +8 -0
  48. package/dist/utils/normalizeUrl.d.ts +7 -0
  49. package/dist/utils/normalizeUrl.js +13 -0
  50. package/dist/utils/sanitizeUrl.d.ts +9 -0
  51. package/dist/utils/sanitizeUrl.js +30 -0
  52. package/package.json +75 -0
@@ -0,0 +1,44 @@
1
+ const ALL_TOOLBAR_FEATURES = ["bold", "italic", "underline", "strikethrough", "heading", "bulletList", "orderedList", "link", "textAlign", "codeSnippet", "table", "image"];
2
+ const DEFAULT_HEADING_LEVELS = [1, 2, 3];
3
+ const DEFAULT_TEXTS = {
4
+ paragraph: "Paragraph",
5
+ bold: "Bold",
6
+ italic: "Italic",
7
+ underline: "Underline",
8
+ strikethrough: "Strikethrough",
9
+ heading: "Heading",
10
+ bulletList: "Bullet list",
11
+ orderedList: "Ordered list",
12
+ link: "Link",
13
+ align: "Align",
14
+ alignLeft: "Align left",
15
+ alignCenter: "Align center",
16
+ alignRight: "Align right",
17
+ codeBlock: "Code block",
18
+ codeSnippet: "Code snippet",
19
+ copySnippet: "Copy",
20
+ copiedSnippet: "Copied",
21
+ editWithAI: "Edit with AI",
22
+ more: "More",
23
+ table: "Table",
24
+ insertTable: "Insert table",
25
+ addRowBefore: "Add row above",
26
+ addRowAfter: "Add row below",
27
+ addColumnBefore: "Add column left",
28
+ addColumnAfter: "Add column right",
29
+ deleteRow: "Delete row",
30
+ deleteColumn: "Delete column",
31
+ deleteTable: "Delete table",
32
+ linkPlaceholder: "Enter URL...",
33
+ linkConfirm: "Apply",
34
+ linkRemove: "Remove link",
35
+ image: "Image",
36
+ imagePlaceholder: "Enter image URL...",
37
+ imageConfirm: "Insert",
38
+ imageUpload: "Upload file"
39
+ };
40
+ export {
41
+ ALL_TOOLBAR_FEATURES,
42
+ DEFAULT_HEADING_LEVELS,
43
+ DEFAULT_TEXTS
44
+ };
@@ -0,0 +1,130 @@
1
+ export type TextAlign = 'left' | 'center' | 'right';
2
+ export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
3
+ export type BoldMark = {
4
+ type: 'bold';
5
+ };
6
+ export type ItalicMark = {
7
+ type: 'italic';
8
+ };
9
+ export type UnderlineMark = {
10
+ type: 'underline';
11
+ };
12
+ export type StrikeMark = {
13
+ type: 'strike';
14
+ };
15
+ export type CodeMark = {
16
+ type: 'code';
17
+ };
18
+ export type LinkMark = {
19
+ type: 'link';
20
+ href: string;
21
+ target?: string;
22
+ rel?: string;
23
+ };
24
+ export type Mark = BoldMark | ItalicMark | UnderlineMark | StrikeMark | CodeMark | LinkMark;
25
+ export type TextNode = {
26
+ type: 'text';
27
+ text: string;
28
+ marks?: Mark[];
29
+ };
30
+ export type HardBreakNode = {
31
+ type: 'hardBreak';
32
+ };
33
+ export type InlineNode = TextNode | HardBreakNode;
34
+ export type ParagraphBlock = {
35
+ type: 'paragraph';
36
+ attrs?: {
37
+ textAlign?: TextAlign;
38
+ };
39
+ children: InlineNode[];
40
+ };
41
+ export type HeadingBlock = {
42
+ type: 'heading';
43
+ attrs: {
44
+ level: HeadingLevel;
45
+ textAlign?: TextAlign;
46
+ };
47
+ children: InlineNode[];
48
+ };
49
+ export type CodeBlockBlock = {
50
+ type: 'codeBlock';
51
+ attrs?: {
52
+ language?: string;
53
+ };
54
+ children: InlineNode[];
55
+ };
56
+ export type CodeSnippetBlock = {
57
+ type: 'codeSnippet';
58
+ children: InlineNode[];
59
+ };
60
+ export type ImageBlock = {
61
+ type: 'image';
62
+ attrs: {
63
+ src: string;
64
+ alt?: string;
65
+ title?: string;
66
+ };
67
+ };
68
+ export type ListItemBlock = {
69
+ type: 'listItem';
70
+ children: Block[];
71
+ };
72
+ export type BulletListBlock = {
73
+ type: 'bulletList';
74
+ children: ListItemBlock[];
75
+ };
76
+ export type OrderedListBlock = {
77
+ type: 'orderedList';
78
+ attrs?: {
79
+ start?: number;
80
+ };
81
+ children: ListItemBlock[];
82
+ };
83
+ export type BlockquoteBlock = {
84
+ type: 'blockquote';
85
+ children: Block[];
86
+ };
87
+ export type HorizontalRuleBlock = {
88
+ type: 'horizontalRule';
89
+ };
90
+ export type TableCellBlock = {
91
+ type: 'tableCell';
92
+ attrs?: {
93
+ colspan?: number;
94
+ rowspan?: number;
95
+ };
96
+ children: Block[];
97
+ };
98
+ export type TableHeaderBlock = {
99
+ type: 'tableHeader';
100
+ attrs?: {
101
+ colspan?: number;
102
+ rowspan?: number;
103
+ };
104
+ children: Block[];
105
+ };
106
+ export type TableRowBlock = {
107
+ type: 'tableRow';
108
+ children: (TableCellBlock | TableHeaderBlock)[];
109
+ };
110
+ export type TableBlock = {
111
+ type: 'table';
112
+ children: TableRowBlock[];
113
+ };
114
+ export type Block = ParagraphBlock | HeadingBlock | CodeBlockBlock | CodeSnippetBlock | ImageBlock | BulletListBlock | OrderedListBlock | BlockquoteBlock | HorizontalRuleBlock | TableBlock;
115
+ export type RichTextDocument = {
116
+ type: 'doc';
117
+ version: 1;
118
+ children: Block[];
119
+ };
120
+ export type PMNode = {
121
+ type: string;
122
+ attrs?: Record<string, unknown>;
123
+ content?: PMNode[];
124
+ marks?: PMMark[];
125
+ text?: string;
126
+ };
127
+ export type PMMark = {
128
+ type: string;
129
+ attrs?: Record<string, unknown>;
130
+ };
@@ -0,0 +1,2 @@
1
+ export type { TextAlign, HeadingLevel, BoldMark, ItalicMark, UnderlineMark, StrikeMark, CodeMark, LinkMark, Mark, TextNode, HardBreakNode, InlineNode, ParagraphBlock, HeadingBlock, CodeBlockBlock, ImageBlock, ListItemBlock, BulletListBlock, OrderedListBlock, BlockquoteBlock, HorizontalRuleBlock, TableCellBlock, TableHeaderBlock, TableRowBlock, TableBlock, Block, RichTextDocument, PMNode, PMMark, } from './blockModel.types';
2
+ export { tiptapJsonToDocument, documentToTiptapJson, documentToHtml, documentToMarkdown, markdownToDocument, htmlToDocument, } from './serializers';
@@ -0,0 +1,14 @@
1
+ import { tiptapJsonToDocument } from "./serializers/tiptapToDocument.js";
2
+ import { documentToTiptapJson } from "./serializers/documentToTiptap.js";
3
+ import { documentToHtml } from "./serializers/documentToHtml.js";
4
+ import { documentToMarkdown } from "./serializers/documentToMarkdown.js";
5
+ import { markdownToDocument } from "./serializers/markdownToDocument.js";
6
+ import { htmlToDocument } from "./serializers/htmlToDocument.js";
7
+ export {
8
+ documentToHtml,
9
+ documentToMarkdown,
10
+ documentToTiptapJson,
11
+ htmlToDocument,
12
+ markdownToDocument,
13
+ tiptapJsonToDocument
14
+ };
@@ -0,0 +1,2 @@
1
+ import { RichTextDocument } from '../blockModel.types';
2
+ export declare const documentToHtml: (doc: RichTextDocument) => string;
@@ -0,0 +1,101 @@
1
+ import { sanitizeUrl } from "../../utils/sanitizeUrl.js";
2
+ const escapeHtml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3
+ const serializeMark = (mark, inner) => {
4
+ switch (mark.type) {
5
+ case "bold":
6
+ return `<strong>${inner}</strong>`;
7
+ case "italic":
8
+ return `<em>${inner}</em>`;
9
+ case "underline":
10
+ return `<u>${inner}</u>`;
11
+ case "strike":
12
+ return `<s>${inner}</s>`;
13
+ case "code":
14
+ return `<code>${inner}</code>`;
15
+ case "link": {
16
+ const attrs = [`href="${escapeHtml(sanitizeUrl(mark.href))}"`];
17
+ if (mark.target) {
18
+ attrs.push(`target="${escapeHtml(mark.target)}"`);
19
+ }
20
+ if (mark.rel) {
21
+ attrs.push(`rel="${escapeHtml(mark.rel)}"`);
22
+ }
23
+ return `<a ${attrs.join(" ")}>${inner}</a>`;
24
+ }
25
+ }
26
+ };
27
+ const serializeInline = (node) => {
28
+ if (node.type === "hardBreak") {
29
+ return "<br>";
30
+ }
31
+ let html = escapeHtml(node.text);
32
+ if (node.marks) {
33
+ for (const mark of node.marks) {
34
+ html = serializeMark(mark, html);
35
+ }
36
+ }
37
+ return html;
38
+ };
39
+ const alignAttr = (textAlign) => textAlign ? ` style="text-align: ${textAlign}"` : "";
40
+ const serializeBlock = (block) => {
41
+ switch (block.type) {
42
+ case "paragraph": {
43
+ const inner = block.children.map(serializeInline).join("");
44
+ return `<p${alignAttr(block.attrs?.textAlign)}>${inner}</p>`;
45
+ }
46
+ case "heading": {
47
+ const tag = `h${block.attrs.level}`;
48
+ const inner = block.children.map(serializeInline).join("");
49
+ return `<${tag}${alignAttr(block.attrs.textAlign)}>${inner}</${tag}>`;
50
+ }
51
+ case "codeBlock": {
52
+ const inner = block.children.map(serializeInline).join("");
53
+ return `<pre><code>${inner}</code></pre>`;
54
+ }
55
+ case "codeSnippet": {
56
+ const inner = block.children.map(serializeInline).join("");
57
+ return `<pre data-type="code-snippet"><code>${inner}</code></pre>`;
58
+ }
59
+ case "image": {
60
+ const attrs = [`src="${escapeHtml(block.attrs.src)}"`];
61
+ if (block.attrs.alt) {
62
+ attrs.push(`alt="${escapeHtml(block.attrs.alt)}"`);
63
+ }
64
+ if (block.attrs.title) {
65
+ attrs.push(`title="${escapeHtml(block.attrs.title)}"`);
66
+ }
67
+ return `<img ${attrs.join(" ")}>`;
68
+ }
69
+ case "bulletList":
70
+ return `<ul>${block.children.map((li) => `<li>${li.children.map(serializeBlock).join("")}</li>`).join("")}</ul>`;
71
+ case "orderedList": {
72
+ const startAttr = block.attrs?.start && block.attrs.start !== 1 ? ` start="${block.attrs.start}"` : "";
73
+ return `<ol${startAttr}>${block.children.map((li) => `<li>${li.children.map(serializeBlock).join("")}</li>`).join("")}</ol>`;
74
+ }
75
+ case "blockquote":
76
+ return `<blockquote>${block.children.map(serializeBlock).join("")}</blockquote>`;
77
+ case "horizontalRule":
78
+ return "<hr>";
79
+ case "table":
80
+ return `<table><tbody>${block.children.map(serializeRow).join("")}</tbody></table>`;
81
+ }
82
+ };
83
+ const spanAttrs = (cell) => {
84
+ const attrs = [];
85
+ if (cell.attrs?.colspan && cell.attrs.colspan !== 1) {
86
+ attrs.push(`colspan="${cell.attrs.colspan}"`);
87
+ }
88
+ if (cell.attrs?.rowspan && cell.attrs.rowspan !== 1) {
89
+ attrs.push(`rowspan="${cell.attrs.rowspan}"`);
90
+ }
91
+ return attrs.length ? ` ${attrs.join(" ")}` : "";
92
+ };
93
+ const serializeCell = (cell) => {
94
+ const tag = cell.type === "tableHeader" ? "th" : "td";
95
+ return `<${tag}${spanAttrs(cell)}>${cell.children.map(serializeBlock).join("")}</${tag}>`;
96
+ };
97
+ const serializeRow = (row) => `<tr>${row.children.map(serializeCell).join("")}</tr>`;
98
+ const documentToHtml = (doc) => doc.children.map(serializeBlock).join("");
99
+ export {
100
+ documentToHtml
101
+ };
@@ -0,0 +1,2 @@
1
+ import { RichTextDocument } from '../blockModel.types';
2
+ export declare const documentToMarkdown: (doc: RichTextDocument) => string;
@@ -0,0 +1,84 @@
1
+ const serializeMark = (mark, inner) => {
2
+ switch (mark.type) {
3
+ case "bold":
4
+ return `**${inner}**`;
5
+ case "italic":
6
+ return `*${inner}*`;
7
+ case "underline":
8
+ return `<u>${inner}</u>`;
9
+ case "strike":
10
+ return `~~${inner}~~`;
11
+ case "code":
12
+ return `\`${inner}\``;
13
+ case "link":
14
+ return `[${inner}](${mark.href})`;
15
+ }
16
+ };
17
+ const serializeInline = (node) => {
18
+ if (node.type === "hardBreak") {
19
+ return " \n";
20
+ }
21
+ let text = node.text;
22
+ if (node.marks) {
23
+ for (const mark of node.marks) {
24
+ text = serializeMark(mark, text);
25
+ }
26
+ }
27
+ return text;
28
+ };
29
+ const serializeInlines = (children) => children.map(serializeInline).join("");
30
+ const indentLines = (text, indent) => text.split("\n").map((line) => line ? indent + line : line).join("\n");
31
+ const serializeListItem = (item, marker, indent) => {
32
+ const [first, ...rest] = item.children;
33
+ const firstText = first ? serializeBlock(first) : "";
34
+ const continuation = indent + " ".repeat(marker.length);
35
+ let line = `${indent}${marker}${firstText}`;
36
+ for (const child of rest) {
37
+ line += `
38
+ ${indentLines(serializeBlock(child), continuation)}`;
39
+ }
40
+ return line;
41
+ };
42
+ const serializeBlock = (block, indent = "") => {
43
+ switch (block.type) {
44
+ case "paragraph":
45
+ return serializeInlines(block.children);
46
+ case "heading":
47
+ return `${"#".repeat(block.attrs.level)} ${serializeInlines(block.children)}`;
48
+ case "codeBlock":
49
+ return `\`\`\`
50
+ ${serializeInlines(block.children)}
51
+ \`\`\``;
52
+ case "codeSnippet":
53
+ return `\`${serializeInlines(block.children)}\``;
54
+ case "image":
55
+ return `![${block.attrs.alt ?? ""}](${block.attrs.src})`;
56
+ case "bulletList":
57
+ return block.children.map((li) => serializeListItem(li, "- ", indent)).join("\n");
58
+ case "orderedList": {
59
+ const start = block.attrs?.start ?? 1;
60
+ return block.children.map((li, i) => serializeListItem(li, `${start + i}. `, indent)).join("\n");
61
+ }
62
+ case "blockquote":
63
+ return block.children.map((child) => indentLines(serializeBlock(child), "> ")).join("\n>\n");
64
+ case "horizontalRule":
65
+ return "---";
66
+ case "table": {
67
+ const rows = block.children;
68
+ if (rows.length === 0) {
69
+ return "";
70
+ }
71
+ const renderRow = (cells) => `| ${cells.map(cellToMarkdown).join(" | ")} |`;
72
+ const colCount = rows[0].children.length;
73
+ const header = renderRow(rows[0].children);
74
+ const separator = `| ${Array(colCount).fill("---").join(" | ")} |`;
75
+ const body = rows.slice(1).map((row) => renderRow(row.children));
76
+ return [header, separator, ...body].join("\n");
77
+ }
78
+ }
79
+ };
80
+ const cellToMarkdown = (cell) => cell.children.map((child) => serializeBlock(child)).join(" ").replace(/\n+/g, " ").replace(/\|/g, "\\|").trim();
81
+ const documentToMarkdown = (doc) => doc.children.map((block) => serializeBlock(block)).join("\n\n");
82
+ export {
83
+ documentToMarkdown
84
+ };
@@ -0,0 +1,2 @@
1
+ import { PMNode, RichTextDocument } from '../blockModel.types';
2
+ export declare const documentToTiptapJson: (doc: RichTextDocument) => PMNode;
@@ -0,0 +1,133 @@
1
+ const convertMark = (mark) => {
2
+ if (mark.type === "link") {
3
+ return {
4
+ type: "link",
5
+ attrs: {
6
+ href: mark.href,
7
+ ...mark.target && {
8
+ target: mark.target
9
+ },
10
+ ...mark.rel && {
11
+ rel: mark.rel
12
+ }
13
+ }
14
+ };
15
+ }
16
+ return {
17
+ type: mark.type
18
+ };
19
+ };
20
+ const convertInlineNode = (node) => {
21
+ if (node.type === "hardBreak") {
22
+ return {
23
+ type: "hardBreak"
24
+ };
25
+ }
26
+ return {
27
+ type: "text",
28
+ text: node.text,
29
+ ...node.marks?.length && {
30
+ marks: node.marks.map(convertMark)
31
+ }
32
+ };
33
+ };
34
+ const convertBlock = (block) => {
35
+ switch (block.type) {
36
+ case "paragraph":
37
+ return {
38
+ type: "paragraph",
39
+ ...block.attrs?.textAlign && {
40
+ attrs: {
41
+ textAlign: block.attrs.textAlign
42
+ }
43
+ },
44
+ content: block.children.map(convertInlineNode)
45
+ };
46
+ case "heading":
47
+ return {
48
+ type: "heading",
49
+ attrs: {
50
+ level: block.attrs.level,
51
+ ...block.attrs.textAlign && {
52
+ textAlign: block.attrs.textAlign
53
+ }
54
+ },
55
+ content: block.children.map(convertInlineNode)
56
+ };
57
+ case "codeBlock":
58
+ return {
59
+ type: "codeBlock",
60
+ ...block.attrs?.language && {
61
+ attrs: {
62
+ language: block.attrs.language
63
+ }
64
+ },
65
+ content: block.children.map(convertInlineNode)
66
+ };
67
+ case "codeSnippet":
68
+ return {
69
+ type: "codeSnippet",
70
+ content: block.children.map(convertInlineNode)
71
+ };
72
+ case "image":
73
+ return {
74
+ type: "image",
75
+ attrs: {
76
+ ...block.attrs
77
+ }
78
+ };
79
+ case "bulletList":
80
+ return {
81
+ type: "bulletList",
82
+ content: block.children.map((li) => ({
83
+ type: "listItem",
84
+ content: li.children.map(convertBlock)
85
+ }))
86
+ };
87
+ case "orderedList":
88
+ return {
89
+ type: "orderedList",
90
+ ...block.attrs?.start && {
91
+ attrs: {
92
+ start: block.attrs.start
93
+ }
94
+ },
95
+ content: block.children.map((li) => ({
96
+ type: "listItem",
97
+ content: li.children.map(convertBlock)
98
+ }))
99
+ };
100
+ case "blockquote":
101
+ return {
102
+ type: "blockquote",
103
+ content: block.children.map(convertBlock)
104
+ };
105
+ case "horizontalRule":
106
+ return {
107
+ type: "horizontalRule"
108
+ };
109
+ case "table":
110
+ return {
111
+ type: "table",
112
+ content: block.children.map((row) => ({
113
+ type: "tableRow",
114
+ content: row.children.map((cell) => ({
115
+ type: cell.type,
116
+ ...cell.attrs && {
117
+ attrs: {
118
+ ...cell.attrs
119
+ }
120
+ },
121
+ content: cell.children.map(convertBlock)
122
+ }))
123
+ }))
124
+ };
125
+ }
126
+ };
127
+ const documentToTiptapJson = (doc) => ({
128
+ type: "doc",
129
+ content: doc.children.map(convertBlock)
130
+ });
131
+ export {
132
+ documentToTiptapJson
133
+ };
@@ -0,0 +1,2 @@
1
+ import { RichTextDocument } from '../blockModel.types';
2
+ export declare const htmlToDocument: (html: string) => RichTextDocument;