@churchapps/apphelper 0.3.16 → 0.3.17

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 (38) hide show
  1. package/dist/components/markdownEditor/Editor.d.ts +3 -1
  2. package/dist/components/markdownEditor/Editor.d.ts.map +1 -1
  3. package/dist/components/markdownEditor/Editor.js +25 -4
  4. package/dist/components/markdownEditor/Editor.js.map +1 -1
  5. package/dist/components/markdownEditor/MarkdownPreview.d.ts +3 -1
  6. package/dist/components/markdownEditor/MarkdownPreview.d.ts.map +1 -1
  7. package/dist/components/markdownEditor/MarkdownPreview.js +14 -2
  8. package/dist/components/markdownEditor/MarkdownPreview.js.map +1 -1
  9. package/dist/components/markdownEditor/MarkdownPreviewLight.d.ts.map +1 -1
  10. package/dist/components/markdownEditor/MarkdownPreviewLight.js +5 -1
  11. package/dist/components/markdownEditor/MarkdownPreviewLight.js.map +1 -1
  12. package/dist/components/markdownEditor/plugins/FloatingTextMenu/FloatingTextFormatToolbarPlugin.d.ts +13 -0
  13. package/dist/components/markdownEditor/plugins/FloatingTextMenu/FloatingTextFormatToolbarPlugin.d.ts.map +1 -0
  14. package/dist/components/markdownEditor/plugins/FloatingTextMenu/FloatingTextFormatToolbarPlugin.js +311 -0
  15. package/dist/components/markdownEditor/plugins/FloatingTextMenu/FloatingTextFormatToolbarPlugin.js.map +1 -0
  16. package/dist/components/markdownEditor/plugins/FloatingTextMenu/getDOMRangeRect.d.ts +2 -0
  17. package/dist/components/markdownEditor/plugins/FloatingTextMenu/getDOMRangeRect.d.ts.map +1 -0
  18. package/dist/components/markdownEditor/plugins/FloatingTextMenu/getDOMRangeRect.js +20 -0
  19. package/dist/components/markdownEditor/plugins/FloatingTextMenu/getDOMRangeRect.js.map +1 -0
  20. package/dist/components/markdownEditor/plugins/FloatingTextMenu/getSelectNode.d.ts +2 -0
  21. package/dist/components/markdownEditor/plugins/FloatingTextMenu/getSelectNode.d.ts.map +1 -0
  22. package/dist/components/markdownEditor/plugins/FloatingTextMenu/getSelectNode.js +22 -0
  23. package/dist/components/markdownEditor/plugins/FloatingTextMenu/getSelectNode.js.map +1 -0
  24. package/dist/components/markdownEditor/plugins/FloatingTextMenu/setFloatingElemPosition.d.ts +2 -0
  25. package/dist/components/markdownEditor/plugins/FloatingTextMenu/setFloatingElemPosition.d.ts.map +1 -0
  26. package/dist/components/markdownEditor/plugins/FloatingTextMenu/setFloatingElemPosition.js +30 -0
  27. package/dist/components/markdownEditor/plugins/FloatingTextMenu/setFloatingElemPosition.js.map +1 -0
  28. package/dist/components/markdownEditor/plugins/MarkdownTransformers.js +2 -2
  29. package/dist/components/markdownEditor/plugins/MarkdownTransformers.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/components/markdownEditor/Editor.tsx +33 -16
  32. package/src/components/markdownEditor/MarkdownPreview.tsx +5 -3
  33. package/src/components/markdownEditor/MarkdownPreviewLight.tsx +5 -1
  34. package/src/components/markdownEditor/plugins/FloatingTextMenu/FloatingTextFormatToolbarPlugin.tsx +445 -0
  35. package/src/components/markdownEditor/plugins/FloatingTextMenu/getDOMRangeRect.tsx +17 -0
  36. package/src/components/markdownEditor/plugins/FloatingTextMenu/getSelectNode.tsx +17 -0
  37. package/src/components/markdownEditor/plugins/FloatingTextMenu/setFloatingElemPosition.tsx +33 -0
  38. package/src/components/markdownEditor/plugins/MarkdownTransformers.ts +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getSelectNode.js","sourceRoot":"","sources":["../../../../../src/components/markdownEditor/plugins/FloatingTextMenu/getSelectNode.tsx"],"names":[],"mappings":";;;AAAA,kDAAkD;AAElD,SAAgB,eAAe,CAAC,SAAc;IAC5C,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAChC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;IAC9B,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;IAC9C,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAC5C,IAAI,UAAU,KAAK,SAAS,EAAE;QAC5B,OAAO,UAAU,CAAC;KACnB;IACD,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,EAAE,CAAC;IAC1C,IAAI,UAAU,EAAE;QACd,OAAO,IAAA,wBAAY,EAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;KACrD;SAAM;QACL,OAAO,IAAA,wBAAY,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;KACtD;AACH,CAAC;AAdD,0CAcC"}
