Przeglądaj źródła

Merge pull request #14317 from overleaf/mj-table-gen-update

[visual] in-place table generator updates

GitOrigin-RevId: 410411fd9158e6c8c6fe6a5300556800732a252a
Mathias Jakobsen 3 lat temu
rodzic
commit
2d15ce8d05

+ 1 - 1
services/web/frontend/js/features/source-editor/components/table-generator/cell.tsx

@@ -118,7 +118,7 @@ export const Cell: FC<{
     if (renderDiv.current && !editing) {
       const tree = parser.parse(toDisplay)
       const node = tree.topNode
-
+      renderDiv.current.innerText = ''
       typesetNodeIntoElement(
         node,
         renderDiv.current,

+ 10 - 0
services/web/frontend/js/features/source-editor/components/table-generator/contexts/selection-context.tsx

@@ -166,6 +166,16 @@ export class TableSelection {
       maxY === totalRows - 1
     )
   }
+
+  width() {
+    const { minX, maxX } = this.normalized()
+    return maxX - minX + 1
+  }
+
+  height() {
+    const { minY, maxY } = this.normalized()
+    return maxY - minY + 1
+  }
 }
 
 const SelectionContext = createContext<

+ 2 - 2
services/web/frontend/js/features/source-editor/components/table-generator/contexts/table-context.tsx

@@ -1,5 +1,5 @@
 import { FC, createContext, useContext } from 'react'
-import { Positions, TableData } from '../tabular'
+import { Positions, TableData, TableRenderingError } from '../tabular'
 import {
   CellPosition,
   CellSeparator,
@@ -41,7 +41,7 @@ export const TableProvider: FC<{
   // TODO: Validate better that the table matches the column definition
   for (const row of tableData.table.rows) {
     if (row.cells.length !== tableData.table.columns.length) {
-      throw new Error("Table doesn't match column definition")
+      return <TableRenderingError view={view} codePosition={tabularNode.from} />
     }
   }
 

+ 29 - 1
services/web/frontend/js/features/source-editor/components/table-generator/toolbar/commands.ts

@@ -173,8 +173,15 @@ export const removeRowOrColumns = (
   const numberOfRows = positions.rowPositions.length
 
   if (selection.spansEntireTable(numberOfColumns, numberOfRows)) {
-    return emptyTable(view, columnSpecification, positions)
+    emptyTable(view, columnSpecification, positions)
+    return new TableSelection({ cell: 0, row: 0 })
   }
+  const removedRows =
+    Number(selection.isRowSelected(startRow, numberOfColumns)) *
+    selection.height()
+  const removedColumns =
+    Number(selection.isColumnSelected(startCell, numberOfRows)) *
+    selection.width()
 
   for (let row = startRow; row <= endRow; row++) {
     if (selection.isRowSelected(row, numberOfColumns)) {
@@ -221,6 +228,13 @@ export const removeRowOrColumns = (
     insert: newSpecification,
   })
   view.dispatch({ changes })
+  const updatedNumberOfRows = numberOfRows - removedRows
+  const updatedNumberOfColumns = numberOfColumns - removedColumns
+  // Clamp selection to new table size
+  return new TableSelection({
+    cell: Math.max(0, Math.min(updatedNumberOfColumns - 1, startCell)),
+    row: Math.max(0, Math.min(updatedNumberOfRows - 1, startRow)),
+  })
 }
 
 const emptyTable = (
@@ -262,6 +276,13 @@ export const insertRow = (
   const numberOfColumns = positions.cells[maxY].length
   const insert = `\n${' &'.repeat(numberOfColumns - 1)}\\\\`
   view.dispatch({ changes: { from, to: from, insert } })
+  if (!below) {
+    return selection
+  }
+  return new TableSelection(
+    { cell: 0, row: maxY + 1 },
+    { cell: numberOfColumns - 1, row: maxY + 1 }
+  )
 }
 
 export const insertColumn = (
@@ -300,6 +321,13 @@ export const insertColumn = (
     insert: generateColumnSpecification(columnSpecification),
   })
   view.dispatch({ changes })
+  if (!after) {
+    return selection
+  }
+  return new TableSelection(
+    { cell: maxX + 1, row: 0 },
+    { cell: maxX + 1, row: positions.rowPositions.length - 1 }
+  )
 }
 
 export const removeNodes = (

+ 0 - 1
services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar-button.tsx

@@ -36,7 +36,6 @@ export const ToolbarButton = memo<{
       if (command) {
         event.preventDefault()
         command(view)
-        view.focus()
       }
     },
     [command, view]

+ 21 - 12
services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar-dropdown.tsx

@@ -23,10 +23,9 @@ export const ToolbarDropdown: FC<{
   disabled,
   disabledTooltip,
 }) => {
-  const { open, onToggle } = useDropdown()
+  const { open, onToggle, ref } = useDropdown()
   const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
   const { ref: tabularRef } = useTabularContext()
-
   const button = (
     <button
       ref={toggleButtonRef}
@@ -34,8 +33,13 @@ export const ToolbarDropdown: FC<{
       id={id}
       aria-haspopup="true"
       className={btnClassName}
-      onMouseDown={event => event.preventDefault()}
-      onClick={() => onToggle(!open)}
+      onMouseDown={event => {
+        event.preventDefault()
+        event.stopPropagation()
+      }}
+      onClick={() => {
+        onToggle(!open)
+      }}
       aria-label={tooltip}
       disabled={disabled}
       aria-disabled={disabled}
@@ -44,26 +48,30 @@ export const ToolbarDropdown: FC<{
       <MaterialIcon type={icon} />
     </button>
   )
-  const overlay = open && tabularRef.current && (
+  const overlay = tabularRef.current && (
     <Overlay
-      show
-      onHide={() => onToggle(false)}
-      animation={false}
+      show={open}
+      target={toggleButtonRef.current ?? undefined}
+      placement="bottom"
       container={tabularRef.current}
+      animation
       containerPadding={0}
-      placement="bottom"
-      rootClose
-      target={toggleButtonRef.current ?? undefined}
+      onHide={() => onToggle(false)}
     >
       <Popover
         id={`${id}-popover`}
+        ref={ref}
         className="table-generator-toolbar-dropdown-popover"
       >
+        {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions,
+                                     jsx-a11y/click-events-have-key-events */}
         <div
           className="table-generator-toolbar-dropdown-menu"
           id={`${id}-menu`}
-          role="menu"
           aria-labelledby={id}
+          onClick={() => {
+            onToggle(false)
+          }}
         >
           {children}
         </div>
@@ -83,6 +91,7 @@ export const ToolbarDropdown: FC<{
   return (
     <>
       <Tooltip
+        hidden={open}
         id={id}
         description={disabled && disabledTooltip ? disabledTooltip : tooltip}
         overlayProps={{ placement: 'bottom' }}

+ 8 - 6
services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar.tsx

@@ -19,7 +19,7 @@ import { useCodeMirrorViewContext } from '../../codemirror-editor'
 import { useTableContext } from '../contexts/table-context'
 
 export const Toolbar = memo(function Toolbar() {
-  const { selection } = useSelectionContext()
+  const { selection, setSelection } = useSelectionContext()
   const view = useCodeMirrorViewContext()
   const { positions, rowSeparators, cellSeparators, tableEnvironment } =
     useTableContext()
@@ -159,7 +159,9 @@ export const Toolbar = memo(function Toolbar() {
             ) && !selection.isAnyColumnSelected(positions.rowPositions.length)
           }
           command={() =>
-            removeRowOrColumns(view, selection, positions, cellSeparators)
+            setSelection(
+              removeRowOrColumns(view, selection, positions, cellSeparators)
+            )
           }
         />
         <ToolbarDropdown
@@ -174,7 +176,7 @@ export const Toolbar = memo(function Toolbar() {
             role="menuitem"
             type="button"
             onClick={() => {
-              insertColumn(view, selection, positions, false)
+              setSelection(insertColumn(view, selection, positions, false))
             }}
           >
             <span className="table-generator-button-label">
@@ -186,7 +188,7 @@ export const Toolbar = memo(function Toolbar() {
             role="menuitem"
             type="button"
             onClick={() => {
-              insertColumn(view, selection, positions, true)
+              setSelection(insertColumn(view, selection, positions, true))
             }}
           >
             <span className="table-generator-button-label">
@@ -199,7 +201,7 @@ export const Toolbar = memo(function Toolbar() {
             role="menuitem"
             type="button"
             onClick={() => {
-              insertRow(view, selection, positions, false)
+              setSelection(insertRow(view, selection, positions, false))
             }}
           >
             <span className="table-generator-button-label">
@@ -211,7 +213,7 @@ export const Toolbar = memo(function Toolbar() {
             role="menuitem"
             type="button"
             onClick={() => {
-              insertRow(view, selection, positions, true)
+              setSelection(insertRow(view, selection, positions, true))
             }}
           >
             <span className="table-generator-button-label">

+ 13 - 0
services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/tabular.tsx

@@ -38,6 +38,19 @@ export class TabularWidget extends WidgetType {
     )
   }
 
+  updateDOM(dom: HTMLElement, view: EditorView): boolean {
+    this.element = dom
+    ReactDOM.render(
+      <Tabular
+        view={view}
+        tabularNode={this.tabularNode}
+        tableNode={this.tableNode}
+      />,
+      this.element
+    )
+    return true
+  }
+
   destroy() {
     console.debug('destroying tabular widget')
     if (this.element) {