@squiz/formatted-text-editor 2.3.0 → 2.5.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 (202) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/demo/{App.tsx → diff/App.tsx} +3 -2
  3. package/demo/{AppContext.tsx → diff/AppContext.tsx} +1 -2
  4. package/demo/diff/index.html +14 -0
  5. package/demo/{main.tsx → diff/main.tsx} +1 -1
  6. package/demo/index.html +47 -2
  7. package/demo/portals/Accordion.tsx +50 -0
  8. package/demo/portals/App.tsx +150 -0
  9. package/demo/portals/index.html +13 -0
  10. package/demo/portals/index.scss +8 -0
  11. package/demo/portals/index.tsx +12 -0
  12. package/demo/portals/preview.html +91 -0
  13. package/demo/portals/preview.tsx +10 -0
  14. package/lib/Editor/Editor.d.ts +21 -0
  15. package/lib/Editor/Editor.js +76 -0
  16. package/lib/Editor/EditorContext.d.ts +10 -0
  17. package/lib/Editor/EditorContext.js +16 -0
  18. package/lib/EditorToolbar/FloatingToolbar.d.ts +2 -0
  19. package/lib/EditorToolbar/FloatingToolbar.js +76 -0
  20. package/lib/EditorToolbar/Toolbar.d.ts +8 -0
  21. package/lib/EditorToolbar/Toolbar.js +51 -0
  22. package/lib/EditorToolbar/Tools/Bold/BoldButton.d.ts +3 -0
  23. package/lib/EditorToolbar/Tools/Bold/BoldButton.js +23 -0
  24. package/lib/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.d.ts +3 -0
  25. package/lib/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.js +57 -0
  26. package/lib/EditorToolbar/Tools/ContentTools/ContentToolsDropdown.d.ts +3 -0
  27. package/lib/EditorToolbar/Tools/ContentTools/ContentToolsDropdown.js +104 -0
  28. package/lib/EditorToolbar/Tools/HorizontalLine/HorizontalLineButton.d.ts +3 -0
  29. package/lib/EditorToolbar/Tools/HorizontalLine/HorizontalLineButton.js +25 -0
  30. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.d.ts +19 -0
  31. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +183 -0
  32. package/lib/EditorToolbar/Tools/Image/ImageButton.d.ts +6 -0
  33. package/lib/EditorToolbar/Tools/Image/ImageButton.js +79 -0
  34. package/lib/EditorToolbar/Tools/Image/ImageModal.d.ts +9 -0
  35. package/lib/EditorToolbar/Tools/Image/ImageModal.js +26 -0
  36. package/lib/EditorToolbar/Tools/Italic/ItalicButton.d.ts +3 -0
  37. package/lib/EditorToolbar/Tools/Italic/ItalicButton.js +23 -0
  38. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +19 -0
  39. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +63 -0
  40. package/lib/EditorToolbar/Tools/Link/LinkButton.d.ts +6 -0
  41. package/lib/EditorToolbar/Tools/Link/LinkButton.js +71 -0
  42. package/lib/EditorToolbar/Tools/Link/LinkModal.d.ts +9 -0
  43. package/lib/EditorToolbar/Tools/Link/LinkModal.js +27 -0
  44. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.d.ts +4 -0
  45. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +54 -0
  46. package/lib/EditorToolbar/Tools/Lists/ListButtons.d.ts +3 -0
  47. package/lib/EditorToolbar/Tools/Lists/ListButtons.js +14 -0
  48. package/lib/EditorToolbar/Tools/Lists/OrderedList/OrderedListButton.d.ts +3 -0
  49. package/lib/EditorToolbar/Tools/Lists/OrderedList/OrderedListButton.js +22 -0
  50. package/lib/EditorToolbar/Tools/Lists/UnorderedList/UnorderedListButton.d.ts +3 -0
  51. package/lib/EditorToolbar/Tools/Lists/UnorderedList/UnorderedListButton.js +22 -0
  52. package/lib/EditorToolbar/Tools/Redo/RedoButton.d.ts +3 -0
  53. package/lib/EditorToolbar/Tools/Redo/RedoButton.js +22 -0
  54. package/lib/EditorToolbar/Tools/Table/TableButton.d.ts +3 -0
  55. package/lib/EditorToolbar/Tools/Table/TableButton.js +20 -0
  56. package/lib/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.d.ts +3 -0
  57. package/lib/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.js +22 -0
  58. package/lib/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.d.ts +3 -0
  59. package/lib/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.js +22 -0
  60. package/lib/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.d.ts +3 -0
  61. package/lib/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.js +22 -0
  62. package/lib/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.d.ts +3 -0
  63. package/lib/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.js +22 -0
  64. package/lib/EditorToolbar/Tools/TextAlign/TextAlignButtons.d.ts +3 -0
  65. package/lib/EditorToolbar/Tools/TextAlign/TextAlignButtons.js +21 -0
  66. package/lib/EditorToolbar/Tools/TextType/CodeBlock/CodeBlockButton.d.ts +3 -0
  67. package/lib/EditorToolbar/Tools/TextType/CodeBlock/CodeBlockButton.js +22 -0
  68. package/lib/EditorToolbar/Tools/TextType/Heading/HeadingButton.d.ts +6 -0
  69. package/lib/EditorToolbar/Tools/TextType/Heading/HeadingButton.js +37 -0
  70. package/lib/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.d.ts +3 -0
  71. package/lib/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.js +21 -0
  72. package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.d.ts +3 -0
  73. package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.js +22 -0
  74. package/lib/EditorToolbar/Tools/TextType/TextTypeDropdown.d.ts +3 -0
  75. package/lib/EditorToolbar/Tools/TextType/TextTypeDropdown.js +46 -0
  76. package/lib/EditorToolbar/Tools/Underline/UnderlineButton.d.ts +3 -0
  77. package/lib/EditorToolbar/Tools/Underline/UnderlineButton.js +23 -0
  78. package/lib/EditorToolbar/Tools/Undo/UndoButton.d.ts +3 -0
  79. package/lib/EditorToolbar/Tools/Undo/UndoButton.js +22 -0
  80. package/lib/EditorToolbar/index.d.ts +2 -0
  81. package/lib/EditorToolbar/index.js +18 -0
  82. package/lib/Extensions/ClearFormattingExtension/ClearFormattingExtension.d.ts +5 -0
  83. package/lib/Extensions/ClearFormattingExtension/ClearFormattingExtension.js +63 -0
  84. package/lib/Extensions/CodeBlockExtension/CodeBlockExtension.d.ts +15 -0
  85. package/lib/Extensions/CodeBlockExtension/CodeBlockExtension.js +96 -0
  86. package/lib/Extensions/CommandsExtension/CommandsExtension.d.ts +20 -0
  87. package/lib/Extensions/CommandsExtension/CommandsExtension.js +52 -0
  88. package/lib/Extensions/Extensions.d.ts +18 -0
  89. package/lib/Extensions/Extensions.js +76 -0
  90. package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.d.ts +12 -0
  91. package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.js +69 -0
  92. package/lib/Extensions/ImageExtension/AssetImageExtension.d.ts +17 -0
  93. package/lib/Extensions/ImageExtension/AssetImageExtension.js +91 -0
  94. package/lib/Extensions/ImageExtension/DAMImageExtension.d.ts +17 -0
  95. package/lib/Extensions/ImageExtension/DAMImageExtension.js +97 -0
  96. package/lib/Extensions/ImageExtension/ImageExtension.d.ts +7 -0
  97. package/lib/Extensions/ImageExtension/ImageExtension.js +18 -0
  98. package/lib/Extensions/LinkExtension/AssetLinkExtension.d.ts +27 -0
  99. package/lib/Extensions/LinkExtension/AssetLinkExtension.js +101 -0
  100. package/lib/Extensions/LinkExtension/LinkExtension.d.ts +23 -0
  101. package/lib/Extensions/LinkExtension/LinkExtension.js +87 -0
  102. package/lib/Extensions/LinkExtension/common.d.ts +7 -0
  103. package/lib/Extensions/LinkExtension/common.js +14 -0
  104. package/lib/Extensions/PreformattedExtension/PreformattedExtension.d.ts +15 -0
  105. package/lib/Extensions/PreformattedExtension/PreformattedExtension.js +83 -0
  106. package/lib/Extensions/UnsuportedExtension/UnsupportedNodeExtension.d.ts +10 -0
  107. package/lib/Extensions/UnsuportedExtension/UnsupportedNodeExtension.js +76 -0
  108. package/lib/Icons/AiIcon.d.ts +2 -0
  109. package/lib/Icons/AiIcon.js +60 -0
  110. package/lib/hooks/index.d.ts +3 -0
  111. package/lib/hooks/index.js +19 -0
  112. package/lib/hooks/useExpandedSelection/useExpandedSelection.d.ts +23 -0
  113. package/lib/hooks/useExpandedSelection/useExpandedSelection.js +37 -0
  114. package/lib/hooks/useExtensionNames/useExtensionNames.d.ts +1 -0
  115. package/lib/hooks/useExtensionNames/useExtensionNames.js +16 -0
  116. package/lib/hooks/useFocus/useFocus.d.ts +6 -0
  117. package/lib/hooks/useFocus/useFocus.js +57 -0
  118. package/lib/index.css +5894 -0
  119. package/lib/index.d.ts +8 -0
  120. package/lib/index.js +16 -0
  121. package/lib/types.d.ts +7 -0
  122. package/lib/types.js +2 -0
  123. package/lib/ui/Button/Button.d.ts +12 -0
  124. package/lib/ui/Button/Button.js +13 -0
  125. package/lib/ui/CollapseBox/CollapseBox.d.ts +7 -0
  126. package/lib/ui/CollapseBox/CollapseBox.js +48 -0
  127. package/lib/ui/EditorInput/EditorInput.d.ts +3 -0
  128. package/lib/ui/EditorInput/EditorInput.js +49 -0
  129. package/lib/ui/EditorInput/EditorInput.props.d.ts +4 -0
  130. package/lib/ui/EditorInput/EditorInput.props.js +2 -0
  131. package/lib/ui/Fields/Checkbox/Checkbox.d.ts +9 -0
  132. package/lib/ui/Fields/Checkbox/Checkbox.js +47 -0
  133. package/lib/ui/Fields/Input/Input.d.ts +3 -0
  134. package/lib/ui/Fields/Input/Input.js +33 -0
  135. package/lib/ui/Fields/InputContainer/InputContainer.d.ts +9 -0
  136. package/lib/ui/Fields/InputContainer/InputContainer.js +16 -0
  137. package/lib/ui/Fields/MatrixAsset/MatrixAsset.d.ts +19 -0
  138. package/lib/ui/Fields/MatrixAsset/MatrixAsset.js +30 -0
  139. package/lib/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.d.ts +28 -0
  140. package/lib/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.js +88 -0
  141. package/lib/ui/Modal/FormModal.d.ts +5 -0
  142. package/lib/ui/Modal/FormModal.js +39 -0
  143. package/lib/ui/Modal/Modal.d.ts +11 -0
  144. package/lib/ui/Modal/Modal.js +79 -0
  145. package/lib/ui/Tabs/Tabs.d.ts +11 -0
  146. package/lib/ui/Tabs/Tabs.js +46 -0
  147. package/lib/ui/ToolbarDropdown/ToolbarDropdown.d.ts +7 -0
  148. package/lib/ui/ToolbarDropdown/ToolbarDropdown.js +48 -0
  149. package/lib/ui/ToolbarDropdownButton/ToolbarDropdownButton.d.ts +11 -0
  150. package/lib/ui/ToolbarDropdownButton/ToolbarDropdownButton.js +15 -0
  151. package/lib/utils/converters/htmlToSquizNode/htmlToSquizNode.d.ts +5 -0
  152. package/lib/utils/converters/htmlToSquizNode/htmlToSquizNode.js +23 -0
  153. package/lib/utils/converters/htmlToSquizNode/htmlToSquizNode.props.d.ts +3 -0
  154. package/lib/utils/converters/htmlToSquizNode/htmlToSquizNode.props.js +2 -0
  155. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.d.ts +11 -0
  156. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +235 -0
  157. package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.d.ts +9 -0
  158. package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +208 -0
  159. package/lib/utils/createToolbarPositioner.d.ts +18 -0
  160. package/lib/utils/createToolbarPositioner.js +96 -0
  161. package/lib/utils/getCursorRect.d.ts +2 -0
  162. package/lib/utils/getCursorRect.js +7 -0
  163. package/lib/utils/getMarkNamesByGroup.d.ts +2 -0
  164. package/lib/utils/getMarkNamesByGroup.js +9 -0
  165. package/lib/utils/getNodeNamesByGroup.d.ts +2 -0
  166. package/lib/utils/getNodeNamesByGroup.js +9 -0
  167. package/lib/utils/getShortcutSymbol.d.ts +1 -0
  168. package/lib/utils/getShortcutSymbol.js +8 -0
  169. package/lib/utils/undefinedIfEmpty.d.ts +1 -0
  170. package/lib/utils/undefinedIfEmpty.js +7 -0
  171. package/lib/utils/validation.d.ts +3 -0
  172. package/lib/utils/validation.js +16 -0
  173. package/package.json +1 -1
  174. package/src/Editor/Editor.spec.tsx +35 -10
  175. package/src/Editor/Editor.tsx +48 -44
  176. package/src/Editor/_editor.scss +4 -0
  177. package/src/EditorToolbar/Toolbar.tsx +8 -4
  178. package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +0 -3
  179. package/src/EditorToolbar/Tools/TextType/CodeBlock/CodeBlockButton.tsx +3 -3
  180. package/src/EditorToolbar/_toolbar.scss +3 -2
  181. package/src/Extensions/CodeBlockExtension/CodeBlockExtension.props.ts +3 -0
  182. package/src/Extensions/CodeBlockExtension/CodeBlockExtension.spec.ts +59 -0
  183. package/src/Extensions/CodeBlockExtension/CodeBlockExtension.ts +82 -7
  184. package/src/Extensions/Extensions.ts +4 -4
  185. package/src/Extensions/PreformattedExtension/PreformattedExtension.ts +15 -3
  186. package/src/hooks/index.ts +3 -2
  187. package/src/hooks/useFocus/useFocus.spec.tsx +48 -0
  188. package/src/hooks/useFocus/useFocus.ts +71 -0
  189. package/src/ui/EditorInput/EditorInput.props.ts +5 -0
  190. package/src/ui/EditorInput/EditorInput.spec.tsx +38 -0
  191. package/src/ui/EditorInput/EditorInput.tsx +30 -0
  192. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +1 -3
  193. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +0 -4
  194. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +1 -4
  195. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +0 -5
  196. package/src/hooks/useFocus.ts +0 -61
  197. /package/demo/{index.scss → diff/index.scss} +0 -0
  198. /package/demo/{resources.json → diff/resources.json} +0 -0
  199. /package/demo/{sources.json → diff/sources.json} +0 -0
  200. /package/demo/{vite-env.d.ts → diff/vite-env.d.ts} +0 -0
  201. /package/src/hooks/{useExpandedSelection.ts → useExpandedSelection/useExpandedSelection.ts} +0 -0
  202. /package/src/hooks/{useExtensionNames.ts → useExtensionNames/useExtensionNames.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change Log
2
2
 
3
+ ## 2.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0025f46: Allow mounting the editor and toolbar at arbitrary locations in the DOM.
8
+
9
+ ## 2.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 0d281a8: test changeset to trigger job
14
+
3
15
  ## 2.3.0
4
16
 
5
17
  ### Minor Changes
@@ -1,13 +1,14 @@
1
1
  import React, { useState } from 'react';
2
- import { Editor, remirrorNodeToSquizNode, squizNodeToRemirrorNode } from '../src';
2
+ import { Editor, remirrorNodeToSquizNode, squizNodeToRemirrorNode } from '../../src';
3
3
  import { RemirrorEventListener, Extension } from '@remirror/core';
4
4
  import ReactDiffViewer from 'react-diff-viewer-continued';
5
5
  import { AppContext } from './AppContext';
6
- import Button from '../src/ui/Button/Button';
6
+ import Button from '../../src/ui/Button/Button';
7
7
  import TextFieldsOutlinedIcon from '@mui/icons-material/TextFieldsOutlined';
8
8
  import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
9
9
  import { VerticalDivider } from '@remirror/react-components';
10
10
  import { Dialog, useDialogStore } from '@squiz/sds';
11
+
11
12
  const ComponentHandlers = () => (
12
13
  <div style={{ display: 'flex', justifyContent: 'flex-end', maxHeight: '2rem' }}>
13
14
  <Button icon={<TextFieldsOutlinedIcon />} onClick={(x) => x} />
@@ -8,8 +8,7 @@ import MatrixResourceBrowserPlugin, {
8
8
  import { ResourceBrowserContextProvider, ResourceBrowserSource } from '@squiz/resource-browser';
9
9
  import React, { PropsWithChildren } from 'react';
10
10
  import { QueryClient, QueryClientProvider } from 'react-query';
11
- import { EditorContext } from '../src';
12
- import { ResolveNodeType } from '../src/types';
11
+ import { EditorContext, ResolveNodeType } from '../../src';
13
12
  import resources from './resources.json';
14
13
  import sources from './sources.json';
15
14
 
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon-dxp.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Formatted text editor - DXP package (SQUIZ)</title>
8
+ <style rel="./index.scss" lang="scss"></style>
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="./main.tsx"></script>
13
+ </body>
14
+ </html>
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import ReactDOM from 'react-dom/client';
3
3
  import App from './App';
4
4
  import '@squiz/resource-browser/lib/index.css';
5
- import '../src/index.scss';
5
+ import '../../src/index.scss';
6
6
  import './index.scss';
7
7
 
8
8
  ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
package/demo/index.html CHANGED
@@ -5,9 +5,54 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon-dxp.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Formatted text editor - DXP package (SQUIZ)</title>
8
+ <style>
9
+ pre {
10
+ white-space: pre-line;
11
+ }
12
+ </style>
8
13
  </head>
9
14
  <body>
10
- <div id="root"></div>
11
- <script type="module" src="./main.tsx"></script>
15
+ <h1>Formatted text editor demo</h1>
16
+ <h2>Diff</h2>
17
+ <p>
18
+ <a href="./diff/index.html">View demo</a>
19
+ </p>
20
+ <pre>
21
+ Demonstrates how the formatted text editor is typically rendered.
22
+
23
+ Beneath the editor is 2 code blocks. On the left is the Remirror JSON representation of the content.
24
+ On the right is the Squiz JSON representation of the content.
25
+
26
+ You should typically expect there is no highlighted (red/green) content in either block. If there is
27
+ highlighted content this is indicative of the transformation logic not producing identical input
28
+ when run through a cycle of Remirror -> Squiz -> Remirror -> Squiz conversion. This may indicate that
29
+ the transformation logic is losing information during one of the transformation phases.
30
+ </pre>
31
+
32
+ <h2>Portals</h2>
33
+ <p>
34
+ <a href="./portals/index.html?withRemirror">View demo</a>
35
+ (<a href="./portals/index.html">without formatted text editor</a>)
36
+ </p>
37
+ <pre>
38
+ Demonstrates rendering the input and toolbar of a formatted text editor in arbitrary DOM locations.
39
+
40
+ Intended to show an "inline editing" variant where the editor will attempt to minimize how much it changes
41
+ the DOM where it is mounted. Including:
42
+ * Code/pre blocks don't have the decorative icons around them.
43
+ * Code blocks don't have a wrapping &lt;pre&gt; element.
44
+
45
+ The following manipulations still occur due to limitations which have not been overcome:
46
+ * Each editor input has 3 wrapping &lt;div&gt; elements.
47
+ * Each list item is wrapped in a &lt;p&gt; element.
48
+ * Remirror is strongly opinionated about its DOM structure. This is fine if the content being placed
49
+ in the editor was constructed by Remirror previously but if it wasn't then anything unexpected will
50
+ be transformed.
51
+
52
+ Including:
53
+ * Rendering the input in an iframe, with the other parts of the editor rendered outside.
54
+ * Attaching an editor to formatted content that is dynamically mounted after initially loaded
55
+ (collapse/expand accordion).
56
+ </pre>
12
57
  </body>
13
58
  </html>
@@ -0,0 +1,50 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import clsx from 'clsx';
3
+
4
+ export type AccordionProps = {
5
+ items: Array<{ title: string; content: string; defaultExpanded: boolean }>;
6
+ };
7
+
8
+ export const Accordion = ({ items }: AccordionProps) => {
9
+ const [expandedItems, setExpandedItems] = useState(() => items.map((item) => !!item.defaultExpanded));
10
+ const toggleItem = useCallback((index: number) => {
11
+ setExpandedItems((previous) => {
12
+ const updated = [...previous];
13
+ updated[index] = !previous[index];
14
+ return updated;
15
+ });
16
+ }, []);
17
+
18
+ return (
19
+ <>
20
+ {items.map((item, index) => (
21
+ <div
22
+ key={index}
23
+ className={clsx(
24
+ 'accordion-item',
25
+ expandedItems[index] && 'accordion-item-expanded',
26
+ !expandedItems[index] && 'accordion-item-collapsed',
27
+ )}
28
+ >
29
+ <button
30
+ type="button"
31
+ className="accordion-item-header"
32
+ data-sq-content-type="string"
33
+ data-sq-field="items[0].title"
34
+ onClick={() => toggleItem(index)}
35
+ >
36
+ {item.title}
37
+ </button>
38
+ {expandedItems[index] && (
39
+ <div
40
+ className="accordion-item-content"
41
+ data-sq-content-type="formatted-text"
42
+ data-sq-field="items[0].content"
43
+ dangerouslySetInnerHTML={{ __html: item.content }}
44
+ ></div>
45
+ )}
46
+ </div>
47
+ ))}
48
+ </>
49
+ );
50
+ };
@@ -0,0 +1,150 @@
1
+ import { Editor } from '../../src';
2
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
3
+
4
+ const editorIdentifierAttribute = 'data-sq-editor-key';
5
+ let editorIndex = 0;
6
+
7
+ const extractFormattedTextBlocks = (root: Element): Record<string, { container: Element; content: string }> => {
8
+ const selector = '[data-sq-content-type="formatted-text"][data-sq-block-index]';
9
+ const ungrouped = root.matches(selector) ? [root] : Array.from(root.querySelectorAll(selector));
10
+ const grouped: Record<string, Array<Element>> = {};
11
+ const editors: Record<string, { container: Element; content: string }> = {};
12
+
13
+ ungrouped?.forEach((element) => {
14
+ if (!element.parentElement) {
15
+ return;
16
+ }
17
+
18
+ const index = element.getAttribute('data-sq-block-index') as string;
19
+
20
+ grouped[index] ||= [];
21
+ grouped[index].push(element);
22
+ });
23
+
24
+ Object.values(grouped).forEach((elements) => {
25
+ const container = document.createElement('div');
26
+ const content = elements.map((element) => element.outerHTML).join('');
27
+ const key = `editor-${editorIndex++}`;
28
+
29
+ container.setAttribute(editorIdentifierAttribute, key);
30
+ elements[0].parentElement?.insertBefore(container, elements[0]);
31
+ elements.forEach((element) => element.remove());
32
+
33
+ editors[key] = { container, content };
34
+ });
35
+
36
+ return editors;
37
+ };
38
+
39
+ const extractFormattedTextComponentInputs = (
40
+ root: Element,
41
+ ): Record<string, { container: Element; content: string }> => {
42
+ const selector = '[data-sq-content-type="formatted-text"][data-sq-field]';
43
+ const elements = root.matches(selector) ? [root] : Array.from(root.querySelectorAll(selector));
44
+ const editors: Record<string, { container: Element; content: string }> = {};
45
+
46
+ elements.forEach((element) => {
47
+ const container = element;
48
+ const content = container.innerHTML;
49
+ let key = container.getAttribute(editorIdentifierAttribute);
50
+
51
+ if (!key) {
52
+ key = `editor-${editorIndex++}`;
53
+ container.setAttribute(editorIdentifierAttribute, key);
54
+ }
55
+
56
+ container.innerHTML = '';
57
+
58
+ editors[key] = { container, content };
59
+ });
60
+
61
+ return editors;
62
+ };
63
+
64
+ export const App = () => {
65
+ const toolbar = useRef<HTMLDivElement>(null);
66
+ const preview = useRef<HTMLIFrameElement>(null);
67
+ const mutationObserver = useRef<MutationObserver>();
68
+ const [, setIsMounting] = useState(true);
69
+ const [editors, setEditors] = useState<Record<string, { container: Element; content: string }>>({});
70
+ const withRemirror = window.location.search.includes('withRemirror');
71
+ const handleLoad = useCallback(() => {
72
+ if (!withRemirror) {
73
+ return;
74
+ }
75
+
76
+ const document = preview.current?.contentDocument;
77
+
78
+ if (!document) {
79
+ throw new Error('Preview is not loaded.');
80
+ }
81
+
82
+ setEditors({
83
+ ...extractFormattedTextBlocks(preview.current.contentDocument.body),
84
+ ...extractFormattedTextComponentInputs(preview.current.contentDocument.body),
85
+ });
86
+ }, [withRemirror]);
87
+
88
+ useEffect(() => {
89
+ mutationObserver.current?.disconnect();
90
+
91
+ if (!preview.current?.contentDocument) {
92
+ return;
93
+ }
94
+
95
+ const updatedEditors = { ...editors };
96
+ let hasUpdatedEditors = false;
97
+
98
+ mutationObserver.current = new MutationObserver((mutations) => {
99
+ mutations.forEach((mutation) => {
100
+ if (mutation.removedNodes.length > 0) {
101
+ Object.keys(updatedEditors).forEach((key) => {
102
+ if (!updatedEditors[key].container.parentElement) {
103
+ delete updatedEditors[key];
104
+ hasUpdatedEditors = true;
105
+ }
106
+ });
107
+ } else if (mutation.addedNodes.length > 0) {
108
+ mutation.addedNodes.forEach((node) => {
109
+ if (node.nodeType === Node.ELEMENT_NODE) {
110
+ const newEditors = extractFormattedTextComponentInputs(node as Element);
111
+ Object.assign(updatedEditors, newEditors);
112
+ hasUpdatedEditors ||= Object.keys(newEditors).length > 0;
113
+ }
114
+ });
115
+ }
116
+ });
117
+
118
+ if (hasUpdatedEditors) {
119
+ setEditors(updatedEditors);
120
+ }
121
+ });
122
+
123
+ mutationObserver.current.observe(preview.current?.contentDocument, {
124
+ childList: true,
125
+ subtree: true,
126
+ });
127
+ }, [editors]);
128
+
129
+ useEffect(() => {
130
+ setIsMounting(false);
131
+ }, []);
132
+
133
+ return (
134
+ <>
135
+ <div ref={toolbar} id="toolbar"></div>
136
+ <iframe ref={preview} id="preview" title="Preview" src="./preview.html" onLoad={handleLoad} />
137
+ {Object.keys(editors).map((key) => (
138
+ <Editor
139
+ key={key}
140
+ enableDecorations={false}
141
+ content={editors[key].content}
142
+ containers={{
143
+ input: editors[key].container,
144
+ toolbar: toolbar.current,
145
+ }}
146
+ />
147
+ ))}
148
+ </>
149
+ );
150
+ };
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon-dxp.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Formatted text editor - DXP package (SQUIZ)</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="./index.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,8 @@
1
+ #toolbar {
2
+ height: 32px;
3
+ }
4
+
5
+ #preview {
6
+ width: 100%;
7
+ height: calc(100vh - 32px);
8
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import { App } from './App';
4
+ import '@squiz/resource-browser/lib/index.css';
5
+ import '../../src/index.scss';
6
+ import './index.scss';
7
+
8
+ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
9
+ <React.StrictMode>
10
+ <App />
11
+ </React.StrictMode>,
12
+ );
@@ -0,0 +1,91 @@
1
+ <html>
2
+ <head>
3
+ <title>Remirror portal example</title>
4
+ <style>
5
+ body {
6
+ font-family: Palatino, Georgia, Times, 'Times New Roman', Serif;
7
+ }
8
+
9
+ #content {
10
+ margin: 24px;
11
+ }
12
+
13
+ .separator {
14
+ background-color: yellow;
15
+ }
16
+
17
+ .underline {
18
+ text-decoration: underline;
19
+ }
20
+
21
+ .accordion-item-collapsed .accordion-item-content {
22
+ display: none;
23
+ }
24
+ </style>
25
+ </head>
26
+ <body>
27
+ <div id="content" spellcheck="false">
28
+ <h1 data-sq-content-type="formatted-text" data-sq-block-index="1">Remirror example</h1>
29
+ <!-- comment goes here la -->
30
+ <div data-sq-content-type="formatted-text" data-sq-block-index="1">
31
+ Australian governments at all levels have
32
+ <u
33
+ ><strong><em>endorsed</em></strong></u
34
+ >
35
+ WCAG 2.0, and require all government websites (federal, state and territory) to meet the new guidelines at the
36
+ minimum compliance level (Single A) by the end of 2012. In addition, the Australian Government requires all
37
+ federal websites to meet the medium conformance level (Double A) by the end of 2014.
38
+ </div>
39
+ <p data-sq-content-type="formatted-text" data-sq-block-index="1">
40
+ Federal government agencies must update <strong>all government</strong> websites (as specified within scope
41
+ under the Website Accessibility National Transition Strategy (NTS)) to WCAG 2.0 conformance. Agencies should use
42
+ the principle of progressive enhancement when building and managing websites, and test for conformance across
43
+ multiple browsers and operating system configurations.
44
+ </p>
45
+
46
+ <p class="separator">The below section shows most formatting options/supported element tags.</p>
47
+
48
+ <p data-sq-content-type="formatted-text" data-sq-block-index="2">
49
+ <img
50
+ src="https://www.squiz.net/__data/assets/file/0014/11075/glyphsquiz-darkblue.svg"
51
+ alt="Squiz logo"
52
+ width="200px"
53
+ />
54
+ </p>
55
+ <p data-sq-content-type="formatted-text" data-sq-block-index="2">
56
+ Visit <a href="https://www.squiz.net/" target="_blank">squiz.net</a>.
57
+ </p>
58
+ <code data-sq-content-type="formatted-text" data-sq-block-index="2">This is a code block.</code>
59
+ <pre data-sq-content-type="formatted-text" data-sq-block-index="2">This is a pre block.</pre>
60
+ <ul data-sq-content-type="formatted-text" data-sq-block-index="2">
61
+ <li>Item 1</li>
62
+ <li>Item 2</li>
63
+ <li>Item 3</li>
64
+ </ul>
65
+ <ol data-sq-content-type="formatted-text" data-sq-block-index="2">
66
+ <li>Item 1</li>
67
+ <li>Item 2</li>
68
+ <li>Item 3</li>
69
+ </ol>
70
+ <hr data-sq-content-type="formatted-text" data-sq-block-index="2" />
71
+ <h1 data-sq-content-type="formatted-text" data-sq-block-index="2">Heading</h1>
72
+ <p data-sq-content-type="formatted-text" data-sq-block-index="2">
73
+ <strong>Bold</strong>, <em>Italics</em>, <span class="underline">underline</span>
74
+ </p>
75
+
76
+ <p class="separator">
77
+ The below section shows a React mounted element with formatted text input. The input is dynamically
78
+ added/removed from the DOM by clicking the button.
79
+ </p>
80
+
81
+ <div
82
+ data-sq-content-type="component"
83
+ data-sq-block-index="3"
84
+ class="accordion"
85
+ data-hydration-component="accordion"
86
+ data-hydration-props='{"items":[{"title":"Accordion title","content":"<p><strong>Accordion content</strong></p>","defaultExpanded":true}]}'
87
+ ></div>
88
+ </div>
89
+ <script type="module" src="./preview.tsx"></script>
90
+ </body>
91
+ </html>
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import { Accordion } from './Accordion';
4
+
5
+ document.querySelectorAll('[data-hydration-component="accordion"]').forEach((element) => {
6
+ const props = JSON.parse(element.getAttribute('data-hydration-props') || '');
7
+ const root = ReactDOM.createRoot(element);
8
+
9
+ root.render(<Accordion {...props} />);
10
+ });
@@ -0,0 +1,21 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { RemirrorContentType, RemirrorEventListener, Extension } from '@remirror/core';
3
+ type EditorProps = {
4
+ attributes?: Record<string, string>;
5
+ border?: boolean;
6
+ children?: ReactNode;
7
+ className?: string;
8
+ containers?: {
9
+ input?: Element | null;
10
+ toolbar?: Element | null;
11
+ };
12
+ content?: RemirrorContentType;
13
+ editable?: boolean;
14
+ enableDecorations?: boolean;
15
+ enableTableTool?: boolean;
16
+ isFocused?: boolean;
17
+ label?: string;
18
+ onChange?: RemirrorEventListener<Extension>;
19
+ };
20
+ declare const Editor: ({ attributes, border, children, className, containers, content, editable, enableDecorations, enableTableTool, isFocused: isInitiallyFocused, label, onChange, }: EditorProps) => React.JSX.Element;
21
+ export default Editor;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const react_1 = __importStar(require("react"));
30
+ const react_2 = require("@remirror/react");
31
+ const clsx_1 = __importDefault(require("clsx"));
32
+ const EditorToolbar_1 = require("../EditorToolbar");
33
+ const EditorContext_1 = require("./EditorContext");
34
+ const Extensions_1 = require("../Extensions/Extensions");
35
+ const hooks_1 = require("../hooks");
36
+ const extension_react_tables_1 = require("@remirror/extension-react-tables");
37
+ const EditorInput_1 = require("../ui/EditorInput/EditorInput");
38
+ const Editor = ({ attributes, border = true, children, className, containers, content, editable = true, enableDecorations = true, enableTableTool = false, isFocused: isInitiallyFocused = false, label = 'Text editor', onChange, }) => {
39
+ const isEmpty = containers?.toolbar && containers.input && !children;
40
+ const { manager, state, setState } = (0, react_2.useRemirror)({
41
+ extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext), enableDecorations),
42
+ content,
43
+ selection: 'start',
44
+ stringHandler: 'html',
45
+ });
46
+ const handleChange = (parameter) => {
47
+ setState(parameter.state);
48
+ onChange?.(parameter);
49
+ };
50
+ const wrapperRef = (0, react_1.useRef)(null);
51
+ const { isFocused, handleFocus, handleBlur } = (0, hooks_1.useFocus)(isInitiallyFocused, (0, react_1.useCallback)((element) => {
52
+ return Boolean(wrapperRef.current?.contains(element) ||
53
+ containers?.input?.contains(element) ||
54
+ containers?.toolbar?.contains(element));
55
+ }, [containers?.input, containers?.toolbar]));
56
+ // On initial load, check if we need to focus the actual text content
57
+ (0, react_1.useEffect)(() => {
58
+ if (isInitiallyFocused) {
59
+ manager.view.dom.focus();
60
+ }
61
+ // TODO: May want to come back to this and see if there's a better solution
62
+ // We have to add a type button attribute to the delete buttons so they don't cause a submit by accident.
63
+ const tableDeleteButtons = document.querySelectorAll('.remirror-table-delete-inner-button');
64
+ tableDeleteButtons.forEach((button) => {
65
+ button.setAttribute('type', 'button');
66
+ });
67
+ }, []);
68
+ return (react_1.default.createElement("div", { ref: wrapperRef, onBlurCapture: handleBlur, onFocusCapture: handleFocus, className: (0, clsx_1.default)('squiz-fte-scope', 'squiz-fte-scope__editor', !editable && 'squiz-fte-scope__editor--is-disabled', border && 'squiz-fte-scope__editor--bordered', isEmpty && 'squiz-fte-scope__editor--empty', className) },
69
+ react_1.default.createElement(react_2.Remirror, { manager: manager, initialContent: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: label, attributes: attributes },
70
+ editable && (react_1.default.createElement(EditorToolbar_1.Toolbar, { isVisible: isFocused, enableTableTool: enableTableTool, container: containers?.toolbar })),
71
+ children && react_1.default.createElement("div", { className: "squiz-fte-scope__editor__children" }, children),
72
+ react_1.default.createElement(EditorInput_1.EditorInput, { container: containers?.input }),
73
+ enableTableTool && react_1.default.createElement(extension_react_tables_1.TableComponents, { enableTableCellMenu: false }),
74
+ editable && isFocused && react_1.default.createElement(EditorToolbar_1.FloatingToolbar, null))));
75
+ };
76
+ exports.default = Editor;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { ResolveNodeToUrl } from '../types';
3
+ export type EditorContextOptions = {
4
+ matrix: {
5
+ matrixDomain: string;
6
+ };
7
+ resolveNodeToUrl: ResolveNodeToUrl;
8
+ };
9
+ export declare const defaultEditorContext: EditorContextOptions;
10
+ export declare const EditorContext: React.Context<EditorContextOptions>;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EditorContext = exports.defaultEditorContext = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ exports.defaultEditorContext = {
9
+ matrix: {
10
+ matrixDomain: '',
11
+ },
12
+ resolveNodeToUrl: () => {
13
+ throw new Error('resolveNodeToUrl has not been configured.');
14
+ },
15
+ };
16
+ exports.EditorContext = react_1.default.createContext(exports.defaultEditorContext);
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const FloatingToolbar: () => React.JSX.Element;