@atlaskit/editor-plugin-block-menu 3.2.0 → 3.2.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.
- package/CHANGELOG.md +18 -0
- package/dist/cjs/blockMenuPlugin.js +5 -2
- package/dist/cjs/editor-commands/formatNode.js +48 -1
- package/dist/cjs/editor-commands/transforms/container-transforms.js +15 -4
- package/dist/cjs/editor-commands/transforms/layout-transforms.js +29 -18
- package/dist/cjs/editor-commands/transforms/utils.js +20 -1
- package/dist/cjs/ui/block-menu-components.js +5 -14
- package/dist/cjs/ui/block-menu-provider.js +40 -0
- package/dist/cjs/ui/block-menu.js +14 -4
- package/dist/cjs/ui/copy-block.js +11 -3
- package/dist/cjs/ui/copy-section.js +7 -0
- package/dist/cjs/ui/delete-section.js +23 -0
- package/dist/cjs/ui/format-menu-nested.js +28 -0
- package/dist/cjs/ui/utils/checkIsFormatMenuHidden.js +35 -1
- package/dist/es2019/blockMenuPlugin.js +5 -2
- package/dist/es2019/editor-commands/formatNode.js +53 -2
- package/dist/es2019/editor-commands/transforms/container-transforms.js +18 -5
- package/dist/es2019/editor-commands/transforms/layout-transforms.js +22 -11
- package/dist/es2019/editor-commands/transforms/utils.js +13 -0
- package/dist/es2019/ui/block-menu-components.js +6 -15
- package/dist/es2019/ui/block-menu-provider.js +31 -0
- package/dist/es2019/ui/block-menu.js +15 -4
- package/dist/es2019/ui/copy-block.js +9 -3
- package/dist/es2019/ui/copy-section.js +7 -0
- package/dist/es2019/ui/delete-section.js +17 -0
- package/dist/es2019/ui/format-menu-nested.js +23 -0
- package/dist/es2019/ui/utils/checkIsFormatMenuHidden.js +35 -1
- package/dist/esm/blockMenuPlugin.js +5 -2
- package/dist/esm/editor-commands/formatNode.js +49 -2
- package/dist/esm/editor-commands/transforms/container-transforms.js +16 -5
- package/dist/esm/editor-commands/transforms/layout-transforms.js +28 -17
- package/dist/esm/editor-commands/transforms/utils.js +18 -0
- package/dist/esm/ui/block-menu-components.js +6 -15
- package/dist/esm/ui/block-menu-provider.js +32 -0
- package/dist/esm/ui/block-menu.js +14 -4
- package/dist/esm/ui/copy-block.js +11 -3
- package/dist/esm/ui/copy-section.js +7 -0
- package/dist/esm/ui/delete-section.js +16 -0
- package/dist/esm/ui/format-menu-nested.js +21 -0
- package/dist/esm/ui/utils/checkIsFormatMenuHidden.js +35 -1
- package/dist/types/editor-commands/transforms/layout-transforms.d.ts +3 -0
- package/dist/types/editor-commands/transforms/utils.d.ts +2 -1
- package/dist/types/ui/block-menu-components.d.ts +1 -2
- package/dist/types/ui/block-menu-provider.d.ts +18 -0
- package/dist/types/ui/block-menu.d.ts +1 -1
- package/dist/types/ui/copy-section.d.ts +1 -1
- package/dist/types/ui/delete-section.d.ts +7 -0
- package/dist/types/ui/format-menu-nested.d.ts +4 -0
- package/dist/types-ts4.5/editor-commands/transforms/layout-transforms.d.ts +3 -0
- package/dist/types-ts4.5/editor-commands/transforms/utils.d.ts +2 -1
- package/dist/types-ts4.5/ui/block-menu-components.d.ts +1 -2
- package/dist/types-ts4.5/ui/block-menu-provider.d.ts +18 -0
- package/dist/types-ts4.5/ui/block-menu.d.ts +1 -1
- package/dist/types-ts4.5/ui/copy-section.d.ts +1 -1
- package/dist/types-ts4.5/ui/delete-section.d.ts +7 -0
- package/dist/types-ts4.5/ui/format-menu-nested.d.ts +4 -0
- package/package.json +4 -4
|
@@ -1,5 +1,50 @@
|
|
|
1
|
-
import { findParentNodeOfType, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
1
|
+
import { findParentNodeOfType, findSelectedNodeOfType, safeInsert as pmSafeInsert } from '@atlaskit/editor-prosemirror/utils';
|
|
2
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
3
|
+
import { createDefaultLayoutSection } from './transforms/layout-transforms';
|
|
2
4
|
import { transformNodeToTargetType } from './transforms/transformNodeToTargetType';
|
|
5
|
+
/**
|
|
6
|
+
* Handles formatting when selection is empty by inserting a new target node
|
|
7
|
+
*/
|
|
8
|
+
const formatNodeWhenSelectionEmpty = (tr, targetType, nodePos, schema) => {
|
|
9
|
+
var _pmSafeInsert;
|
|
10
|
+
const {
|
|
11
|
+
nodes
|
|
12
|
+
} = schema;
|
|
13
|
+
const {
|
|
14
|
+
paragraph
|
|
15
|
+
} = nodes;
|
|
16
|
+
// if not using the ' ' here, the safeInsert from editor-common will fail to insert the heading
|
|
17
|
+
// and the pmSafeInsert introduce an issue that after inserting heading, and click on the handle, selection will go to top of the doc
|
|
18
|
+
// as an workaround, use the spaceTextNode here
|
|
19
|
+
const spaceTextNode = schema.text(' ');
|
|
20
|
+
let targetNode;
|
|
21
|
+
if (targetType.startsWith('heading')) {
|
|
22
|
+
const levelString = targetType.slice(-1);
|
|
23
|
+
const level = parseInt(levelString, 10);
|
|
24
|
+
if (isNaN(level) || level < 1 || level > 6) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
targetNode = nodes.heading.createAndFill({
|
|
28
|
+
level
|
|
29
|
+
}, spaceTextNode);
|
|
30
|
+
} else if (targetType === 'paragraph') {
|
|
31
|
+
targetNode = nodes.paragraph.createAndFill({}, spaceTextNode);
|
|
32
|
+
} else if (targetType === 'layoutSection') {
|
|
33
|
+
const contentAsParagraph = paragraph.createAndFill({}, spaceTextNode);
|
|
34
|
+
if (contentAsParagraph) {
|
|
35
|
+
targetNode = createDefaultLayoutSection(schema, contentAsParagraph);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
const targetNodeType = nodes[targetType];
|
|
39
|
+
targetNode = targetNodeType.createAndFill();
|
|
40
|
+
}
|
|
41
|
+
if (!targetNode) {
|
|
42
|
+
return tr;
|
|
43
|
+
}
|
|
44
|
+
tr = (_pmSafeInsert = pmSafeInsert(targetNode, nodePos)(tr)) !== null && _pmSafeInsert !== void 0 ? _pmSafeInsert : tr;
|
|
45
|
+
return tr;
|
|
46
|
+
};
|
|
47
|
+
|
|
3
48
|
/**
|
|
4
49
|
* Formats the current node or selection to the specified target type
|
|
5
50
|
* @param targetType - The target node type to convert to
|
|
@@ -11,14 +56,20 @@ export const formatNode = targetType => {
|
|
|
11
56
|
const {
|
|
12
57
|
selection
|
|
13
58
|
} = tr;
|
|
59
|
+
const schema = tr.doc.type.schema;
|
|
14
60
|
const {
|
|
15
61
|
nodes
|
|
16
|
-
} =
|
|
62
|
+
} = schema;
|
|
17
63
|
|
|
18
64
|
// Find the node to format from the current selection
|
|
19
65
|
let nodeToFormat;
|
|
20
66
|
let nodePos = selection.from;
|
|
21
67
|
|
|
68
|
+
// when selection is empty, we insert a empty target node
|
|
69
|
+
if (selection.empty && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
|
|
70
|
+
return formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
|
|
71
|
+
}
|
|
72
|
+
|
|
22
73
|
// Try to find the current node from selection
|
|
23
74
|
const selectedNode = findSelectedNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.expand, nodes.codeBlock, nodes.bulletList, nodes.orderedList, nodes.taskList, nodes.layoutSection])(selection);
|
|
24
75
|
if (selectedNode) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
|
|
2
2
|
import { findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
|
|
3
3
|
import { getInlineNodeTextContent } from './inline-node-transforms';
|
|
4
|
-
import { isBlockNodeType, isListNodeType, isContainerNodeType, isBlockNodeForExtraction, convertNodeToInlineContent, getContentSupportChecker, convertCodeBlockContentToParagraphs, filterMarksForTargetNodeType } from './utils';
|
|
4
|
+
import { isBlockNodeType, isListNodeType, isContainerNodeType, isBlockNodeForExtraction, convertNodeToInlineContent, getContentSupportChecker, convertCodeBlockContentToParagraphs, filterMarksForTargetNodeType, getMarksWithBreakout } from './utils';
|
|
5
5
|
const convertInvalidNodeToValidNodeType = (sourceContent, sourceNodeType, validNodeType, withMarks) => {
|
|
6
6
|
const validTransformedContent = [];
|
|
7
7
|
// Headings are not valid inside headings so convert heading nodes to paragraphs
|
|
@@ -28,9 +28,11 @@ export const transformToContainer = ({
|
|
|
28
28
|
const schema = tr.doc.type.schema;
|
|
29
29
|
const content = selection.content().content;
|
|
30
30
|
let transformedContent = content;
|
|
31
|
+
let marks = [];
|
|
31
32
|
if (sourceNode.type === schema.nodes.codeBlock) {
|
|
32
33
|
const paragraphNodes = convertCodeBlockContentToParagraphs(sourceNode, schema);
|
|
33
34
|
transformedContent = Fragment.fromArray(paragraphNodes);
|
|
35
|
+
marks = getMarksWithBreakout(sourceNode, targetNodeType);
|
|
34
36
|
}
|
|
35
37
|
if (targetNodeType === schema.nodes.blockquote) {
|
|
36
38
|
transformedContent = convertInvalidNodeToValidNodeType(transformedContent, schema.nodes.heading, schema.nodes.paragraph, true);
|
|
@@ -42,7 +44,7 @@ export const transformToContainer = ({
|
|
|
42
44
|
if (sourceNode.type === schema.nodes.paragraph || sourceNode.type === schema.nodes.heading) {
|
|
43
45
|
transformedContent = filterMarksForTargetNodeType(transformedContent, targetNodeType);
|
|
44
46
|
}
|
|
45
|
-
const newNode = targetNodeType.createAndFill(targetAttrs, transformedContent);
|
|
47
|
+
const newNode = targetNodeType.createAndFill(targetAttrs, transformedContent, marks);
|
|
46
48
|
if (!newNode) {
|
|
47
49
|
return null;
|
|
48
50
|
}
|
|
@@ -172,7 +174,8 @@ export const unwrapAndConvertToBlockType = context => {
|
|
|
172
174
|
// if target node is code block, do unwrap and convert to code block
|
|
173
175
|
if (targetNodeType === codeBlock) {
|
|
174
176
|
const codeBlockContent = sourceChildren.map(node => node.content.textBetween(0, node.content.size, '\n')).join('\n');
|
|
175
|
-
|
|
177
|
+
const marks = getMarksWithBreakout(sourceNode, targetNodeType);
|
|
178
|
+
transformedContent = [codeBlock.createChecked({}, schema.text(codeBlockContent), marks)];
|
|
176
179
|
}
|
|
177
180
|
const slice = new Slice(Fragment.fromArray(transformedContent), 0, 0);
|
|
178
181
|
tr.replaceRange(rangeStart, rangeStart + sourceNode.nodeSize, slice);
|
|
@@ -273,6 +276,14 @@ export const transformBetweenContainerTypes = context => {
|
|
|
273
276
|
// Special handling for codeBlock target
|
|
274
277
|
if (targetNodeType.name === 'codeBlock') {
|
|
275
278
|
const contentSplits = splitContentForCodeBlock(sourceNode, targetNodeType, targetAttrs, tr.doc.type.schema);
|
|
279
|
+
if (contentSplits.length === 0) {
|
|
280
|
+
const {
|
|
281
|
+
schema
|
|
282
|
+
} = tr.doc.type;
|
|
283
|
+
const marks = getMarksWithBreakout(sourceNode, targetNodeType);
|
|
284
|
+
const codeBlock = schema.nodes.codeBlock.create(targetAttrs, null, marks);
|
|
285
|
+
return tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, codeBlock);
|
|
286
|
+
}
|
|
276
287
|
return applySplitsToTransaction(tr, sourcePos, sourceNode.nodeSize, contentSplits);
|
|
277
288
|
}
|
|
278
289
|
|
|
@@ -320,7 +331,8 @@ const splitContentForCodeBlock = (sourceNode, targetNodeType, targetAttrs, schem
|
|
|
320
331
|
const flushCurrentCodeBlock = () => {
|
|
321
332
|
if (currentTextContent.length > 0) {
|
|
322
333
|
const codeText = currentTextContent.join('\n');
|
|
323
|
-
const
|
|
334
|
+
const marks = getMarksWithBreakout(sourceNode, targetNodeType);
|
|
335
|
+
const codeBlockNode = targetNodeType.create(targetAttrs, schema.text(codeText), marks);
|
|
324
336
|
splits.push(codeBlockNode);
|
|
325
337
|
currentTextContent = [];
|
|
326
338
|
}
|
|
@@ -391,7 +403,8 @@ const splitContentAroundUnsupportedBlocks = (sourceNode, isContentSupported, tar
|
|
|
391
403
|
}
|
|
392
404
|
const flushCurrentContainer = () => {
|
|
393
405
|
if (currentContainerContent.length > 0) {
|
|
394
|
-
const
|
|
406
|
+
const marks = getMarksWithBreakout(sourceNode, targetNodeType);
|
|
407
|
+
const containerNode = targetNodeType.create(targetAttrs, Fragment.fromArray(currentContainerContent), marks);
|
|
395
408
|
splits.push(containerNode);
|
|
396
409
|
currentContainerContent = [];
|
|
397
410
|
}
|
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
import { DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH } from '@atlaskit/editor-common/styles';
|
|
2
2
|
import { Fragment } from '@atlaskit/editor-prosemirror/model';
|
|
3
3
|
import { convertUnwrappedLayoutContent, unwrapLayoutNodesToTextNodes } from './layout/utils';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
tr,
|
|
7
|
-
sourceNode,
|
|
8
|
-
sourcePos
|
|
9
|
-
} = context;
|
|
4
|
+
import { getMarksWithBreakout, isHeadingOrParagraphNode } from './utils';
|
|
5
|
+
export const createDefaultLayoutSection = (schema, content, marks) => {
|
|
10
6
|
const {
|
|
11
7
|
layoutSection,
|
|
12
8
|
layoutColumn,
|
|
13
9
|
paragraph
|
|
14
|
-
} =
|
|
15
|
-
const content = sourceNode.mark(sourceNode.marks.filter(mark => mark.type.name !== 'breakout'));
|
|
10
|
+
} = schema.nodes;
|
|
16
11
|
const layoutContent = Fragment.fromArray([layoutColumn.createChecked({
|
|
17
12
|
width: DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH
|
|
18
13
|
}, content), layoutColumn.create({
|
|
19
14
|
width: DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH
|
|
20
15
|
}, paragraph.createAndFill())]);
|
|
21
|
-
|
|
16
|
+
return layoutSection.createChecked(undefined, layoutContent, marks);
|
|
17
|
+
};
|
|
18
|
+
export const convertToLayout = context => {
|
|
19
|
+
const {
|
|
20
|
+
tr,
|
|
21
|
+
sourceNode,
|
|
22
|
+
sourcePos
|
|
23
|
+
} = context;
|
|
24
|
+
const content = sourceNode.mark(sourceNode.marks.filter(mark => mark.type.name !== 'breakout'));
|
|
22
25
|
|
|
23
|
-
//
|
|
24
|
-
|
|
26
|
+
// Layout supports breakout mark that can have width attribute
|
|
27
|
+
// When other nodes with breakout (codeBlock and expand) are converted to a layout, the layout should get width of original node
|
|
28
|
+
const marks = getMarksWithBreakout(sourceNode, tr.doc.type.schema.nodes.layoutSection);
|
|
29
|
+
const layoutSectionNode = createDefaultLayoutSection(tr.doc.type.schema, content, marks);
|
|
30
|
+
if (isHeadingOrParagraphNode(sourceNode)) {
|
|
31
|
+
// -1 to fix when sourceNode is the last node in the document, unable to convert to layout
|
|
32
|
+
tr.replaceRangeWith(sourcePos > 0 ? sourcePos - 1 : sourcePos, sourcePos + sourceNode.nodeSize - 1, layoutSectionNode);
|
|
33
|
+
} else {
|
|
34
|
+
tr.replaceRangeWith(sourcePos, sourcePos + sourceNode.nodeSize, layoutSectionNode);
|
|
35
|
+
}
|
|
25
36
|
return tr;
|
|
26
37
|
};
|
|
27
38
|
export const transformLayoutNode = context => {
|
|
@@ -196,4 +196,17 @@ export const convertCodeBlockContentToParagraphs = (codeBlockNode, schema) => {
|
|
|
196
196
|
paragraphNodes.push(paragraphNode);
|
|
197
197
|
});
|
|
198
198
|
return paragraphNodes;
|
|
199
|
+
};
|
|
200
|
+
const isBreakoutMarkSupported = nodeType => {
|
|
201
|
+
return ['codeBlock', 'expand', 'layoutSection'].includes(nodeType.name);
|
|
202
|
+
};
|
|
203
|
+
export const getMarksWithBreakout = (sourceNode, targetNodeType) => {
|
|
204
|
+
const allowedMarks = targetNodeType.allowedMarks(sourceNode.marks);
|
|
205
|
+
const sourceBreakoutMark = sourceNode.marks.find(mark => mark.type.name === 'breakout');
|
|
206
|
+
if (sourceBreakoutMark && isBreakoutMarkSupported(targetNodeType)) {
|
|
207
|
+
// Check if breakout mark is already in allowedMarks to avoid duplicates
|
|
208
|
+
const hasBreakoutMark = allowedMarks.some(mark => mark.type.name === 'breakout');
|
|
209
|
+
return hasBreakoutMark ? allowedMarks : [...allowedMarks, sourceBreakoutMark];
|
|
210
|
+
}
|
|
211
|
+
return allowedMarks;
|
|
199
212
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { MOVE_UP_MENU_ITEM, MOVE_UP_DOWN_MENU_SECTION, MOVE_DOWN_MENU_ITEM, MOVE_BLOCK_SECTION_RANK, PRIMARY_MENU_SECTION, BLOCK_MENU_SECTION_RANK, COPY_MENU_SECTION, COPY_BLOCK_MENU_ITEM, COPY_MENU_SECTION_RANK, COPY_LINK_MENU_ITEM, DELETE_MENU_SECTION, DELETE_MENU_ITEM, DELETE_SECTION_RANK, NESTED_FORMAT_MENU_SECTION, NESTED_FORMAT_MENU } from '@atlaskit/editor-common/block-menu';
|
|
3
|
-
import { ToolbarDropdownItemSection
|
|
4
|
-
import ChangesIcon from '@atlaskit/icon/core/changes';
|
|
5
|
-
import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
|
|
3
|
+
import { ToolbarDropdownItemSection } from '@atlaskit/editor-toolbar';
|
|
6
4
|
import CopyBlockMenuItem from './copy-block';
|
|
7
5
|
import { CopyLinkDropdownItem } from './copy-link';
|
|
8
6
|
import { CopySection } from './copy-section';
|
|
9
7
|
import { DeleteDropdownItem } from './delete-button';
|
|
8
|
+
import { DeleteSection } from './delete-section';
|
|
9
|
+
import { FormatMenuComponent } from './format-menu-nested';
|
|
10
10
|
import { FormatMenuSection } from './format-menu-section';
|
|
11
11
|
import { MoveDownDropdownItem } from './move-down';
|
|
12
12
|
import { MoveUpDropdownItem } from './move-up';
|
|
@@ -49,16 +49,7 @@ const getFormatMenuComponents = api => {
|
|
|
49
49
|
} = {
|
|
50
50
|
children: null
|
|
51
51
|
}) => {
|
|
52
|
-
return /*#__PURE__*/React.createElement(
|
|
53
|
-
text: "Format",
|
|
54
|
-
elemBefore: /*#__PURE__*/React.createElement(ChangesIcon, {
|
|
55
|
-
label: ""
|
|
56
|
-
}),
|
|
57
|
-
elemAfter: /*#__PURE__*/React.createElement(ChevronRightIcon, {
|
|
58
|
-
label: 'example nested menu'
|
|
59
|
-
}),
|
|
60
|
-
enableMaxHeight: true
|
|
61
|
-
}, children);
|
|
52
|
+
return /*#__PURE__*/React.createElement(FormatMenuComponent, null, children);
|
|
62
53
|
}
|
|
63
54
|
}, {
|
|
64
55
|
type: 'block-menu-section',
|
|
@@ -144,8 +135,8 @@ export const getBlockMenuComponents = ({
|
|
|
144
135
|
component: ({
|
|
145
136
|
children
|
|
146
137
|
}) => {
|
|
147
|
-
return /*#__PURE__*/React.createElement(
|
|
148
|
-
|
|
138
|
+
return /*#__PURE__*/React.createElement(DeleteSection, {
|
|
139
|
+
api: api
|
|
149
140
|
}, children);
|
|
150
141
|
}
|
|
151
142
|
}, ...getMoveUpMoveDownMenuComponents(api), {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { useCallback, createContext, useContext } from 'react';
|
|
2
|
+
const BlockMenuContext = /*#__PURE__*/createContext({
|
|
3
|
+
onDropdownOpenChanged: () => {}
|
|
4
|
+
});
|
|
5
|
+
export const useBlockMenu = () => {
|
|
6
|
+
const context = useContext(BlockMenuContext);
|
|
7
|
+
if (!context) {
|
|
8
|
+
throw new Error('useBlockMenu must be used within BlockMenuProvider');
|
|
9
|
+
}
|
|
10
|
+
return context;
|
|
11
|
+
};
|
|
12
|
+
export const BlockMenuProvider = ({
|
|
13
|
+
children,
|
|
14
|
+
api
|
|
15
|
+
}) => {
|
|
16
|
+
const onDropdownOpenChanged = useCallback(isOpen => {
|
|
17
|
+
if (!isOpen) {
|
|
18
|
+
// On Dropdown closed, return focus to editor
|
|
19
|
+
setTimeout(() => requestAnimationFrame(() => {
|
|
20
|
+
api === null || api === void 0 ? void 0 : api.core.actions.focus({
|
|
21
|
+
scrollIntoView: false
|
|
22
|
+
});
|
|
23
|
+
}), 1);
|
|
24
|
+
}
|
|
25
|
+
}, [api]);
|
|
26
|
+
return /*#__PURE__*/React.createElement(BlockMenuContext.Provider, {
|
|
27
|
+
value: {
|
|
28
|
+
onDropdownOpenChanged
|
|
29
|
+
}
|
|
30
|
+
}, children);
|
|
31
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* block-menu.tsx generated by @compiled/babel-plugin v0.
|
|
1
|
+
/* block-menu.tsx generated by @compiled/babel-plugin v0.38.1 */
|
|
2
2
|
import "./block-menu.compiled.css";
|
|
3
3
|
import { ax, ix } from "@compiled/react/runtime";
|
|
4
4
|
import React, { useContext, useEffect } from 'react';
|
|
@@ -11,6 +11,8 @@ import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners } from
|
|
|
11
11
|
import { akEditorFloatingOverlapPanelZIndex } from '@atlaskit/editor-shared-styles';
|
|
12
12
|
import { ToolbarDropdownItem, ToolbarDropdownItemSection, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
|
|
13
13
|
import { Box } from '@atlaskit/primitives/compiled';
|
|
14
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
15
|
+
import { useBlockMenu } from './block-menu-provider';
|
|
14
16
|
import { BlockMenuRenderer } from './block-menu-renderer';
|
|
15
17
|
const styles = {
|
|
16
18
|
base: "_2rko12b0 _bfhk1bhr _16qs1cd0"
|
|
@@ -62,15 +64,23 @@ const BlockMenu = ({
|
|
|
62
64
|
currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent
|
|
63
65
|
};
|
|
64
66
|
});
|
|
67
|
+
const {
|
|
68
|
+
onDropdownOpenChanged
|
|
69
|
+
} = useBlockMenu();
|
|
65
70
|
const hasFocus = (_editorView$hasFocus = editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) !== null && _editorView$hasFocus !== void 0 ? _editorView$hasFocus : false;
|
|
66
71
|
const hasSelection = !!editorView && !editorView.state.selection.empty;
|
|
72
|
+
const emptyLineEnabled = expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true);
|
|
73
|
+
|
|
74
|
+
// hasSelection true, always show block menu
|
|
75
|
+
// hasSelection false, only show block menu when empty line experiment is enabled
|
|
76
|
+
const shouldShowBlockMenuForEmptyLine = hasSelection || emptyLineEnabled && !hasSelection;
|
|
67
77
|
useEffect(() => {
|
|
68
78
|
var _api$userIntent;
|
|
69
|
-
if (!isMenuOpen || !menuTriggerBy || !isSelectedViaDragHandle || !hasFocus || !
|
|
79
|
+
if (!isMenuOpen || !menuTriggerBy || !isSelectedViaDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
|
|
70
80
|
return;
|
|
71
81
|
}
|
|
72
82
|
api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.commands.setCurrentUserIntent('blockMenuOpen'));
|
|
73
|
-
}, [api, isMenuOpen, menuTriggerBy, isSelectedViaDragHandle, hasFocus,
|
|
83
|
+
}, [api, isMenuOpen, menuTriggerBy, isSelectedViaDragHandle, hasFocus, shouldShowBlockMenuForEmptyLine, currentUserIntent]);
|
|
74
84
|
if (!isMenuOpen) {
|
|
75
85
|
return null;
|
|
76
86
|
}
|
|
@@ -84,13 +94,14 @@ const BlockMenu = ({
|
|
|
84
94
|
})({
|
|
85
95
|
tr
|
|
86
96
|
});
|
|
97
|
+
onDropdownOpenChanged(false);
|
|
87
98
|
api === null || api === void 0 ? void 0 : (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 ? void 0 : _api$userIntent2.commands.setCurrentUserIntent(currentUserIntent === 'blockMenuOpen' ? 'default' : currentUserIntent || 'default')({
|
|
88
99
|
tr
|
|
89
100
|
});
|
|
90
101
|
return tr;
|
|
91
102
|
});
|
|
92
103
|
};
|
|
93
|
-
if (!menuTriggerBy || !isSelectedViaDragHandle || !hasFocus || !
|
|
104
|
+
if (!menuTriggerBy || !isSelectedViaDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
|
|
94
105
|
closeMenu();
|
|
95
106
|
return null;
|
|
96
107
|
}
|
|
@@ -75,10 +75,16 @@ const CopyBlockMenuItem = ({
|
|
|
75
75
|
// When nodeType.inlineContent is true, it will be treated as an inline node in the copyDomNode function,
|
|
76
76
|
// but we want to treat it as a block node when copying, hence setting it to false here
|
|
77
77
|
if (selection.node.type.name === 'codeBlock') {
|
|
78
|
-
|
|
78
|
+
const codeBlockNodeType = {
|
|
79
|
+
...nodeType,
|
|
80
|
+
inlineContent: false
|
|
81
|
+
};
|
|
82
|
+
const domNode = toDOM(selection.node, schema);
|
|
83
|
+
copyDomNode(domNode, codeBlockNodeType, selection);
|
|
84
|
+
} else {
|
|
85
|
+
const domNode = toDOM(selection.node, schema);
|
|
86
|
+
copyDomNode(domNode, nodeType, selection);
|
|
79
87
|
}
|
|
80
|
-
const domNode = toDOM(selection.node, schema);
|
|
81
|
-
copyDomNode(domNode, nodeType, selection);
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
// close the block menu after copying
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
2
|
import { ToolbarDropdownItemSection } from '@atlaskit/editor-toolbar';
|
|
3
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
3
4
|
import { checkIsFormatMenuHidden } from './utils/checkIsFormatMenuHidden';
|
|
4
5
|
export const CopySection = ({
|
|
5
6
|
api,
|
|
6
7
|
children
|
|
7
8
|
}) => {
|
|
9
|
+
var _api$selection, _api$selection$shared, _api$selection$shared2;
|
|
8
10
|
const isFormatMenuHidden = useCallback(() => {
|
|
9
11
|
return checkIsFormatMenuHidden(api);
|
|
10
12
|
}, [api]);
|
|
13
|
+
const selection = api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : (_api$selection$shared = _api$selection.sharedState) === null || _api$selection$shared === void 0 ? void 0 : (_api$selection$shared2 = _api$selection$shared.currentState()) === null || _api$selection$shared2 === void 0 ? void 0 : _api$selection$shared2.selection;
|
|
14
|
+
const isEmptyLineSelected = !!(selection !== null && selection !== void 0 && selection.empty) && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true);
|
|
15
|
+
if (isEmptyLineSelected) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
11
18
|
return /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, {
|
|
12
19
|
hasSeparator: !isFormatMenuHidden()
|
|
13
20
|
}, children);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ToolbarDropdownItemSection } from '@atlaskit/editor-toolbar';
|
|
3
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
4
|
+
export const DeleteSection = ({
|
|
5
|
+
api,
|
|
6
|
+
children
|
|
7
|
+
}) => {
|
|
8
|
+
var _api$selection, _api$selection$shared, _api$selection$shared2;
|
|
9
|
+
const selection = api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : (_api$selection$shared = _api$selection.sharedState) === null || _api$selection$shared === void 0 ? void 0 : (_api$selection$shared2 = _api$selection$shared.currentState()) === null || _api$selection$shared2 === void 0 ? void 0 : _api$selection$shared2.selection;
|
|
10
|
+
const isEmptyLineSelected = !!(selection !== null && selection !== void 0 && selection.empty) && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true);
|
|
11
|
+
if (isEmptyLineSelected) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, {
|
|
15
|
+
hasSeparator: true
|
|
16
|
+
}, children);
|
|
17
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl-next';
|
|
3
|
+
import { messages } from '@atlaskit/editor-common/block-menu';
|
|
4
|
+
import { ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
|
|
5
|
+
import ChangesIcon from '@atlaskit/icon/core/changes';
|
|
6
|
+
import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
|
|
7
|
+
export const FormatMenuComponent = ({
|
|
8
|
+
children
|
|
9
|
+
}) => {
|
|
10
|
+
const {
|
|
11
|
+
formatMessage
|
|
12
|
+
} = useIntl();
|
|
13
|
+
return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
|
|
14
|
+
text: formatMessage(messages.turnInto),
|
|
15
|
+
elemBefore: /*#__PURE__*/React.createElement(ChangesIcon, {
|
|
16
|
+
label: ""
|
|
17
|
+
}),
|
|
18
|
+
elemAfter: /*#__PURE__*/React.createElement(ChevronRightIcon, {
|
|
19
|
+
label: ""
|
|
20
|
+
}),
|
|
21
|
+
enableMaxHeight: true
|
|
22
|
+
}, children);
|
|
23
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { findParentNodeOfType, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
2
2
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
3
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
3
4
|
import { isNestedNode } from './isNestedNode';
|
|
4
5
|
const getIsFormatMenuHidden = (selection, schema, menuTriggerBy) => {
|
|
5
6
|
const nodes = schema.nodes;
|
|
@@ -26,6 +27,39 @@ const getIsFormatMenuHidden = (selection, schema, menuTriggerBy) => {
|
|
|
26
27
|
const isNested = isNestedNode(selection, menuTriggerBy);
|
|
27
28
|
return !content || isNested;
|
|
28
29
|
};
|
|
30
|
+
const getIsFormatMenuHiddenEmptyLine = (selection, schema, menuTriggerBy) => {
|
|
31
|
+
const nodes = schema.nodes;
|
|
32
|
+
if (!nodes) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const isNested = isNestedNode(selection, menuTriggerBy);
|
|
36
|
+
if (selection.empty || selection.content().size === 0) {
|
|
37
|
+
// if empty selection, show format menu
|
|
38
|
+
return false;
|
|
39
|
+
} else if (isNested) {
|
|
40
|
+
// if nested, always hide format menu
|
|
41
|
+
return true;
|
|
42
|
+
} else {
|
|
43
|
+
let content;
|
|
44
|
+
const allowedNodes = [nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.codeBlock, nodes.bulletList, nodes.orderedList, nodes.taskList];
|
|
45
|
+
if (expValEquals('platform_editor_block_menu_layout_format', 'isEnabled', true)) {
|
|
46
|
+
allowedNodes.push(nodes.layoutSection);
|
|
47
|
+
}
|
|
48
|
+
if (expValEquals('platform_editor_block_menu_expand_format', 'isEnabled', true)) {
|
|
49
|
+
allowedNodes.push(nodes.expand);
|
|
50
|
+
}
|
|
51
|
+
const selectedNode = findSelectedNodeOfType(allowedNodes)(selection);
|
|
52
|
+
if (selectedNode) {
|
|
53
|
+
content = selectedNode.node;
|
|
54
|
+
} else {
|
|
55
|
+
const listTypeOrBlockQuoteNode = findParentNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.listItem, nodes.taskItem])(selection);
|
|
56
|
+
if (listTypeOrBlockQuoteNode) {
|
|
57
|
+
content = listTypeOrBlockQuoteNode.node;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return !content;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
29
63
|
export const checkIsFormatMenuHidden = api => {
|
|
30
64
|
var _api$selection, _api$selection$shared, _api$selection$shared2, _api$core$sharedState, _api$blockControls, _api$blockControls$sh;
|
|
31
65
|
const selection = api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : (_api$selection$shared = _api$selection.sharedState) === null || _api$selection$shared === void 0 ? void 0 : (_api$selection$shared2 = _api$selection$shared.currentState()) === null || _api$selection$shared2 === void 0 ? void 0 : _api$selection$shared2.selection;
|
|
@@ -34,5 +68,5 @@ export const checkIsFormatMenuHidden = api => {
|
|
|
34
68
|
if (!selection || !schema || !menuTriggerBy) {
|
|
35
69
|
return false;
|
|
36
70
|
}
|
|
37
|
-
return getIsFormatMenuHidden(selection, schema, menuTriggerBy);
|
|
71
|
+
return expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true) ? getIsFormatMenuHiddenEmptyLine(selection, schema, menuTriggerBy) : getIsFormatMenuHidden(selection, schema, menuTriggerBy);
|
|
38
72
|
};
|
|
@@ -4,6 +4,7 @@ import { formatNode as _formatNode } from './editor-commands/formatNode';
|
|
|
4
4
|
import { createPlugin } from './pm-plugins/main';
|
|
5
5
|
import BlockMenu from './ui/block-menu';
|
|
6
6
|
import { getBlockMenuComponents } from './ui/block-menu-components';
|
|
7
|
+
import { BlockMenuProvider } from './ui/block-menu-provider';
|
|
7
8
|
export var blockMenuPlugin = function blockMenuPlugin(_ref) {
|
|
8
9
|
var api = _ref.api,
|
|
9
10
|
config = _ref.config;
|
|
@@ -52,13 +53,15 @@ export var blockMenuPlugin = function blockMenuPlugin(_ref) {
|
|
|
52
53
|
popupsMountPoint = _ref2.popupsMountPoint,
|
|
53
54
|
popupsBoundariesElement = _ref2.popupsBoundariesElement,
|
|
54
55
|
popupsScrollableElement = _ref2.popupsScrollableElement;
|
|
55
|
-
return /*#__PURE__*/React.createElement(
|
|
56
|
+
return /*#__PURE__*/React.createElement(BlockMenuProvider, {
|
|
57
|
+
api: api
|
|
58
|
+
}, /*#__PURE__*/React.createElement(BlockMenu, {
|
|
56
59
|
editorView: editorView,
|
|
57
60
|
api: api,
|
|
58
61
|
mountTo: popupsMountPoint,
|
|
59
62
|
boundariesElement: popupsBoundariesElement,
|
|
60
63
|
scrollableElement: popupsScrollableElement
|
|
61
|
-
});
|
|
64
|
+
}));
|
|
62
65
|
}
|
|
63
66
|
};
|
|
64
67
|
};
|
|
@@ -1,5 +1,46 @@
|
|
|
1
|
-
import { findParentNodeOfType, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
1
|
+
import { findParentNodeOfType, findSelectedNodeOfType, safeInsert as pmSafeInsert } from '@atlaskit/editor-prosemirror/utils';
|
|
2
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
3
|
+
import { createDefaultLayoutSection } from './transforms/layout-transforms';
|
|
2
4
|
import { transformNodeToTargetType } from './transforms/transformNodeToTargetType';
|
|
5
|
+
/**
|
|
6
|
+
* Handles formatting when selection is empty by inserting a new target node
|
|
7
|
+
*/
|
|
8
|
+
var formatNodeWhenSelectionEmpty = function formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema) {
|
|
9
|
+
var _pmSafeInsert;
|
|
10
|
+
var nodes = schema.nodes;
|
|
11
|
+
var paragraph = nodes.paragraph;
|
|
12
|
+
// if not using the ' ' here, the safeInsert from editor-common will fail to insert the heading
|
|
13
|
+
// and the pmSafeInsert introduce an issue that after inserting heading, and click on the handle, selection will go to top of the doc
|
|
14
|
+
// as an workaround, use the spaceTextNode here
|
|
15
|
+
var spaceTextNode = schema.text(' ');
|
|
16
|
+
var targetNode;
|
|
17
|
+
if (targetType.startsWith('heading')) {
|
|
18
|
+
var levelString = targetType.slice(-1);
|
|
19
|
+
var level = parseInt(levelString, 10);
|
|
20
|
+
if (isNaN(level) || level < 1 || level > 6) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
targetNode = nodes.heading.createAndFill({
|
|
24
|
+
level: level
|
|
25
|
+
}, spaceTextNode);
|
|
26
|
+
} else if (targetType === 'paragraph') {
|
|
27
|
+
targetNode = nodes.paragraph.createAndFill({}, spaceTextNode);
|
|
28
|
+
} else if (targetType === 'layoutSection') {
|
|
29
|
+
var contentAsParagraph = paragraph.createAndFill({}, spaceTextNode);
|
|
30
|
+
if (contentAsParagraph) {
|
|
31
|
+
targetNode = createDefaultLayoutSection(schema, contentAsParagraph);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
var targetNodeType = nodes[targetType];
|
|
35
|
+
targetNode = targetNodeType.createAndFill();
|
|
36
|
+
}
|
|
37
|
+
if (!targetNode) {
|
|
38
|
+
return tr;
|
|
39
|
+
}
|
|
40
|
+
tr = (_pmSafeInsert = pmSafeInsert(targetNode, nodePos)(tr)) !== null && _pmSafeInsert !== void 0 ? _pmSafeInsert : tr;
|
|
41
|
+
return tr;
|
|
42
|
+
};
|
|
43
|
+
|
|
3
44
|
/**
|
|
4
45
|
* Formats the current node or selection to the specified target type
|
|
5
46
|
* @param targetType - The target node type to convert to
|
|
@@ -8,12 +49,18 @@ export var formatNode = function formatNode(targetType) {
|
|
|
8
49
|
return function (_ref) {
|
|
9
50
|
var tr = _ref.tr;
|
|
10
51
|
var selection = tr.selection;
|
|
11
|
-
var
|
|
52
|
+
var schema = tr.doc.type.schema;
|
|
53
|
+
var nodes = schema.nodes;
|
|
12
54
|
|
|
13
55
|
// Find the node to format from the current selection
|
|
14
56
|
var nodeToFormat;
|
|
15
57
|
var nodePos = selection.from;
|
|
16
58
|
|
|
59
|
+
// when selection is empty, we insert a empty target node
|
|
60
|
+
if (selection.empty && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
|
|
61
|
+
return formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
|
|
62
|
+
}
|
|
63
|
+
|
|
17
64
|
// Try to find the current node from selection
|
|
18
65
|
var selectedNode = findSelectedNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.expand, nodes.codeBlock, nodes.bulletList, nodes.orderedList, nodes.taskList, nodes.layoutSection])(selection);
|
|
19
66
|
if (selectedNode) {
|