@crystallize/design-system 1.9.0 → 1.10.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/CHANGELOG.md +20 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +59 -28
- package/dist/index.mjs +70 -38
- package/package.json +1 -1
- package/src/rich-text-editor/model/crystallize-to-lexical.ts +29 -1
- package/src/rich-text-editor/model/lexical-to-crystallize.ts +54 -29
- package/src/rich-text-editor/rich-text-editor.stories.tsx +28 -0
- package/src/rich-text-editor/tests/rich-text-editor-model-conversions.test.tsx +23 -1
- package/src/rich-text-editor/types/crystallize-rich-text-types/index.ts +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @crystallize/design-system
|
|
2
2
|
|
|
3
|
+
## 1.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 99dbdae: Added support for multiple inline text formats on the same text. This will give you the following model back for a string that is both `strong` and `emphasized`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"kind": "inline",
|
|
12
|
+
"type": "strong",
|
|
13
|
+
"children": [
|
|
14
|
+
{
|
|
15
|
+
"kind": "inline",
|
|
16
|
+
"type": "emphasized",
|
|
17
|
+
"textContent": "Hello there!"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
3
23
|
## 1.9.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -372,11 +372,11 @@ type CrystallizeRichTextHorizontalLineNode = {
|
|
|
372
372
|
};
|
|
373
373
|
type CrystallizeRichTextGenericNode = {
|
|
374
374
|
kind: 'block' | 'inline';
|
|
375
|
-
type?: null | 'abbrevition' | 'address' | 'article' | 'aside' | 'container' | 'details' | '
|
|
375
|
+
type?: null | 'abbrevition' | 'address' | 'article' | 'aside' | 'container' | 'details' | 'figcaption' | 'figure' | 'image' | 'line-break' | 'unordered-list' | 'ordered-list' | 'list-item' | 'picture' | 'preformatted' | 'quote' | 'section' | 'time' | 'title-of-a-work';
|
|
376
376
|
};
|
|
377
377
|
type CrystallizeRichTextInlineFormattedNodes = {
|
|
378
378
|
kind: 'inline';
|
|
379
|
-
type: 'deleted' | 'strong' | 'subscripted' | 'superscripted' | 'underlined';
|
|
379
|
+
type: 'deleted' | 'strong' | 'subscripted' | 'superscripted' | 'underlined' | 'emphasized' | 'code' | 'highlight';
|
|
380
380
|
};
|
|
381
381
|
type CrystallizeRichTextNode = CrystallizeRichTextNodeBase & (CrystallizeRichTextParagraphNode | CrystallizeRichTextHeadingNodes | CrystallizeRichTextCodeNodes | CrystallizeRichTextGenericNode | CrystallizeRichTextLinkNode | CrystallizeRichTextTableNodes | CrystallizeRichTextHorizontalLineNode | CrystallizeRichTextInlineFormattedNodes);
|
|
382
382
|
|
package/dist/index.js
CHANGED
|
@@ -3761,7 +3761,25 @@ function composeInitialState({ richText }) {
|
|
|
3761
3761
|
}
|
|
3762
3762
|
if (lexicalNode) {
|
|
3763
3763
|
if (Array.isArray(children)) {
|
|
3764
|
-
|
|
3764
|
+
if ((0, import_lexical.$isTextNode)(lexicalNode)) {
|
|
3765
|
+
const handleInlineTextChild = (child) => {
|
|
3766
|
+
if ((0, import_lexical.$isTextNode)(lexicalNode)) {
|
|
3767
|
+
const textFormat = getLexicalTextFormat(child);
|
|
3768
|
+
if (textFormat && !lexicalNode.hasFormat(textFormat)) {
|
|
3769
|
+
lexicalNode.toggleFormat(textFormat);
|
|
3770
|
+
}
|
|
3771
|
+
if (child.textContent) {
|
|
3772
|
+
lexicalNode.setTextContent(child.textContent);
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
if ("children" in child) {
|
|
3776
|
+
child.children?.forEach(handleInlineTextChild);
|
|
3777
|
+
}
|
|
3778
|
+
};
|
|
3779
|
+
children.forEach(handleInlineTextChild);
|
|
3780
|
+
} else {
|
|
3781
|
+
children.forEach((n) => handleNode({ crystallizeContentNode: n, lexicalParent: lexicalNode }));
|
|
3782
|
+
}
|
|
3765
3783
|
}
|
|
3766
3784
|
lexicalParent.append(lexicalNode);
|
|
3767
3785
|
}
|
|
@@ -3928,10 +3946,8 @@ function lexicalToCrystallizeRichText({
|
|
|
3928
3946
|
kind: "block",
|
|
3929
3947
|
type: "horizontal-line"
|
|
3930
3948
|
});
|
|
3931
|
-
} else {
|
|
3932
|
-
|
|
3933
|
-
parentChildrenToUse.push(getTextChild(childNode));
|
|
3934
|
-
}
|
|
3949
|
+
} else if ((0, import_lexical2.$isTextNode)(childNode)) {
|
|
3950
|
+
parentChildrenToUse.push(getTextChild(childNode));
|
|
3935
3951
|
}
|
|
3936
3952
|
});
|
|
3937
3953
|
}
|
|
@@ -3941,32 +3957,47 @@ function lexicalToCrystallizeRichText({
|
|
|
3941
3957
|
});
|
|
3942
3958
|
return crystallizeRichText;
|
|
3943
3959
|
}
|
|
3960
|
+
var lexicalFormatToCrystallizeType = {
|
|
3961
|
+
bold: "strong",
|
|
3962
|
+
italic: "emphasized",
|
|
3963
|
+
underline: "underlined",
|
|
3964
|
+
strikethrough: "deleted",
|
|
3965
|
+
subscript: "subscripted",
|
|
3966
|
+
superscript: "superscripted",
|
|
3967
|
+
highlight: "emphasized",
|
|
3968
|
+
code: "code"
|
|
3969
|
+
};
|
|
3944
3970
|
function getTextChild(n) {
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
return {
|
|
3948
|
-
type: "code",
|
|
3949
|
-
kind: "inline",
|
|
3950
|
-
textContent
|
|
3951
|
-
};
|
|
3952
|
-
}
|
|
3953
|
-
const textChild = {
|
|
3954
|
-
kind: "inline",
|
|
3955
|
-
textContent
|
|
3971
|
+
let currentChild = {
|
|
3972
|
+
kind: "inline"
|
|
3956
3973
|
};
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3974
|
+
let textChild = null;
|
|
3975
|
+
function checkFormat(format) {
|
|
3976
|
+
const type = lexicalFormatToCrystallizeType[format];
|
|
3977
|
+
if (n.hasFormat(format) && type) {
|
|
3978
|
+
if (!textChild) {
|
|
3979
|
+
currentChild = textChild = {
|
|
3980
|
+
kind: "inline",
|
|
3981
|
+
type
|
|
3982
|
+
};
|
|
3983
|
+
} else {
|
|
3984
|
+
currentChild.children = [
|
|
3985
|
+
{
|
|
3986
|
+
kind: "inline",
|
|
3987
|
+
type
|
|
3988
|
+
}
|
|
3989
|
+
];
|
|
3990
|
+
currentChild = currentChild.children[0];
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
Object.keys(lexicalFormatToCrystallizeType).forEach(checkFormat);
|
|
3995
|
+
if (!textChild) {
|
|
3996
|
+
currentChild = textChild = {
|
|
3997
|
+
kind: "inline"
|
|
3998
|
+
};
|
|
3969
3999
|
}
|
|
4000
|
+
currentChild.textContent = n.getTextContent();
|
|
3970
4001
|
return textChild;
|
|
3971
4002
|
}
|
|
3972
4003
|
|
package/dist/index.mjs
CHANGED
|
@@ -3576,7 +3576,8 @@ import {
|
|
|
3576
3576
|
$createLineBreakNode,
|
|
3577
3577
|
$createParagraphNode,
|
|
3578
3578
|
$createTextNode,
|
|
3579
|
-
$getRoot
|
|
3579
|
+
$getRoot,
|
|
3580
|
+
$isTextNode
|
|
3580
3581
|
} from "lexical";
|
|
3581
3582
|
import { $createCodeHighlightNode, $createCodeNode } from "@lexical/code";
|
|
3582
3583
|
import { $createLinkNode } from "@lexical/link";
|
|
@@ -3706,7 +3707,25 @@ function composeInitialState({ richText }) {
|
|
|
3706
3707
|
}
|
|
3707
3708
|
if (lexicalNode) {
|
|
3708
3709
|
if (Array.isArray(children)) {
|
|
3709
|
-
|
|
3710
|
+
if ($isTextNode(lexicalNode)) {
|
|
3711
|
+
const handleInlineTextChild = (child) => {
|
|
3712
|
+
if ($isTextNode(lexicalNode)) {
|
|
3713
|
+
const textFormat = getLexicalTextFormat(child);
|
|
3714
|
+
if (textFormat && !lexicalNode.hasFormat(textFormat)) {
|
|
3715
|
+
lexicalNode.toggleFormat(textFormat);
|
|
3716
|
+
}
|
|
3717
|
+
if (child.textContent) {
|
|
3718
|
+
lexicalNode.setTextContent(child.textContent);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
if ("children" in child) {
|
|
3722
|
+
child.children?.forEach(handleInlineTextChild);
|
|
3723
|
+
}
|
|
3724
|
+
};
|
|
3725
|
+
children.forEach(handleInlineTextChild);
|
|
3726
|
+
} else {
|
|
3727
|
+
children.forEach((n) => handleNode({ crystallizeContentNode: n, lexicalParent: lexicalNode }));
|
|
3728
|
+
}
|
|
3710
3729
|
}
|
|
3711
3730
|
lexicalParent.append(lexicalNode);
|
|
3712
3731
|
}
|
|
@@ -3744,7 +3763,7 @@ import {
|
|
|
3744
3763
|
$isElementNode,
|
|
3745
3764
|
$isLineBreakNode,
|
|
3746
3765
|
$isParagraphNode,
|
|
3747
|
-
$isTextNode
|
|
3766
|
+
$isTextNode as $isTextNode2
|
|
3748
3767
|
} from "lexical";
|
|
3749
3768
|
import { $isCodeNode } from "@lexical/code";
|
|
3750
3769
|
import { $isLinkNode } from "@lexical/link";
|
|
@@ -3857,7 +3876,7 @@ function lexicalToCrystallizeRichText({
|
|
|
3857
3876
|
if (crystallizeNode) {
|
|
3858
3877
|
if (children.length === 1 && parent) {
|
|
3859
3878
|
const [first] = children;
|
|
3860
|
-
if ($
|
|
3879
|
+
if ($isTextNode2(first)) {
|
|
3861
3880
|
const childTextNode = getTextChild(first);
|
|
3862
3881
|
if (!childTextNode.type) {
|
|
3863
3882
|
crystallizeNode.textContent = childTextNode.textContent;
|
|
@@ -3879,10 +3898,8 @@ function lexicalToCrystallizeRichText({
|
|
|
3879
3898
|
kind: "block",
|
|
3880
3899
|
type: "horizontal-line"
|
|
3881
3900
|
});
|
|
3882
|
-
} else {
|
|
3883
|
-
|
|
3884
|
-
parentChildrenToUse.push(getTextChild(childNode));
|
|
3885
|
-
}
|
|
3901
|
+
} else if ($isTextNode2(childNode)) {
|
|
3902
|
+
parentChildrenToUse.push(getTextChild(childNode));
|
|
3886
3903
|
}
|
|
3887
3904
|
});
|
|
3888
3905
|
}
|
|
@@ -3892,32 +3909,47 @@ function lexicalToCrystallizeRichText({
|
|
|
3892
3909
|
});
|
|
3893
3910
|
return crystallizeRichText;
|
|
3894
3911
|
}
|
|
3912
|
+
var lexicalFormatToCrystallizeType = {
|
|
3913
|
+
bold: "strong",
|
|
3914
|
+
italic: "emphasized",
|
|
3915
|
+
underline: "underlined",
|
|
3916
|
+
strikethrough: "deleted",
|
|
3917
|
+
subscript: "subscripted",
|
|
3918
|
+
superscript: "superscripted",
|
|
3919
|
+
highlight: "emphasized",
|
|
3920
|
+
code: "code"
|
|
3921
|
+
};
|
|
3895
3922
|
function getTextChild(n) {
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
return {
|
|
3899
|
-
type: "code",
|
|
3900
|
-
kind: "inline",
|
|
3901
|
-
textContent
|
|
3902
|
-
};
|
|
3903
|
-
}
|
|
3904
|
-
const textChild = {
|
|
3905
|
-
kind: "inline",
|
|
3906
|
-
textContent
|
|
3923
|
+
let currentChild = {
|
|
3924
|
+
kind: "inline"
|
|
3907
3925
|
};
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3926
|
+
let textChild = null;
|
|
3927
|
+
function checkFormat(format) {
|
|
3928
|
+
const type = lexicalFormatToCrystallizeType[format];
|
|
3929
|
+
if (n.hasFormat(format) && type) {
|
|
3930
|
+
if (!textChild) {
|
|
3931
|
+
currentChild = textChild = {
|
|
3932
|
+
kind: "inline",
|
|
3933
|
+
type
|
|
3934
|
+
};
|
|
3935
|
+
} else {
|
|
3936
|
+
currentChild.children = [
|
|
3937
|
+
{
|
|
3938
|
+
kind: "inline",
|
|
3939
|
+
type
|
|
3940
|
+
}
|
|
3941
|
+
];
|
|
3942
|
+
currentChild = currentChild.children[0];
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
Object.keys(lexicalFormatToCrystallizeType).forEach(checkFormat);
|
|
3947
|
+
if (!textChild) {
|
|
3948
|
+
currentChild = textChild = {
|
|
3949
|
+
kind: "inline"
|
|
3950
|
+
};
|
|
3920
3951
|
}
|
|
3952
|
+
currentChild.textContent = n.getTextContent();
|
|
3921
3953
|
return textChild;
|
|
3922
3954
|
}
|
|
3923
3955
|
|
|
@@ -4300,7 +4332,7 @@ import { $findMatchingParent, mergeRegister } from "@lexical/utils";
|
|
|
4300
4332
|
|
|
4301
4333
|
// src/rich-text-editor/ui/LinkPreview.tsx
|
|
4302
4334
|
import { Suspense, useEffect as useEffect3, useState as useState4 } from "react";
|
|
4303
|
-
import { $getSelection as $getSelection2, $isTextNode as $
|
|
4335
|
+
import { $getSelection as $getSelection2, $isTextNode as $isTextNode3 } from "lexical";
|
|
4304
4336
|
import { useLexicalComposerContext as useLexicalComposerContext3 } from "@lexical/react/LexicalComposerContext";
|
|
4305
4337
|
import { Fragment as Fragment3, jsx as jsx81, jsxs as jsxs60 } from "react/jsx-runtime";
|
|
4306
4338
|
var PREVIEW_CACHE = {};
|
|
@@ -4336,7 +4368,7 @@ function LinkPreviewContent({
|
|
|
4336
4368
|
const nodes = sel?.getNodes();
|
|
4337
4369
|
if (hasPreview && nodes?.length === 1) {
|
|
4338
4370
|
const [firstNode] = nodes;
|
|
4339
|
-
if ($
|
|
4371
|
+
if ($isTextNode3(firstNode)) {
|
|
4340
4372
|
setTextContent(firstNode.getTextContent());
|
|
4341
4373
|
}
|
|
4342
4374
|
}
|
|
@@ -4348,7 +4380,7 @@ function LinkPreviewContent({
|
|
|
4348
4380
|
const nodes = sel?.getNodes();
|
|
4349
4381
|
if (hasPreview && nodes?.length === 1) {
|
|
4350
4382
|
const [firstNode] = nodes;
|
|
4351
|
-
if ($
|
|
4383
|
+
if ($isTextNode3(firstNode)) {
|
|
4352
4384
|
firstNode.setTextContent(preview.google.title);
|
|
4353
4385
|
setTextContent(preview.google.title);
|
|
4354
4386
|
}
|
|
@@ -4754,7 +4786,7 @@ import "react";
|
|
|
4754
4786
|
import {
|
|
4755
4787
|
$getSelection as $getSelection4,
|
|
4756
4788
|
$isRangeSelection as $isRangeSelection2,
|
|
4757
|
-
$isTextNode as $
|
|
4789
|
+
$isTextNode as $isTextNode4,
|
|
4758
4790
|
COMMAND_PRIORITY_LOW as COMMAND_PRIORITY_LOW2,
|
|
4759
4791
|
FORMAT_TEXT_COMMAND,
|
|
4760
4792
|
SELECTION_CHANGE_COMMAND as SELECTION_CHANGE_COMMAND2
|
|
@@ -4996,7 +5028,7 @@ function useFloatingTextFormatToolbar(editor, anchorElem) {
|
|
|
4996
5028
|
setIsLink(false);
|
|
4997
5029
|
}
|
|
4998
5030
|
if (!$isCodeHighlightNode(selection.anchor.getNode()) && selection.getTextContent() !== "") {
|
|
4999
|
-
setIsText($
|
|
5031
|
+
setIsText($isTextNode4(node));
|
|
5000
5032
|
} else {
|
|
5001
5033
|
setIsText(false);
|
|
5002
5034
|
}
|
|
@@ -5584,7 +5616,7 @@ import {
|
|
|
5584
5616
|
$getSelection as $getSelection9,
|
|
5585
5617
|
$isRangeSelection as $isRangeSelection7,
|
|
5586
5618
|
$isRootOrShadowRoot,
|
|
5587
|
-
$isTextNode as $
|
|
5619
|
+
$isTextNode as $isTextNode5,
|
|
5588
5620
|
CAN_REDO_COMMAND,
|
|
5589
5621
|
CAN_UNDO_COMMAND,
|
|
5590
5622
|
COMMAND_PRIORITY_CRITICAL as COMMAND_PRIORITY_CRITICAL3,
|
|
@@ -6073,7 +6105,7 @@ function ToolbarPlugin({
|
|
|
6073
6105
|
if ($isRangeSelection7(selection)) {
|
|
6074
6106
|
$selectAll(selection);
|
|
6075
6107
|
selection.getNodes().forEach((node) => {
|
|
6076
|
-
if ($
|
|
6108
|
+
if ($isTextNode5(node)) {
|
|
6077
6109
|
node.setFormat(0);
|
|
6078
6110
|
node.setStyle("");
|
|
6079
6111
|
$getNearestBlockElementAncestorOrThrow(node).setFormat("");
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
$createParagraphNode,
|
|
4
4
|
$createTextNode,
|
|
5
5
|
$getRoot,
|
|
6
|
+
$isTextNode,
|
|
6
7
|
LexicalNode,
|
|
7
8
|
TextFormatType,
|
|
8
9
|
} from 'lexical';
|
|
@@ -161,7 +162,34 @@ export function composeInitialState({ richText }: { richText: CrystallizeRichTex
|
|
|
161
162
|
|
|
162
163
|
if (lexicalNode) {
|
|
163
164
|
if (Array.isArray(children)) {
|
|
164
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Support multiple nested inline text formats. A text can have both
|
|
167
|
+
* a strong _and_ underlined (and more) formats applied.
|
|
168
|
+
*/
|
|
169
|
+
if ($isTextNode(lexicalNode)) {
|
|
170
|
+
const handleInlineTextChild = (child: CrystallizeRichTextNode) => {
|
|
171
|
+
if ($isTextNode(lexicalNode)) {
|
|
172
|
+
const textFormat = getLexicalTextFormat(child);
|
|
173
|
+
|
|
174
|
+
if (textFormat && !lexicalNode.hasFormat(textFormat)) {
|
|
175
|
+
lexicalNode.toggleFormat(textFormat);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Text content will be stored at the deepest nested child
|
|
179
|
+
if (child.textContent) {
|
|
180
|
+
lexicalNode.setTextContent(child.textContent);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if ('children' in child) {
|
|
185
|
+
child.children?.forEach(handleInlineTextChild);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
children.forEach(handleInlineTextChild);
|
|
190
|
+
} else {
|
|
191
|
+
children.forEach(n => handleNode({ crystallizeContentNode: n, lexicalParent: lexicalNode as LexicalNode }));
|
|
192
|
+
}
|
|
165
193
|
}
|
|
166
194
|
|
|
167
195
|
lexicalParent.append(lexicalNode);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ElementNode,
|
|
9
9
|
type EditorState,
|
|
10
10
|
LexicalEditor,
|
|
11
|
+
TextFormatType,
|
|
11
12
|
} from 'lexical';
|
|
12
13
|
import { $isCodeNode } from '@lexical/code';
|
|
13
14
|
import { $isLinkNode } from '@lexical/link';
|
|
@@ -16,7 +17,11 @@ import { $isHorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode'
|
|
|
16
17
|
import { $isHeadingNode, $isQuoteNode, HeadingTagType } from '@lexical/rich-text';
|
|
17
18
|
import { $isTableCellNode, $isTableNode, $isTableRowNode } from '@lexical/table';
|
|
18
19
|
|
|
19
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
CrystallizeRichTextNode,
|
|
22
|
+
CrystallizeRichText,
|
|
23
|
+
CrystallizeRichTextInlineFormattedNodes,
|
|
24
|
+
} from '../types/crystallize-rich-text-types';
|
|
20
25
|
import type { CrystallizeRichTextHeadingTypes } from '../types/crystallize-rich-text-types/headings';
|
|
21
26
|
|
|
22
27
|
const headingMapper: Record<HeadingTagType, CrystallizeRichTextHeadingTypes> = {
|
|
@@ -182,10 +187,8 @@ export function lexicalToCrystallizeRichText({
|
|
|
182
187
|
kind: 'block',
|
|
183
188
|
type: 'horizontal-line',
|
|
184
189
|
});
|
|
185
|
-
} else {
|
|
186
|
-
|
|
187
|
-
parentChildrenToUse.push(getTextChild(childNode));
|
|
188
|
-
}
|
|
190
|
+
} else if ($isTextNode(childNode)) {
|
|
191
|
+
parentChildrenToUse.push(getTextChild(childNode));
|
|
189
192
|
}
|
|
190
193
|
});
|
|
191
194
|
}
|
|
@@ -197,36 +200,58 @@ export function lexicalToCrystallizeRichText({
|
|
|
197
200
|
return crystallizeRichText;
|
|
198
201
|
}
|
|
199
202
|
|
|
203
|
+
const lexicalFormatToCrystallizeType: Record<TextFormatType, CrystallizeRichTextInlineFormattedNodes['type']> = {
|
|
204
|
+
bold: 'strong',
|
|
205
|
+
italic: 'emphasized',
|
|
206
|
+
underline: 'underlined',
|
|
207
|
+
strikethrough: 'deleted',
|
|
208
|
+
subscript: 'subscripted',
|
|
209
|
+
superscript: 'superscripted',
|
|
210
|
+
highlight: 'emphasized',
|
|
211
|
+
code: 'code',
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Creates a rich text node with the ability to have
|
|
216
|
+
* nested inline text nodes.
|
|
217
|
+
*/
|
|
200
218
|
function getTextChild(n: TextNode): CrystallizeRichTextNode {
|
|
201
|
-
|
|
219
|
+
let currentChild: CrystallizeRichTextNode = {
|
|
220
|
+
kind: 'inline',
|
|
221
|
+
};
|
|
222
|
+
let textChild: CrystallizeRichTextNode | null = null;
|
|
223
|
+
|
|
224
|
+
function checkFormat(format: TextFormatType) {
|
|
225
|
+
const type = lexicalFormatToCrystallizeType[format];
|
|
226
|
+
if (n.hasFormat(format) && type) {
|
|
227
|
+
if (!textChild) {
|
|
228
|
+
currentChild = textChild = {
|
|
229
|
+
kind: 'inline',
|
|
230
|
+
type,
|
|
231
|
+
} as CrystallizeRichTextNode;
|
|
232
|
+
} else {
|
|
233
|
+
currentChild.children = [
|
|
234
|
+
{
|
|
235
|
+
kind: 'inline',
|
|
236
|
+
type,
|
|
237
|
+
},
|
|
238
|
+
];
|
|
202
239
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
240
|
+
currentChild = currentChild.children[0];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
(Object.keys(lexicalFormatToCrystallizeType) as TextFormatType[]).forEach(checkFormat);
|
|
246
|
+
|
|
247
|
+
// Safe guard for unknown lexical types
|
|
248
|
+
if (!textChild) {
|
|
249
|
+
currentChild = textChild = {
|
|
207
250
|
kind: 'inline',
|
|
208
|
-
textContent,
|
|
209
251
|
};
|
|
210
252
|
}
|
|
211
253
|
|
|
212
|
-
|
|
213
|
-
kind: 'inline',
|
|
214
|
-
textContent,
|
|
215
|
-
} as CrystallizeRichTextNode;
|
|
216
|
-
|
|
217
|
-
if (n.hasFormat('bold')) {
|
|
218
|
-
textChild.type = 'strong';
|
|
219
|
-
} else if (n.hasFormat('italic')) {
|
|
220
|
-
textChild.type = 'emphasized';
|
|
221
|
-
} else if (n.hasFormat('underline')) {
|
|
222
|
-
textChild.type = 'underlined';
|
|
223
|
-
} else if (n.hasFormat('strikethrough')) {
|
|
224
|
-
textChild.type = 'deleted';
|
|
225
|
-
} else if (n.hasFormat('subscript')) {
|
|
226
|
-
textChild.type = 'subscripted';
|
|
227
|
-
} else if (n.hasFormat('superscript')) {
|
|
228
|
-
textChild.type = 'superscripted';
|
|
229
|
-
}
|
|
254
|
+
currentChild.textContent = n.getTextContent();
|
|
230
255
|
|
|
231
256
|
return textChild;
|
|
232
257
|
}
|
|
@@ -421,3 +421,31 @@ export const Quote: Story = {
|
|
|
421
421
|
},
|
|
422
422
|
},
|
|
423
423
|
};
|
|
424
|
+
|
|
425
|
+
export const MultipleInlineFormats: Story = {
|
|
426
|
+
args: {
|
|
427
|
+
initialData: [
|
|
428
|
+
{
|
|
429
|
+
type: 'paragraph',
|
|
430
|
+
kind: 'block',
|
|
431
|
+
children: [
|
|
432
|
+
{
|
|
433
|
+
kind: 'inline',
|
|
434
|
+
type: 'strong',
|
|
435
|
+
children: [
|
|
436
|
+
{
|
|
437
|
+
kind: 'inline',
|
|
438
|
+
textContent: 'placed',
|
|
439
|
+
type: 'emphasized',
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
onChange(data) {
|
|
447
|
+
console.log('onChange');
|
|
448
|
+
console.log(JSON.stringify(data, null, 1));
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
};
|
|
@@ -189,11 +189,33 @@ const dataTypes: { name: string; model: CrystallizeRichText }[] = [
|
|
|
189
189
|
},
|
|
190
190
|
],
|
|
191
191
|
},
|
|
192
|
+
{
|
|
193
|
+
name: 'Multiple inline formats',
|
|
194
|
+
model: [
|
|
195
|
+
{
|
|
196
|
+
type: 'paragraph',
|
|
197
|
+
kind: 'block',
|
|
198
|
+
children: [
|
|
199
|
+
{
|
|
200
|
+
kind: 'inline',
|
|
201
|
+
type: 'strong',
|
|
202
|
+
children: [
|
|
203
|
+
{
|
|
204
|
+
kind: 'inline',
|
|
205
|
+
textContent: 'placed',
|
|
206
|
+
type: 'emphasized',
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
},
|
|
192
214
|
];
|
|
193
215
|
|
|
194
216
|
describe('RichTextEditor model conversions', () => {
|
|
195
217
|
dataTypes.forEach(dataType => {
|
|
196
|
-
it('correctly converts
|
|
218
|
+
it('correctly converts ' + dataType.name, async () => {
|
|
197
219
|
const onChange = vi.fn();
|
|
198
220
|
|
|
199
221
|
render(<RichTextEditor initialData={dataType.model} onChange={onChange} triggerOnChangeOnAutoFocus autoFocus />);
|
|
@@ -34,10 +34,8 @@ type CrystallizeRichTextGenericNode = {
|
|
|
34
34
|
| 'aside'
|
|
35
35
|
| 'container'
|
|
36
36
|
| 'details'
|
|
37
|
-
| 'emphasized'
|
|
38
37
|
| 'figcaption'
|
|
39
38
|
| 'figure'
|
|
40
|
-
| 'highlight'
|
|
41
39
|
| 'image'
|
|
42
40
|
| 'line-break'
|
|
43
41
|
| 'unordered-list'
|
|
@@ -51,9 +49,9 @@ type CrystallizeRichTextGenericNode = {
|
|
|
51
49
|
| 'title-of-a-work';
|
|
52
50
|
};
|
|
53
51
|
|
|
54
|
-
type CrystallizeRichTextInlineFormattedNodes = {
|
|
52
|
+
export type CrystallizeRichTextInlineFormattedNodes = {
|
|
55
53
|
kind: 'inline';
|
|
56
|
-
type: 'deleted' | 'strong' | 'subscripted' | 'superscripted' | 'underlined';
|
|
54
|
+
type: 'deleted' | 'strong' | 'subscripted' | 'superscripted' | 'underlined' | 'emphasized' | 'code' | 'highlight';
|
|
57
55
|
};
|
|
58
56
|
|
|
59
57
|
export type CrystallizeRichTextNode = CrystallizeRichTextNodeBase &
|