@contentful/field-editor-markdown 1.15.4-canary.1 → 2.0.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 (36) hide show
  1. package/dist/cjs/MarkdownActions.spec.js +163 -0
  2. package/dist/cjs/MarkdownEditor.js +2 -2
  3. package/dist/cjs/components/HeadingSelector.js +9 -1
  4. package/dist/cjs/components/MarkdownBottomBar.js +3 -3
  5. package/dist/cjs/components/MarkdownConstraints.js +2 -2
  6. package/dist/cjs/components/MarkdownPreview.js +6 -6
  7. package/dist/cjs/components/MarkdownPreviewSkeleton.js +2 -2
  8. package/dist/cjs/components/MarkdownTabs.js +6 -6
  9. package/dist/cjs/components/MarkdownTextarea/MarkdownTextarea.js +6 -6
  10. package/dist/cjs/components/MarkdownToolbar.js +62 -32
  11. package/dist/cjs/components/MarkdownToolbar.spec.js +144 -0
  12. package/dist/cjs/components/icons.js +2 -2
  13. package/dist/cjs/dialogs/CheatsheetModalDialog.js +18 -18
  14. package/dist/cjs/dialogs/EmdebExternalContentDialog.js +4 -4
  15. package/dist/cjs/dialogs/SpecialCharacterModalDialog.js +8 -7
  16. package/dist/cjs/dialogs/ZenModeModalDialog.js +15 -15
  17. package/dist/esm/MarkdownActions.spec.js +159 -0
  18. package/dist/esm/MarkdownEditor.js +1 -1
  19. package/dist/esm/components/HeadingSelector.js +9 -1
  20. package/dist/esm/components/MarkdownBottomBar.js +1 -1
  21. package/dist/esm/components/MarkdownConstraints.js +1 -1
  22. package/dist/esm/components/MarkdownPreview.js +1 -1
  23. package/dist/esm/components/MarkdownPreviewSkeleton.js +1 -1
  24. package/dist/esm/components/MarkdownTabs.js +1 -1
  25. package/dist/esm/components/MarkdownTextarea/MarkdownTextarea.js +1 -1
  26. package/dist/esm/components/MarkdownToolbar.js +55 -25
  27. package/dist/esm/components/MarkdownToolbar.spec.js +94 -0
  28. package/dist/esm/components/icons.js +1 -1
  29. package/dist/esm/dialogs/CheatsheetModalDialog.js +1 -1
  30. package/dist/esm/dialogs/EmdebExternalContentDialog.js +1 -1
  31. package/dist/esm/dialogs/SpecialCharacterModalDialog.js +3 -2
  32. package/dist/esm/dialogs/ZenModeModalDialog.js +1 -1
  33. package/dist/types/MarkdownActions.spec.d.ts +1 -0
  34. package/dist/types/components/HeadingSelector.d.ts +4 -2
  35. package/dist/types/components/MarkdownToolbar.spec.d.ts +1 -0
  36. package/package.json +11 -10
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Skeleton } from '@contentful/f36-components';
3
3
  import tokens from '@contentful/f36-tokens';