@@ -0,0 +1,2 @@
1
+ export declare function setFloatingElemPosition(targetRect: any, floatingElem: any, anchorElem: any, verticalGap?: number, horizontalOffset?: number): void;
2
+ //# sourceMappingURL=setFloatingElemPosition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setFloatingElemPosition.d.ts","sourceRoot":"","sources":["../../../../../src/components/markdownEditor/plugins/FloatingTextMenu/setFloatingElemPosition.tsx"],"names":[],"mappings":"AAGA,wBAAgB,uBAAuB,CAAE,UAAU,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,SAAe,EAAE,gBAAgB,SAAoB,QA6B7J"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setFloatingElemPosition = void 0;
4
+ const VERTICAL_GAP = 10;
5
+ const HORIZONTAL_OFFSET = 5;
6
+ function setFloatingElemPosition(targetRect, floatingElem, anchorElem, verticalGap = VERTICAL_GAP, horizontalOffset = HORIZONTAL_OFFSET) {
7
+ const scrollerElem = anchorElem.parentElement;
8
+ if (targetRect === null || !scrollerElem) {
9
+ floatingElem.style.opacity = "0";
10
+ floatingElem.style.transform = "translate(-10000px, -10000px)";
11
+ return;
12
+ }
13
+ const floatingElemRect = floatingElem.getBoundingClientRect();
14
+ const anchorElementRect = anchorElem.getBoundingClientRect();
15
+ const editorScrollerRect = scrollerElem.getBoundingClientRect();
16
+ let top = targetRect.top - floatingElemRect.height - verticalGap;
17
+ let left = targetRect.left - horizontalOffset;
18
+ if (top < editorScrollerRect.top) {
19
+ top += floatingElemRect.height + targetRect.height + verticalGap * 2;
20
+ }
21
+ if (left + floatingElemRect.width > editorScrollerRect.right) {
22
+ left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset;
23
+ }
24
+ top -= anchorElementRect.top;
25
+ left -= anchorElementRect.left;
26
+ floatingElem.style.opacity = "1";
27
+ floatingElem.style.transform = `translate(${left}px, ${top}px)`;
28
+ }
29
+ exports.setFloatingElemPosition = setFloatingElemPosition;
30
+ //# sourceMappingURL=setFloatingElemPosition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setFloatingElemPosition.js","sourceRoot":"","sources":["../../../../../src/components/markdownEditor/plugins/FloatingTextMenu/setFloatingElemPosition.tsx"],"names":[],"mappings":";;;AAAA,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B,SAAgB,uBAAuB,CAAE,UAAe,EAAE,YAAiB,EAAE,UAAe,EAAE,WAAW,GAAG,YAAY,EAAE,gBAAgB,GAAG,iBAAiB;IAC5J,MAAM,YAAY,GAAG,UAAU,CAAC,aAAa,CAAC;IAE9C,IAAI,UAAU,KAAK,IAAI,IAAI,CAAC,YAAY,EAAE;QACxC,YAAY,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC;QACjC,YAAY,CAAC,KAAK,CAAC,SAAS,GAAG,+BAA+B,CAAC;QAC/D,OAAO;KACR;IAED,MAAM,gBAAgB,GAAG,YAAY,CAAC,qBAAqB,EAAE,CAAC;IAC9D,MAAM,iBAAiB,GAAG,UAAU,CAAC,qBAAqB,EAAE,CAAC;IAC7D,MAAM,kBAAkB,GAAG,YAAY,CAAC,qBAAqB,EAAE,CAAC;IAEhE,IAAI,GAAG,GAAG,UAAU,CAAC,GAAG,GAAG,gBAAgB,CAAC,MAAM,GAAG,WAAW,CAAC;IACjE,IAAI,IAAI,GAAG,UAAU,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAE9C,IAAI,GAAG,GAAG,kBAAkB,CAAC,GAAG,EAAE;QAChC,GAAG,IAAI,gBAAgB,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,GAAG,WAAW,GAAG,CAAC,CAAC;KACtE;IAED,IAAI,IAAI,GAAG,gBAAgB,CAAC,KAAK,GAAG,kBAAkB,CAAC,KAAK,EAAE;QAC5D,IAAI,GAAG,kBAAkB,CAAC,KAAK,GAAG,gBAAgB,CAAC,KAAK,GAAG,gBAAgB,CAAC;KAC7E;IAED,GAAG,IAAI,iBAAiB,CAAC,GAAG,CAAC;IAC7B,IAAI,IAAI,iBAAiB,CAAC,IAAI,CAAC;IAE/B,YAAY,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC;IACjC,YAAY,CAAC,KAAK,CAAC,SAAS,GAAG,aAAa,IAAI,OAAO,GAAG,KAAK,CAAC;AAClE,CAAC;AA7BD,0DA6BC"}
@@ -42,7 +42,7 @@ export type TextMatchTransformer = Readonly<{
42
42
  exports.UNDERLINE = {
43
43
  format: ["underline"],
44
44
  intraword: false,
45
- tag: "_",
45
+ tag: "__",
46
46
  type: "text-format"
47
47
  };
48
48
  /*
@@ -75,11 +75,11 @@ export const UNDERLINE: TextMatchTransformer = {
75
75
  */
76
76
  const modifiedTextTransformers = [EmojiNodeTransform_1.EMOJI_NODE_MARKDOWN_TRANSFORM];
77
77
  exports.PLAYGROUND_TRANSFORMERS = [
78
- exports.UNDERLINE,
79
78
  ...modifiedTextTransformers,
80
79
  exports.HR,
81
80
  ...markdown_1.ELEMENT_TRANSFORMERS,
82
81
  CustomLinkNodeTransformer_1.CUSTOM_LINK_NODE_TRANSFORMER,
83
82
  ...markdown_1.TRANSFORMERS.splice(0, 13),
83
+ exports.UNDERLINE,
84
84
  ];
85
85
  //# sourceMappingURL=MarkdownTransformers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownTransformers.js","sourceRoot":"","sources":["../../../../src/components/markdownEditor/plugins/MarkdownTransformers.ts"],"names":[],"mappings":";;;AAGA,gDAI2B;AAC3B,wFAIkD;AAClD,sFAAsF;AACtF,mEAA2E;AAG9D,QAAA,EAAE,GAAuB;IACpC,YAAY,EAAE,CAAC,8CAAkB,CAAC;IAClC,MAAM,EAAE,CAAC,IAAiB,EAAE,EAAE,CAAC,CAAC,IAAA,iDAAqB,EAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3E,MAAM,EAAE,uBAAuB;IAC/B,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE;QACxC,MAAM,IAAI,GAAG,IAAA,qDAAyB,GAAE,CAAC;QAEzC,iCAAiC;QACjC,IAAI,QAAQ,IAAI,UAAU,CAAC,cAAc,EAAE,IAAI,IAAI,EAAE;YACnD,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;SAC1B;aAAM;YACL,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;SAC/B;QAED,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IACD,IAAI,EAAE,SAAS;CAChB,CAAC;AAQF;;;;;;;;;;;;;;;;EAgBE;AAEW,QAAA,SAAS,GAA0B;IAC9C,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,SAAS,EAAE,KAAK;IAChB,GAAG,EAAE,GAAG;IACR,IAAI,EAAE,aAAa;CACpB,CAAC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BE;AAEF,MAAM,wBAAwB,GAAG,CAAC,kDAA6B,CAAC,CAAC;AAEpD,QAAA,uBAAuB,GAAuB;IACzD,iBAAS;IACT,GAAG,wBAAwB;IAC3B,UAAE;IACF,GAAG,+BAAoB;IAEvB,wDAA4B;IAC5B,GAAG,uBAAY,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC;CAC9B,CAAC"}
1
+ {"version":3,"file":"MarkdownTransformers.js","sourceRoot":"","sources":["../../../../src/components/markdownEditor/plugins/MarkdownTransformers.ts"],"names":[],"mappings":";;;AAGA,gDAI2B;AAC3B,wFAIkD;AAClD,sFAAsF;AACtF,mEAA2E;AAG9D,QAAA,EAAE,GAAuB;IACpC,YAAY,EAAE,CAAC,8CAAkB,CAAC;IAClC,MAAM,EAAE,CAAC,IAAiB,EAAE,EAAE,CAAC,CAAC,IAAA,iDAAqB,EAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3E,MAAM,EAAE,uBAAuB;IAC/B,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE;QACxC,MAAM,IAAI,GAAG,IAAA,qDAAyB,GAAE,CAAC;QAEzC,iCAAiC;QACjC,IAAI,QAAQ,IAAI,UAAU,CAAC,cAAc,EAAE,IAAI,IAAI,EAAE;YACnD,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;SAC1B;aAAM;YACL,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;SAC/B;QAED,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IACD,IAAI,EAAE,SAAS;CAChB,CAAC;AAQF;;;;;;;;;;;;;;;;EAgBE;AAEW,QAAA,SAAS,GAA0B;IAC9C,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,SAAS,EAAE,KAAK;IAChB,GAAG,EAAE,IAAI;IACT,IAAI,EAAE,aAAa;CACpB,CAAC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BE;AAEF,MAAM,wBAAwB,GAAG,CAAC,kDAA6B,CAAC,CAAC;AAEpD,QAAA,uBAAuB,GAAuB;IACzD,GAAG,wBAAwB;IAC3B,UAAE;IACF,GAAG,+BAAoB;IAEvB,wDAA4B;IAC5B,GAAG,uBAAY,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC;IAC7B,iBAAS;CACV,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@churchapps/apphelper",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "description": "Library of helper functions for React and NextJS ChurchApps",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,6 +19,7 @@ import { AutoLinkNode, LinkNode } from "@lexical/link";
19
19
  import { CodeHighlightNode, CodeNode } from "@lexical/code";
20
20
  import { theme } from "./theme";
21
21
  import { ToolbarPlugin, CustomAutoLinkPlugin, ListMaxIndentLevelPlugin, PLAYGROUND_TRANSFORMERS, ReadOnlyPlugin, ControlledEditorPlugin } from "./plugins";
22
+ import FloatingTextFormatToolbarPlugin from "./plugins/FloatingTextMenu/FloatingTextFormatToolbarPlugin";
22
23
  import { MarkdownModal } from "./MarkdownModal";
23
24
  import CustomLinkNodePlugin from "./plugins/customLink/CustomLinkNodePlugin";
24
25
  import { CustomLinkNode } from "./plugins/customLink/CustomLinkNode";
@@ -33,9 +34,11 @@ interface Props {
33
34
  style?: any;
34
35
  textAlign?: "left" | "center" | "right";
35
36
  placeholder?: string;
37
+ element?: any;
38
+ showFloatingEditor?: boolean;
36
39
  }
37
40
 
38
- function Editor({ value, onChange = () => { }, mode = "interactive", textAlign = "left", style, placeholder = "Enter some text..." }: Props) {
41
+ function Editor({ value, onChange = () => {}, mode = "interactive", textAlign = "left", style, placeholder = "Enter some text...", showFloatingEditor = false, ...props }: Props) {
39
42
  const [fullScreen, setFullScreen] = React.useState(false);
40
43
 
41
44
  const handleChange = (editorState: any) => {
@@ -49,7 +52,7 @@ function Editor({ value, onChange = () => { }, mode = "interactive", textAlign =
49
52
  console.error(error);
50
53
  };
51
54
 
52
- const handleModalOnChange = (value : string) => {
55
+ const handleModalOnChange = (value: string) => {
53
56
  onChange(value);
54
57
  };
55
58
 
@@ -81,38 +84,52 @@ function Editor({ value, onChange = () => { }, mode = "interactive", textAlign =
81
84
  with: (node: LinkNode) => (
82
85
  new CustomLinkNode(node.getURL(), node.getTarget(), [])
83
86
  )
84
- }
85
- ]
87
+ },
88
+ ],
89
+ markdown: { transformers: PLAYGROUND_TRANSFORMERS }
86
90
  };
87
91
 
88
- let textAlignClass = ""
92
+ let textAlignClass = "";
89
93
  switch (textAlign) {
90
94
  case "center":
91
- textAlignClass = "text-center"
95
+ textAlignClass = "text-center";
92
96
  break;
93
97
  case "right":
94
- textAlignClass = "text-right"
98
+ textAlignClass = "text-right";
95
99
  break;
96
100
  case "left":
97
101
  default:
98
- textAlignClass = "text-left"
102
+ textAlignClass = "text-left";
99
103
  break;
100
104
  }
101
105
 
102
106
  return (
103
107
  <>
104
108
  <LexicalComposer initialConfig={initialConfig}>
105
- <div className={(mode === "preview") ? `editor-container preview ${textAlignClass}` : `editor-container ${textAlignClass}`} style={Object.assign({ border: mode === "preview" ? "none" : "1px solid lightgray" }, style)}>
106
- {mode !== "preview" && <ToolbarPlugin goFullScreen={() => { setFullScreen(true) }} />}
109
+ <div
110
+ className={mode === "preview" ? `editor-container preview ${textAlignClass}` : `editor-container ${textAlignClass}`}
111
+ style={Object.assign({ border: mode === "preview" ? "none" : "1px solid lightgray" }, style)}
112
+ >
113
+ {mode !== "preview" && (<ToolbarPlugin goFullScreen={() => { setFullScreen(true); }} />)}
107
114
  <div className="editor-inner">
108
- {!fullScreen && <RichTextPlugin
109
- contentEditable={<ContentEditable className="editor-input" style={{ minHeight: mode === "preview" ? "auto" : "150px" }} />}
110
- placeholder={mode !== "preview" ? <div className="editor-placeholder">{placeholder}</div> : null}
111
- ErrorBoundary={LexicalErrorBoundary}
112
- /> }
115
+ {!fullScreen && (
116
+ <RichTextPlugin
117
+ contentEditable={
118
+ <ContentEditable
119
+ className="editor-input"
120
+ style={{ minHeight: mode === "preview" ? "auto" : "150px" }}
121
+ //@ts-ignore
122
+ ref={(node) => { if (node) { const editor = node.closest("[data-lexical-editor]") as HTMLElement; if (editor) { editor.dataset.element = JSON.stringify(props.element) } } }} //Store element in dataset
123
+ />
124
+ }
125
+ placeholder={mode !== "preview" ? (<div className="editor-placeholder">{placeholder}</div>) : null}
126
+ ErrorBoundary={LexicalErrorBoundary}
127
+ />
128
+ )}
113
129
  <CustomLinkNodePlugin />
114
130
  {mode !== "preview" && <EmojiPickerPlugin />}
115
131
  <EmojisPlugin />
132
+ {showFloatingEditor && <FloatingTextFormatToolbarPlugin />}
116
133
  <OnChangePlugin onChange={handleChange} />
117
134
  {mode !== "preview" && <AutoFocusPlugin />}
118
135
  <HistoryPlugin />
@@ -126,7 +143,7 @@ function Editor({ value, onChange = () => { }, mode = "interactive", textAlign =
126
143
  </div>
127
144
  </div>
128
145
  </LexicalComposer>
129
- {fullScreen && <MarkdownModal onChange={handleModalOnChange} value={value} hideModal={handleCloseFullScreen} />}
146
+ {fullScreen && (<MarkdownModal onChange={handleModalOnChange} value={value} hideModal={handleCloseFullScreen} />)}
130
147
  </>
131
148
  );
132
149
  }
@@ -6,11 +6,13 @@ const Editor = lazy(() => import('./Editor'));
6
6
 
7
7
  interface Props {
8
8
  value: string;
9
- textAlign?: "left" | "center" | "right"
9
+ textAlign?: "left" | "center" | "right",
10
+ element?: any,
11
+ showFloatingEditor?: boolean
10
12
  }
11
13
 
12
- export function MarkdownPreview({ value: markdownString = "", textAlign }: Props) {
14
+ export function MarkdownPreview({ value: markdownString = "", textAlign, showFloatingEditor = false, ...props }: Props) {
13
15
  return <Suspense fallback={<div>{markdownString || ""}</div>}>
14
- <Editor mode="preview" value={markdownString || ""} textAlign={textAlign} />
16
+ <Editor mode="preview" value={markdownString || ""} textAlign={textAlign} element={props.element} showFloatingEditor={showFloatingEditor} />
15
17
  </Suspense>
16
18
  }
@@ -42,7 +42,11 @@ export function MarkdownPreviewLight({ value: markdownString = "", textAlign }:
42
42
  if (markdownString === null || markdownString === undefined || !markdownString) return <></>;
43
43
  else {
44
44
  const convertedText = getSpecialLinks(markdownString || "");
45
- const html = marked.parse(convertedText || "")
45
+ let processedMarkdown = convertedText;
46
+ processedMarkdown = convertedText.replace(/__(.*?)__/g, "<u>$1</u>"); //Replace `__underlined__` with `<u>underlined</u>`
47
+ processedMarkdown = processedMarkdown.replace(/```([\s\S]+?)```/g, "<pre class='code-block'><code>$1</code></pre>"); //Convert block code ```code``` to <pre><code>code</code></pre>
48
+ processedMarkdown = processedMarkdown.replace(/`([^`]+)`/g, "<code>$1</code>"); //Convert inline code `code` to <code>code</code>
49
+ const html = marked.parse(processedMarkdown || "")
46
50
  const style = (textAlign) ? {textAlign} : {}
47
51
  return <div style={style} dangerouslySetInnerHTML={{__html: html as string}}></div>
48
52
  }
@@ -0,0 +1,445 @@
1
+ import { $isCodeHighlightNode } from "@lexical/code";
2
+ import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
3
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
4
+ import { mergeRegister } from "@lexical/utils";
5
+ import { $convertToMarkdownString } from "@lexical/markdown";
6
+ import { $createParagraphNode, $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_LOW, FORMAT_TEXT_COMMAND, SELECTION_CHANGE_COMMAND } from "lexical";
7
+ import { $createHeadingNode, $isHeadingNode } from "@lexical/rich-text";
8
+ import { $wrapNodes } from "@lexical/selection";
9
+ import React, { useCallback, useEffect, useRef, useState } from "react";
10
+ import { createPortal } from "react-dom";
11
+ import { Box, styled, IconButton, Icon, Select, MenuItem } from "@mui/material";
12
+
13
+ import { getDOMRangeRect } from "./getDOMRangeRect";
14
+ import { getSelectedNode } from "./getSelectNode";
15
+ import { setFloatingElemPosition } from "./setFloatingElemPosition";
16
+ import { PLAYGROUND_TRANSFORMERS } from "../MarkdownTransformers";
17
+ import { ApiHelper } from "../../../../helpers";
18
+
19
+ export const FloatingDivContainer = styled(Box)({
20
+ display: "flex",
21
+ background: "#fff",
22
+ padding: 4,
23
+ verticalAlign: "middle",
24
+ position: "absolute",
25
+ top: 0,
26
+ left: 0,
27
+ zIndex: 1400,
28
+ opacity: 0,
29
+ backgroundColor: "#fff",
30
+ boxShadow: "0px 5px 10px rgba(0, 0, 0, 0.3)",
31
+ borderRadius: 8,
32
+ transition: "opacity 0.5s",
33
+ height: 35,
34
+ willChange: "transform",
35
+ });
36
+
37
+ function TextFormatFloatingToolbar({ editor, anchorElem, isLink, isBold, isItalic, isUnderline, isCode, isStrikethrough, isSubscript, isSuperscript, blockType, setBlockType }: any) {
38
+ const popupCharStylesEditorRef = useRef(null);
39
+
40
+ const applyFormatting = (command: string) => {
41
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, command);
42
+ saveChanges(editor);
43
+ }
44
+
45
+ const insertLink = useCallback(() => {
46
+ if (!isLink) {
47
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, "https://");
48
+ } else {
49
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
50
+ }
51
+ }, [editor, isLink]);
52
+
53
+ function mouseMoveListener(e: any) {
54
+ if (
55
+ popupCharStylesEditorRef?.current &&
56
+ (e.buttons === 1 || e.buttons === 3)
57
+ ) {
58
+ popupCharStylesEditorRef.current.style.pointerEvents = "none";
59
+ }
60
+ }
61
+ function mouseUpListener(e: any) {
62
+ if (popupCharStylesEditorRef?.current) {
63
+ popupCharStylesEditorRef.current.style.pointerEvents = "auto";
64
+ }
65
+ }
66
+
67
+ useEffect(() => {
68
+ if (popupCharStylesEditorRef?.current) {
69
+ document.addEventListener("mousemove", mouseMoveListener);
70
+ document.addEventListener("mouseup", mouseUpListener);
71
+
72
+ return () => {
73
+ document.removeEventListener("mousemove", mouseMoveListener);
74
+ document.removeEventListener("mouseup", mouseUpListener);
75
+ };
76
+ }
77
+ }, [popupCharStylesEditorRef]);
78
+
79
+ const updateTextFormatFloatingToolbar = useCallback(() => {
80
+ const selection = $getSelection();
81
+
82
+ const popupCharStylesEditorElem = popupCharStylesEditorRef.current;
83
+ const nativeSelection = window.getSelection();
84
+
85
+ if (popupCharStylesEditorElem === null) {
86
+ return;
87
+ }
88
+
89
+ const rootElement = editor.getRootElement();
90
+ if (
91
+ selection !== null &&
92
+ nativeSelection !== null &&
93
+ !nativeSelection.isCollapsed &&
94
+ rootElement !== null &&
95
+ rootElement.contains(nativeSelection.anchorNode)
96
+ ) {
97
+ const rangeRect = getDOMRangeRect(nativeSelection, rootElement);
98
+
99
+ setFloatingElemPosition(rangeRect, popupCharStylesEditorElem, anchorElem);
100
+ }
101
+ }, [editor, anchorElem]);
102
+
103
+ useEffect(() => {
104
+ const scrollerElem = anchorElem.parentElement;
105
+
106
+ const update = () => {
107
+ editor.getEditorState().read(() => {
108
+ updateTextFormatFloatingToolbar();
109
+ });
110
+ };
111
+
112
+ window.addEventListener("resize", update);
113
+ if (scrollerElem) {
114
+ scrollerElem.addEventListener("scroll", update);
115
+ }
116
+
117
+ return () => {
118
+ window.removeEventListener("resize", update);
119
+ if (scrollerElem) {
120
+ scrollerElem.removeEventListener("scroll", update);
121
+ }
122
+ };
123
+ }, [editor, updateTextFormatFloatingToolbar, anchorElem]);
124
+
125
+ useEffect(() => {
126
+ editor.getEditorState().read(() => {
127
+ updateTextFormatFloatingToolbar();
128
+ });
129
+ return mergeRegister(
130
+ editor.registerUpdateListener(({ editorState }: any) => {
131
+ editorState.read(() => {
132
+ updateTextFormatFloatingToolbar();
133
+ });
134
+ }),
135
+
136
+ editor.registerCommand(
137
+ SELECTION_CHANGE_COMMAND,
138
+ () => {
139
+ updateTextFormatFloatingToolbar();
140
+ return false;
141
+ },
142
+ COMMAND_PRIORITY_LOW
143
+ )
144
+ );
145
+ }, [editor, updateTextFormatFloatingToolbar]);
146
+
147
+ const formatBlock = (type: string) => {
148
+ editor.update(() => {
149
+ const selection = $getSelection();
150
+ if ($isRangeSelection(selection)) {
151
+ $wrapNodes(selection, () =>
152
+ //@ts-ignore
153
+ type === "paragraph" ? $createParagraphNode() : $createHeadingNode(type)
154
+ );
155
+ }
156
+ });
157
+ setBlockType(type);
158
+ saveChanges(editor)
159
+ };
160
+
161
+ return (
162
+ <FloatingDivContainer ref={popupCharStylesEditorRef}>
163
+ <>
164
+ <Select
165
+ value={blockType}
166
+ onChange={(e) => formatBlock(e.target.value)}
167
+ sx={{
168
+ minWidth: 120,
169
+ backgroundColor: "#fff",
170
+ borderRadius: 2,
171
+ marginRight: 0.3,
172
+ }}
173
+ >
174
+ <MenuItem value="paragraph">Normal</MenuItem>
175
+ <MenuItem value="h1">Heading 1</MenuItem>
176
+ <MenuItem value="h2">Heading 2</MenuItem>
177
+ <MenuItem value="h3">Heading 3</MenuItem>
178
+ <MenuItem value="h4">Heading 4</MenuItem>
179
+ </Select>
180
+ <IconButton
181
+ onClick={() => {
182
+ applyFormatting("bold");
183
+ }}
184
+ sx={{ backgroundColor: isBold ? "#e0e0e0" : undefined, borderRadius: 2, marginRight: 0.3 }}
185
+ >
186
+ <Icon>format_bold_outline</Icon>
187
+ </IconButton>
188
+
189
+ <IconButton
190
+ onClick={() => {
191
+ applyFormatting("italic");
192
+ }}
193
+ sx={{ backgroundColor: isItalic ? "#e0e0e0" : undefined, borderRadius: 2, marginRight: 0.3 }}
194
+ >
195
+ <Icon>format_italic_outline</Icon>
196
+ </IconButton>
197
+
198
+ <IconButton
199
+ onClick={() => {
200
+ applyFormatting("underline");
201
+ }}
202
+ sx={{ backgroundColor: isUnderline ? "#e0e0e0" : undefined, borderRadius: 2, marginRight: 0.3 }}
203
+ >
204
+ <Icon>format_underlined_outline</Icon>
205
+ </IconButton>
206
+
207
+ {/* <IconButton
208
+ onClick={() => {
209
+ applyFormatting("strikethrough");
210
+ }}
211
+ sx={{ backgroundColor: isStrikethrough ? "#e0e0e0" : undefined, borderRadius: 2, marginRight: 0.3 }}
212
+ >
213
+ <Icon>strikethrough_s_outline</Icon>
214
+ </IconButton> */}
215
+
216
+ <IconButton
217
+ onClick={() => {
218
+ applyFormatting("code");
219
+ }}
220
+ sx={{ backgroundColor: isCode ? "#e0e0e0" : undefined, borderRadius: 2, marginRight: 0.3 }}
221
+ >
222
+ <Icon>code</Icon>
223
+ </IconButton>
224
+
225
+ {/* <IconButton
226
+ onClick={insertLink}
227
+ sx={{ backgroundColor: isLink ? "#e0e0e0" : undefined, borderRadius: 2 }}
228
+ >
229
+ <Icon>insert_link_outline</Icon>
230
+ </IconButton> */}
231
+ </>
232
+ </FloatingDivContainer>
233
+ );
234
+ }
235
+
236
+ let lastSavedText = ""; // Track last saved text
237
+ let lastFormattingState = {}; // Track last formatting state
238
+
239
+ //@ts-ignore
240
+ const getFormattingState = (selection) => {
241
+ const node = getSelectedNode(selection);
242
+ let blockType = "paragraph";
243
+ if ($isHeadingNode(node)) {
244
+ blockType = node.getTag(); // "h1", "h2", etc.
245
+ }
246
+
247
+ return {
248
+ isBold: selection.hasFormat("bold"),
249
+ isItalic: selection.hasFormat("italic"),
250
+ isUnderline: selection.hasFormat("underline"),
251
+ // isStrikethrough: selection.hasFormat("strikethrough"),
252
+ isCode: selection.hasFormat("code"),
253
+ blockType
254
+ }
255
+ }
256
+
257
+ const saveChanges = (editor: any) => {
258
+ editor.update(() => {
259
+ const selection = $getSelection();
260
+ if ($isRangeSelection(selection)) {
261
+ const text = selection.getTextContent().trim();
262
+ const newFormattingState = getFormattingState(selection); // Get current formatting
263
+
264
+ // Get the parent block node (ensuring it's not just a text node)
265
+ const node = getSelectedNode(selection);
266
+ const parentNode = node.getParent(); // Get the parent block-level node
267
+ let blockType = "paragraph";
268
+
269
+ if ($isHeadingNode(parentNode)) {
270
+ blockType = parentNode.getTag(); // Get heading type
271
+ } else if ($isHeadingNode(node)) {
272
+ blockType = node.getTag();
273
+ }
274
+
275
+ //@ts-ignore
276
+ if (JSON.stringify(newFormattingState) !== JSON.stringify(lastFormattingState) || blockType !== lastFormattingState.blockType) {
277
+ lastSavedText = text;
278
+ lastFormattingState = { ...newFormattingState, blockType };
279
+
280
+ const editorNode = editor.getRootElement();
281
+ const elementJSON = editorNode?.dataset?.element;
282
+ if (elementJSON) {
283
+ const markdown = $convertToMarkdownString(PLAYGROUND_TRANSFORMERS)
284
+ const element: any = JSON.parse(elementJSON);
285
+
286
+ element.answers.text = markdown;
287
+ element.answersJSON = JSON.stringify(element.answers);
288
+
289
+ ApiHelper.post("/elements", [element], "ContentApi");
290
+ }
291
+ }
292
+ }
293
+ })
294
+ }
295
+
296
+ const updateFormattingState = (editor: any) => {
297
+ editor.update(() => {
298
+ const selection = $getSelection();
299
+ if ($isRangeSelection(selection)) {
300
+ lastFormattingState = getFormattingState(selection);
301
+ }
302
+ })
303
+ }
304
+
305
+ function useFloatingTextFormatToolbar(editor: any, anchorElem: any) {
306
+ const [isText, setIsText] = useState(false);
307
+ const [isLink, setIsLink] = useState(false);
308
+ const [isBold, setIsBold] = useState(false);
309
+ const [isItalic, setIsItalic] = useState(false);
310
+ const [isUnderline, setIsUnderline] = useState(false);
311
+ const [isStrikethrough, setIsStrikethrough] = useState(false);
312
+ const [isSubscript, setIsSubscript] = useState(false);
313
+ const [isSuperscript, setIsSuperscript] = useState(false);
314
+ const [isCode, setIsCode] = useState(false);
315
+ const [blockType, setBlockType] = useState("paragraph");
316
+
317
+ const updatePopup = useCallback(() => {
318
+ editor.getEditorState().read(() => {
319
+ // Should not to pop up the floating toolbar when using IME input
320
+ if (editor.isComposing()) {
321
+ return;
322
+ }
323
+ const selection = $getSelection();
324
+ const nativeSelection = window.getSelection();
325
+ const rootElement = editor.getRootElement();
326
+
327
+ if (
328
+ nativeSelection !== null &&
329
+ (!$isRangeSelection(selection) ||
330
+ rootElement === null ||
331
+ !rootElement.contains(nativeSelection.anchorNode))
332
+ ) {
333
+ setIsText(false);
334
+ return;
335
+ }
336
+
337
+ if (!$isRangeSelection(selection)) {
338
+ return;
339
+ }
340
+
341
+ const node = getSelectedNode(selection);
342
+
343
+ // Update text format
344
+ setIsBold(selection.hasFormat("bold"));
345
+ setIsItalic(selection.hasFormat("italic"));
346
+ setIsUnderline(selection.hasFormat("underline"));
347
+ setIsStrikethrough(selection.hasFormat("strikethrough"));
348
+ setIsSubscript(selection.hasFormat("subscript"));
349
+ setIsSuperscript(selection.hasFormat("superscript"));
350
+ setIsCode(selection.hasFormat("code"));
351
+
352
+ // Update links
353
+ const parent = node.getParent();
354
+ if ($isLinkNode(parent) || $isLinkNode(node)) {
355
+ setIsLink(true);
356
+ } else {
357
+ setIsLink(false);
358
+ }
359
+
360
+ if (
361
+ !$isCodeHighlightNode(selection.anchor.getNode()) &&
362
+ selection.getTextContent() !== ""
363
+ ) {
364
+ setIsText($isTextNode(node));
365
+ } else {
366
+ setIsText(false);
367
+ }
368
+
369
+ const rawTextContent = selection.getTextContent().replace(/\n/g, "");
370
+ if (!selection.isCollapsed() && rawTextContent === "") {
371
+ setIsText(false);
372
+ return;
373
+ }
374
+
375
+ if ($isRangeSelection(selection)) {
376
+ const text = selection.getTextContent().trim();
377
+ if (text && JSON.stringify(lastFormattingState === "{}")) {
378
+ updateFormattingState(editor);
379
+ }
380
+ }
381
+
382
+ let type = "paragraph";
383
+ if ($isHeadingNode(parent)) {
384
+ type = parent.getTag();
385
+ } else if ($isHeadingNode(node)) {
386
+ type = node.getTag();
387
+ }
388
+ setBlockType(type);
389
+ });
390
+
391
+ }, [editor]);
392
+
393
+ useEffect(() => {
394
+ document.addEventListener("selectionchange", updatePopup);
395
+ return () => {
396
+ document.removeEventListener("selectionchange", updatePopup);
397
+ };
398
+ }, [updatePopup]);
399
+
400
+ useEffect(() => {
401
+ return mergeRegister(
402
+ editor.registerUpdateListener(() => {
403
+ updatePopup();
404
+ }),
405
+ editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
406
+ updatePopup();
407
+ return false;
408
+ },COMMAND_PRIORITY_LOW),
409
+ editor.registerRootListener(() => {
410
+ if (editor.getRootElement() === null) {
411
+ setIsText(false);
412
+ }
413
+ })
414
+ );
415
+ }, [editor, updatePopup]);
416
+
417
+ if (!isText || isLink) {
418
+ return null;
419
+ }
420
+
421
+ return createPortal(
422
+ <TextFormatFloatingToolbar
423
+ editor={editor}
424
+ anchorElem={anchorElem}
425
+ isLink={isLink}
426
+ isBold={isBold}
427
+ isItalic={isItalic}
428
+ isStrikethrough={isStrikethrough}
429
+ isSubscript={isSubscript}
430
+ isSuperscript={isSuperscript}
431
+ isUnderline={isUnderline}
432
+ isCode={isCode}
433
+ blockType={blockType}
434
+ setBlockType={setBlockType}
435
+ />,
436
+ anchorElem
437
+ );
438
+ }
439
+
440
+ export default function FloatingTextFormatToolbarPlugin({
441
+ anchorElem = document.body,
442
+ }) {
443
+ const [editor] = useLexicalComposerContext();
444
+ return useFloatingTextFormatToolbar(editor, anchorElem);
445
+ }