@atlaskit/editor-plugin-alignment
Version:
Alignment plugin for @atlaskit/editor-core
186 lines (184 loc) • 6.55 kB
JavaScript
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;
};