@atlaskit/editor-plugin-code-block
Version:
Code block plugin for @atlaskit/editor-core
217 lines (214 loc) • 8.7 kB
JavaScript
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, MODE, PLATFORMS } from '@atlaskit/editor-common/analytics';
import { copyToClipboard } from '@atlaskit/editor-common/clipboard';
import { codeBlockWrappedStates, isCodeBlockWordWrapEnabled } from '@atlaskit/editor-common/code-block';
import { withAnalytics } from '@atlaskit/editor-common/editor-analytics';
import { contentAllowedInCodeBlock, shouldSplitSelectedNodeOnNodeInsertion } from '@atlaskit/editor-common/insert';
import { findCodeBlock } from '@atlaskit/editor-common/transforms';
import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
import { findParentNodeOfType, findSelectedNodeOfType, isNodeSelection, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
import { ACTIONS } from '../pm-plugins/actions';
import { copySelectionPluginKey } from '../pm-plugins/codeBlockCopySelectionPlugin';
import { pluginKey } from '../pm-plugins/plugin-key';
import { transformToCodeBlockAction } from '../pm-plugins/transform-to-code-block';
export var removeCodeBlock = function removeCodeBlock(state, dispatch) {
var nodes = state.schema.nodes,
tr = state.tr;
if (dispatch) {
var removeTr = tr;
if (findSelectedNodeOfType(nodes.codeBlock)(tr.selection)) {
removeTr = removeSelectedNode(tr);
} else {
removeTr = removeParentNodeOfType(nodes.codeBlock)(tr);
}
dispatch(removeTr);
}
return true;
};
export var changeLanguage = function changeLanguage(editorAnalyticsAPI) {
return function (language) {
return function (state, dispatch) {
var _pluginKey$getState;
var codeBlock = state.schema.nodes.codeBlock;
var pos = (_pluginKey$getState = pluginKey.getState(state)) === null || _pluginKey$getState === void 0 ? void 0 : _pluginKey$getState.pos;
if (typeof pos !== 'number') {
return false;
}
var tr = state.tr.setNodeMarkup(pos, codeBlock, {
language: language
}).setMeta('scrollIntoView', false);
var selection = isNodeSelection(state.selection) ? NodeSelection.create(tr.doc, pos) : tr.selection;
var result = tr.setSelection(selection);
if (dispatch) {
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
action: ACTION.LANGUAGE_SELECTED,
actionSubject: ACTION_SUBJECT.CODE_BLOCK,
attributes: {
language: language
},
eventType: EVENT_TYPE.TRACK
})(result);
dispatch(result);
}
return true;
};
};
};
export var copyContentToClipboard = function copyContentToClipboard(state, dispatch) {
var nodes = state.schema.nodes,
tr = state.tr;
var codeBlock = findParentNodeOfType(nodes.codeBlock)(tr.selection);
var textContent = codeBlock && codeBlock.node.textContent;
if (textContent) {
copyToClipboard(textContent);
var copyToClipboardTr = tr;
copyToClipboardTr.setMeta(pluginKey, {
type: ACTIONS.SET_COPIED_TO_CLIPBOARD,
data: true
});
copyToClipboardTr.setMeta(copySelectionPluginKey, 'remove-selection');
if (dispatch) {
dispatch(copyToClipboardTr);
}
}
return true;
};
export var resetCopiedState = function resetCopiedState(state, dispatch) {
var tr = state.tr;
var codeBlockState = pluginKey.getState(state);
var resetCopiedStateTr = tr;
if (codeBlockState && codeBlockState.contentCopied) {
resetCopiedStateTr.setMeta(pluginKey, {
type: ACTIONS.SET_COPIED_TO_CLIPBOARD,
data: false
});
resetCopiedStateTr.setMeta(copySelectionPluginKey, 'remove-selection');
if (dispatch) {
dispatch(resetCopiedStateTr);
}
} else {
var clearSelectionStateTransaction = state.tr;
clearSelectionStateTransaction.setMeta(copySelectionPluginKey, 'remove-selection');
// note: dispatch should always be defined when called from the
// floating toolbar. Howver the Command type which floating toolbar uses
// (and resetCopiedState) uses suggests it's optional.
if (dispatch) {
dispatch(clearSelectionStateTransaction);
}
}
return true;
};
export var ignoreFollowingMutations = function ignoreFollowingMutations(state, dispatch) {
var tr = state.tr;
var ignoreFollowingMutationsTr = tr;
ignoreFollowingMutationsTr.setMeta(pluginKey, {
type: ACTIONS.SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS,
data: true
});
if (dispatch) {
dispatch(ignoreFollowingMutationsTr);
}
return true;
};
export var resetShouldIgnoreFollowingMutations = function resetShouldIgnoreFollowingMutations(state, dispatch) {
var tr = state.tr;
var ignoreFollowingMutationsTr = tr;
ignoreFollowingMutationsTr.setMeta(pluginKey, {
type: ACTIONS.SET_SHOULD_IGNORE_FOLLOWING_MUTATIONS,
data: false
});
if (dispatch) {
dispatch(ignoreFollowingMutationsTr);
}
return true;
};
/**
* This function creates a new transaction that inserts a code block,
* if there is text selected it will wrap the current selection if not it will
* append the codeblock to the end of the document.
*/
export function createInsertCodeBlockTransaction(_ref) {
var state = _ref.state,
isNestingInQuoteSupported = _ref.isNestingInQuoteSupported;
var tr = state.tr;
var _state$selection = state.selection,
from = _state$selection.from,
$from = _state$selection.$from;
var codeBlock = state.schema.nodes.codeBlock;
var grandParentNode = state.selection.$from.node(-1);
var grandParentNodeType = grandParentNode === null || grandParentNode === void 0 ? void 0 : grandParentNode.type;
var parentNodeType = state.selection.$from.parent.type;
/** We always want to append a codeBlock unless we're inserting into a paragraph
* AND it's a valid child of the grandparent node.
* Insert the current selection as codeBlock content unless it contains nodes other
* than paragraphs and inline.
*/
var canInsertCodeBlock = shouldSplitSelectedNodeOnNodeInsertion({
parentNodeType: parentNodeType,
grandParentNodeType: grandParentNodeType,
content: codeBlock.createAndFill()
}) && contentAllowedInCodeBlock(state);
if (canInsertCodeBlock) {
tr = transformToCodeBlockAction(state, from, undefined, isNestingInQuoteSupported);
} else if (!isNestingInQuoteSupported && (grandParentNodeType === null || grandParentNodeType === void 0 ? void 0 : grandParentNodeType.name) === 'blockquote') {
/** we only allow the insertion of a codeblock inside a blockquote if nesting in quotes is supported */
var grandparentEndPos = $from.start(-1) + grandParentNode.nodeSize - 1;
safeInsert(codeBlock.createAndFill(), grandparentEndPos)(tr).scrollIntoView();
} else {
safeInsert(codeBlock.createAndFill())(tr).scrollIntoView();
}
return tr;
}
export function insertCodeBlockWithAnalytics(inputMethod, analyticsAPI, isNestingInQuoteSupported) {
return withAnalytics(analyticsAPI, {
action: ACTION.INSERTED,
actionSubject: ACTION_SUBJECT.DOCUMENT,
actionSubjectId: ACTION_SUBJECT_ID.CODE_BLOCK,
attributes: {
inputMethod: inputMethod
},
eventType: EVENT_TYPE.TRACK
})(function (state, dispatch) {
var tr = createInsertCodeBlockTransaction({
state: state,
isNestingInQuoteSupported: isNestingInQuoteSupported
});
if (dispatch) {
dispatch(tr);
}
return true;
});
}
/**
* Add the given node to the codeBlockWrappedStates WeakMap with the toggle boolean value.
*/
export var toggleWordWrapStateForCodeBlockNode = function toggleWordWrapStateForCodeBlockNode(editorAnalyticsAPI) {
return function (state, dispatch) {
var _findCodeBlock;
var codeBlockNode = (_findCodeBlock = findCodeBlock(state)) === null || _findCodeBlock === void 0 ? void 0 : _findCodeBlock.node;
var tr = state.tr;
if (!codeBlockWrappedStates || !codeBlockNode) {
return false;
}
var updatedToggleState = !isCodeBlockWordWrapEnabled(codeBlockNode);
codeBlockWrappedStates.set(codeBlockNode, updatedToggleState);
tr.setMeta(pluginKey, {
type: ACTIONS.SET_IS_WRAPPED,
data: updatedToggleState
});
if (dispatch) {
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
action: ACTION.TOGGLE_CODE_BLOCK_WRAP,
actionSubject: ACTION_SUBJECT.CODE_BLOCK,
attributes: {
platform: PLATFORMS.WEB,
mode: MODE.EDITOR,
wordWrapEnabled: updatedToggleState,
codeBlockNodeSize: codeBlockNode.nodeSize
},
eventType: EVENT_TYPE.TRACK
})(tr);
dispatch(tr);
}
return true;
};
};