@contentful/field-editor-rich-text 3.12.0 → 3.12.2

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.
@@ -90,28 +90,39 @@ const Asset = ({ command , selectedItem })=>_react.createElement("button", {
90
90
  }, _react.createElement(_f36components.Flex, {
91
91
  alignItems: "center",
92
92
  gap: "spacingS"
93
- }, command.thumbnail && _react.createElement("img", {
93
+ }, command.thumbnail ? _react.createElement("img", {
94
94
  width: "30",
95
95
  height: "30",
96
96
  src: command.thumbnail,
97
97
  alt: "",
98
98
  className: _CommandListstyles.default.thumbnail
99
+ }) : _react.createElement(_f36components.AssetIcon, {
100
+ width: "30",
101
+ height: "30",
102
+ className: _CommandListstyles.default.thumbnail
99
103
  }), _react.createElement("span", null, command.label)));
100
- const Item = ({ command })=>_react.createElement("button", {
104
+ const Item = ({ command , selectedItem })=>_react.createElement("button", {
101
105
  key: command.id,
102
106
  id: command.id,
103
- className: _CommandListstyles.default.menuItem
107
+ className: (0, _emotion.cx)(_CommandListstyles.default.menuItem, {
108
+ [_CommandListstyles.default.menuItemSelected]: command.id === selectedItem
109
+ }),
110
+ onClick: command.callback
104
111
  }, command.label);
105
112
  const CommandListItems = ({ commandItems , selectedItem })=>{
106
113
  return _react.createElement(_react.Fragment, null, commandItems.map((command)=>{
107
114
  return 'group' in command ? _react.createElement(Group, {
115
+ key: command.group,
108
116
  commandGroup: command,
109
117
  selectedItem: selectedItem
110
- }) : command.callback ? _react.createElement(Asset, {
118
+ }) : command.asset ? _react.createElement(Asset, {
119
+ key: command.id,
111
120
  command: command,
112
121
  selectedItem: selectedItem
113
122
  }) : _react.createElement(Item, {
114
- command: command
123
+ key: command.id,
124
+ command: command,
125
+ selectedItem: selectedItem
115
126
  });
116
127
  }));
117
128
  };
@@ -9,8 +9,15 @@ Object.defineProperty(exports, "CommandPrompt", {
9
9
  }
10
10
  });
11
11
  const _react = _interop_require_wildcard(require("react"));
12
+ const _f36tokens = _interop_require_default(require("@contentful/f36-tokens"));
13
+ const _emotion = require("emotion");
12
14
  const _trimLeadingSlash = require("../utils/trimLeadingSlash");
13
15
  const _CommandList = require("./CommandList");
16
+ function _interop_require_default(obj) {
17
+ return obj && obj.__esModule ? obj : {
18
+ default: obj
19
+ };
20
+ }
14
21
  function _getRequireWildcardCache(nodeInterop) {
15
22
  if (typeof WeakMap !== "function") return null;
16
23
  var cacheBabelInterop = new WeakMap();
@@ -50,6 +57,11 @@ function _interop_require_wildcard(obj, nodeInterop) {
50
57
  }
51
58
  return newObj;
52
59
  }
60
+ const styles = {
61
+ commandPrompt: (0, _emotion.css)({
62
+ color: _f36tokens.default.blue400
63
+ })
64
+ };
53
65
  const CommandPrompt = (props)=>{
54
66
  const query = _react.useMemo(()=>(0, _trimLeadingSlash.trimLeadingSlash)(props.text.text), [
55
67
  props.text.text
@@ -57,6 +69,7 @@ const CommandPrompt = (props)=>{
57
69
  const editor = props.editor;
58
70
  const [textElement, setTextElement] = _react.useState();
59
71
  return _react.createElement("span", {
72
+ className: styles.commandPrompt,
60
73
  ref: (e)=>{
61
74
  setTextElement(e);
62
75
  },
@@ -64,9 +64,37 @@ function getCommandPermissions(sdk, editor) {
64
64
  assetsAllowed: assetsAllowed && canInsertBlocks
65
65
  };
66
66
  }
67
+ const getAllowedContentTypesFromValidation = (validations)=>{
68
+ const types = [
69
+ _richtexttypes.BLOCKS.EMBEDDED_ENTRY,
70
+ _richtexttypes.INLINES.EMBEDDED_ENTRY
71
+ ];
72
+ return validations.reduce((acc, validation)=>{
73
+ types.forEach((type)=>{
74
+ const linkContentTypes = validation.nodes?.[type]?.[0]?.linkContentType;
75
+ if (linkContentTypes) {
76
+ if (!acc[type]) {
77
+ acc[type] = {};
78
+ }
79
+ linkContentTypes.forEach((contentType)=>{
80
+ acc[type][contentType] = true;
81
+ });
82
+ }
83
+ });
84
+ return acc;
85
+ }, {});
86
+ };
67
87
  const useCommands = (sdk, query, editor)=>{
68
88
  const contentTypes = sdk.space.getCachedContentTypes();
69
89
  const { inlineAllowed , entriesAllowed , assetsAllowed } = getCommandPermissions(sdk, editor);
90
+ const allowedContentTypesFromValidation = getAllowedContentTypesFromValidation(sdk.field.validations);
91
+ const filterContentTypesByValidation = (type)=>contentTypes.filter((contentType)=>allowedContentTypesFromValidation[type]?.[contentType.sys.id]);
92
+ const filteredBlockContentTypes = filterContentTypesByValidation(_richtexttypes.BLOCKS.EMBEDDED_ENTRY);
93
+ const filteredInlineContentTypes = filterContentTypesByValidation(_richtexttypes.INLINES.EMBEDDED_ENTRY);
94
+ const getContentTypeToUse = (allowed, isFiltered, filteredTypes)=>allowed ? isFiltered ? filteredTypes : contentTypes : [];
95
+ const blockContentTypesToUse = getContentTypeToUse(entriesAllowed, filteredBlockContentTypes.length > 0, filteredBlockContentTypes);
96
+ const inlineContentTypesToUse = getContentTypeToUse(inlineAllowed, filteredInlineContentTypes.length > 0, filteredInlineContentTypes);
97
+ const relevantContentTypes = contentTypes.filter((ct)=>blockContentTypesToUse.includes(ct) || inlineContentTypesToUse.includes(ct));
70
98
  const [commands, setCommands] = (0, _react.useState)(()=>{
71
99
  const getEmbedEntry = (contentType)=>{
72
100
  return {
@@ -83,16 +111,16 @@ const useCommands = (sdk, query, editor)=>{
83
111
  }
84
112
  ]);
85
113
  } else {
86
- setCommands(entries.map((entry)=>{
114
+ setCommands(entries.map((ct)=>{
87
115
  return {
88
- id: `${entry.id}-${entry.displayTitle.replace(/\W+/g, '-').toLowerCase()}`,
89
- label: entry.displayTitle,
116
+ id: ct.entry.sys.id,
117
+ label: ct.displayTitle,
90
118
  callback: ()=>{
91
119
  removeCommand(editor);
92
120
  if (editor.selection) {
93
121
  const selection = editor.selection;
94
122
  editor.insertSoftBreak();
95
- (0, _insertBlock.insertBlock)(editor, _richtexttypes.BLOCKS.EMBEDDED_ENTRY, entry.entry);
123
+ (0, _insertBlock.insertBlock)(editor, _richtexttypes.BLOCKS.EMBEDDED_ENTRY, ct.entry);
96
124
  (0, _transforms.select)(editor, selection);
97
125
  editor.tracking.onCommandPaletteAction('insert', {
98
126
  nodeType: _richtexttypes.BLOCKS.EMBEDDED_ENTRY
@@ -121,12 +149,12 @@ const useCommands = (sdk, query, editor)=>{
121
149
  }
122
150
  ]);
123
151
  } else {
124
- setCommands(entries.map((entry)=>{
152
+ setCommands(entries.map((ct)=>{
125
153
  return {
126
- id: `${entry.id}-${entry.displayTitle.replace(/\W+/g, '-').toLowerCase()}`,
127
- label: entry.displayTitle,
154
+ id: ct.entry.sys.id,
155
+ label: ct.displayTitle,
128
156
  callback: ()=>{
129
- const inlineNode = (0, _createInlineEntryNode.createInlineEntryNode)(entry.id);
157
+ const inlineNode = (0, _createInlineEntryNode.createInlineEntryNode)(ct.entry.sys.id);
130
158
  removeCommand(editor);
131
159
  (0, _transforms.insertNodes)(editor, inlineNode);
132
160
  editor.insertText('');
@@ -141,17 +169,19 @@ const useCommands = (sdk, query, editor)=>{
141
169
  }
142
170
  };
143
171
  };
144
- const contentTypeCommands = entriesAllowed || inlineAllowed ? contentTypes.map((contentType)=>{
172
+ const contentTypeCommands = entriesAllowed || inlineAllowed ? relevantContentTypes.map((contentType)=>{
173
+ const blockEmbedAllowed = blockContentTypesToUse.some((ct)=>ct.sys.id === contentType.sys.id);
174
+ const inlineEmbedAllowed = inlineContentTypesToUse.some((ct)=>ct.sys.id === contentType.sys.id);
175
+ const commands = [];
176
+ if (entriesAllowed && blockEmbedAllowed) {
177
+ commands.push(getEmbedEntry(contentType));
178
+ }
179
+ if (inlineAllowed && inlineEmbedAllowed) {
180
+ commands.push(getEmbedInline(contentType));
181
+ }
145
182
  return {
146
183
  group: contentType.name,
147
- commands: entriesAllowed && inlineAllowed ? [
148
- getEmbedEntry(contentType),
149
- getEmbedInline(contentType)
150
- ] : entriesAllowed ? [
151
- getEmbedEntry(contentType)
152
- ] : [
153
- getEmbedInline(contentType)
154
- ]
184
+ commands: commands
155
185
  };
156
186
  }) : [];
157
187
  if (assetsAllowed) {
@@ -174,9 +204,10 @@ const useCommands = (sdk, query, editor)=>{
174
204
  } else {
175
205
  setCommands(assets.map((asset)=>{
176
206
  return {
177
- id: `${asset.id}-${asset.displayTitle.replace(/\W+/g, '-').toLowerCase()}`,
207
+ id: asset.entity.sys.id,
178
208
  label: asset.displayTitle,
179
209
  thumbnail: asset.thumbnail,
210
+ asset: true,
180
211
  callback: ()=>{
181
212
  removeCommand(editor);
182
213
  if (editor.selection) {
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { usePopper } from 'react-popper';
3
- import { Popover, Stack, SectionHeading, ScreenReaderOnly, Flex } from '@contentful/f36-components';
3
+ import { Popover, Stack, SectionHeading, ScreenReaderOnly, Flex, AssetIcon } from '@contentful/f36-components';
4
4
  import { Portal } from '@contentful/f36-utils';
5
5
  import { cx } from 'emotion';
6
6
  import { useSdkContext } from '../../../SdkProvider';
@@ -36,28 +36,39 @@ const Asset = ({ command , selectedItem })=>React.createElement("button", {
36
36
  }, React.createElement(Flex, {
37
37
  alignItems: "center",
38
38
  gap: "spacingS"
39
- }, command.thumbnail && React.createElement("img", {
39
+ }, command.thumbnail ? React.createElement("img", {
40
40
  width: "30",
41
41
  height: "30",
42
42
  src: command.thumbnail,
43
43
  alt: "",
44
44
  className: styles.thumbnail
45
+ }) : React.createElement(AssetIcon, {
46
+ width: "30",
47
+ height: "30",
48
+ className: styles.thumbnail
45
49
  }), React.createElement("span", null, command.label)));
46
- const Item = ({ command })=>React.createElement("button", {
50
+ const Item = ({ command , selectedItem })=>React.createElement("button", {
47
51
  key: command.id,
48
52
  id: command.id,
49
- className: styles.menuItem
53
+ className: cx(styles.menuItem, {
54
+ [styles.menuItemSelected]: command.id === selectedItem
55
+ }),
56
+ onClick: command.callback
50
57
  }, command.label);
51
58
  const CommandListItems = ({ commandItems , selectedItem })=>{
52
59
  return React.createElement(React.Fragment, null, commandItems.map((command)=>{
53
60
  return 'group' in command ? React.createElement(Group, {
61
+ key: command.group,
54
62
  commandGroup: command,
55
63
  selectedItem: selectedItem
56
- }) : command.callback ? React.createElement(Asset, {
64
+ }) : command.asset ? React.createElement(Asset, {
65
+ key: command.id,
57
66
  command: command,
58
67
  selectedItem: selectedItem
59
68
  }) : React.createElement(Item, {
60
- command: command
69
+ key: command.id,
70
+ command: command,
71
+ selectedItem: selectedItem
61
72
  });
62
73
  }));
63
74
  };
@@ -1,6 +1,13 @@
1
1
  import * as React from 'react';
2
+ import tokens from '@contentful/f36-tokens';
3
+ import { css } from 'emotion';
2
4
  import { trimLeadingSlash } from '../utils/trimLeadingSlash';
3
5
  import { CommandList } from './CommandList';
6
+ const styles = {
7
+ commandPrompt: css({
8
+ color: tokens.blue400
9
+ })
10
+ };
4
11
  export const CommandPrompt = (props)=>{
5
12
  const query = React.useMemo(()=>trimLeadingSlash(props.text.text), [
6
13
  props.text.text
@@ -8,6 +15,7 @@ export const CommandPrompt = (props)=>{
8
15
  const editor = props.editor;
9
16
  const [textElement, setTextElement] = React.useState();
10
17
  return React.createElement("span", {
18
+ className: styles.commandPrompt,
11
19
  ref: (e)=>{
12
20
  setTextElement(e);
13
21
  },
@@ -46,9 +46,37 @@ function getCommandPermissions(sdk, editor) {
46
46
  assetsAllowed: assetsAllowed && canInsertBlocks
47
47
  };
48
48
  }
49
+ const getAllowedContentTypesFromValidation = (validations)=>{
50
+ const types = [
51
+ BLOCKS.EMBEDDED_ENTRY,
52
+ INLINES.EMBEDDED_ENTRY
53
+ ];
54
+ return validations.reduce((acc, validation)=>{
55
+ types.forEach((type)=>{
56
+ const linkContentTypes = validation.nodes?.[type]?.[0]?.linkContentType;
57
+ if (linkContentTypes) {
58
+ if (!acc[type]) {
59
+ acc[type] = {};
60
+ }
61
+ linkContentTypes.forEach((contentType)=>{
62
+ acc[type][contentType] = true;
63
+ });
64
+ }
65
+ });
66
+ return acc;
67
+ }, {});
68
+ };
49
69
  export const useCommands = (sdk, query, editor)=>{
50
70
  const contentTypes = sdk.space.getCachedContentTypes();
51
71
  const { inlineAllowed , entriesAllowed , assetsAllowed } = getCommandPermissions(sdk, editor);
72
+ const allowedContentTypesFromValidation = getAllowedContentTypesFromValidation(sdk.field.validations);
73
+ const filterContentTypesByValidation = (type)=>contentTypes.filter((contentType)=>allowedContentTypesFromValidation[type]?.[contentType.sys.id]);
74
+ const filteredBlockContentTypes = filterContentTypesByValidation(BLOCKS.EMBEDDED_ENTRY);
75
+ const filteredInlineContentTypes = filterContentTypesByValidation(INLINES.EMBEDDED_ENTRY);
76
+ const getContentTypeToUse = (allowed, isFiltered, filteredTypes)=>allowed ? isFiltered ? filteredTypes : contentTypes : [];
77
+ const blockContentTypesToUse = getContentTypeToUse(entriesAllowed, filteredBlockContentTypes.length > 0, filteredBlockContentTypes);
78
+ const inlineContentTypesToUse = getContentTypeToUse(inlineAllowed, filteredInlineContentTypes.length > 0, filteredInlineContentTypes);
79
+ const relevantContentTypes = contentTypes.filter((ct)=>blockContentTypesToUse.includes(ct) || inlineContentTypesToUse.includes(ct));
52
80
  const [commands, setCommands] = useState(()=>{
53
81
  const getEmbedEntry = (contentType)=>{
54
82
  return {
@@ -65,16 +93,16 @@ export const useCommands = (sdk, query, editor)=>{
65
93
  }
66
94
  ]);
67
95
  } else {
68
- setCommands(entries.map((entry)=>{
96
+ setCommands(entries.map((ct)=>{
69
97
  return {
70
- id: `${entry.id}-${entry.displayTitle.replace(/\W+/g, '-').toLowerCase()}`,
71
- label: entry.displayTitle,
98
+ id: ct.entry.sys.id,
99
+ label: ct.displayTitle,
72
100
  callback: ()=>{
73
101
  removeCommand(editor);
74
102
  if (editor.selection) {
75
103
  const selection = editor.selection;
76
104
  editor.insertSoftBreak();
77
- insertBlock(editor, BLOCKS.EMBEDDED_ENTRY, entry.entry);
105
+ insertBlock(editor, BLOCKS.EMBEDDED_ENTRY, ct.entry);
78
106
  select(editor, selection);
79
107
  editor.tracking.onCommandPaletteAction('insert', {
80
108
  nodeType: BLOCKS.EMBEDDED_ENTRY
@@ -103,12 +131,12 @@ export const useCommands = (sdk, query, editor)=>{
103
131
  }
104
132
  ]);
105
133
  } else {
106
- setCommands(entries.map((entry)=>{
134
+ setCommands(entries.map((ct)=>{
107
135
  return {
108
- id: `${entry.id}-${entry.displayTitle.replace(/\W+/g, '-').toLowerCase()}`,
109
- label: entry.displayTitle,
136
+ id: ct.entry.sys.id,
137
+ label: ct.displayTitle,
110
138
  callback: ()=>{
111
- const inlineNode = createInlineEntryNode(entry.id);
139
+ const inlineNode = createInlineEntryNode(ct.entry.sys.id);
112
140
  removeCommand(editor);
113
141
  insertNodes(editor, inlineNode);
114
142
  editor.insertText('');
@@ -123,17 +151,19 @@ export const useCommands = (sdk, query, editor)=>{
123
151
  }
124
152
  };
125
153
  };
126
- const contentTypeCommands = entriesAllowed || inlineAllowed ? contentTypes.map((contentType)=>{
154
+ const contentTypeCommands = entriesAllowed || inlineAllowed ? relevantContentTypes.map((contentType)=>{
155
+ const blockEmbedAllowed = blockContentTypesToUse.some((ct)=>ct.sys.id === contentType.sys.id);
156
+ const inlineEmbedAllowed = inlineContentTypesToUse.some((ct)=>ct.sys.id === contentType.sys.id);
157
+ const commands = [];
158
+ if (entriesAllowed && blockEmbedAllowed) {
159
+ commands.push(getEmbedEntry(contentType));
160
+ }
161
+ if (inlineAllowed && inlineEmbedAllowed) {
162
+ commands.push(getEmbedInline(contentType));
163
+ }
127
164
  return {
128
165
  group: contentType.name,
129
- commands: entriesAllowed && inlineAllowed ? [
130
- getEmbedEntry(contentType),
131
- getEmbedInline(contentType)
132
- ] : entriesAllowed ? [
133
- getEmbedEntry(contentType)
134
- ] : [
135
- getEmbedInline(contentType)
136
- ]
166
+ commands: commands
137
167
  };
138
168
  }) : [];
139
169
  if (assetsAllowed) {
@@ -156,9 +186,10 @@ export const useCommands = (sdk, query, editor)=>{
156
186
  } else {
157
187
  setCommands(assets.map((asset)=>{
158
188
  return {
159
- id: `${asset.id}-${asset.displayTitle.replace(/\W+/g, '-').toLowerCase()}`,
189
+ id: asset.entity.sys.id,
160
190
  label: asset.displayTitle,
161
191
  thumbnail: asset.thumbnail,
192
+ asset: true,
162
193
  callback: ()=>{
163
194
  removeCommand(editor);
164
195
  if (editor.selection) {
@@ -5,6 +5,7 @@ export interface Command {
5
5
  thumbnail?: string;
6
6
  label: string;
7
7
  callback?: () => void;
8
+ asset?: boolean;
8
9
  }
9
10
  export interface CommandGroup {
10
11
  group: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/field-editor-rich-text",
3
- "version": "3.12.0",
3
+ "version": "3.12.2",
4
4
  "source": "./src/index.tsx",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -81,5 +81,5 @@
81
81
  "prism-react-renderer": "2.0.5",
82
82
  "react": ">=16.14.0"
83
83
  },
84
- "gitHead": "277d527c724264183021838773059048750f9266"
84
+ "gitHead": "9bad77d2a0b24a95df55d6af5057d84a68a7c880"
85
85
  }