@squiz/formatted-text-editor 2.0.1 → 2.2.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 (53) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/demo/App.tsx +5 -0
  3. package/demo/AppContext.tsx +111 -51
  4. package/jest.config.ts +1 -1
  5. package/jest.setup.ts +16 -0
  6. package/lib/EditorToolbar/FloatingToolbar.js +1 -1
  7. package/lib/EditorToolbar/Toolbar.js +3 -1
  8. package/lib/EditorToolbar/Tools/ContentTools/ContentToolsDropdown.d.ts +3 -0
  9. package/lib/EditorToolbar/Tools/ContentTools/ContentToolsDropdown.js +35 -0
  10. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.d.ts +2 -1
  11. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +53 -10
  12. package/lib/EditorToolbar/Tools/Image/ImageButton.js +8 -5
  13. package/lib/EditorToolbar/Tools/Image/ImageModal.js +3 -1
  14. package/lib/EditorToolbar/Tools/Table/TableButton.js +1 -3
  15. package/lib/Extensions/Extensions.d.ts +1 -0
  16. package/lib/Extensions/Extensions.js +3 -0
  17. package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.js +6 -0
  18. package/lib/Extensions/ImageExtension/DAMImageExtension.d.ts +17 -0
  19. package/lib/Extensions/ImageExtension/DAMImageExtension.js +97 -0
  20. package/lib/Icons/AiIcon.d.ts +2 -0
  21. package/lib/Icons/AiIcon.js +60 -0
  22. package/lib/index.css +4224 -0
  23. package/lib/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.d.ts +28 -0
  24. package/lib/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.js +88 -0
  25. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +9 -0
  26. package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +9 -0
  27. package/package.json +6 -2
  28. package/src/EditorToolbar/FloatingToolbar.spec.tsx +3 -1
  29. package/src/EditorToolbar/FloatingToolbar.tsx +1 -1
  30. package/src/EditorToolbar/Toolbar.tsx +2 -0
  31. package/src/EditorToolbar/Tools/ContentTools/ContentToolsDropdown.spec.tsx +78 -0
  32. package/src/EditorToolbar/Tools/ContentTools/ContentToolsDropdown.tsx +46 -0
  33. package/src/EditorToolbar/Tools/ContentTools/_content-tools.scss +45 -0
  34. package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +27 -2
  35. package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +61 -14
  36. package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +70 -2
  37. package/src/EditorToolbar/Tools/Image/ImageButton.tsx +12 -6
  38. package/src/EditorToolbar/Tools/Image/ImageModal.tsx +4 -1
  39. package/src/EditorToolbar/Tools/Table/TableButton.tsx +0 -2
  40. package/src/Extensions/Extensions.ts +3 -0
  41. package/src/Extensions/FetchUrlExtension/FetchUrlExtension.ts +9 -0
  42. package/src/Extensions/ImageExtension/DAMImageExtension.spec.ts +87 -0
  43. package/src/Extensions/ImageExtension/DAMImageExtension.ts +119 -0
  44. package/src/Icons/AiIcon.tsx +140 -0
  45. package/src/index.scss +4 -0
  46. package/src/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.spec.tsx +219 -0
  47. package/src/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.tsx +109 -0
  48. package/src/utils/converters/mocks/squizNodeJson.mock.ts +21 -0
  49. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +10 -0
  50. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +8 -0
  51. package/src/utils/getNodeNamesByGroup.spec.ts +1 -0
  52. package/tests/index.ts +1 -0
  53. package/tests/mockResourceBrowser.tsx +46 -0
@@ -1,11 +1,18 @@
1
1
  import '@testing-library/jest-dom';
2
2
  import { screen, fireEvent, waitForElementToBeRemoved, act, waitFor } from '@testing-library/react';
3
+ import { ResourceBrowserResource } from '@squiz/resource-browser';
3
4
  import { NodeSelection } from 'prosemirror-state';
4
5
  import React from 'react';
5
- import { renderWithEditor, mockResourceBrowserContext } from '../../../../tests';
6
+ import { renderWithEditor, mockResourceBrowserContext, mockResourceBrowser } from '../../../../tests';
6
7
  import ImageButton from './ImageButton';
7
8
  import { getImageSize } from 'react-image-size';
