@squiz/formatted-text-editor 2.4.0 → 2.6.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 +12 -0
- package/demo/{App.tsx → diff/App.tsx} +3 -7
- package/demo/{AppContext.tsx → diff/AppContext.tsx} +15 -10
- package/demo/diff/index.html +14 -0
- package/demo/{index.scss → diff/index.scss} +3 -0
- package/demo/{main.tsx → diff/main.tsx} +1 -1
- package/demo/index.html +47 -2
- package/demo/portals/Accordion.tsx +50 -0
- package/demo/portals/App.tsx +150 -0
- package/demo/portals/index.html +13 -0
- package/demo/portals/index.scss +8 -0
- package/demo/portals/index.tsx +12 -0
- package/demo/portals/preview.html +91 -0
- package/demo/portals/preview.tsx +10 -0
- package/lib/Editor/Editor.d.ts +11 -6
- package/lib/Editor/Editor.js +17 -26
- package/lib/EditorToolbar/Toolbar.d.ts +2 -1
- package/lib/EditorToolbar/Toolbar.js +4 -2
- package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +0 -3
- package/lib/Extensions/CodeBlockExtension/CodeBlockExtension.d.ts +13 -3
- package/lib/Extensions/CodeBlockExtension/CodeBlockExtension.js +74 -8
- package/lib/Extensions/Extensions.d.ts +1 -1
- package/lib/Extensions/Extensions.js +3 -3
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.d.ts +5 -2
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.js +8 -1
- package/lib/hooks/index.d.ts +3 -2
- package/lib/hooks/index.js +3 -2
- package/lib/hooks/useFocus/useFocus.d.ts +6 -0
- package/lib/hooks/{useFocus.js → useFocus/useFocus.js} +29 -15
- package/lib/index.css +164 -5
- package/lib/ui/EditorInput/EditorInput.d.ts +3 -0
- package/lib/ui/EditorInput/EditorInput.js +49 -0
- package/lib/ui/EditorInput/EditorInput.props.d.ts +4 -0
- package/lib/ui/EditorInput/EditorInput.props.js +2 -0
- package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +0 -3
- package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +0 -6
- package/package.json +5 -4
- package/src/Editor/Editor.spec.tsx +36 -10
- package/src/Editor/Editor.tsx +48 -44
- package/src/Editor/_editor.scss +4 -0
- package/src/EditorToolbar/Toolbar.tsx +8 -4
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +0 -3
- package/src/EditorToolbar/Tools/TextType/CodeBlock/CodeBlockButton.tsx +3 -3
- package/src/EditorToolbar/_toolbar.scss +3 -2
- package/src/Extensions/CodeBlockExtension/CodeBlockExtension.props.ts +3 -0
- package/src/Extensions/CodeBlockExtension/CodeBlockExtension.spec.ts +59 -0
- package/src/Extensions/CodeBlockExtension/CodeBlockExtension.ts +82 -7
- package/src/Extensions/Extensions.ts +4 -4
- package/src/Extensions/PreformattedExtension/PreformattedExtension.ts +15 -3
- package/src/hooks/index.ts +3 -2
- package/src/hooks/useFocus/useFocus.spec.tsx +48 -0
- package/src/hooks/useFocus/useFocus.ts +71 -0
- package/src/ui/EditorInput/EditorInput.props.ts +5 -0
- package/src/ui/EditorInput/EditorInput.spec.tsx +38 -0
- package/src/ui/EditorInput/EditorInput.tsx +30 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +1 -3
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +0 -4
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +1 -4
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +0 -5
- package/tests/mockResourceBrowserContext.tsx +17 -1
- package/tests/renderWithContext.tsx +3 -0
- package/lib/hooks/useFocus.d.ts +0 -8
- package/src/hooks/useFocus.ts +0 -61
- /package/demo/{resources.json → diff/resources.json} +0 -0
- /package/demo/{sources.json → diff/sources.json} +0 -0
- /package/demo/{vite-env.d.ts → diff/vite-env.d.ts} +0 -0
- /package/lib/hooks/{useExpandedSelection.d.ts → useExpandedSelection/useExpandedSelection.d.ts} +0 -0
- /package/lib/hooks/{useExpandedSelection.js → useExpandedSelection/useExpandedSelection.js} +0 -0
- /package/lib/hooks/{useExtensionNames.d.ts → useExtensionNames/useExtensionNames.d.ts} +0 -0
- /package/lib/hooks/{useExtensionNames.js → useExtensionNames/useExtensionNames.js} +0 -0
- /package/src/hooks/{useExpandedSelection.ts → useExpandedSelection/useExpandedSelection.ts} +0 -0
- /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.6.0
|
4
|
+
|
5
|
+
### Minor Changes
|
6
|
+
|
7
|
+
- ec6d330: Updated packages to use resource browser v3
|
8
|
+
|
9
|
+
## 2.5.0
|
10
|
+
|
11
|
+
### Minor Changes
|
12
|
+
|
13
|
+
- 0025f46: Allow mounting the editor and toolbar at arbitrary locations in the DOM.
|
14
|
+
|
3
15
|
## 2.4.0
|
4
16
|
|
5
17
|
### Minor Changes
|
@@ -1,13 +1,14 @@
|
|
1
1
|
import React, { useState } from 'react';
|
2
|
-
import { Editor, remirrorNodeToSquizNode, squizNodeToRemirrorNode } from '
|
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 '
|
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} />
|
@@ -83,11 +84,6 @@ function App() {
|
|
83
84
|
<a href="https://www.google.com"><strong>Mr Bean</strong></a>, nice to <a href="https://www.google.com">meet you</a>.
|
84
85
|
<img src="https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif" height="150" width="200"/>
|
85
86
|
<img data-matrix-asset-id="1" data-matrix-identifier="matrixIdentifier" data-matrix-domain="https://matrix-domain.squiz.net">
|
86
|
-
<img
|
87
|
-
data-dam-object-id="CA87B42E-9410-402F-B3669D6900DD0793"
|
88
|
-
data-dam-system-identifier="byder001"
|
89
|
-
data-dam-system-type="bynder"
|
90
|
-
>
|
91
87
|
</p>
|
92
88
|
`}
|
93
89
|
onChange={handleEditorChange}
|
@@ -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 '
|
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
|
|
@@ -31,6 +30,7 @@ export const AppContext = ({ children }: AppContextProps) => (
|
|
31
30
|
<QueryClientProvider client={testQueryClient}>
|
32
31
|
<ResourceBrowserContextProvider
|
33
32
|
value={{
|
33
|
+
searchEnabled: true,
|
34
34
|
onRequestSources: (): Promise<ResourceBrowserSource[]> =>
|
35
35
|
Promise.resolve([
|
36
36
|
{
|
@@ -38,14 +38,6 @@ export const AppContext = ({ children }: AppContextProps) => (
|
|
38
38
|
id: 'matrixIdentifier',
|
39
39
|
type: 'matrix',
|
40
40
|
},
|
41
|
-
{
|
42
|
-
name: 'Bynder',
|
43
|
-
id: 'byder001',
|
44
|
-
type: 'dam',
|
45
|
-
configuration: {
|
46
|
-
externalType: 'bynder',
|
47
|
-
},
|
48
|
-
},
|
49
41
|
]),
|
50
42
|
plugins: [
|
51
43
|
DamResourceBrowserPlugin(),
|
@@ -67,6 +59,19 @@ export const AppContext = ({ children }: AppContextProps) => (
|
|
67
59
|
flattenResources(resources).find((resource) => resource.id === reference.resource) || null,
|
68
60
|
);
|
69
61
|
},
|
62
|
+
onSearchRequest: () =>
|
63
|
+
Promise.resolve({
|
64
|
+
results: [],
|
65
|
+
filters: [],
|
66
|
+
resultsSummary: {
|
67
|
+
totalMatching: 0,
|
68
|
+
numRanks: 10,
|
69
|
+
currStart: 0,
|
70
|
+
currEnd: 0,
|
71
|
+
prevStart: null,
|
72
|
+
nextStart: null,
|
73
|
+
},
|
74
|
+
}),
|
70
75
|
}),
|
71
76
|
],
|
72
77
|
}}
|
@@ -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 '
|
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
|
-
<
|
11
|
-
<
|
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 <pre> element.
|
44
|
+
|
45
|
+
The following manipulations still occur due to limitations which have not been overcome:
|
46
|
+
* Each editor input has 3 wrapping <div> elements.
|
47
|
+
* Each list item is wrapped in a <p> 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,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
|
+
});
|
package/lib/Editor/Editor.d.ts
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
import React, { ReactNode } from 'react';
|
2
2
|
import { RemirrorContentType, RemirrorEventListener, Extension } from '@remirror/core';
|
3
3
|
type EditorProps = {
|
4
|
+
attributes?: Record<string, string>;
|
5
|
+
border?: boolean;
|
6
|
+
children?: ReactNode;
|
4
7
|
className?: string;
|
8
|
+
containers?: {
|
9
|
+
input?: Element | null;
|
10
|
+
toolbar?: Element | null;
|
11
|
+
};
|
5
12
|
content?: RemirrorContentType;
|
6
|
-
onChange?: RemirrorEventListener<Extension>;
|
7
13
|
editable?: boolean;
|
8
|
-
|
9
|
-
|
14
|
+
enableDecorations?: boolean;
|
15
|
+
enableTableTool?: boolean;
|
10
16
|
isFocused?: boolean;
|
11
17
|
label?: string;
|
12
|
-
|
13
|
-
enableTableTool?: boolean;
|
18
|
+
onChange?: RemirrorEventListener<Extension>;
|
14
19
|
};
|
15
|
-
declare const Editor: ({
|
20
|
+
declare const Editor: ({ attributes, border, children, className, containers, content, editable, enableDecorations, enableTableTool, isFocused: isInitiallyFocused, label, onChange, }: EditorProps) => React.JSX.Element;
|
16
21
|
export default Editor;
|
package/lib/Editor/Editor.js
CHANGED
@@ -32,27 +32,13 @@ const clsx_1 = __importDefault(require("clsx"));
|
|
32
32
|
const EditorToolbar_1 = require("../EditorToolbar");
|
33
33
|
const EditorContext_1 = require("./EditorContext");
|
34
34
|
const Extensions_1 = require("../Extensions/Extensions");
|
35
|
-
const
|
35
|
+
const hooks_1 = require("../hooks");
|
36
36
|
const extension_react_tables_1 = require("@remirror/extension-react-tables");
|
37
|
-
const
|
38
|
-
|
39
|
-
|
40
|
-
const pastedData = clipboardData?.files[0];
|
41
|
-
if (pastedData?.type &&
|
42
|
-
pastedData?.type.startsWith('image/') &&
|
43
|
-
// Still allow paste of any text that came through (Word, etc)
|
44
|
-
!clipboardData?.types.includes('text/plain')) {
|
45
|
-
event.preventDefault();
|
46
|
-
}
|
47
|
-
// Allow other paste event handlers to be run.
|
48
|
-
return false;
|
49
|
-
}, []);
|
50
|
-
(0, react_2.useEditorEvent)('paste', preventImagePaste);
|
51
|
-
return react_1.default.createElement(react_2.EditorComponent, null);
|
52
|
-
};
|
53
|
-
const Editor = ({ content, className, border = true, editable = true, onChange, children, isFocused, attributes, enableTableTool = false, }) => {
|
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;
|
54
40
|
const { manager, state, setState } = (0, react_2.useRemirror)({
|
55
|
-
extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext)),
|
41
|
+
extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext), enableDecorations),
|
56
42
|
content,
|
57
43
|
selection: 'start',
|
58
44
|
stringHandler: 'html',
|
@@ -61,10 +47,15 @@ const Editor = ({ content, className, border = true, editable = true, onChange,
|
|
61
47
|
setState(parameter.state);
|
62
48
|
onChange?.(parameter);
|
63
49
|
};
|
64
|
-
const
|
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]));
|
65
56
|
// On initial load, check if we need to focus the actual text content
|
66
57
|
(0, react_1.useEffect)(() => {
|
67
|
-
if (
|
58
|
+
if (isInitiallyFocused) {
|
68
59
|
manager.view.dom.focus();
|
69
60
|
}
|
70
61
|
// TODO: May want to come back to this and see if there's a better solution
|
@@ -74,12 +65,12 @@ const Editor = ({ content, className, border = true, editable = true, onChange,
|
|
74
65
|
button.setAttribute('type', 'button');
|
75
66
|
});
|
76
67
|
}, []);
|
77
|
-
return (react_1.default.createElement("div", { ref: wrapperRef,
|
78
|
-
react_1.default.createElement(react_2.Remirror, { manager: manager, initialContent: state, editable: editable, onChange: handleChange, placeholder: "Write something", label:
|
79
|
-
editable && react_1.default.createElement(EditorToolbar_1.Toolbar, { isVisible:
|
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 })),
|
80
71
|
children && react_1.default.createElement("div", { className: "squiz-fte-scope__editor__children" }, children),
|
81
|
-
react_1.default.createElement(
|
72
|
+
react_1.default.createElement(EditorInput_1.EditorInput, { container: containers?.input }),
|
82
73
|
enableTableTool && react_1.default.createElement(extension_react_tables_1.TableComponents, { enableTableCellMenu: false }),
|
83
|
-
editable &&
|
74
|
+
editable && isFocused && react_1.default.createElement(EditorToolbar_1.FloatingToolbar, null))));
|
84
75
|
};
|
85
76
|
exports.default = Editor;
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
2
2
|
type ToolbarProps = {
|
3
3
|
isVisible: boolean;
|
4
4
|
enableTableTool: boolean;
|
5
|
+
container?: Element | null;
|
5
6
|
};
|
6
|
-
export declare const Toolbar: ({ isVisible, enableTableTool }: ToolbarProps) => React.JSX.Element;
|
7
|
+
export declare const Toolbar: ({ isVisible, enableTableTool, container }: ToolbarProps) => React.JSX.Element;
|
7
8
|
export {};
|