@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
251 lines (237 loc) • 10.6 kB
JavaScript
import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
import { akEditorDefaultLayoutWidth, akEditorFullWidthLayoutWidth, akEditorGutterPadding, akEditorGutterPaddingDynamic, breakoutWideScaleRatio } from '@atlaskit/editor-shared-styles';
import { floatingLayouts, isRichMediaInsideOfBlockNode } from '../utils/rich-media-utils';
import { DEFAULT_IMAGE_WIDTH, DEFAULT_ROUNDING_INTERVAL, MEDIA_SINGLE_DEFAULT_MIN_PIXEL_WIDTH, MEDIA_SINGLE_VIDEO_MIN_PIXEL_WIDTH, wrappedLayouts } from './constants';
/**
* Convert media node width to pixel
*
* for legacy experience, image is aligned inside resize handle bar with a gap. So gutterOffset is used to for this use case.
* for new experience, image is aligned with resize handle bar, so gutterOffset is 0
*
* @param width - media single node width
* @param editorWidth - width of editor
* @param widthType - width type is defined in the adf document for mediaSingle node, and it is associated with the `width`
* @param gutterOffset - resize handle bar offset, determines whether align with resize handle bar
* @returns pixel number for media single node
*/
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/max-params
export function getMediaSinglePixelWidth(width, editorWidth) {
var widthType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'percentage';
var gutterOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
if (widthType === 'pixel') {
return width;
}
return Math.ceil((editorWidth + gutterOffset) * (width / 100) - gutterOffset);
}
/**
* Convert width attribute to pixel value for legacy (resized or not resisized) and new media single node for new experience
* @param width node width attribute
* @param widthType node widthType attribute
* @param origWidth original media width
* @param layout node layout attribute
* @param contentWidth editor content width
* @param containerWidth editor container width
* @param gutterOffset gap between resizer handle and media
* @returns pixel width of the node
*/
export var calcMediaSinglePixelWidth = function calcMediaSinglePixelWidth(_ref) {
var width = _ref.width,
_ref$widthType = _ref.widthType,
widthType = _ref$widthType === void 0 ? 'percentage' : _ref$widthType,
origWidth = _ref.origWidth,
layout = _ref.layout,
contentWidth = _ref.contentWidth,
containerWidth = _ref.containerWidth,
_ref$gutterOffset = _ref.gutterOffset,
gutterOffset = _ref$gutterOffset === void 0 ? 0 : _ref$gutterOffset;
if (widthType === 'pixel' && width) {
return width;
}
switch (layout) {
case 'wide':
return calcLegacyWideWidth(containerWidth, origWidth, contentWidth);
case 'full-width':
// legacy and new experience have different definitions of full-width,
// since it's for new experience, we convert to the new definition
return calcMediaSingleMaxWidth(containerWidth);
default:
if (width) {
return Math.ceil(((contentWidth || containerWidth) + gutterOffset) * (width / 100) - gutterOffset);
}
}
// Handle the case of not resized node with wrapped layout
// It's possible that the node is first inserted with align layout (e.g. jira)
// in which the legacy image would render the width as min(origWidth, halfContentWidth).
// However, new experience won't be able to distinguish the two. Thus, we render halfContentWidth
// to make sure confluence legacy node is renderered correctly
if (wrappedLayouts.includes(layout)) {
return Math.ceil((contentWidth || containerWidth) / 2);
}
// set initial width for not resized legacy image
return getMediaSingleInitialWidth(origWidth,
// in case containerWidth is 0, we fallback to undefined to use akEditorDefaultLayoutWidth
contentWidth || containerWidth || undefined);
};
/**
* Calculate pixel width for legacy media single
* @param contentWidth editor content width
* @param containerWidth editor container width
*/
var calcLegacyWideWidth = function calcLegacyWideWidth(containerWidth, origWidth, contentWidth) {
if (contentWidth) {
var wideWidth = Math.ceil(contentWidth * breakoutWideScaleRatio);
return wideWidth > containerWidth ? contentWidth : wideWidth;
}
return origWidth;
};
/**
* Calculate maximum width allowed for media single node in fix-width editor in new experience
* @param containerWidth width of editor container
*/
export var calcMediaSingleMaxWidth = function calcMediaSingleMaxWidth(containerWidth, editorAppearance) {
var fullWidthPadding = editorAppearance === 'full-page' ? akEditorGutterPaddingDynamic() * 2 : akEditorGutterPadding * 2;
return Math.min(containerWidth - fullWidthPadding, akEditorFullWidthLayoutWidth);
};
/**
* Calculate initial media single pixel width.
* Make it fall between max width and min width
* @param origWidth original width of image (media node width)
* @param maxWidth default to akEditorDefaultLayoutWidth (760)
* @param minWidth default to MEDIA_SINGLE_DEFAULT_MIN_PIXEL_WIDTH (24)
*/
export var getMediaSingleInitialWidth = function getMediaSingleInitialWidth() {
var origWidth = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_IMAGE_WIDTH;
var maxWidth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : akEditorDefaultLayoutWidth;
var minWidth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : MEDIA_SINGLE_DEFAULT_MIN_PIXEL_WIDTH;
return Math.max(Math.min(origWidth, maxWidth), minWidth);
};
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/max-params
export function calculateOffsetLeft(insideInlineLike, insideLayout, pmViewDom, wrapper) {
var offsetLeft = 0;
if (wrapper && insideInlineLike && !insideLayout) {
var currentNode = wrapper;
var boundingRect = currentNode.getBoundingClientRect();
offsetLeft = boundingRect.left - pmViewDom.getBoundingClientRect().left;
}
return offsetLeft;
}
/**
* Returns the number rounded to the nearest interval.
* @param {number} value The number to round
* @param {number} interval The numeric interval to round to, default to 0.5
* @return {number} the rounded number
*/
export var roundToNearest = function roundToNearest(value) {
var interval = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_ROUNDING_INTERVAL;
return Math.round(value / interval) * interval;
};
/**
* Retuns minimum value for media single node
* @param isVideoFile is child media of video type
* @param contentWidth parent content width
*/
export var calcMinWidth = function calcMinWidth(isVideoFile, contentWidth) {
return Math.min(contentWidth, isVideoFile ? MEDIA_SINGLE_VIDEO_MIN_PIXEL_WIDTH : MEDIA_SINGLE_DEFAULT_MIN_PIXEL_WIDTH);
};
/**
* Get parent width for a nested media single node
* @param view Editor view
* @param pos node position
*/
export var getMaxWidthForNestedNode = function getMaxWidthForNestedNode(view, pos) {
if (typeof pos !== 'number') {
return null;
}
if (isRichMediaInsideOfBlockNode(view, pos)) {
var $pos = view.state.doc.resolve(pos);
var domNode = view.nodeDOM($pos.pos);
if ($pos.nodeAfter && floatingLayouts.indexOf($pos.nodeAfter.attrs.layout) > -1 && domNode && domNode.parentElement) {
return domNode.parentElement.offsetWidth;
}
if (domNode instanceof HTMLElement) {
return domNode.offsetWidth;
}
}
return null;
};
var calcParentPadding = function calcParentPadding(view, resolvedPos) {
// since table has constant padding, use hardcoded constant instead of query the dom
var tablePadding = 8;
var _view$state$schema$no = view.state.schema.nodes,
tableCell = _view$state$schema$no.tableCell,
tableHeader = _view$state$schema$no.tableHeader;
return [tableCell, tableHeader].includes(resolvedPos.parent.type) ? tablePadding * 2 : 0;
};
/**
* Get parent width for a nested media single node for new experience
* We don't check for mediaSingle selection in this function.
* @param view Editor view
* @param pos node position
* @param forInsertion for insertion
*/
export var getMaxWidthForNestedNodeNext = function getMaxWidthForNestedNodeNext(view, pos, forInsertion) {
if (typeof pos !== 'number') {
return null;
}
var $pos = view.state.doc.resolve(pos);
if ($pos && $pos.parent.type.name !== 'doc') {
return forInsertion ? getParentWidthForNestedMediaSingleNodeForInsertion($pos, view) : getParentWidthForNestedMediaSingleNode($pos, view);
}
return null;
};
/**
* Get parent content width for nested media single node.
* @param resolvedPos resolved Position of the node
* @param view editor view
* @returns parent content width for nested node
*/
export var getParentWidthForNestedMediaSingleNode = function getParentWidthForNestedMediaSingleNode(resolvedPos, view) {
var domNode = view.nodeDOM(resolvedPos.pos);
if (resolvedPos.nodeAfter && floatingLayouts.includes(resolvedPos.nodeAfter.attrs.layout) && domNode && domNode.parentElement) {
var parentPadding = calcParentPadding(view, resolvedPos);
return domNode.parentElement.offsetWidth - parentPadding;
}
if (domNode instanceof HTMLElement) {
return domNode.offsetWidth;
}
return null;
};
/**
* Get parent width for nested media single nodes
* @param resolvedPos resolved Position of the node
* @param view editor view
* @returns parent width used for media single initial width on insertion
*/
export var getParentWidthForNestedMediaSingleNodeForInsertion = function getParentWidthForNestedMediaSingleNodeForInsertion(resolvedPos, view) {
var parentPos = resolvedPos.before(resolvedPos.depth);
var parentDomNode = view.nodeDOM(parentPos);
var parentPadding = calcParentPadding(view, resolvedPos);
if (parentDomNode instanceof HTMLElement) {
return parentDomNode.offsetWidth - parentPadding;
}
return null;
};
/**
*
* @param editorState current editor state
* @returns selected media node (child of mediaSingle only) with position
*/
export var currentMediaNodeWithPos = function currentMediaNodeWithPos(editorState) {
var doc = editorState.doc,
selection = editorState.selection,
schema = editorState.schema;
if (!doc || !selection || !(selection instanceof NodeSelection) || selection.node.type !== schema.nodes.mediaSingle) {
return;
}
var pos = selection.$anchor.pos + 1;
var node = doc.nodeAt(pos);
if (!node || node.type !== schema.nodes.media) {
return;
}
return {
node: node,
pos: pos
};
};