8
9
 
10
+ const { setSelectedResource, setShouldUseMockResourceBrowser, mockResourceBrowserImpl } = mockResourceBrowser();
11
+ jest.mock('@squiz/resource-browser', () => ({
12
+ ...jest.requireActual('@squiz/resource-browser'),
13
+ ResourceBrowser: (props: any) => mockResourceBrowserImpl(props),
14
+ }));
15
+
9
16
  jest.mock('react-image-size');
10
17
 
11
18
  describe('ImageButton', () => {
@@ -295,7 +302,7 @@ describe('ImageButton', () => {
295
302
  });
296
303
  });
297
304
 
298
- it('Updates the attributes of an existing asset image', async () => {
305
+ it('assetImage: Updates the attributes of an existing asset image', async () => {
299
306
  const matrixIdentifier = 'matrix-api-identifier';
300
307
  const matrixDomain = 'https://my-matrix.squiz.net';
301
308
  const { MockResourceBrowserContext, selectResource } = mockResourceBrowserContext({
@@ -362,6 +369,67 @@ describe('ImageButton', () => {
362
369
  });
363
370
  });
364
371
 
372
+ it('DAMImage: Updates the attributes of an existing dam image', async () => {
373
+ setShouldUseMockResourceBrowser(true);
374
+ setSelectedResource({
375
+ id: 'my-resource-id',
376
+ name: 'My resource',
377
+ url: 'myResourceUrl',
378
+ source: {
379
+ id: 'my-source-id',
380
+ type: 'dam',
381
+ configuration: {
382
+ externalType: 'bynder',
383
+ },
384
+ },
385
+ } as unknown as ResourceBrowserResource);
386
+
387
+ const { editor, getJsonContent } = await renderWithEditor(<ImageButton />, {
388
+ content: 'Some <img src="https://httpcats.com/529.jpg" alt="hi" /> nonsense',
389
+ context: {
390
+ editor: {
391
+ matrix: {
392
+ matrixDomain: 'https://my-matrix.squiz.net',
393
+ },
394
+ },
395
+ },
396
+ });
397
+
398
+ await act(() => editor.selectText(new NodeSelection(editor.state.doc.resolve(6))));
399
+
400
+ await openModal();
401
+ fireEvent.click(screen.getByRole('button', { name: 'From source' }));
402
+ // Trigger the mock Resource Browser to invoke its onChange callback to fill the form with data
403
+ await waitFor(() => {
404
+ expect(screen.getByRole('button', { name: 'Select ResourceBrowser Resource' })).toBeInTheDocument();
405
+ });
406
+ fireEvent.click(await screen.findByRole('button', { name: 'Select ResourceBrowser Resource' }));
407
+ // Close the image selection modal
408
+ await act(() => fireEvent.click(screen.getByRole('button', { name: 'Apply' })));
409
+
410
+ expect(getJsonContent()).toEqual({
411
+ type: 'paragraph',
412
+ attrs: expect.any(Object),
413
+ content: [
414
+ {
415
+ text: 'Some ',
416
+ type: 'text',
417
+ },
418
+ {
419
+ type: 'DAMImage',
420
+ attrs: {
421
+ damObjectId: 'my-resource-id',
422
+ damSystemIdentifier: 'my-source-id',
423
+ damAdditional: undefined,
424
+ damSystemType: 'bynder',
425
+ src: 'myResourceUrl',
426
+ },
427
+ },
428
+ { type: 'text', text: ' nonsense' },
429
+ ],
430
+ });
431
+ });
432
+
365
433
  it('Shows an error if a resource is not selected', async () => {
366
434
  await renderWithEditor(<ImageButton />);
367
435
  await openModal();
@@ -6,7 +6,8 @@ import { ImageFormData } from './Form/ImageForm';
6
6
  import Button from '../../../ui/Button/Button';
7
7
  import { ImageExtension } from '../../../Extensions/ImageExtension/ImageExtension';
8
8
  import { NodeName } from '../../../Extensions/Extensions';
9
- import { AssetImageExtension } from '../../../Extensions/ImageExtension/AssetImageExtension';
9
+ import { AssetImageExtension, AssetImageAttributes } from '../../../Extensions/ImageExtension/AssetImageExtension';
10
+ import { DAMImageExtension, DAMImageAttributes } from '../../../Extensions/ImageExtension/DAMImageExtension';
10
11
  import { CodeBlockExtension } from 'remirror/dist-types/extensions';
11
12
  import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
12
13
 
@@ -16,11 +17,14 @@ type ImageButtonProps = {
16
17
 
17
18
  const ImageButton = ({ inPopover = false }: ImageButtonProps) => {
18
19
  const [showModal, setShowModal] = useState(false);
19
- const { insertImage, insertAssetImage } = useCommands<ImageExtension | AssetImageExtension>();
20
+ const { insertImage, insertAssetImage, insertDAMImage } = useCommands<
21
+ ImageExtension | AssetImageExtension | DAMImageExtension
22
+ >();
20
23
  const active = useActive<ImageExtension | AssetImageExtension | CodeBlockExtension>();
21
24
  const selection = useCurrentSelection();
22
25
  // if the active selection is not an image, disable the button as it means it will be text
23
- const disabled = (!selection.empty && !active.image() && !active.assetImage()) || active.codeBlock();
26
+ const disabled =
27
+ (!selection.empty && !active.image() && !active.assetImage() && !active.DAMImage()) || active.codeBlock();
24
28
 
25
29
  const handleClick = () => {
26
30
  if (!showModal) {
@@ -29,11 +33,13 @@ const ImageButton = ({ inPopover = false }: ImageButtonProps) => {
29
33
  };
30
34
 
31
35
  const insertImageFromData = (data: ImageFormData) => {
32
- const { imageType, image, assetImage } = data;
36
+ const { imageType, image, resourceImage } = data;
33
37
  if (imageType === NodeName.Image) {
34
38
  insertImage(image);
39
+ } else if (imageType === NodeName.DAMImage) {
40
+ insertDAMImage(resourceImage as DAMImageAttributes);
35
41
  } else {
36
- insertAssetImage(assetImage);
42
+ insertAssetImage(resourceImage as AssetImageAttributes);
37
43
  }
38
44
  };
39
45
 
@@ -59,7 +65,7 @@ const ImageButton = ({ inPopover = false }: ImageButtonProps) => {
59
65
  <>
60
66
  <Button
61
67
  handleOnClick={handleClick}
62
- isActive={active.image() || active.assetImage()}
68
+ isActive={active.image() || active.assetImage() || active.DAMImage()}
63
69
  icon={<ImageRoundedIcon />}
64
70
  label={`Image (${getShortcutSymbol()}+L)`}
65
71
  isDisabled={disabled}
@@ -19,7 +19,10 @@ const ImageModal = ({ onCancel, onSubmit }: ImageModalProps) => {
19
19
  const formData = {
20
20
  imageType: currentImage?.type.name === NodeName.Image ? NodeName.Image : NodeName.AssetImage,
21
21
  image: currentImage?.type?.name === NodeName.Image ? currentImageAttrs : {},
22
- assetImage: currentImage?.type?.name === NodeName.AssetImage ? currentImageAttrs : {},
22
+ resourceImage:
23
+ currentImage?.type?.name === NodeName.DAMImage || currentImage?.type?.name === NodeName.AssetImage
24
+ ? currentImageAttrs
25
+ : {},
23
26
  };
24
27
 
25
28
  return (
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
2
  import { useCommands, useActive } from '@remirror/react';
3
- import { VerticalDivider } from '@remirror/react-components';
4
3
  import { TableExtension } from '@remirror/extension-react-tables';
5
4
  import Button from '../../../ui/Button/Button';
6
5
  import TableViewRoundedIcon from '@mui/icons-material/TableViewRounded';
@@ -24,7 +23,6 @@ const TableButton = () => {
24
23
  icon={<TableViewRoundedIcon />}
25
24
  label="Insert table"
26
25
  />
27
- <VerticalDivider />
28
26
  </>
29
27
  );
30
28
  };
@@ -23,6 +23,7 @@ import { ImageExtension } from './ImageExtension/ImageExtension';
23
23
  import { CommandsExtension } from './CommandsExtension/CommandsExtension';
24
24
  import { EditorContextOptions } from '../Editor/EditorContext';
25
25
  import { AssetImageExtension } from './ImageExtension/AssetImageExtension';
26
+ import { DAMImageExtension } from './ImageExtension/DAMImageExtension';
26
27
  import { ExtendedCodeBlockExtension } from './CodeBlockExtension/CodeBlockExtension';
27
28
  import { ClearFormattingExtension } from './ClearFormattingExtension/ClearFormattingExtension';
28
29
  import { UnsupportedNodeExtension } from './UnsuportedExtension/UnsupportedNodeExtension';
@@ -34,6 +35,7 @@ export enum NodeName {
34
35
  Image = 'image',
35
36
  CodeBlock = 'codeBlock',
36
37
  AssetImage = 'assetImage',
38
+ DAMImage = 'DAMImage',
37
39
  Text = 'text',
38
40
  TableControllerCell = 'tableControllerCell',
39
41
  tableCell = 'tableCell',
@@ -66,6 +68,7 @@ export const createExtensions = (context: EditorContextOptions) => {
66
68
  new AssetImageExtension({
67
69
  matrixDomain: context.matrix.matrixDomain,
68
70
  }),
71
+ new DAMImageExtension(),
69
72
  new LinkExtension(),
70
73
  new AssetLinkExtension({
71
74
  matrixDomain: context.matrix.matrixDomain,
@@ -33,6 +33,15 @@ export class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
33
33
  );
34
34
  }
35
35
 
36
+ if (node.type.name === NodeName.DAMImage && node.attrs.src === '') {
37
+ promises.push(
38
+ this.fetchAndReplace(node.attrs, (url: string) => {
39
+ const newNode = state.schema.nodes[NodeName.DAMImage].create({ ...node.attrs, src: url });
40
+ tr.replaceWith(pos, pos + node.nodeSize, newNode);
41
+ }),
42
+ );
43
+ }
44
+
36
45
  const assetLinkMark = this.findAssetLinkMark(node.marks as Mark[]);
37
46
  if (node.type.name === 'text' && assetLinkMark) {
38
47
  promises.push(
@@ -0,0 +1,87 @@
1
+ import { renderWithEditor } from '../../../tests';
2
+
3
+ describe('DAMImageExtension', () => {
4
+ it('Parses HTML content representing an asset image', async () => {
5
+ const { getJsonContent } = await renderWithEditor(null, {
6
+ content: `<img
7
+ src="https://my-matrix.squiz.net/?a=this-is-actually-ignored"
8
+ data-dam-object-id="5ce8e8dc-1adc-4254-87a8-d1f5a5c9045a"
9
+ data-dam-system-identifier="byder001"
10
+ data-dam-system-type="bynder"
11
+ data-dam-additional='{"variant":"xyz"}'
12
+ />`,
13
+ });
14
+
15
+ expect(getJsonContent()).toEqual({
16
+ type: 'paragraph',
17
+ attrs: expect.any(Object),
18
+ content: [
19
+ {
20
+ type: 'DAMImage',
21
+ attrs: {
22
+ damObjectId: '5ce8e8dc-1adc-4254-87a8-d1f5a5c9045a',
23
+ damSystemIdentifier: 'byder001',
24
+ damSystemType: 'bynder',
25
+ damAdditional: {
26
+ variant: 'xyz',
27
+ },
28
+ src: 'https://default-resource/',
29
+ },
30
+ },
31
+ ],
32
+ });
33
+ });
34
+
35
+ it('Resolves to a regular image if HTML content is missing some of the expected attributes', async () => {
36
+ const { getJsonContent } = await renderWithEditor(null, {
37
+ content:
38
+ '<img src="https://my-matrix.squiz.net/?a=123" data-dam-object-id="5ce8e8dc-1adc-4254-87a8-d1f5a5c9045a" />',
39
+ });
40
+
41
+ expect(getJsonContent()).toEqual({
42
+ type: 'paragraph',
43
+ attrs: expect.any(Object),
44
+ content: [
45
+ {
46
+ type: 'image',
47
+ attrs: expect.objectContaining({
48
+ src: 'https://my-matrix.squiz.net/?a=123',
49
+ }),
50
+ },
51
+ ],
52
+ });
53
+ });
54
+
55
+ it('Outputs expected HTML', async () => {
56
+ const { getHtmlContent } = await renderWithEditor(null, {
57
+ content: {
58
+ type: 'paragraph',
59
+ content: [
60
+ {
61
+ type: 'DAMImage',
62
+ attrs: {
63
+ damObjectId: '5ce8e8dc-1adc-4254-87a8-d1f5a5c9045a',
64
+ damSystemIdentifier: 'byder001',
65
+ damSystemType: 'bynder',
66
+ damAdditional: {
67
+ variant: 'xyz',
68
+ },
69
+ },
70
+ },
71
+ ],
72
+ },
73
+ });
74
+
75
+ expect(getHtmlContent()).toEqual(
76
+ '<img ' +
77
+ 'src="https://default-resource/" ' +
78
+ 'data-dam-object-id="5ce8e8dc-1adc-4254-87a8-d1f5a5c9045a" ' +
79
+ 'data-dam-system-identifier="byder001" ' +
80
+ 'data-dam-system-type="bynder" ' +
81
+ 'data-dam-additional="{&quot;variant&quot;:&quot;xyz&quot;}" ' +
82
+ 'draggable="true">' +
83
+ '<img class="ProseMirror-separator" alt="">' +
84
+ '<br class="ProseMirror-trailingBreak">',
85
+ );
86
+ });
87
+ });
@@ -0,0 +1,119 @@
1
+ import {
2
+ ApplySchemaAttributes,
3
+ command,
4
+ extension,
5
+ ExtensionPriority,
6
+ ExtensionTag,
7
+ isElementDomNode,
8
+ NodeExtension,
9
+ NodeExtensionSpec,
10
+ NodeSpecOverride,
11
+ omitExtraAttributes,
12
+ CommandFunction,
13
+ } from '@remirror/core';
14
+ import { getTextSelection } from 'remirror';
15
+ import { NodeName } from '../Extensions';
16
+
17
+ export type DAMImageAttributes = {
18
+ damObjectId: string;
19
+ damSystemIdentifier: string;
20
+ damSystemType: string;
21
+ damAdditional?: {
22
+ variant: string;
23
+ };
24
+ url: string;
25
+ };
26
+
27
+ @extension({
28
+ defaultOptions: {},
29
+ defaultPriority: ExtensionPriority.High,
30
+ })
31
+ export class DAMImageExtension extends NodeExtension {
32
+ get name() {
33
+ return NodeName.DAMImage as const;
34
+ }
35
+
36
+ createTags() {
37
+ return [ExtensionTag.InlineNode, ExtensionTag.Media];
38
+ }
39
+
40
+ createNodeSpec(extra: ApplySchemaAttributes, override: NodeSpecOverride): NodeExtensionSpec {
41
+ return {
42
+ inline: true,
43
+ draggable: true,
44
+ selectable: true,
45
+ ...override,
46
+ attrs: {
47
+ ...extra.defaults(),
48
+ damObjectId: {},
49
+ damSystemIdentifier: {},
50
+ damSystemType: {},
51
+ damAdditional: { default: undefined },
52
+ src: { default: '' },
53
+ },
54
+ parseDOM: [
55
+ {
56
+ tag: 'img[data-dam-object-id]',
57
+ getAttrs: (node) => {
58
+ if (!isElementDomNode(node)) {
59
+ return false;
60
+ }
61
+
62
+ const damObjectId = node.getAttribute('data-dam-object-id');
63
+ const damSystemIdentifier = node.getAttribute('data-dam-system-identifier');
64
+ const damSystemType = node.getAttribute('data-dam-system-type');
65
+ let damAdditional = node.getAttribute('data-dam-additional') || undefined;
66
+
67
+ if (!damObjectId || !damSystemIdentifier || !damSystemType) {
68
+ return false;
69
+ }
70
+
71
+ if (damAdditional) {
72
+ damAdditional = JSON.parse(damAdditional);
73
+ }
74
+
75
+ return {
76
+ ...extra.parse(node),
77
+ damObjectId,
78
+ damSystemIdentifier,
79
+ damSystemType,
80
+ damAdditional,
81
+ src: '',
82
+ };
83
+ },
84
+ },
85
+ ],
86
+ toDOM: (node) => {
87
+ const { damObjectId, damSystemIdentifier, damSystemType, damAdditional, src, ...rest } = omitExtraAttributes(
88
+ node.attrs,
89
+ extra,
90
+ );
91
+
92
+ return [
93
+ 'img',
94
+ {
95
+ ...extra.dom(node),
96
+ ...rest,
97
+ src,
98
+ 'data-dam-object-id': damObjectId,
99
+ 'data-dam-system-identifier': damSystemIdentifier,
100
+ 'data-dam-system-type': damSystemType,
101
+ 'data-dam-additional': damAdditional ? JSON.stringify(damAdditional) : undefined,
102
+ },
103
+ ];
104
+ },
105
+ };
106
+ }
107
+
108
+ @command()
109
+ insertDAMImage(attrs: DAMImageAttributes): CommandFunction {
110
+ return ({ tr, dispatch }) => {
111
+ const { from, to } = getTextSelection(tr.selection, tr.doc);
112
+ const node = this.type.create({ ...attrs, src: attrs.url });
113
+
114
+ dispatch?.(tr.replaceRangeWith(from, to, node));
115
+
116
+ return true;
117
+ };
118
+ }
119
+ }
@@ -0,0 +1,140 @@
1
+ import React from 'react';
2
+
3
+ export const ICON_AI = (
4
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
5
+ <g id="sq-AI-icon" clipPath="url(#clip0_3060_44545)">
6
+ <g id="circle" filter="url(#filter0_dd_3060_44545)">
7
+ <rect x="2" y="2" width="20" height="20" rx="10" fill="url(#paint0_linear_3060_44545)" />
8
+ <rect x="1.5" y="1.5" width="21" height="21" rx="10.5" stroke="white" />
9
+ </g>
10
+ <g id="Vector" filter="url(#filter1_d_3060_44545)">
11
+ <path
12
+ d="M11.2084 9.79157L10.1219 7.39977C9.88269 6.86674 9.11731 6.86674 8.87813 7.39977L7.79157 9.79157L5.39977 10.8781C4.86674 11.1241 4.86674 11.8827 5.39977 12.1219L7.79157 13.2084L8.87813 15.6002C9.12415 16.1333 9.88269 16.1333 10.1219 15.6002L11.2084 13.2084L13.6002 12.1219C14.1333 11.8759 14.1333 11.1173 13.6002 10.8781L11.2084 9.79157Z"
13
+ fill="white"
14
+ />
15
+ </g>
16
+ <g id="Vector_2" opacity="0.8" filter="url(#filter2_d_3060_44545)">
17
+ <path
18
+ d="M17.4491 10.4491L16.8493 11.7779C16.7126 12.074 16.2874 12.074 16.1507 11.7779L15.5509 10.4415L14.2221 9.84169C13.926 9.70501 13.926 9.2874 14.2221 9.15072L15.5585 8.55087L16.1583 7.2221C16.295 6.92597 16.7126 6.92597 16.8493 7.2221L17.4491 8.55847L18.7779 9.15831C19.074 9.29499 19.074 9.7126 18.7779 9.84928L17.4491 10.4491Z"
19
+ fill="white"
20
+ />
21
+ </g>
22
+ <g id="Ellipse 55" opacity="0.8" filter="url(#filter3_d_3060_44545)">
23
+ <circle cx="14.5" cy="16.5" r="1.5" fill="white" />
24
+ </g>
25
+ </g>
26
+ <defs>
27
+ <filter
28
+ id="filter0_dd_3060_44545"
29
+ x="0"
30
+ y="0"
31
+ width="24"
32
+ height="24"
33
+ filterUnits="userSpaceOnUse"
34
+ colorInterpolationFilters="sRGB"
35
+ >
36
+ <feFlood floodOpacity="0" result="BackgroundImageFix" />
37
+ <feColorMatrix
38
+ in="SourceAlpha"
39
+ type="matrix"
40
+ values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
41
+ result="hardAlpha"
42
+ />
43
+ <feOffset dx="1" dy="1" />
44
+ <feComposite in2="hardAlpha" operator="out" />
45
+ <feColorMatrix type="matrix" values="0 0 0 0 0.2 0 0 0 0 0.709941 0 0 0 0 1 0 0 0 0.64 0" />
46
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3060_44545" />
47
+ <feColorMatrix
48
+ in="SourceAlpha"
49
+ type="matrix"
50
+ values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
51
+ result="hardAlpha"
52
+ />
53
+ <feOffset dx="-1" dy="-1" />
54
+ <feComposite in2="hardAlpha" operator="out" />
55
+ <feColorMatrix type="matrix" values="0 0 0 0 0.96 0 0 0 0 0.76 0 0 0 0 0.48 0 0 0 0.64 0" />
56
+ <feBlend mode="normal" in2="effect1_dropShadow_3060_44545" result="effect2_dropShadow_3060_44545" />
57
+ <feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_3060_44545" result="shape" />
58
+ </filter>
59
+ <filter
60
+ id="filter1_d_3060_44545"
61
+ x="5"
62
+ y="7"
63
+ width="9"
64
+ height="10"
65
+ filterUnits="userSpaceOnUse"
66
+ colorInterpolationFilters="sRGB"
67
+ >
68
+ <feFlood floodOpacity="0" result="BackgroundImageFix" />
69
+ <feColorMatrix
70
+ in="SourceAlpha"
71
+ type="matrix"
72
+ values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
73
+ result="hardAlpha"
74
+ />
75
+ <feOffset dy="1" />
76
+ <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0" />
77
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3060_44545" />
78
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3060_44545" result="shape" />
79
+ </filter>
80
+ <filter
81
+ id="filter2_d_3060_44545"
82
+ x="14"
83
+ y="7"
84
+ width="5"
85
+ height="6.30682"
86
+ filterUnits="userSpaceOnUse"
87
+ colorInterpolationFilters="sRGB"
88
+ >
89
+ <feFlood floodOpacity="0" result="BackgroundImageFix" />
90
+ <feColorMatrix
91
+ in="SourceAlpha"
92
+ type="matrix"
93
+ values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
94
+ result="hardAlpha"
95
+ />
96
+ <feOffset dy="1.30682" />
97
+ <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0" />
98
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3060_44545" />
99
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3060_44545" result="shape" />
100
+ </filter>
101
+ <filter
102
+ id="filter3_d_3060_44545"
103
+ x="13"
104
+ y="15"
105
+ width="3"
106
+ height="4.32373"
107
+ filterUnits="userSpaceOnUse"
108
+ colorInterpolationFilters="sRGB"
109
+ >
110
+ <feFlood floodOpacity="0" result="BackgroundImageFix" />
111
+ <feColorMatrix
112
+ in="SourceAlpha"
113
+ type="matrix"
114
+ values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
115
+ result="hardAlpha"
116
+ />
117
+ <feOffset dy="1.32373" />
118
+ <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0" />
119
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3060_44545" />
120
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3060_44545" result="shape" />
121
+ </filter>
122
+ <linearGradient
123
+ id="paint0_linear_3060_44545"
124
+ x1="6.54545"
125
+ y1="3.81818"
126
+ x2="17"
127
+ y2="20.1818"
128
+ gradientUnits="userSpaceOnUse"
129
+ >
130
+ <stop stopColor="#F5D6AB" />
131
+ <stop offset="0.251162" stopColor="#F895A7" />
132
+ <stop offset="0.585" stopColor="#77A1F1" />
133
+ <stop offset="0.861262" stopColor="#68C8FF" />
134
+ </linearGradient>
135
+ <clipPath id="clip0_3060_44545">
136
+ <rect width="24" height="24" fill="white" />
137
+ </clipPath>
138
+ </defs>
139
+ </svg>
140
+ );
package/src/index.scss CHANGED
@@ -3,6 +3,8 @@
3
3
  @import 'tailwindcss/components';
4
4
  @import 'tailwindcss/utilities';
5
5
 
6
+ @import '@squiz/sds/lib/package.css';
7
+
6
8
  /* So we can use icons inside of FTE content */
7
9
  @import 'https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded';
8
10
 
@@ -22,3 +24,5 @@
22
24
  @import './ui/CollapseBox/collapseBox';
23
25
 
24
26
  @import './ui/Modal/modal';
27
+
28
+ @import './EditorToolbar/Tools/ContentTools/content-tools';