UNPKG

@atlaskit/editor-plugin-alignment

Version:

Alignment plugin for @atlaskit/editor-core

186 lines (184 loc) • 6.55 kB
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics/types/enums'; import { changeImageAlignment, changeImageAlignmentNext, toggleBlockMark, toggleBlockMarkNext } from '@atlaskit/editor-common/commands'; import { withAnalytics } from '@atlaskit/editor-common/withAnalytics'; import { Selection } from '@atlaskit/editor-prosemirror/state'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled'; /** * Iterates over the commands one after the other, * passes the tr through and dispatches the cumulated transaction */ function cascadeCommands(cmds) { return (state, dispatch) => { const { tr: baseTr } = state; let shouldDispatch = false; const onDispatchAction = tr => { const selectionJSON = tr.selection.toJSON(); baseTr.setSelection(Selection.fromJSON(baseTr.doc, selectionJSON)); tr.steps.forEach(st => { baseTr.step(st); }); shouldDispatch = true; }; cmds.forEach(cmd => cmd(state, onDispatchAction)); if (dispatch && shouldDispatch) { dispatch(baseTr); return true; } return false; }; } export const isAlignable = align => (state, dispatch) => { const { nodes: { paragraph, heading }, marks: { alignment } } = state.schema; if (!dispatch && state.selection instanceof CellSelection && isExperimentEnabled('platform_editor_improve_inp_batch_2')) { if (!align) { return false; } // toggleBlockMark returns the result of the last cell, rather than // combining results. Preserve that behavior without building a transaction. // CellSelection moves the head range first, leaving the rest in document // order. Compare both ends to find the final cell visited by forEachCell. const { ranges } = state.selection; const firstRange = ranges[0]; const lastRange = ranges[ranges.length - 1]; const lastCellRange = firstRange.$from.pos > lastRange.$from.pos ? firstRange : lastRange; const lastCell = lastCellRange.$from.parent; const hasAlignableBlock = parent => { for (let index = 0; index < parent.childCount; index++) { const node = parent.child(index); if (!node.isBlock) { continue; } if ((node.type === paragraph || node.type === heading) && parent.type.allowsMarkType(alignment)) { return true; } if (hasAlignableBlock(node)) { return true; } } return false; }; return hasAlignableBlock(lastCell); } return toggleBlockMark(alignment, () => !align ? undefined : align === 'start' ? false : { align }, [paragraph, heading])(state, dispatch); }; const changeBlockAlignmentWithAnalytics = (editorAnalyticsApi, align, inputMethod) => (state, dispatch) => { const { nodes: { paragraph, heading }, marks: { alignment } } = state.schema; return withAnalytics(editorAnalyticsApi, { eventType: EVENT_TYPE.TRACK, actionSubject: ACTION_SUBJECT.ALIGNMENT, action: ACTION.UPDATED, actionSubjectId: ACTION_SUBJECT_ID.TEXT, attributes: { alignmentType: align, inputMethod } })(toggleBlockMark(alignment, () => !align ? undefined : align === 'start' ? false : { align }, [paragraph, heading]))(state, dispatch); }; const changeBlockAlignmentWithAnalyticsTr = (api, align, inputMethod) => ({ tr }) => { const { nodes: { paragraph, heading }, marks: { alignment } } = tr.doc.type.schema; const alignmentApplied = toggleBlockMarkNext(alignment, () => !align ? undefined : align === 'start' ? false : { align }, [paragraph, heading])(tr); if (alignmentApplied) { var _api$analytics; api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.attachAnalyticsEvent({ eventType: EVENT_TYPE.TRACK, actionSubject: ACTION_SUBJECT.ALIGNMENT, action: ACTION.UPDATED, actionSubjectId: ACTION_SUBJECT_ID.TEXT, attributes: { alignmentType: align, inputMethod } })(tr); } return tr; }; const changeImageAlignmentWithAnalytics = (editorAnalyticsApi, align, inputMethod) => (state, dispatch) => { return withAnalytics(editorAnalyticsApi, { eventType: EVENT_TYPE.TRACK, actionSubject: ACTION_SUBJECT.ALIGNMENT, action: ACTION.UPDATED, actionSubjectId: ACTION_SUBJECT_ID.MEDIA_SINGLE, attributes: { alignmentType: align, inputMethod } })(changeImageAlignment(align))(state, dispatch); }; const changeImageAlignmentWithAnalyticsTr = (api, align, inputMethod) => ({ tr }) => { const alignmentApplied = changeImageAlignmentNext(align)(tr); if (alignmentApplied) { var _api$analytics2; api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.attachAnalyticsEvent({ eventType: EVENT_TYPE.TRACK, actionSubject: ACTION_SUBJECT.ALIGNMENT, action: ACTION.UPDATED, actionSubjectId: ACTION_SUBJECT_ID.MEDIA_SINGLE, attributes: { alignmentType: align, inputMethod } })(tr); } return tr; }; // eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required /** * Command-based alignment used by the legacy primary/floating toolbars and alignment keymap. * The new toolbar uses changeAlignmentTr. * * @deprecated use changeAlignmentTr instead */ export const changeAlignment = (align, api, inputMethod) => (state, dispatch) => { var _api$analytics3, _api$analytics4; return cascadeCommands([changeImageAlignmentWithAnalytics(api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions, align, inputMethod), changeBlockAlignmentWithAnalytics(api === null || api === void 0 ? void 0 : (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : _api$analytics4.actions, align, inputMethod)])(state, dispatch); }; export const changeAlignmentTr = (api, align, inputMethod) => ({ tr }) => { changeBlockAlignmentWithAnalyticsTr(api, align, inputMethod)({ tr }); changeImageAlignmentWithAnalyticsTr(api, align, inputMethod)({ tr }); return tr; };