PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.1.5
Tableberg – Simple Gutenberg Table Block v1.1.5
1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 trunk 0.0.2 0.2.1 0.3.2 0.3.3 0.4.1 0.5.0 0.5.1 0.5.2 0.5.3 0.5.4 0.5.5 0.5.6 0.5.7 All 42 releases
tableberg / src / blocks / text / index.tsx

index.tsx in Tableberg – Simple Gutenberg Table Block 1.1.5, at src/blocks/text/index.tsx

234 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import {
2 BlockSettingsMenuControls,
3 RichText,
4 useBlockProps,
5 } from "@wordpress/block-editor";
6 import { MenuItem } from "@wordpress/components";
7 import {
8 BlockEditProps,
9 createBlock,
10 parse,
11 registerBlockType,
12 } from "@wordpress/blocks";
13 import { __ } from "@wordpress/i18n";
14 import { paragraph } from "@wordpress/icons";
15 import { mergeAttrsWithDefaultsAndApplyBindings } from "@tableberg/shared/utils/merge-attrs-with-defaults-and-apply-bindings";
16
17 import {
18 TextElementAttributes,
19 sidesToShorthand,
20 textAttributeDefaults,
21 } from "./element";
22 import metadata from "./block.json";
23 import { TextElementControls } from "./controls";
24 import { ElementBindings } from "../../dynamic-data/types";
25 import { CellElement } from "../../attributes";
26 import {
27 applyElementStyleClipboardPayload,
28 parseElementStyleClipboardPayload,
29 readClipboardText,
30 serializeElementStyleClipboardPayload,
31 writeClipboardText,
32 } from "../../hooks/block-editor-compat/elementClipboard";
33 import {
34 ElementAlignmentToolbar,
35 alignmentWrapperStyle,
36 } from "../element-edit-common";
37
38 function TextEdit({
39 attributes,
40 setAttributes,
41 isSelected,
42 onReplace,
43 onRemove,
44 insertBlocksAfter,
45 clientId,
46 }: BlockEditProps<TextElementAttributes> & {
47 onReplace?: (blocks: unknown[]) => void;
48 onRemove?: () => void;
49 insertBlocksAfter?: (blocks: unknown[]) => void;
50 }) {
51 const merged = mergeAttrsWithDefaultsAndApplyBindings(
52 attributes,
53 textAttributeDefaults,
54 undefined,
55 {},
56 __("(No data)", "tableberg")
57 );
58 const { content, align, styles } = merged;
59
60 const textElement = {
61 name: "text",
62 attributes: merged,
63 } as CellElement;
64
65 const pasteStyles = async () => {
66 const payload = parseElementStyleClipboardPayload(
67 await readClipboardText()
68 );
69 if (!payload) {
70 return;
71 }
72
73 const styledElement = applyElementStyleClipboardPayload(
74 textElement,
75 payload
76 );
77 const styledAttributes =
78 styledElement.attributes as TextElementAttributes;
79
80 setAttributes({
81 align: styledAttributes.align,
82 styles: styledAttributes.styles,
83 });
84 };
85
86 const blockProps = useBlockProps({
87 style: alignmentWrapperStyle(align),
88 });
89
90 const paddingValue = sidesToShorthand(styles.padding);
91 const marginValue = sidesToShorthand(styles.margin);
92
93 const textStyle: Record<string, string | number | undefined> = {
94 "margin": marginValue ?? 0,
95 "padding": paddingValue,
96 "color": styles.textColor || undefined,
97 "fontSize": styles.fontSize || undefined,
98 "backgroundColor": styles.backgroundColor || undefined,
99 "--tableberg-text-link-color": styles.linkColor || undefined,
100 };
101
102 // Pasting a copied tableberg element while the caret is inside this text
103 // block: RichText would inline the markup as plain text, so route it to
104 // real block insertion instead (empty text gets replaced, otherwise the
105 // blocks land right below).
106 const onPasteCapture = (event: React.ClipboardEvent) => {
107 const plain = event.clipboardData?.getData("text/plain") ?? "";
108 if (!plain.includes("<!-- wp:tableberg/")) {
109 return;
110 }
111
112 const blocks = parse(plain);
113 if (blocks.length === 0 || blocks.some(b => !b.name)) {
114 return;
115 }
116
117 event.preventDefault();
118 event.stopPropagation();
119
120 if (!content && onReplace) {
121 onReplace(blocks);
122 } else if (insertBlocksAfter) {
123 insertBlocksAfter(blocks);
124 }
125 };
126
127 return (
128 <div {...blockProps} onPasteCapture={onPasteCapture}>
129 {isSelected && (
130 <>
131 <ElementAlignmentToolbar
132 align={align}
133 onChange={newAlign =>
134 setAttributes({ align: newAlign })
135 }
136 />
137 <BlockSettingsMenuControls>
138 {({ onClose }: { onClose: () => void }) => (
139 <>
140 <MenuItem
141 onClick={() => {
142 void writeClipboardText(
143 serializeElementStyleClipboardPayload(
144 textElement
145 )
146 );
147 onClose();
148 }}
149 >
150 {__("Copy styles", "tableberg")}
151 </MenuItem>
152 <MenuItem
153 onClick={() => {
154 void pasteStyles();
155 onClose();
156 }}
157 >
158 {__("Paste styles", "tableberg")}
159 </MenuItem>
160 </>
161 )}
162 </BlockSettingsMenuControls>
163 </>
164 )}
165 {isSelected && (
166 <TextElementControls
167 attributes={merged}
168 bindings={
169 (attributes as { bindings?: ElementBindings }).bindings
170 }
171 updateStyles={newStyles =>
172 setAttributes({
173 styles: {
174 ...(attributes.styles ?? merged.styles),
175 ...newStyles,
176 },
177 })
178 }
179 />
180 )}
181 <RichText
182 tagName="p"
183 className="tableberg-text-element"
184 identifier="content"
185 allowedFormats={[
186 "core/bold",
187 "core/italic",
188 "core/link",
189 "core/strikethrough",
190 "core/underline",
191 ]}
192 placeholder={__("Type here", "tableberg")}
193 value={content}
194 onChange={newContent =>
195 setAttributes({ content: newContent })
196 }
197 // Behave like core/paragraph: Enter splits into a new text
198 // block; together with the slash-inserter support this makes
199 // cells feel like the native canvas.
200 onSplit={(value: string, isOriginal?: boolean) => {
201 let newBlock;
202 if (isOriginal || value) {
203 newBlock = createBlock("tableberg/text", {
204 ...attributes,
205 content: value,
206 });
207 } else {
208 newBlock = createBlock("tableberg/text", {
209 ...attributes,
210 content: "",
211 });
212 }
213 if (isOriginal) {
214 (newBlock as { clientId: string }).clientId = clientId;
215 }
216 return newBlock;
217 }}
218 onReplace={onReplace}
219 onRemove={onRemove}
220 style={textStyle}
221 />
222 </div>
223 );
224 }
225
226 export function registerTextBlock() {
227 registerBlockType(metadata.name, {
228 ...(metadata as any),
229 icon: paragraph,
230 edit: TextEdit,
231 save: () => null,
232 });
233 }
234