Path: blob/master/src/packages/frontend/editors/slate/elements/table/editable.tsx
1698 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { CSSProperties as CSS } from "react";6import { register } from "../register";7import { useFocused, useSelected } from "../hooks";8import { padLeft, padRight, padCenter } from "../../util";9import { serialize } from "../../slate-to-markdown";10import getStyles from "./style";1112function fromSlate({ node, children, info, childInfo }) {13switch (node.type) {14case "table": // a table15const i = children.indexOf("\n");16const thead = children.slice(0, i);17const tbody = children.slice(i + 1);18let sep = "|",19headings: { align: string }[];20try {21headings = (node as any).children[0].children[0].children;22} catch (_err) {23headings = [];24}25for (let i = 0; i < headings.length; i++) {26const n = (childInfo.table?.[i]?.width ?? 5) - 2;27let bar = "-";28for (let j = 0; j < n; j++) bar += "-";29switch (headings[i].align) {30case "center":31bar = ":" + bar.slice(1) + ":";32break;33case "right":34bar = bar + ":";35break;36case "left":37default:38bar = ":" + bar;39break;40}41sep += ` ${bar} |`;42}43return `${thead}\n${sep}\n${tbody}\n`;4445case "thead": // the heading row of a table46return children; // the one child is a tr, which renders fine by itself4748case "tbody": // the body of the table49return children;5051case "tr": // a row of a table52return "| " + children.trimEnd() + "\n";5354case "th": // a heading entry in a row in the thead55case "td": // a data entry in a row56if (info.index != null) {57const data = info.table?.[info.index];58if (data != null) {59switch (data.align) {60case "left":61children = padRight(children, data.width);62break;63case "right":64children = padLeft(children, data.width);65break;66case "center":67children = padCenter(children, data.width);68break;69}70}71}72children = children.trimEnd();73return children + " | ";74}75}7677export const Element = ({ attributes, children, element }) => {78const focused = useFocused();79const selected = useSelected();80let backgroundColor: string | undefined = undefined;8182switch (element.type) {83/* We render *editable* tables using straight HTML and the antd84CSS classes. We do NOT use the actual antd Table85class, since it doesn't play well with slatejs.86I just looked at the DOM in a debugger to figure out87these tags; the main risk is that things change, but88it's purely style so that is OK.89*/9091case "table":92const { divStyle, tableStyle } = getStyles(focused && selected);93return (94<div {...attributes} style={divStyle}>95<table style={tableStyle}>{children}</table>96</div>97);98case "thead":99return <thead {...attributes}>{children}</thead>;100case "tbody":101return <tbody {...attributes}>{children}</tbody>;102case "tr":103return <tr {...attributes}>{children}</tr>;104case "th":105backgroundColor = focused && selected ? "#e8f2ff" : undefined;106return (107<th108{...attributes}109style={{ backgroundColor, textAlign: element.align ?? "left" } as CSS}110>111{children}112</th>113);114case "td":115backgroundColor = focused && selected ? "#e8f2ff" : undefined;116return (117<td118{...attributes}119style={{ backgroundColor, textAlign: element.align ?? "left" } as CSS}120>121{children}122</td>123);124default:125throw Error("not a table element type " + element.type);126}127};128129register({130slateType: ["thead", "tbody", "tr", "th", "td"],131Element,132fromSlate,133});134135// NOTE/OPTIMIZATION: We end up serializing the cells twice; first to136// get their length, then later to do a final render and pad everything137// to look nice.138// table is extra global information used in formatting columns.139type TableInfo = { width: number; align: "left" | "center" | "right" }[];140141register({142slateType: "table",143Element,144childInfoHook: ({ childInfo, node }) => {145const thead_tr = (node as any).children[0].children[0];146const tbody_rows = (node as any).children[1]?.children ?? []; // can have no tbody147const info: TableInfo = [];148const n = thead_tr.children?.length ?? 0;149for (let i = 0; i < n; i++) {150info.push({151width: Math.max(1523,153serialize(thead_tr.children[i], {154parent: thead_tr,155no_escape: false,156lastChild: i == n - 1,157}).length - 3158),159align: thead_tr.children[i].align,160});161}162for (const tr of tbody_rows) {163const n = tr.children?.length ?? 0;164for (let i = 0; i < n; i++) {165if (info[i] == null) continue;166info[i].width = Math.max(167info[i].width ?? 3,168serialize(tr.children[i], {169parent: tr,170no_escape: false,171lastChild: i == n - 1,172}).length - 3173);174}175}176childInfo.table = info;177},178fromSlate,179});180181182