DEV Community

Cover image for Try to make Draft.js support simple table
Yadong Zhang
Yadong Zhang

Posted on • Updated on

Try to make Draft.js support simple table

Introduction

My requirement is to implement an online paper typesetting editor and export the pdf generated by LaTeX.

-- Updated --
https://github.com/facebook/draft-js/pull/2590
-- Orignial --

Project link:Eorg

Relative projects are Overleaf and Resumake.

I prefer to React.js, so I chose Draft.js, a rich text editor that was also developed for Facebook. Paper writing can't hide from table insertion, but Draft.js does not have a ready-made table plug-in. There are also a few tools for generating tables. Totally unnecessary to write another table support by my own, but it is convenient for users, and the table does not need to be too complicated. You can export a booktabs-like table, so I plan to try to implement table support by myself

Content

Idea:

A robust table is to use new ContentBlock to write metadata into block,which is an approach closed to Draft.js !
draft-js-rte

Relatively speaking, my implementation is more trick, which borrow from offical TeX example ,using AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, ' ') API to add metadata to React.js props:

  1. rows and columns
const contentStateWithEntity = contentState.createEntity(
    'TABLE',
    'IMMUTABLE',
    {
        row, column, caption,  // data
    },
)

// ...

const { row, column, caption } = props // Table Component
Enter fullscreen mode Exit fullscreen mode
  1. table cell
// createTable.js

/**
 * cell = {
 *     0: ["cell-0,0", "cell-0,1", ..., "cell-0,m"],
 *     1: ["cell-1,0", "cell-1,1", ..., "cell-1,m"],
 *     ...,
 *     n: ["cell-n,0", "cell-n,1", ..., "cell-n,m"],
 * }
 */
const cell = Object.fromEntries(Array.from(
    { length: row },
    (_, i) => [
        i,
        Array.from({ length: column }, (_, j) => `cell-${i},${j}`)
    ])
)

const contentStateWithEntity = contentState.createEntity(
    'TABLE',
    'IMMUTABLE',
    {
        ..., cell, // data
    },
)

// ...

const { ..., cell } = props // Table Component
Enter fullscreen mode Exit fullscreen mode

and initialize a table:

// TableBlock.js

// tbody -- version 1
const coordinate = []

if (row > 1) {
    for (let i = 1; i < row; i += 1) {
    const cols = []
    for (let j = 0; j < column; j += 1) {
        cols.push(
        <td key={i + j} >
            {cell[i][j]}
        </td>,
        )
    }
    rows.push(<tr key={i}>{cols}</tr>)
    }
}
Enter fullscreen mode Exit fullscreen mode

store row, column, caption, cell to props

store row, column, caption, cell to React.js props

  1. obtain cell coordinate:

The first idea is to calculate the Dom Node location,which means find the index of <tr> at closest('td') at closest('table')

Later, more better approach is retrive the key value in <tr> and <td>, and its coordinate is (x1, y1):

// TableBlock.js

// tbody -- version 2
const coordinate = []

if (row > 1) { // thead need to calculate separately
    for (let i = 1; i < row; i += 1) {
    const cols = []
    for (let j = 0; j < column; j += 1) {
        cols.push(
        <td
            key={i + j} // TODO key-1
            onDoubleClick={() => coordinate.push([i, j])}
        >
            {cell[i][j]}
        </td>,
        )
    }
    rows.push(<tr key={i}>{cols}</tr>)
    }
}
Enter fullscreen mode Exit fullscreen mode

Above key-1 is not stable,we can work with nanoid library:

key = {`i+j+${nanoid()}`}
Enter fullscreen mode Exit fullscreen mode

Now it's stable and we can store cell values:

// find the coordinate of the node clicked
const x1 = coordinate[coordinate.length - 1][0]
const y1 = coordinate[coordinate.length - 1][1]

// update cell[i][j]
cell[x1][y1] = evt.target.innerHTML
Enter fullscreen mode Exit fullscreen mode

cell value will be stored into props

cell-2,1 can be stored to props

Summary

The table is not yet complete, for example:

  1. how to process cursor
  2. it's mutable

Next

To support adding and deleting cell.

Top comments (0)