@atlaskit/editor-plugin-block-type 1.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.
- package/.eslintrc.js +14 -0
- package/CHANGELOG.md +1 -0
- package/LICENSE.md +13 -0
- package/README.md +32 -0
- package/consts/package.json +15 -0
- package/dist/cjs/consts.js +66 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/messages.js +19 -0
- package/dist/cjs/plugin/block-types.js +106 -0
- package/dist/cjs/plugin/commands/block-type.js +183 -0
- package/dist/cjs/plugin/commands/delete-and-move-cursor.js +56 -0
- package/dist/cjs/plugin/commands/delete-block-content.js +45 -0
- package/dist/cjs/plugin/commands/index.js +69 -0
- package/dist/cjs/plugin/consts.js +15 -0
- package/dist/cjs/plugin/index.js +217 -0
- package/dist/cjs/plugin/messages.js +160 -0
- package/dist/cjs/plugin/pm-plugins/input-rule.js +104 -0
- package/dist/cjs/plugin/pm-plugins/keymap.js +34 -0
- package/dist/cjs/plugin/pm-plugins/main.js +151 -0
- package/dist/cjs/plugin/styles.js +15 -0
- package/dist/cjs/plugin/types.js +5 -0
- package/dist/cjs/plugin/ui/ToolbarBlockType/blocktype-button.js +60 -0
- package/dist/cjs/plugin/ui/ToolbarBlockType/index.js +208 -0
- package/dist/cjs/plugin/ui/ToolbarBlockType/styled.js +34 -0
- package/dist/cjs/plugin/ui/ToolbarBlockType/toolbar-messages.js +15 -0
- package/dist/cjs/plugin/utils.js +87 -0
- package/dist/cjs/styles.js +12 -0
- package/dist/es2019/consts.js +1 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/messages.js +2 -0
- package/dist/es2019/plugin/block-types.js +84 -0
- package/dist/es2019/plugin/commands/block-type.js +170 -0
- package/dist/es2019/plugin/commands/delete-and-move-cursor.js +55 -0
- package/dist/es2019/plugin/commands/delete-block-content.js +42 -0
- package/dist/es2019/plugin/commands/index.js +8 -0
- package/dist/es2019/plugin/consts.js +8 -0
- package/dist/es2019/plugin/index.js +204 -0
- package/dist/es2019/plugin/messages.js +153 -0
- package/dist/es2019/plugin/pm-plugins/input-rule.js +93 -0
- package/dist/es2019/plugin/pm-plugins/keymap.js +25 -0
- package/dist/es2019/plugin/pm-plugins/main.js +137 -0
- package/dist/es2019/plugin/styles.js +8 -0
- package/dist/es2019/plugin/types.js +1 -0
- package/dist/es2019/plugin/ui/ToolbarBlockType/blocktype-button.js +50 -0
- package/dist/es2019/plugin/ui/ToolbarBlockType/index.js +185 -0
- package/dist/es2019/plugin/ui/ToolbarBlockType/styled.js +49 -0
- package/dist/es2019/plugin/ui/ToolbarBlockType/toolbar-messages.js +8 -0
- package/dist/es2019/plugin/utils.js +76 -0
- package/dist/es2019/styles.js +1 -0
- package/dist/esm/consts.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/messages.js +2 -0
- package/dist/esm/plugin/block-types.js +84 -0
- package/dist/esm/plugin/commands/block-type.js +169 -0
- package/dist/esm/plugin/commands/delete-and-move-cursor.js +50 -0
- package/dist/esm/plugin/commands/delete-block-content.js +39 -0
- package/dist/esm/plugin/commands/index.js +8 -0
- package/dist/esm/plugin/consts.js +8 -0
- package/dist/esm/plugin/index.js +205 -0
- package/dist/esm/plugin/messages.js +153 -0
- package/dist/esm/plugin/pm-plugins/input-rule.js +96 -0
- package/dist/esm/plugin/pm-plugins/keymap.js +25 -0
- package/dist/esm/plugin/pm-plugins/main.js +142 -0
- package/dist/esm/plugin/styles.js +7 -0
- package/dist/esm/plugin/types.js +1 -0
- package/dist/esm/plugin/ui/ToolbarBlockType/blocktype-button.js +50 -0
- package/dist/esm/plugin/ui/ToolbarBlockType/index.js +201 -0
- package/dist/esm/plugin/ui/ToolbarBlockType/styled.js +20 -0
- package/dist/esm/plugin/ui/ToolbarBlockType/toolbar-messages.js +8 -0
- package/dist/esm/plugin/utils.js +77 -0
- package/dist/esm/styles.js +1 -0
- package/dist/types/consts.d.ts +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/messages.d.ts +2 -0
- package/dist/types/plugin/block-types.d.ts +19 -0
- package/dist/types/plugin/commands/block-type.d.ts +18 -0
- package/dist/types/plugin/commands/delete-and-move-cursor.d.ts +12 -0
- package/dist/types/plugin/commands/delete-block-content.d.ts +10 -0
- package/dist/types/plugin/commands/index.d.ts +9 -0
- package/dist/types/plugin/consts.d.ts +1 -0
- package/dist/types/plugin/index.d.ts +18 -0
- package/dist/types/plugin/messages.d.ts +152 -0
- package/dist/types/plugin/pm-plugins/input-rule.d.ts +6 -0
- package/dist/types/plugin/pm-plugins/keymap.d.ts +5 -0
- package/dist/types/plugin/pm-plugins/main.d.ts +17 -0
- package/dist/types/plugin/styles.d.ts +2 -0
- package/dist/types/plugin/types.d.ts +22 -0
- package/dist/types/plugin/ui/ToolbarBlockType/blocktype-button.d.ts +22 -0
- package/dist/types/plugin/ui/ToolbarBlockType/index.d.ts +29 -0
- package/dist/types/plugin/ui/ToolbarBlockType/styled.d.ts +8 -0
- package/dist/types/plugin/ui/ToolbarBlockType/toolbar-messages.d.ts +7 -0
- package/dist/types/plugin/utils.d.ts +16 -0
- package/dist/types/styles.d.ts +1 -0
- package/dist/types-ts4.5/consts.d.ts +1 -0
- package/dist/types-ts4.5/index.d.ts +6 -0
- package/dist/types-ts4.5/messages.d.ts +2 -0
- package/dist/types-ts4.5/plugin/block-types.d.ts +19 -0
- package/dist/types-ts4.5/plugin/commands/block-type.d.ts +18 -0
- package/dist/types-ts4.5/plugin/commands/delete-and-move-cursor.d.ts +12 -0
- package/dist/types-ts4.5/plugin/commands/delete-block-content.d.ts +10 -0
- package/dist/types-ts4.5/plugin/commands/index.d.ts +9 -0
- package/dist/types-ts4.5/plugin/consts.d.ts +1 -0
- package/dist/types-ts4.5/plugin/index.d.ts +20 -0
- package/dist/types-ts4.5/plugin/messages.d.ts +152 -0
- package/dist/types-ts4.5/plugin/pm-plugins/input-rule.d.ts +6 -0
- package/dist/types-ts4.5/plugin/pm-plugins/keymap.d.ts +5 -0
- package/dist/types-ts4.5/plugin/pm-plugins/main.d.ts +17 -0
- package/dist/types-ts4.5/plugin/styles.d.ts +2 -0
- package/dist/types-ts4.5/plugin/types.d.ts +22 -0
- package/dist/types-ts4.5/plugin/ui/ToolbarBlockType/blocktype-button.d.ts +22 -0
- package/dist/types-ts4.5/plugin/ui/ToolbarBlockType/index.d.ts +29 -0
- package/dist/types-ts4.5/plugin/ui/ToolbarBlockType/styled.d.ts +8 -0
- package/dist/types-ts4.5/plugin/ui/ToolbarBlockType/toolbar-messages.d.ts +7 -0
- package/dist/types-ts4.5/plugin/utils.d.ts +16 -0
- package/dist/types-ts4.5/styles.d.ts +1 -0
- package/messages/package.json +15 -0
- package/package.json +105 -0
- package/report.api.md +108 -0
- package/styles/package.json +15 -0
- package/tmp/api-report-tmp.d.ts +75 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { insertBlock } from '@atlaskit/editor-common/commands';
|
|
3
|
+
import { inputRuleWithAnalytics } from '@atlaskit/editor-common/utils';
|
|
4
|
+
import { createPlugin, createRule, leafNodeReplacementCharacter } from '@atlaskit/prosemirror-input-rules';
|
|
5
|
+
import { createJoinNodesRule, createWrappingTextBlockRule } from '../utils';
|
|
6
|
+
const MAX_HEADING_LEVEL = 6;
|
|
7
|
+
function getHeadingLevel(match) {
|
|
8
|
+
return {
|
|
9
|
+
level: match[1].length
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function headingRule(nodeType, maxLevel) {
|
|
13
|
+
return createWrappingTextBlockRule({
|
|
14
|
+
match: new RegExp('^(#{1,' + maxLevel + '})\\s$'),
|
|
15
|
+
nodeType,
|
|
16
|
+
getAttrs: getHeadingLevel
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function blockQuoteRule(nodeType) {
|
|
20
|
+
return createJoinNodesRule(/^\s*>\s$/, nodeType);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get heading rules
|
|
25
|
+
*
|
|
26
|
+
* @param {Schema} schema
|
|
27
|
+
* @returns {InputRuleWithHandler[]}
|
|
28
|
+
*/
|
|
29
|
+
function getHeadingRules(editorAnalyticsAPI, schema) {
|
|
30
|
+
// '# ' for h1, '## ' for h2 and etc
|
|
31
|
+
const hashRule = headingRule(schema.nodes.heading, MAX_HEADING_LEVEL);
|
|
32
|
+
const leftNodeReplacementHashRule = createRule(new RegExp(`${leafNodeReplacementCharacter}(#{1,6})\\s$`), (state, match, start, end) => {
|
|
33
|
+
const level = match[1].length;
|
|
34
|
+
return insertBlock(state, schema.nodes.heading, start, end, {
|
|
35
|
+
level
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// New analytics handler
|
|
40
|
+
const ruleWithHeadingAnalytics = inputRuleWithAnalytics((_state, matchResult) => ({
|
|
41
|
+
action: ACTION.FORMATTED,
|
|
42
|
+
actionSubject: ACTION_SUBJECT.TEXT,
|
|
43
|
+
eventType: EVENT_TYPE.TRACK,
|
|
44
|
+
actionSubjectId: ACTION_SUBJECT_ID.FORMAT_HEADING,
|
|
45
|
+
attributes: {
|
|
46
|
+
inputMethod: INPUT_METHOD.FORMATTING,
|
|
47
|
+
newHeadingLevel: getHeadingLevel(matchResult).level
|
|
48
|
+
}
|
|
49
|
+
}), editorAnalyticsAPI);
|
|
50
|
+
return [ruleWithHeadingAnalytics(hashRule), ruleWithHeadingAnalytics(leftNodeReplacementHashRule)];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get all block quote input rules
|
|
55
|
+
*
|
|
56
|
+
* @param {Schema} schema
|
|
57
|
+
* @returns {InputRuleWithHandler[]}
|
|
58
|
+
*/
|
|
59
|
+
function getBlockQuoteRules(editorAnalyticsAPI, schema) {
|
|
60
|
+
// '> ' for blockquote
|
|
61
|
+
const greatherThanRule = blockQuoteRule(schema.nodes.blockquote);
|
|
62
|
+
const leftNodeReplacementGreatherRule = createRule(new RegExp(`${leafNodeReplacementCharacter}\\s*>\\s$`), (state, _match, start, end) => {
|
|
63
|
+
return insertBlock(state, schema.nodes.blockquote, start, end);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Analytics V3 handler
|
|
67
|
+
const ruleWithBlockQuoteAnalytics = inputRuleWithAnalytics({
|
|
68
|
+
action: ACTION.FORMATTED,
|
|
69
|
+
actionSubject: ACTION_SUBJECT.TEXT,
|
|
70
|
+
eventType: EVENT_TYPE.TRACK,
|
|
71
|
+
actionSubjectId: ACTION_SUBJECT_ID.FORMAT_BLOCK_QUOTE,
|
|
72
|
+
attributes: {
|
|
73
|
+
inputMethod: INPUT_METHOD.FORMATTING
|
|
74
|
+
}
|
|
75
|
+
}, editorAnalyticsAPI);
|
|
76
|
+
return [ruleWithBlockQuoteAnalytics(greatherThanRule), ruleWithBlockQuoteAnalytics(leftNodeReplacementGreatherRule)];
|
|
77
|
+
}
|
|
78
|
+
function inputRulePlugin(editorAnalyticsAPI, schema, featureFlags) {
|
|
79
|
+
const rules = [];
|
|
80
|
+
if (schema.nodes.heading) {
|
|
81
|
+
rules.push(...getHeadingRules(editorAnalyticsAPI, schema));
|
|
82
|
+
}
|
|
83
|
+
if (schema.nodes.blockquote) {
|
|
84
|
+
rules.push(...getBlockQuoteRules(editorAnalyticsAPI, schema));
|
|
85
|
+
}
|
|
86
|
+
if (rules.length !== 0) {
|
|
87
|
+
return createPlugin('block-type', rules, {
|
|
88
|
+
isBlockNodeRule: true
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
export default inputRulePlugin;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { backspace, bindKeymapWithCommand, deleteKey, findKeyMapForBrowser, findShortcutByKeymap, forwardDelete, insertNewLine, keymap, moveDown, moveUp, redo as redoKeymap, toggleBlockQuote, undo as undoKeymap } from '@atlaskit/editor-common/keymaps';
|
|
3
|
+
import { createNewParagraphAbove, createNewParagraphBelow, deleteEmptyParagraphAndMoveBlockUp, insertNewLineWithAnalytics } from '@atlaskit/editor-common/utils';
|
|
4
|
+
import { chainCommands } from '@atlaskit/editor-prosemirror/commands';
|
|
5
|
+
import { redo, undo } from '@atlaskit/editor-prosemirror/history';
|
|
6
|
+
import * as blockTypes from '../block-types';
|
|
7
|
+
import { cleanUpAtTheStartOfDocument, deleteAndMoveCursor, deleteBlockContent, insertBlockQuoteWithAnalytics } from '../commands';
|
|
8
|
+
import { isNodeAWrappingBlockNode } from '../utils';
|
|
9
|
+
const backspaceCommand = chainCommands(cleanUpAtTheStartOfDocument, deleteBlockContent(isNodeAWrappingBlockNode), deleteAndMoveCursor);
|
|
10
|
+
const del = chainCommands(deleteEmptyParagraphAndMoveBlockUp(isNodeAWrappingBlockNode), deleteBlockContent(isNodeAWrappingBlockNode), deleteAndMoveCursor);
|
|
11
|
+
export default function keymapPlugin(editorAnalyticsApi, schema, _featureFlags) {
|
|
12
|
+
const list = {};
|
|
13
|
+
bindKeymapWithCommand(insertNewLine.common, insertNewLineWithAnalytics(editorAnalyticsApi), list);
|
|
14
|
+
bindKeymapWithCommand(moveUp.common, createNewParagraphAbove, list);
|
|
15
|
+
bindKeymapWithCommand(moveDown.common, createNewParagraphBelow, list);
|
|
16
|
+
bindKeymapWithCommand(findKeyMapForBrowser(redoKeymap), redo, list);
|
|
17
|
+
bindKeymapWithCommand(undoKeymap.common, undo, list);
|
|
18
|
+
bindKeymapWithCommand(backspace.common, backspaceCommand, list);
|
|
19
|
+
bindKeymapWithCommand(deleteKey.common, del, list);
|
|
20
|
+
bindKeymapWithCommand(forwardDelete.mac, del, list);
|
|
21
|
+
if (schema.nodes[blockTypes.BLOCK_QUOTE.nodeName]) {
|
|
22
|
+
bindKeymapWithCommand(findShortcutByKeymap(toggleBlockQuote), insertBlockQuoteWithAnalytics(INPUT_METHOD.KEYBOARD, editorAnalyticsApi), list);
|
|
23
|
+
}
|
|
24
|
+
return keymap(list);
|
|
25
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
3
|
+
import { browser } from '@atlaskit/editor-common/utils';
|
|
4
|
+
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
5
|
+
import { BLOCK_QUOTE, CODE_BLOCK, HEADING_1, HEADING_2, HEADING_3, HEADING_4, HEADING_5, HEADING_6, HEADINGS_BY_LEVEL, NORMAL_TEXT, OTHER, PANEL, TEXT_BLOCK_TYPES, WRAPPER_BLOCK_TYPES } from '../block-types';
|
|
6
|
+
import { setHeadingWithAnalytics, setNormalTextWithAnalytics } from '../commands';
|
|
7
|
+
import { HEADING_KEYS } from '../consts';
|
|
8
|
+
import { areBlockTypesDisabled } from '../utils';
|
|
9
|
+
const blockTypeForNode = (node, schema) => {
|
|
10
|
+
if (node.type === schema.nodes.heading) {
|
|
11
|
+
const maybeNode = HEADINGS_BY_LEVEL[node.attrs['level']];
|
|
12
|
+
if (maybeNode) {
|
|
13
|
+
return maybeNode;
|
|
14
|
+
}
|
|
15
|
+
} else if (node.type === schema.nodes.paragraph) {
|
|
16
|
+
return NORMAL_TEXT;
|
|
17
|
+
}
|
|
18
|
+
return OTHER;
|
|
19
|
+
};
|
|
20
|
+
const isBlockTypeSchemaSupported = (blockType, state) => {
|
|
21
|
+
switch (blockType) {
|
|
22
|
+
case NORMAL_TEXT:
|
|
23
|
+
return !!state.schema.nodes.paragraph;
|
|
24
|
+
case HEADING_1:
|
|
25
|
+
case HEADING_2:
|
|
26
|
+
case HEADING_3:
|
|
27
|
+
case HEADING_4:
|
|
28
|
+
case HEADING_5:
|
|
29
|
+
case HEADING_6:
|
|
30
|
+
return !!state.schema.nodes.heading;
|
|
31
|
+
case BLOCK_QUOTE:
|
|
32
|
+
return !!state.schema.nodes.blockquote;
|
|
33
|
+
case CODE_BLOCK:
|
|
34
|
+
return !!state.schema.nodes.codeBlock;
|
|
35
|
+
case PANEL:
|
|
36
|
+
return !!state.schema.nodes.panel;
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
};
|
|
40
|
+
const detectBlockType = (availableBlockTypes, state) => {
|
|
41
|
+
// Before a document is loaded, there is no selection.
|
|
42
|
+
if (!state.selection) {
|
|
43
|
+
return NORMAL_TEXT;
|
|
44
|
+
}
|
|
45
|
+
let blockType;
|
|
46
|
+
const {
|
|
47
|
+
$from,
|
|
48
|
+
$to
|
|
49
|
+
} = state.selection;
|
|
50
|
+
state.doc.nodesBetween($from.pos, $to.pos, node => {
|
|
51
|
+
const nodeBlockType = availableBlockTypes.filter(blockType => blockType === blockTypeForNode(node, state.schema));
|
|
52
|
+
if (nodeBlockType.length > 0) {
|
|
53
|
+
if (!blockType) {
|
|
54
|
+
blockType = nodeBlockType[0];
|
|
55
|
+
} else if (blockType !== OTHER && blockType !== nodeBlockType[0]) {
|
|
56
|
+
blockType = OTHER;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return blockType || OTHER;
|
|
61
|
+
};
|
|
62
|
+
const autoformatHeading = (headingLevel, view, editorAnalyticsApi) => {
|
|
63
|
+
if (headingLevel === 0) {
|
|
64
|
+
setNormalTextWithAnalytics(INPUT_METHOD.FORMATTING, editorAnalyticsApi)(view.state, view.dispatch);
|
|
65
|
+
} else {
|
|
66
|
+
setHeadingWithAnalytics(headingLevel, INPUT_METHOD.FORMATTING, editorAnalyticsApi)(view.state, view.dispatch);
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
export const pluginKey = new PluginKey('blockTypePlugin');
|
|
71
|
+
export const createPlugin = (editorAnalyticsApi, dispatch, lastNodeMustBeParagraph) => {
|
|
72
|
+
let altKeyLocation = 0;
|
|
73
|
+
return new SafePlugin({
|
|
74
|
+
appendTransaction(_transactions, _oldState, newState) {
|
|
75
|
+
if (lastNodeMustBeParagraph) {
|
|
76
|
+
const pos = newState.doc.resolve(newState.doc.content.size - 1);
|
|
77
|
+
const lastNode = pos.node(1);
|
|
78
|
+
const {
|
|
79
|
+
paragraph
|
|
80
|
+
} = newState.schema.nodes;
|
|
81
|
+
if (lastNode && lastNode.isBlock && lastNode.type !== paragraph) {
|
|
82
|
+
return newState.tr.insert(newState.doc.content.size, newState.schema.nodes.paragraph.create()).setMeta('addToHistory', false);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
state: {
|
|
87
|
+
init(_config, state) {
|
|
88
|
+
const availableBlockTypes = TEXT_BLOCK_TYPES.filter(blockType => isBlockTypeSchemaSupported(blockType, state));
|
|
89
|
+
const availableWrapperBlockTypes = WRAPPER_BLOCK_TYPES.filter(blockType => isBlockTypeSchemaSupported(blockType, state));
|
|
90
|
+
return {
|
|
91
|
+
currentBlockType: detectBlockType(availableBlockTypes, state),
|
|
92
|
+
blockTypesDisabled: areBlockTypesDisabled(state),
|
|
93
|
+
availableBlockTypes,
|
|
94
|
+
availableWrapperBlockTypes
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
apply(_tr, oldPluginState, _oldState, newState) {
|
|
98
|
+
const newPluginState = {
|
|
99
|
+
...oldPluginState,
|
|
100
|
+
currentBlockType: detectBlockType(oldPluginState.availableBlockTypes, newState),
|
|
101
|
+
blockTypesDisabled: areBlockTypesDisabled(newState)
|
|
102
|
+
};
|
|
103
|
+
if (newPluginState.currentBlockType !== oldPluginState.currentBlockType || newPluginState.blockTypesDisabled !== oldPluginState.blockTypesDisabled) {
|
|
104
|
+
dispatch(pluginKey, newPluginState);
|
|
105
|
+
}
|
|
106
|
+
return newPluginState;
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
key: pluginKey,
|
|
110
|
+
props: {
|
|
111
|
+
/**
|
|
112
|
+
* As we only want the left alt key to work for headings shortcuts on Windows
|
|
113
|
+
* we can't use prosemirror-keymap and need to handle these shortcuts specially
|
|
114
|
+
* Shortcut on Mac: Cmd-Opt-{heading level}
|
|
115
|
+
* Shortcut on Windows: Ctrl-LeftAlt-{heading level}
|
|
116
|
+
*/
|
|
117
|
+
handleKeyDown: (view, event) => {
|
|
118
|
+
const headingLevel = HEADING_KEYS.indexOf(event.keyCode);
|
|
119
|
+
if (headingLevel > -1 && event.altKey) {
|
|
120
|
+
if (browser.mac && event.metaKey) {
|
|
121
|
+
return autoformatHeading(headingLevel, view, editorAnalyticsApi);
|
|
122
|
+
} else if (!browser.mac && event.ctrlKey && altKeyLocation !== event.DOM_KEY_LOCATION_RIGHT) {
|
|
123
|
+
return autoformatHeading(headingLevel, view, editorAnalyticsApi);
|
|
124
|
+
}
|
|
125
|
+
} else if (event.key === 'Alt') {
|
|
126
|
+
// event.location is for the current key only; when a user hits Ctrl-Alt-1 the
|
|
127
|
+
// location refers to the location of the '1' key
|
|
128
|
+
// We store the location of the Alt key when it is hit to check against later
|
|
129
|
+
altKeyLocation = event.location;
|
|
130
|
+
} else if (!event.altKey) {
|
|
131
|
+
altKeyLocation = 0;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { css } from '@emotion/react';
|
|
2
|
+
import { blockquoteSharedStyles, headingsSharedStyles } from '@atlaskit/editor-common/styles';
|
|
3
|
+
export const blocktypeStyles = props => css`
|
|
4
|
+
.ProseMirror {
|
|
5
|
+
${blockquoteSharedStyles};
|
|
6
|
+
${headingsSharedStyles(props)};
|
|
7
|
+
}
|
|
8
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { jsx } from '@emotion/react';
|
|
4
|
+
import { defineMessages, FormattedMessage } from 'react-intl-next';
|
|
5
|
+
import { wrapperStyle } from '@atlaskit/editor-common/styles';
|
|
6
|
+
import { ToolbarButton } from '@atlaskit/editor-common/ui-menu';
|
|
7
|
+
import ExpandIcon from '@atlaskit/icon/glyph/chevron-down';
|
|
8
|
+
import TextStyleIcon from '@atlaskit/icon/glyph/editor/text-style';
|
|
9
|
+
import { NORMAL_TEXT } from '../../block-types';
|
|
10
|
+
import { buttonContentReducedSpacingStyle, buttonContentStyle, expandIconWrapperStyle, wrapperSmallStyle } from './styled';
|
|
11
|
+
export const messages = defineMessages({
|
|
12
|
+
textStyles: {
|
|
13
|
+
id: 'fabric.editor.textStyles',
|
|
14
|
+
defaultMessage: 'Text styles',
|
|
15
|
+
description: 'Menu provides access to various heading styles or normal text'
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
export const BlockTypeButton = props => {
|
|
19
|
+
const labelTextStyles = props.formatMessage(messages.textStyles);
|
|
20
|
+
return jsx(ToolbarButton, {
|
|
21
|
+
spacing: props.isReducedSpacing ? 'none' : 'default',
|
|
22
|
+
selected: props.selected,
|
|
23
|
+
className: "block-type-btn",
|
|
24
|
+
disabled: props.disabled,
|
|
25
|
+
onClick: props.onClick,
|
|
26
|
+
onKeyDown: props.onKeyDown,
|
|
27
|
+
title: labelTextStyles,
|
|
28
|
+
"aria-label": labelTextStyles,
|
|
29
|
+
"aria-haspopup": true,
|
|
30
|
+
"aria-expanded": props['aria-expanded'],
|
|
31
|
+
iconAfter: jsx("span", {
|
|
32
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
33
|
+
css: [wrapperStyle, props.isSmall && wrapperSmallStyle],
|
|
34
|
+
"data-testid": "toolbar-block-type-text-styles-icon"
|
|
35
|
+
}, props.isSmall && jsx(TextStyleIcon, {
|
|
36
|
+
label: labelTextStyles
|
|
37
|
+
}), jsx("span", {
|
|
38
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
39
|
+
css: expandIconWrapperStyle
|
|
40
|
+
}, jsx(ExpandIcon, {
|
|
41
|
+
label: ""
|
|
42
|
+
})))
|
|
43
|
+
}, !props.isSmall && jsx("span", {
|
|
44
|
+
css: [
|
|
45
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
46
|
+
buttonContentStyle,
|
|
47
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
48
|
+
props.isReducedSpacing && buttonContentReducedSpacingStyle]
|
|
49
|
+
}, jsx(FormattedMessage, props.title || NORMAL_TEXT.title)));
|
|
50
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
/** @jsx jsx */
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { jsx } from '@emotion/react';
|
|
5
|
+
import { injectIntl } from 'react-intl-next';
|
|
6
|
+
import { findKeymapByDescription, getAriaKeyshortcuts, tooltip } from '@atlaskit/editor-common/keymaps';
|
|
7
|
+
import { separatorStyles, wrapperStyle } from '@atlaskit/editor-common/styles';
|
|
8
|
+
import { DropdownMenuWithKeyboardNavigation as DropdownMenu } from '@atlaskit/editor-common/ui-menu';
|
|
9
|
+
import { akEditorMenuZIndex } from '@atlaskit/editor-shared-styles';
|
|
10
|
+
import { BlockTypeButton } from './blocktype-button';
|
|
11
|
+
import { blockTypeMenuItemStyle, keyboardShortcut, keyboardShortcutSelect } from './styled';
|
|
12
|
+
// eslint-disable-next-line @repo/internal/react/no-class-components
|
|
13
|
+
class ToolbarBlockType extends React.PureComponent {
|
|
14
|
+
constructor(...args) {
|
|
15
|
+
super(...args);
|
|
16
|
+
_defineProperty(this, "state", {
|
|
17
|
+
active: false,
|
|
18
|
+
isOpenedByKeyboard: false
|
|
19
|
+
});
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
_defineProperty(this, "onOpenChange", attrs => {
|
|
22
|
+
this.setState({
|
|
23
|
+
...this.state,
|
|
24
|
+
active: attrs.isOpen,
|
|
25
|
+
isOpenedByKeyboard: attrs.isOpenedByKeyboard
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
_defineProperty(this, "handleTriggerClick", () => {
|
|
29
|
+
this.onOpenChange({
|
|
30
|
+
isOpen: !this.state.active,
|
|
31
|
+
isOpenedByKeyboard: false
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
_defineProperty(this, "handleTriggerByKeyboard", event => {
|
|
35
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
this.onOpenChange({
|
|
38
|
+
isOpen: !this.state.active,
|
|
39
|
+
isOpenedByKeyboard: true
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
_defineProperty(this, "createItems", () => {
|
|
44
|
+
const {
|
|
45
|
+
intl: {
|
|
46
|
+
formatMessage
|
|
47
|
+
}
|
|
48
|
+
} = this.props;
|
|
49
|
+
const {
|
|
50
|
+
currentBlockType,
|
|
51
|
+
availableBlockTypes
|
|
52
|
+
} = this.props.pluginState;
|
|
53
|
+
const items = availableBlockTypes.map((blockType, index) => {
|
|
54
|
+
const isActive = currentBlockType === blockType;
|
|
55
|
+
const tagName = blockType.tagName || 'p';
|
|
56
|
+
const Tag = tagName;
|
|
57
|
+
const keyMap = findKeymapByDescription(blockType.title.defaultMessage);
|
|
58
|
+
return {
|
|
59
|
+
content:
|
|
60
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
61
|
+
jsx("div", {
|
|
62
|
+
css: blockTypeMenuItemStyle(tagName, isActive)
|
|
63
|
+
}, jsx(Tag, null, formatMessage(blockType.title))),
|
|
64
|
+
value: blockType,
|
|
65
|
+
label: formatMessage(blockType.title),
|
|
66
|
+
'aria-label': tooltip(keyMap, formatMessage(blockType.title)),
|
|
67
|
+
keyShortcuts: getAriaKeyshortcuts(keyMap),
|
|
68
|
+
key: `${blockType.name}-${index}`,
|
|
69
|
+
elemAfter:
|
|
70
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
71
|
+
jsx("div", {
|
|
72
|
+
css: [keyboardShortcut, isActive && keyboardShortcutSelect]
|
|
73
|
+
}, tooltip(keyMap)),
|
|
74
|
+
isActive
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
return [{
|
|
78
|
+
items
|
|
79
|
+
}];
|
|
80
|
+
});
|
|
81
|
+
_defineProperty(this, "handleSelectBlockType", ({
|
|
82
|
+
item,
|
|
83
|
+
shouldCloseMenu = true
|
|
84
|
+
}) => {
|
|
85
|
+
const blockType = item.value;
|
|
86
|
+
this.props.setBlockType(blockType.name);
|
|
87
|
+
if (shouldCloseMenu) {
|
|
88
|
+
this.setState({
|
|
89
|
+
...this.state,
|
|
90
|
+
active: false
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
render() {
|
|
96
|
+
const {
|
|
97
|
+
active,
|
|
98
|
+
isOpenedByKeyboard
|
|
99
|
+
} = this.state;
|
|
100
|
+
const {
|
|
101
|
+
popupsMountPoint,
|
|
102
|
+
popupsBoundariesElement,
|
|
103
|
+
popupsScrollableElement,
|
|
104
|
+
isSmall,
|
|
105
|
+
isReducedSpacing,
|
|
106
|
+
pluginState: {
|
|
107
|
+
currentBlockType,
|
|
108
|
+
blockTypesDisabled,
|
|
109
|
+
availableBlockTypes
|
|
110
|
+
},
|
|
111
|
+
intl: {
|
|
112
|
+
formatMessage
|
|
113
|
+
}
|
|
114
|
+
} = this.props;
|
|
115
|
+
const isHeadingDisabled = !availableBlockTypes.some(blockType => blockType.nodeName === 'heading');
|
|
116
|
+
if (isHeadingDisabled) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const blockTypeTitles = availableBlockTypes.filter(blockType => blockType.name === currentBlockType.name).map(blockType => blockType.title);
|
|
120
|
+
if (!this.props.isDisabled && !blockTypesDisabled) {
|
|
121
|
+
const items = this.createItems();
|
|
122
|
+
return (
|
|
123
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
124
|
+
jsx("span", {
|
|
125
|
+
css: wrapperStyle
|
|
126
|
+
}, jsx(DropdownMenu, {
|
|
127
|
+
items: items,
|
|
128
|
+
onOpenChange: this.onOpenChange,
|
|
129
|
+
onItemActivated: this.handleSelectBlockType,
|
|
130
|
+
isOpen: active,
|
|
131
|
+
mountTo: popupsMountPoint,
|
|
132
|
+
boundariesElement: popupsBoundariesElement,
|
|
133
|
+
scrollableElement: popupsScrollableElement,
|
|
134
|
+
zIndex: akEditorMenuZIndex,
|
|
135
|
+
fitHeight: 360,
|
|
136
|
+
fitWidth: 106,
|
|
137
|
+
shouldUseDefaultRole: true,
|
|
138
|
+
shouldFocusFirstItem: () => {
|
|
139
|
+
if (isOpenedByKeyboard) {
|
|
140
|
+
// eslint-disable-next-line @repo/internal/react/no-set-state-inside-render
|
|
141
|
+
this.setState({
|
|
142
|
+
...this.state,
|
|
143
|
+
isOpenedByKeyboard: false
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return isOpenedByKeyboard;
|
|
147
|
+
}
|
|
148
|
+
}, jsx(BlockTypeButton, {
|
|
149
|
+
isSmall: isSmall,
|
|
150
|
+
isReducedSpacing: isReducedSpacing,
|
|
151
|
+
selected: active,
|
|
152
|
+
disabled: false,
|
|
153
|
+
title: blockTypeTitles[0],
|
|
154
|
+
onClick: this.handleTriggerClick,
|
|
155
|
+
onKeyDown: this.handleTriggerByKeyboard,
|
|
156
|
+
formatMessage: formatMessage,
|
|
157
|
+
"aria-expanded": active
|
|
158
|
+
})), jsx("span", {
|
|
159
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
160
|
+
css: separatorStyles
|
|
161
|
+
}))
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return (
|
|
165
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
166
|
+
jsx("span", {
|
|
167
|
+
css: wrapperStyle
|
|
168
|
+
}, jsx(BlockTypeButton, {
|
|
169
|
+
isSmall: isSmall,
|
|
170
|
+
isReducedSpacing: isReducedSpacing,
|
|
171
|
+
selected: active,
|
|
172
|
+
disabled: true,
|
|
173
|
+
title: blockTypeTitles[0],
|
|
174
|
+
onClick: this.handleTriggerClick,
|
|
175
|
+
onKeyDown: this.handleTriggerByKeyboard,
|
|
176
|
+
formatMessage: formatMessage,
|
|
177
|
+
"aria-expanded": active
|
|
178
|
+
}), jsx("span", {
|
|
179
|
+
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
|
|
180
|
+
css: separatorStyles
|
|
181
|
+
}))
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export default injectIntl(ToolbarBlockType);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { css } from '@emotion/react';
|
|
3
|
+
import { headingsSharedStyles } from '@atlaskit/editor-common/styles';
|
|
4
|
+
import { shortcutStyle } from '@atlaskit/editor-shared-styles/shortcut';
|
|
5
|
+
import { N400 } from '@atlaskit/theme/colors';
|
|
6
|
+
export const blockTypeMenuItemStyle = (tagName, selected) => {
|
|
7
|
+
// TEMP FIX: See https://product-fabric.atlassian.net/browse/ED-13878
|
|
8
|
+
const selectedStyle = selected ? `${tagName} { color: ${"var(--ds-text, white)"} !important; }` : '';
|
|
9
|
+
return themeProps => css`
|
|
10
|
+
${headingsSharedStyles(themeProps)};
|
|
11
|
+
> {
|
|
12
|
+
h1,
|
|
13
|
+
h2,
|
|
14
|
+
h3,
|
|
15
|
+
h4,
|
|
16
|
+
h5,
|
|
17
|
+
h6 {
|
|
18
|
+
margin-top: 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
${selectedStyle};
|
|
22
|
+
`;
|
|
23
|
+
};
|
|
24
|
+
export const keyboardShortcut = css`
|
|
25
|
+
${shortcutStyle}
|
|
26
|
+
margin-left: ${"var(--ds-space-200, 16px)"};
|
|
27
|
+
`;
|
|
28
|
+
export const keyboardShortcutSelect = css`
|
|
29
|
+
color: ${`var(--ds-icon, ${N400})`};
|
|
30
|
+
`;
|
|
31
|
+
export const buttonContentStyle = css`
|
|
32
|
+
display: flex;
|
|
33
|
+
min-width: 80px;
|
|
34
|
+
align-items: center;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
padding: ${"var(--ds-space-075, 6px)"};
|
|
39
|
+
`;
|
|
40
|
+
export const buttonContentReducedSpacingStyle = css`
|
|
41
|
+
padding: ${"var(--ds-space-100, 8px)"};
|
|
42
|
+
`;
|
|
43
|
+
export const wrapperSmallStyle = css`
|
|
44
|
+
margin-left: ${"var(--ds-space-050, 4px)"};
|
|
45
|
+
min-width: 40px;
|
|
46
|
+
`;
|
|
47
|
+
export const expandIconWrapperStyle = css`
|
|
48
|
+
margin-left: -8px;
|
|
49
|
+
`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineMessages } from 'react-intl-next';
|
|
2
|
+
export const toolbarMessages = defineMessages({
|
|
3
|
+
textStyles: {
|
|
4
|
+
id: 'fabric.editor.textStyles',
|
|
5
|
+
defaultMessage: 'Text styles',
|
|
6
|
+
description: 'Menu provides access to various heading styles or normal text'
|
|
7
|
+
}
|
|
8
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createWrappingJoinRule } from '@atlaskit/editor-common/utils';
|
|
2
|
+
import { createRule } from '@atlaskit/prosemirror-input-rules';
|
|
3
|
+
import { WRAPPER_BLOCK_TYPES } from './block-types';
|
|
4
|
+
export const isNodeAWrappingBlockNode = node => {
|
|
5
|
+
if (!node) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
return WRAPPER_BLOCK_TYPES.some(blockNode => blockNode.name === node.type.name);
|
|
9
|
+
};
|
|
10
|
+
export const createJoinNodesRule = (match, nodeType) => {
|
|
11
|
+
return createWrappingJoinRule({
|
|
12
|
+
nodeType,
|
|
13
|
+
match,
|
|
14
|
+
getAttrs: {},
|
|
15
|
+
joinPredicate: (_, node) => node.type === nodeType
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
export const createWrappingTextBlockRule = ({
|
|
19
|
+
match,
|
|
20
|
+
nodeType,
|
|
21
|
+
getAttrs
|
|
22
|
+
}) => {
|
|
23
|
+
const handler = (state, match, start, end) => {
|
|
24
|
+
const fixedStart = Math.max(start, 1);
|
|
25
|
+
const $start = state.doc.resolve(fixedStart);
|
|
26
|
+
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
|
|
27
|
+
const nodeBefore = $start.node(-1);
|
|
28
|
+
if (nodeBefore && !nodeBefore.canReplaceWith($start.index(-1), $start.indexAfter(-1), nodeType)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return state.tr.delete(fixedStart, end).setBlockType(fixedStart, fixedStart, nodeType, attrs);
|
|
32
|
+
};
|
|
33
|
+
return createRule(match, handler);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Function will create a list of wrapper blocks present in a selection.
|
|
38
|
+
*/
|
|
39
|
+
function getSelectedWrapperNodes(state) {
|
|
40
|
+
const nodes = [];
|
|
41
|
+
if (state.selection) {
|
|
42
|
+
const {
|
|
43
|
+
$from,
|
|
44
|
+
$to
|
|
45
|
+
} = state.selection;
|
|
46
|
+
const {
|
|
47
|
+
blockquote,
|
|
48
|
+
panel,
|
|
49
|
+
orderedList,
|
|
50
|
+
bulletList,
|
|
51
|
+
listItem,
|
|
52
|
+
codeBlock,
|
|
53
|
+
decisionItem,
|
|
54
|
+
decisionList,
|
|
55
|
+
taskItem,
|
|
56
|
+
taskList
|
|
57
|
+
} = state.schema.nodes;
|
|
58
|
+
state.doc.nodesBetween($from.pos, $to.pos, node => {
|
|
59
|
+
if (node.isBlock && [blockquote, panel, orderedList, bulletList, listItem, codeBlock, decisionItem, decisionList, taskItem, taskList].indexOf(node.type) >= 0) {
|
|
60
|
+
nodes.push(node.type);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return nodes;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Function will check if changing block types: Paragraph, Heading is enabled.
|
|
69
|
+
*/
|
|
70
|
+
export function areBlockTypesDisabled(state) {
|
|
71
|
+
const nodesTypes = getSelectedWrapperNodes(state);
|
|
72
|
+
const {
|
|
73
|
+
panel
|
|
74
|
+
} = state.schema.nodes;
|
|
75
|
+
return nodesTypes.filter(type => type !== panel).length > 0;
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { blocktypeStyles } from './plugin/styles';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BLOCK_QUOTE, CODE_BLOCK, HEADING_1, HEADING_2, HEADING_3, HEADING_4, HEADING_5, HEADING_6, NORMAL_TEXT, PANEL } from './plugin/block-types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { blockTypePlugin } from './plugin';
|