4
- import { css } from 'emotion';
4
+ import { css } from '@emotion/css';
5
5
  const styles = {
6
6
  root: css({
7
7
  border: `1px solid ${tokens.gray400}`,
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import tokens from '@contentful/f36-tokens';
3
- import { css, cx } from 'emotion';
3
+ import { css, cx } from '@emotion/css';
4
4
  const styles = {
5
5
  root: css({
6
6
  display: 'flex',
@@ -1,6 +1,6 @@
1
1
  import React, { useRef, useEffect, useState } from 'react';
2
2
  import tokens from '@contentful/f36-tokens';
3
- import { css, cx } from 'emotion';
3
+ import { css, cx } from '@emotion/css';
4
4
  import { createMarkdownEditor } from './createMarkdownEditor';
5
5
  const styles = {
6
6
  root: css`
@@ -1,8 +1,8 @@
1
1
  import * as React from 'react';
2
- import { Flex, IconButton, Tooltip } from '@contentful/f36-components';
2
+ import { Flex, IconButton } from '@contentful/f36-components';
3
3
  import { CodeSimpleIcon, TextBIcon, TextItalicIcon, TextHIcon, MinusIcon, LinkSimpleIcon, ListBulletsIcon, ListNumbersIcon, DotsThreeIcon, QuotesIcon } from '@contentful/f36-icons';
4
4
  import tokens from '@contentful/f36-tokens';
5
- import { css, cx } from 'emotion';
5
+ import { css, cx } from '@emotion/css';
6
6
  import { HeadingSelector } from './HeadingSelector';
7
7
  import * as Icons from './icons';
8
8
  import { InsertLinkSelector } from './InsertLinkSelector';
@@ -33,27 +33,52 @@ const styles = {
33
33
  backgroundColor: tokens.gray400
34
34
  }),
35
35
  tooltip: css({
36
- zIndex: Number(tokens.zIndexTooltip)
36
+ zIndex: Number(tokens.zIndexTooltip),
37
+ pointerEvents: 'none'
37
38
  })
38
39
  };
39
40
  const ToolbarButton = /*#__PURE__*/ React.forwardRef((props, ref)=>{
40
41
  const { tooltip, onClick, children, className, variant = 'transparent', tooltipPlace = 'top', isDisabled = false, ...otherProps } = props;
41
- return /*#__PURE__*/ React.createElement(Tooltip, {
42
- className: styles.tooltip,
43
- usePortal: true,
44
- placement: tooltipPlace,
45
- content: tooltip
46
- }, /*#__PURE__*/ React.createElement(IconButton, {
42
+ const pointerTriggeredRef = React.useRef(false);
43
+ const handlePointerDown = React.useCallback((event)=>{
44
+ if (isDisabled || !onClick || event.button !== 0) {
45
+ return;
46
+ }
47
+ event.preventDefault();
48
+ pointerTriggeredRef.current = true;
49
+ onClick(event);
50
+ }, [
51
+ isDisabled,
52
+ onClick
53
+ ]);
54
+ const handleClick = React.useCallback((event)=>{
55
+ if (pointerTriggeredRef.current) {
56
+ pointerTriggeredRef.current = false;
57
+ return;
58
+ }
59
+ onClick?.(event);
60
+ }, [
61
+ onClick
62
+ ]);
63
+ return /*#__PURE__*/ React.createElement(IconButton, {
47
64
  ...otherProps,
48
65
  ref: ref,
49
66
  className: cx(styles.button, className),
50
67
  isDisabled: isDisabled,
51
- onClick: onClick,
68
+ onMouseDown: handlePointerDown,
69
+ onClick: handleClick,
52
70
  variant: variant,
53
71
  size: "small",
54
72
  icon: children,
55
- "aria-label": tooltip
56
- }));
73
+ "aria-label": tooltip,
74
+ withTooltip: true,
75
+ tooltipProps: {
76
+ className: styles.tooltip,
77
+ usePortal: true,
78
+ placement: tooltipPlace,
79
+ content: tooltip
80
+ }
81
+ });
57
82
  });
58
83
  ToolbarButton.displayName = 'ToolbarButton';
59
84
  function MainButtons(props) {
@@ -63,16 +88,19 @@ function MainButtons(props) {
63
88
  if (heading && props.actions.headings[heading]) {
64
89
  props.actions.headings[heading]();
65
90
  }
66
- }
67
- }, /*#__PURE__*/ React.createElement(ToolbarButton, {
68
- isDisabled: props.disabled,
69
- testId: "markdown-action-button-heading",
70
- tooltip: "Headings",
71
- tooltipPlace: tooltipPlace
72
- }, /*#__PURE__*/ React.createElement(TextHIcon, {
73
- "aria-label": "Headings",
74
- className: styles.icon
75
- }))), /*#__PURE__*/ React.createElement(ToolbarButton, {
91
+ },
92
+ renderTrigger: ({ isOpen, onClick })=>/*#__PURE__*/ React.createElement(ToolbarButton, {
93
+ isDisabled: props.disabled,
94
+ testId: "markdown-action-button-heading",
95
+ tooltip: "Headings",
96
+ tooltipPlace: tooltipPlace,
97
+ onClick: onClick,
98
+ "aria-expanded": isOpen
99
+ }, /*#__PURE__*/ React.createElement(TextHIcon, {
100
+ "aria-label": "Headings",
101
+ className: styles.icon
102
+ }))
103
+ }), /*#__PURE__*/ React.createElement(ToolbarButton, {
76
104
  isDisabled: props.disabled,
77
105
  testId: "markdown-action-button-bold",
78
106
  tooltip: "Bold",
@@ -233,6 +261,9 @@ function AdditionalButtons(props) {
233
261
  }
234
262
  export function DefaultMarkdownToolbar(props) {
235
263
  const [showAdditional, setShowAdditional] = React.useState(false);
264
+ const toggleAdditionalActions = React.useCallback(()=>{
265
+ setShowAdditional((current)=>!current);
266
+ }, []);
236
267
  return /*#__PURE__*/ React.createElement("div", {
237
268
  className: styles.root
238
269
  }, /*#__PURE__*/ React.createElement(Flex, {
@@ -244,9 +275,8 @@ export function DefaultMarkdownToolbar(props) {
244
275
  isDisabled: props.disabled,
245
276
  testId: "markdown-action-button-toggle-additional",
246
277
  tooltip: showAdditional ? 'Hide additional actions' : 'More actions',
247
- onClick: ()=>{
248
- setShowAdditional(!showAdditional);
249
- }
278
+ "aria-expanded": showAdditional,
279
+ onClick: toggleAdditionalActions
250
280
  }, /*#__PURE__*/ React.createElement(DotsThreeIcon, {
251
281
  className: styles.icon
252
282
  }))), /*#__PURE__*/ React.createElement(Flex, null, /*#__PURE__*/ React.createElement(InsertLinkSelector, {
@@ -0,0 +1,94 @@
1
+ import * as React from 'react';
2
+ import '@testing-library/jest-dom/extend-expect';
3
+ import { configure, render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { MarkdownToolbar } from './MarkdownToolbar';
6
+ configure({
7
+ testIdAttribute: 'data-test-id'
8
+ });
9
+ const createProps = ()=>({
10
+ canUploadAssets: true,
11
+ disabled: false,
12
+ mode: 'default',
13
+ actions: {
14
+ headings: {
15
+ h1: jest.fn(),
16
+ h2: jest.fn(),
17
+ h3: jest.fn()
18
+ },
19
+ simple: {
20
+ bold: jest.fn(),
21
+ italic: jest.fn(),
22
+ quote: jest.fn(),
23
+ ol: jest.fn(),
24
+ ul: jest.fn(),
25
+ strike: jest.fn(),
26
+ code: jest.fn(),
27
+ hr: jest.fn(),
28
+ indent: jest.fn(),
29
+ dedent: jest.fn()
30
+ },
31
+ history: {
32
+ undo: jest.fn(),
33
+ redo: jest.fn()
34
+ },
35
+ insertLink: jest.fn(),
36
+ insertSpecialCharacter: jest.fn(),
37
+ insertTable: jest.fn(),
38
+ organizeLinks: jest.fn(),
39
+ embedExternalContent: jest.fn(),
40
+ linkExistingMedia: jest.fn(),
41
+ addNewMedia: jest.fn(),
42
+ openZenMode: jest.fn(),
43
+ closeZenMode: jest.fn()
44
+ }
45
+ });
46
+ describe('MarkdownToolbar', ()=>{
47
+ beforeEach(()=>{
48
+ jest.clearAllMocks();
49
+ });
50
+ it('opens the heading menu and calls the selected heading action', async ()=>{
51
+ const props = createProps();
52
+ render(/*#__PURE__*/ React.createElement(MarkdownToolbar, props));
53
+ await userEvent.click(screen.getByRole('button', {
54
+ name: 'Headings'
55
+ }));
56
+ await userEvent.click(screen.getByRole('menuitem', {
57
+ name: 'Heading 1'
58
+ }));
59
+ expect(props.actions.headings.h1).toHaveBeenCalledTimes(1);
60
+ });
61
+ it('toggles additional actions and exposes the expanded state', async ()=>{
62
+ const props = createProps();
63
+ render(/*#__PURE__*/ React.createElement(MarkdownToolbar, props));
64
+ const toggle = screen.getByRole('button', {
65
+ name: 'More actions'
66
+ });
67
+ expect(toggle).toHaveAttribute('aria-expanded', 'false');
68
+ await userEvent.click(toggle);
69
+ expect(screen.getByRole('button', {
70
+ name: 'Hide additional actions'
71
+ })).toHaveAttribute('aria-expanded', 'true');
72
+ expect(screen.getByRole('button', {
73
+ name: 'Undo'
74
+ })).toBeInTheDocument();
75
+ });
76
+ it('triggers a toolbar action once per click', async ()=>{
77
+ const props = createProps();
78
+ render(/*#__PURE__*/ React.createElement(MarkdownToolbar, props));
79
+ await userEvent.click(screen.getByRole('button', {
80
+ name: 'Bold'
81
+ }));
82
+ expect(props.actions.simple.bold).toHaveBeenCalledTimes(1);
83
+ });
84
+ it('still supports keyboard-style activation', async ()=>{
85
+ const props = createProps();
86
+ render(/*#__PURE__*/ React.createElement(MarkdownToolbar, props));
87
+ const bold = screen.getByRole('button', {
88
+ name: 'Bold'
89
+ });
90
+ bold.focus();
91
+ await userEvent.keyboard('{Enter}');
92
+ expect(props.actions.simple.bold).toHaveBeenCalledTimes(1);
93
+ });
94
+ });
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { css } from 'emotion';
2
+ import { css } from '@emotion/css';
3
3
  const srOnly = css({
4
4
  position: 'absolute',
5
5
  width: '1px',
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { Heading, ModalContent, TextLink } from '@contentful/f36-components';
3
3
  import tokens from '@contentful/f36-tokens';
4
- import { css, cx } from 'emotion';
4
+ import { css, cx } from '@emotion/css';
5
5
  import { MarkdownDialogType } from '../types';
6
6
  const styles = {
7
7
  flexColumnContainer: css({
@@ -1,8 +1,8 @@
1
1
  import React, { useState, useRef, useEffect } from 'react';
2
2
  import { ModalContent, ModalControls, Text, TextLink, Button, Checkbox, Radio, Form, FormControl, TextInput } from '@contentful/f36-components';
3
3
  import tokens from '@contentful/f36-tokens';
4
+ import { css } from '@emotion/css';
4
5
  import { i18n as $_i18n } from "@lingui/core";
5
- import { css } from 'emotion';
6
6
  import { MarkdownDialogType } from '../types';
7
7
  import { isValidUrl } from '../utils/isValidUrl';
8
8
  const styles = {
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from 'react';
2
2
  import { ModalContent, ModalControls, Text, Flex, Button, Tooltip } from '@contentful/f36-components';
3
3
  import tokens from '@contentful/f36-tokens';
4
- import { css } from 'emotion';
4
+ import { css } from '@emotion/css';
5
5
  import { MarkdownDialogType } from '../types';
6
6
  import { specialCharacters } from '../utils/specialCharacters';
7
7
  const styles = {
@@ -21,7 +21,8 @@ const styles = {
21
21
  backgroundColor: tokens.gray100
22
22
  }),
23
23
  tooltip: css({
24
- zIndex: 1000
24
+ zIndex: 1000,
25
+ pointerEvents: 'none'
25
26
  }),
26
27
  button: css({
27
28
  marginTop: tokens.spacingM,
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
  import { Grid } from '@contentful/f36-components';
3
3
  import { CaretLeftIcon, CaretRightIcon } from '@contentful/f36-icons';
4
4
  import tokens from '@contentful/f36-tokens';
5
- import { css, cx } from 'emotion';
5
+ import { css, cx } from '@emotion/css';
6
6
  import { MarkdownBottomBar, MarkdownHelp } from '../components/MarkdownBottomBar';
7
7
  import MarkdownPreviewSkeleton from '../components/MarkdownPreviewSkeleton';
8
8
  import { MarkdownTextarea } from '../components/MarkdownTextarea/MarkdownTextarea';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import { HeadingType } from '../types';
3
3
  export declare const HeadingSelector: (props: {
4
- children: React.ReactElement;
5
4
  onSelect: (heading: HeadingType) => void;
6
- tooltip?: string | undefined;
5
+ renderTrigger: (props: {
6
+ isOpen: boolean;
7
+ onClick: () => void;
8
+ }) => React.ReactElement;
7
9
  }) => React.JSX.Element;
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/extend-expect';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/field-editor-markdown",
3
- "version": "1.15.4-canary.1+82cb101d",
3
+ "version": "2.0.0",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -36,13 +36,13 @@
36
36
  "tsc": "tsc -p ./ --noEmit"
37
37
  },
38
38
  "dependencies": {
39
- "@contentful/f36-components": "^5.4.1",
40
- "@contentful/f36-icons": "^5.4.1",
41
- "@contentful/f36-tokens": "^5.1.0",
42
- "@contentful/field-editor-shared": "^3.0.1-canary.1+82cb101d",
39
+ "@contentful/f36-components": "^6.7.1",
40
+ "@contentful/f36-icons": "^6.7.1",
41
+ "@contentful/f36-tokens": "^6.1.2",
42
+ "@contentful/field-editor-shared": "^3.0.0",
43
+ "@emotion/css": "^11.13.5",
43
44
  "@types/codemirror": "0.0.109",
44
45
  "codemirror": "^5.65.11",
45
- "emotion": "^10.0.17",
46
46
  "lodash": "^4.17.15",
47
47
  "react-markdown": "^9.0.1",
48
48
  "rehype-raw": "^7.0.0",
@@ -52,16 +52,17 @@
52
52
  "devDependencies": {
53
53
  "@babel/core": "^7.5.5",
54
54
  "@contentful/app-sdk": "^4.29.0",
55
- "@contentful/field-editor-test-utils": "^1.8.1-canary.49+82cb101d",
56
- "@lingui/core": "5.3.0"
55
+ "@contentful/field-editor-test-utils": "^1.8.0",
56
+ "@lingui/core": "5.3.0",
57
+ "@testing-library/user-event": "^13.1.9"
57
58
  },
58
59
  "peerDependencies": {
59
60
  "@contentful/app-sdk": "^4.29.0",
60
61
  "@lingui/core": "^5.3.0",
61
- "react": ">=16.8.0"
62
+ "react": ">=18.3.1"
62
63
  },
63
64
  "publishConfig": {
64
65
  "registry": "https://npm.pkg.github.com/"
65
66
  },
66
- "gitHead": "82cb101d676beeda9178245deec48f983d664f88"
67
+ "gitHead": "edde3372ed00ed0fd6a798233284618983383869"
67
68
  }