@atlaskit/editor-plugin-card
Version:
Card plugin for @atlaskit/editor-core
253 lines (248 loc) • 10.8 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
/**
* @jsxRuntime classic
* @jsx jsx
*/
import React from 'react';
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports
import { jsx } from '@emotion/react';
import ReactNodeView from '@atlaskit/editor-common/react-node-view';
import { DATASOURCE_INNER_CONTAINER_CLASSNAME, SmartCardSharedCssClassName } from '@atlaskit/editor-common/styles';
import { UnsupportedInline } from '@atlaskit/editor-common/ui';
import { calcBreakoutWidth } from '@atlaskit/editor-common/utils';
import { DatasourceTableView } from '@atlaskit/link-datasource';
import { EditorSmartCardProvider, EditorSmartCardProviderValueGuard } from '@atlaskit/link-provider';
import { DATASOURCE_DEFAULT_LAYOUT } from '@atlaskit/linking-common';
import { DatasourceErrorBoundary } from '../ui/datasourceErrorBoundary';
import { EditorAnalyticsContext } from '../ui/EditorAnalyticsContext';
const getPosSafely = pos => {
if (!pos || typeof pos === 'boolean') {
return;
}
try {
return pos();
} catch (e) {
// Can blow up in rare cases, when node has been removed.
}
};
// eslint-disable-next-line @repo/internal/react/no-class-components
export class DatasourceComponent extends React.PureComponent {
constructor(props) {
super(props);
_defineProperty(this, "getDatasource", () => this.props.node.attrs.datasource);
_defineProperty(this, "getTableView", () => {
const views = this.getDatasource().views;
return views.find(view => view.type === 'table') || undefined;
});
_defineProperty(this, "handleColumnChange", columnKeys => {
const {
wrappedColumnKeys = [],
columnCustomSizes = {}
} = this.getColumnsInfo();
this.updateTableProperties(columnKeys, columnCustomSizes, wrappedColumnKeys);
});
_defineProperty(this, "handleColumnResize", (key, width) => {
const {
wrappedColumnKeys = [],
columnCustomSizes = {},
visibleColumnKeys = []
} = this.getColumnsInfo();
const newColumnCustomSizes = {
...columnCustomSizes,
[key]: width
};
this.updateTableProperties(visibleColumnKeys, newColumnCustomSizes, wrappedColumnKeys);
});
_defineProperty(this, "handleWrappedColumnChange", (key, shouldWrap) => {
const {
wrappedColumnKeys = [],
columnCustomSizes = {},
visibleColumnKeys = []
} = this.getColumnsInfo();
const wrappedColumnKeysSet = new Set(wrappedColumnKeys);
if (shouldWrap) {
wrappedColumnKeysSet.add(key);
} else {
wrappedColumnKeysSet.delete(key);
}
this.updateTableProperties(visibleColumnKeys, columnCustomSizes, Array.from(wrappedColumnKeysSet));
});
_defineProperty(this, "onError", ({
err
}) => {
if (err) {
throw err;
}
});
}
updateTableProperties(columnKeysArg, columnCustomSizes, wrappedColumnKeys) {
const {
state,
dispatch
} = this.props.view;
const pos = getPosSafely(this.props.getPos);
if (pos === undefined) {
return;
}
// In case for some reason there are no visible keys stored in ADF, we take them
// from incoming sets of attributes like column sizes and wrapped column keys
// columnKeys are needed to update ADF (
// since attributes (like custom width and wrapped state) only make sense for a visible column )
// So this part effectively adds a visible column if it wasn't there but attributes were given.
const columnKeys = columnKeysArg.length > 0 ? columnKeysArg : Array.from(new Set([...Object.keys(columnCustomSizes), ...wrappedColumnKeys]));
const views = [{
type: 'table',
properties: {
columns: columnKeys.map(key => {
const width = columnCustomSizes[key];
const isWrapped = wrappedColumnKeys.includes(key);
return {
key,
...(width ? {
width
} : {}),
...(isWrapped ? {
isWrapped
} : {})
};
})
}
}];
const attrs = this.props.node.attrs;
const tr = state.tr.setNodeMarkup(pos, undefined, {
...attrs,
datasource: {
...attrs.datasource,
views
}
});
// Ensures dispatch does not contribute to undo history (otherwise user requires three undo's to revert table)
tr.setMeta('addToHistory', false);
tr.setMeta('scrollIntoView', false);
dispatch(tr);
}
getColumnsInfo() {
var _tableView$properties;
const tableView = this.getTableView();
const columnsProp = tableView === null || tableView === void 0 ? void 0 : (_tableView$properties = tableView.properties) === null || _tableView$properties === void 0 ? void 0 : _tableView$properties.columns;
const visibleColumnKeys = columnsProp === null || columnsProp === void 0 ? void 0 : columnsProp.map(({
key
}) => key);
let columnCustomSizes;
const columnsWithWidth = columnsProp === null || columnsProp === void 0 ? void 0 : columnsProp.filter(c => !!c.width);
if (columnsWithWidth) {
const keyWidthPairs = columnsWithWidth.map(c => [c.key, c.width]);
columnCustomSizes = Object.fromEntries(keyWidthPairs);
}
const wrappedColumnKeys = columnsProp === null || columnsProp === void 0 ? void 0 : columnsProp.filter(c => c.isWrapped).map(c => c.key);
return {
visibleColumnKeys,
columnCustomSizes,
wrappedColumnKeys
};
}
render() {
const datasource = this.getDatasource();
const attrs = this.props.node.attrs;
const tableView = this.getTableView();
if (tableView) {
const {
visibleColumnKeys,
columnCustomSizes,
wrappedColumnKeys
} = this.getColumnsInfo();
return jsx(EditorSmartCardProviderValueGuard, null, jsx(EditorAnalyticsContext, {
editorView: this.props.view
}, jsx(EditorSmartCardProvider, null, jsx(DatasourceTableView, {
datasourceId: datasource.id,
parameters: datasource.parameters,
visibleColumnKeys: visibleColumnKeys,
onVisibleColumnKeysChange: this.handleColumnChange,
url: attrs === null || attrs === void 0 ? void 0 : attrs.url,
onColumnResize: this.handleColumnResize,
columnCustomSizes: columnCustomSizes,
onWrappedColumnChange: this.handleWrappedColumnChange,
wrappedColumnKeys: wrappedColumnKeys
}))));
}
return null;
}
}
export class Datasource extends ReactNodeView {
constructor(props) {
var _props$pluginInjectio, _props$pluginInjectio2, _sharedState$currentS;
super(props.node, props.view, props.getPos, props.portalProviderAPI, props.eventDispatcher, props);
const sharedState = props === null || props === void 0 ? void 0 : (_props$pluginInjectio = props.pluginInjectionApi) === null || _props$pluginInjectio === void 0 ? void 0 : (_props$pluginInjectio2 = _props$pluginInjectio.width) === null || _props$pluginInjectio2 === void 0 ? void 0 : _props$pluginInjectio2.sharedState;
this.tableWidth = sharedState === null || sharedState === void 0 ? void 0 : (_sharedState$currentS = sharedState.currentState()) === null || _sharedState$currentS === void 0 ? void 0 : _sharedState$currentS.width;
this.isNodeNested = props.isNodeNested;
sharedState === null || sharedState === void 0 ? void 0 : sharedState.onChange(({
nextSharedState
}) => {
if (nextSharedState !== null && nextSharedState !== void 0 && nextSharedState.width && this.tableWidth !== (nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.width)) {
this.tableWidth = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.width;
this.update(this.node, []); // required to update the width when page is resized.
}
});
}
// Need this function to check if the datasource attribute was added or not to a blockCard.
// If not, we return false so we can get the node to re-render properly as a block node instead.
// Otherwise, the node view will still consider the node as a Datasource and render a such.
validUpdate(_, newNode) {
var _newNode$attrs;
return !!((_newNode$attrs = newNode.attrs) !== null && _newNode$attrs !== void 0 && _newNode$attrs.datasource);
}
update(node, decorations, _innerDecorations) {
return super.update(node, decorations, _innerDecorations, this.validUpdate);
}
createDomRef() {
const domRef = document.createElement('div');
domRef.setAttribute('contenteditable', 'true');
domRef.classList.add(SmartCardSharedCssClassName.DATASOURCE_CONTAINER);
return domRef;
}
/**
* Node views may render interactive elements that should not have their events reach editor
*
* We should the stop editor from handling events from following elements:
* - typing in input
* - activating buttons via spacebar
*
* Can be used to prevent the editor view from trying to handle some or all DOM events that bubble up from the node view.
* Events for which this returns true are not handled by the editor.
* @see {@link https://prosemirror.net/docs/ref/#view.NodeView.stopEvent}
*/
stopEvent(event) {
const isFormElement = [HTMLButtonElement, HTMLInputElement].some(element => event.target instanceof element);
if (isFormElement) {
return true;
}
return false;
}
render() {
var _this$domRef, _attrs$datasource;
const {
attrs
} = this.node;
// EDM-10607: Workaround to remove datasource table draggable attribute
// @ts-ignore TS2341: Property domRef is private
(_this$domRef = this.domRef) === null || _this$domRef === void 0 ? void 0 : _this$domRef.setAttribute('draggable', 'false');
return jsx(DatasourceErrorBoundary, {
unsupportedComponent: UnsupportedInline,
view: this.view,
url: attrs.url,
datasourceId: attrs === null || attrs === void 0 ? void 0 : (_attrs$datasource = attrs.datasource) === null || _attrs$datasource === void 0 ? void 0 : _attrs$datasource.id
}, jsx("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: DATASOURCE_INNER_CONTAINER_CLASSNAME,
style: {
minWidth: this.isNodeNested ? '100%' :
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
calcBreakoutWidth(attrs.layout || DATASOURCE_DEFAULT_LAYOUT, this.tableWidth)
}
}, jsx(DatasourceComponent, {
node: this.node,
view: this.view,
getPos: this.getPos
})));
}
}