Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/elements/table/editable.tsx
1698 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { CSSProperties as CSS } from "react";
7
import { register } from "../register";
8
import { useFocused, useSelected } from "../hooks";
9
import { padLeft, padRight, padCenter } from "../../util";
10
import { serialize } from "../../slate-to-markdown";
11
import getStyles from "./style";
12
13
function fromSlate({ node, children, info, childInfo }) {
14
switch (node.type) {
15
case "table": // a table
16
const i = children.indexOf("\n");
17
const thead = children.slice(0, i);
18
const tbody = children.slice(i + 1);
19
let sep = "|",
20
headings: { align: string }[];
21
try {
22
headings = (node as any).children[0].children[0].children;
23
} catch (_err) {
24
headings = [];
25
}
26
for (let i = 0; i < headings.length; i++) {
27
const n = (childInfo.table?.[i]?.width ?? 5) - 2;
28
let bar = "-";
29
for (let j = 0; j < n; j++) bar += "-";
30
switch (headings[i].align) {
31
case "center":
32
bar = ":" + bar.slice(1) + ":";
33
break;
34
case "right":
35
bar = bar + ":";
36
break;
37
case "left":
38
default:
39
bar = ":" + bar;
40
break;
41
}
42
sep += ` ${bar} |`;
43
}
44
return `${thead}\n${sep}\n${tbody}\n`;
45
46
case "thead": // the heading row of a table
47
return children; // the one child is a tr, which renders fine by itself
48
49
case "tbody": // the body of the table
50
return children;
51
52
case "tr": // a row of a table
53
return "| " + children.trimEnd() + "\n";
54
55
case "th": // a heading entry in a row in the thead
56
case "td": // a data entry in a row
57
if (info.index != null) {
58
const data = info.table?.[info.index];
59
if (data != null) {
60
switch (data.align) {
61
case "left":
62
children = padRight(children, data.width);
63
break;
64
case "right":
65
children = padLeft(children, data.width);
66
break;
67
case "center":
68
children = padCenter(children, data.width);
69
break;
70
}
71
}
72
}
73
children = children.trimEnd();
74
return children + " | ";
75
}
76
}
77
78
export const Element = ({ attributes, children, element }) => {
79
const focused = useFocused();
80
const selected = useSelected();
81
let backgroundColor: string | undefined = undefined;
82
83
switch (element.type) {
84
/* We render *editable* tables using straight HTML and the antd
85
CSS classes. We do NOT use the actual antd Table
86
class, since it doesn't play well with slatejs.
87
I just looked at the DOM in a debugger to figure out
88
these tags; the main risk is that things change, but
89
it's purely style so that is OK.
90
*/
91
92
case "table":
93
const { divStyle, tableStyle } = getStyles(focused && selected);
94
return (
95
<div {...attributes} style={divStyle}>
96
<table style={tableStyle}>{children}</table>
97
</div>
98
);
99
case "thead":
100
return <thead {...attributes}>{children}</thead>;
101
case "tbody":
102
return <tbody {...attributes}>{children}</tbody>;
103
case "tr":
104
return <tr {...attributes}>{children}</tr>;
105
case "th":
106
backgroundColor = focused && selected ? "#e8f2ff" : undefined;
107
return (
108
<th
109
{...attributes}
110
style={{ backgroundColor, textAlign: element.align ?? "left" } as CSS}
111
>
112
{children}
113
</th>
114
);
115
case "td":
116
backgroundColor = focused && selected ? "#e8f2ff" : undefined;
117
return (
118
<td
119
{...attributes}
120
style={{ backgroundColor, textAlign: element.align ?? "left" } as CSS}
121
>
122
{children}
123
</td>
124
);
125
default:
126
throw Error("not a table element type " + element.type);
127
}
128
};
129
130
register({
131
slateType: ["thead", "tbody", "tr", "th", "td"],
132
Element,
133
fromSlate,
134
});
135
136
// NOTE/OPTIMIZATION: We end up serializing the cells twice; first to
137
// get their length, then later to do a final render and pad everything
138
// to look nice.
139
// table is extra global information used in formatting columns.
140
type TableInfo = { width: number; align: "left" | "center" | "right" }[];
141
142
register({
143
slateType: "table",
144
Element,
145
childInfoHook: ({ childInfo, node }) => {
146
const thead_tr = (node as any).children[0].children[0];
147
const tbody_rows = (node as any).children[1]?.children ?? []; // can have no tbody
148
const info: TableInfo = [];
149
const n = thead_tr.children?.length ?? 0;
150
for (let i = 0; i < n; i++) {
151
info.push({
152
width: Math.max(
153
3,
154
serialize(thead_tr.children[i], {
155
parent: thead_tr,
156
no_escape: false,
157
lastChild: i == n - 1,
158
}).length - 3
159
),
160
align: thead_tr.children[i].align,
161
});
162
}
163
for (const tr of tbody_rows) {
164
const n = tr.children?.length ?? 0;
165
for (let i = 0; i < n; i++) {
166
if (info[i] == null) continue;
167
info[i].width = Math.max(
168
info[i].width ?? 3,
169
serialize(tr.children[i], {
170
parent: tr,
171
no_escape: false,
172
lastChild: i == n - 1,
173
}).length - 3
174
);
175
}
176
}
177
childInfo.table = info;
178
},
179
fromSlate,
180
});
181
182