@atlaskit/editor-plugin-breakout
Version:
Breakout plugin for @atlaskit/editor-core
199 lines (197 loc) • 9.29 kB
JavaScript
import { akEditorGutterPaddingDynamic, akEditorGutterPadding, akEditorGutterPaddingReduced, akEditorFullPageNarrowBreakout, akEditorDefaultLayoutWidth, akEditorFullWidthLayoutWidth, akEditorCalculatedWideLayoutWidth, akEditorMaxLayoutWidth } from '@atlaskit/editor-shared-styles';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { setBreakoutWidth } from '../editor-commands/set-breakout-width';
import { getGuidelines } from './get-guidelines';
import { LOCAL_RESIZE_PROPERTY } from './resizing-mark-view';
import { resizingPluginKey } from './resizing-plugin';
import { generateResizeFrameRatePayloads, generateResizedEventPayload } from './utils/analytics';
import { measureFramerate, reduceResizeFrameRateSamples } from './utils/measure-framerate';
const RESIZE_RATIO = 2;
const SNAP_GAP = 10;
const WIDTHS = {
MIN: akEditorDefaultLayoutWidth,
WIDE: akEditorCalculatedWideLayoutWidth,
FULL: akEditorFullWidthLayoutWidth,
MAX: akEditorMaxLayoutWidth
};
export function getProposedWidth({
initialWidth,
location,
api,
source
}) {
var _api$width$sharedStat, _api$width$sharedStat2;
const directionMultiplier = source.data.handleSide === 'left' ? -1 : 1;
const diffX = (location.current.input.clientX - location.initial.input.clientX) * RESIZE_RATIO * directionMultiplier;
const width = (api === null || api === void 0 ? void 0 : (_api$width$sharedStat = api.width.sharedState) === null || _api$width$sharedStat === void 0 ? void 0 : (_api$width$sharedStat2 = _api$width$sharedStat.currentState()) === null || _api$width$sharedStat2 === void 0 ? void 0 : _api$width$sharedStat2.width) || 0;
const padding = width <= akEditorFullPageNarrowBreakout && editorExperiment('platform_editor_preview_panel_responsiveness', true, {
exposure: true
}) ? akEditorGutterPaddingReduced : akEditorGutterPaddingDynamic();
const containerWidth = width - 2 * padding - akEditorGutterPadding;
// the node width may be greater than the container width so we resize using the smaller value
const proposedWidth = Math.min(initialWidth, containerWidth) + diffX;
const snapPoints = [WIDTHS.MIN, WIDTHS.WIDE, Math.min(containerWidth, WIDTHS.FULL)];
if (expValEquals('editor_tinymce_full_width_mode', 'isEnabled', true) || expValEquals('confluence_max_width_content_appearance', 'isEnabled', true)) {
snapPoints.push(Math.min(containerWidth, WIDTHS.MAX));
}
for (const snapPoint of snapPoints) {
if (snapPoint - SNAP_GAP < proposedWidth && snapPoint + SNAP_GAP > proposedWidth) {
return snapPoint;
}
}
const hardMax = expValEquals('editor_tinymce_full_width_mode', 'isEnabled', true) || expValEquals('confluence_max_width_content_appearance', 'isEnabled', true) ? Math.min(containerWidth, WIDTHS.MAX) : Math.min(containerWidth, WIDTHS.FULL);
return Math.max(WIDTHS.MIN, Math.min(proposedWidth, hardMax));
}
export function createResizerCallbacks({
dom,
view,
mark,
api
}) {
let node = null;
let guidelines = [];
const {
startMeasure,
endMeasure,
countFrames
} = measureFramerate();
const getEditorWidth = () => {
var _api$width;
return api === null || api === void 0 ? void 0 : (_api$width = api.width) === null || _api$width === void 0 ? void 0 : _api$width.sharedState.currentState();
};
return {
onDragStart: () => {
startMeasure();
const pos = view.posAtDOM(dom, 0);
node = view.state.doc.nodeAt(pos);
api === null || api === void 0 ? void 0 : api.core.actions.execute(({
tr
}) => {
var _api$userIntent;
(_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.commands.setCurrentUserIntent('dragging')({
tr
});
tr.setMeta('is-resizer-resizing', true);
tr.setMeta(resizingPluginKey, {
type: 'UPDATE_BREAKOUT_NODE',
data: {
node,
pos,
start: pos,
depth: 0
}
});
return tr;
});
},
onDrag: ({
location,
source
}) => {
var _node, _api$guideline, _api$guideline$action, _api$breakout$sharedS;
countFrames();
const initialWidth = mark.attrs.width;
const newWidth = getProposedWidth({
initialWidth,
location,
api,
source
});
guidelines = getGuidelines(true, newWidth, getEditorWidth, (_node = node) === null || _node === void 0 ? void 0 : _node.type);
api === null || api === void 0 ? void 0 : (_api$guideline = api.guideline) === null || _api$guideline === void 0 ? void 0 : (_api$guideline$action = _api$guideline.actions) === null || _api$guideline$action === void 0 ? void 0 : _api$guideline$action.displayGuideline(view)({
guidelines
});
const activeGuideline = guidelines.find(guideline => guideline.active && !guideline.key.startsWith('grid'));
if (activeGuideline) {
api === null || api === void 0 ? void 0 : api.core.actions.execute(({
tr
}) => {
tr.setMeta(resizingPluginKey, {
type: 'UPDATE_ACTIVE_GUIDELINE_KEY',
data: {
activeGuidelineKey: activeGuideline.key
}
});
return tr;
});
}
if (!activeGuideline && api !== null && api !== void 0 && (_api$breakout$sharedS = api.breakout.sharedState.currentState()) !== null && _api$breakout$sharedS !== void 0 && _api$breakout$sharedS.activeGuidelineKey) {
api === null || api === void 0 ? void 0 : api.core.actions.execute(({
tr
}) => {
tr.setMeta(resizingPluginKey, {
type: 'CLEAR_ACTIVE_GUIDELINE_KEY'
});
return tr;
});
}
// dom is used for width calculations
dom.style.setProperty(LOCAL_RESIZE_PROPERTY, `${newWidth}px`);
},
onDrop({
location,
source
}) {
var _api$guideline2, _api$guideline2$actio, _api$editorViewMode, _api$editorViewMode$s;
let payloads = [];
const frameRateSamples = endMeasure();
payloads = generateResizeFrameRatePayloads({
docSize: view.state.doc.nodeSize,
frameRateSamples: reduceResizeFrameRateSamples(frameRateSamples),
originalNode: node
});
const isResizedToFullWidth = !!guidelines.find(guideline => guideline.key.startsWith('full_width') && guideline.active);
let isResizedToMaxWidth = false;
if (expValEquals('editor_tinymce_full_width_mode', 'isEnabled', true) || expValEquals('confluence_max_width_content_appearance', 'isEnabled', true)) {
isResizedToMaxWidth = !!guidelines.find(guideline => guideline.key.startsWith('max_width') && guideline.active);
}
guidelines = getGuidelines(false, 0, getEditorWidth);
api === null || api === void 0 ? void 0 : (_api$guideline2 = api.guideline) === null || _api$guideline2 === void 0 ? void 0 : (_api$guideline2$actio = _api$guideline2.actions) === null || _api$guideline2$actio === void 0 ? void 0 : _api$guideline2$actio.displayGuideline(view)({
guidelines
});
const pos = view.posAtDOM(dom, 0);
const mode = mark.attrs.mode;
const initialWidth = mark.attrs.width;
let newWidth = isResizedToFullWidth ? WIDTHS.FULL : getProposedWidth({
initialWidth,
location,
api,
source
});
if (expValEquals('editor_tinymce_full_width_mode', 'isEnabled', true) || expValEquals('confluence_max_width_content_appearance', 'isEnabled', true)) {
if (isResizedToMaxWidth) {
newWidth = WIDTHS.MAX;
}
}
const isEditMode = (api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : (_api$editorViewMode$s = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode$s === void 0 ? void 0 : _api$editorViewMode$s.mode) === 'edit';
setBreakoutWidth(newWidth, mode, pos, isEditMode)(view.state, view.dispatch);
dom.style.removeProperty(LOCAL_RESIZE_PROPERTY);
if (node) {
const resizedPayload = generateResizedEventPayload({
node,
prevWidth: initialWidth,
newWidth
});
payloads.push(resizedPayload);
}
api === null || api === void 0 ? void 0 : api.core.actions.execute(({
tr
}) => {
var _api$userIntent2;
(_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 ? void 0 : _api$userIntent2.commands.setCurrentUserIntent('default')({
tr
});
payloads.forEach(payload => {
var _api$analytics, _api$analytics$action;
(_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : (_api$analytics$action = _api$analytics.actions) === null || _api$analytics$action === void 0 ? void 0 : _api$analytics$action.attachAnalyticsEvent(payload)(tr);
});
tr.setMeta('is-resizer-resizing', false).setMeta('scrollIntoView', false);
tr.setMeta(resizingPluginKey, {
type: 'RESET_STATE'
});
return tr;
});
}
};
}