import React from 'react'; import { Node as PmNode } from 'prosemirror-model'; import { TableMap } from '@atlaskit/editor-tables/table-map'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { findDomRefAtPos } from 'prosemirror-utils'; import { findTable } from '@atlaskit/editor-tables/utils'; import { EditorView } from 'prosemirror-view'; import { InjectedIntlProps, injectIntl } from 'react-intl'; import { Popup } from '@atlaskit/editor-common'; import { closestElement } from '../../../../utils/dom'; import { INPUT_METHOD } from '../../../analytics'; import { insertColumnWithAnalytics, insertRowWithAnalytics, } from '../../commands-with-analytics'; import { TableCssClassName as ClassName } from '../../types'; import { checkIfNumberColumnEnabled } from '../../utils'; import getPopupOptions from './getPopupOptions'; import InsertButton from './InsertButton'; import { DispatchAnalyticsEvent, AnalyticsEventPayload, } from '../../../analytics/types'; export interface Props { editorView: EditorView; tableRef?: HTMLElement; tableNode?: PmNode; insertColumnButtonIndex?: number; insertRowButtonIndex?: number; isHeaderColumnEnabled?: boolean; isHeaderRowEnabled?: boolean; mountPoint?: HTMLElement; boundariesElement?: HTMLElement; scrollableElement?: HTMLElement; hasStickyHeaders?: boolean; dispatchAnalyticsEvent?: DispatchAnalyticsEvent; } import { ACTION, ACTION_SUBJECT, EVENT_TYPE, CONTENT_COMPONENT, } from '../../../analytics/types/enums'; class FloatingInsertButton extends React.Component< Props & InjectedIntlProps, any > { static displayName = 'FloatingInsertButton'; constructor(props: Props & InjectedIntlProps) { super(props); this.insertColumn = this.insertColumn.bind(this); this.insertRow = this.insertRow.bind(this); } render() { const { tableNode, editorView, insertColumnButtonIndex, insertRowButtonIndex, tableRef, mountPoint, boundariesElement, isHeaderColumnEnabled, isHeaderRowEnabled, dispatchAnalyticsEvent, } = this.props; const type = typeof insertColumnButtonIndex !== 'undefined' ? 'column' : typeof insertRowButtonIndex !== 'undefined' ? 'row' : null; if (!tableNode || !tableRef || !type) { return null; } // We can’t display the insert button for row|colum index 0 // when the header row|colum is enabled, this feature will be change on the future if ( (type === 'column' && isHeaderColumnEnabled && insertColumnButtonIndex === 0) || (type === 'row' && isHeaderRowEnabled && insertRowButtonIndex === 0) ) { return null; } const { state: { tr }, } = editorView; if ( tr.selection instanceof CellSelection && ((tr.selection as CellSelection).isColSelection() || (tr.selection as CellSelection).isRowSelection()) ) { return null; } const cellPosition = this.getCellPosition(type); if (!cellPosition) { return null; } const tablePos = findTable(editorView.state.selection); if (!tablePos) { return null; } const domAtPos = editorView.domAtPos.bind(editorView); const pos = cellPosition + tablePos.start + 1; let target: Node | undefined; try { target = findDomRefAtPos(pos, domAtPos); } catch (error) { // eslint-disable-next-line no-console console.warn(error); if (dispatchAnalyticsEvent) { const payload: AnalyticsEventPayload = { action: ACTION.ERRORED, actionSubject: ACTION_SUBJECT.CONTENT_COMPONENT, eventType: EVENT_TYPE.OPERATIONAL, attributes: { component: CONTENT_COMPONENT.FLOATING_INSERT_BUTTON, selection: editorView.state.selection.toJSON(), position: pos, docSize: editorView.state.doc.nodeSize, error: error.toString(), errorStack: error.stack || undefined, }, }; dispatchAnalyticsEvent(payload); } } if (!target || !(target instanceof HTMLElement)) { return null; } const targetCellRef = type === 'row' ? closestElement(target, 'tr') : closestElement(target, 'td, th'); if (!targetCellRef) { return null; } const tableContainerWrapper = closestElement( targetCellRef, `.${ClassName.TABLE_CONTAINER}`, ); const tableWrapper = closestElement( targetCellRef, `.${ClassName.TABLE_NODE_WRAPPER}`, ); const index: number = type === 'column' ? insertColumnButtonIndex! : insertRowButtonIndex!; const hasNumberedColumns = checkIfNumberColumnEnabled(editorView.state); return ( ); } private getCellPosition(type: 'column' | 'row'): number | null { const { tableNode, insertColumnButtonIndex, insertRowButtonIndex, } = this.props; const tableMap = TableMap.get(tableNode!); if (type === 'column') { const columnIndex = insertColumnButtonIndex === 0 ? 0 : insertColumnButtonIndex! - 1; if (columnIndex > tableMap.width - 1) { return null; } return tableMap.positionAt(0, columnIndex!, tableNode!); } else { const rowIndex = insertRowButtonIndex === 0 ? 0 : insertRowButtonIndex! - 1; if (rowIndex > tableMap.height - 1) { return null; } return tableMap.positionAt(rowIndex!, 0, tableNode!); } } private insertRow(event: React.SyntheticEvent) { const { editorView, insertRowButtonIndex } = this.props; if (typeof insertRowButtonIndex !== 'undefined') { event.preventDefault(); const { state, dispatch } = editorView; insertRowWithAnalytics(INPUT_METHOD.BUTTON, { index: insertRowButtonIndex, moveCursorToInsertedRow: true, })(state, dispatch); } } private insertColumn(event: React.SyntheticEvent) { const { editorView, insertColumnButtonIndex } = this.props; if (typeof insertColumnButtonIndex !== 'undefined') { event.preventDefault(); const { state, dispatch } = editorView; insertColumnWithAnalytics(INPUT_METHOD.BUTTON, insertColumnButtonIndex)( state, dispatch, ); } } } export default injectIntl(FloatingInsertButton);