PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.2.0
Tableberg – Simple Gutenberg Table Block v1.2.0
1.2.0 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 All 43 releases
tableberg / src / blocks / text / index.tsx

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

236 lines 7.8 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 styles.linkColor ? " tableberg-has-link-color" : ""
185 }`}
186 identifier="content"
187 allowedFormats={[
188 "core/bold",
189 "core/italic",
190 "core/link",
191 "core/strikethrough",
192 "core/underline",
193 ]}
194 placeholder={__("Type here", "tableberg")}
195 value={content}
196 onChange={newContent =>
197 setAttributes({ content: newContent })
198 }
199 // Behave like core/paragraph: Enter splits into a new text
200 // block; together with the slash-inserter support this makes
201 // cells feel like the native canvas.
202 onSplit={(value: string, isOriginal?: boolean) => {
203 let newBlock;
204 if (isOriginal || value) {
205 newBlock = createBlock("tableberg/text", {
206 ...attributes,
207 content: value,
208 });
209 } else {
210 newBlock = createBlock("tableberg/text", {
211 ...attributes,
212 content: "",
213 });
214 }
215 if (isOriginal) {
216 (newBlock as { clientId: string }).clientId = clientId;
217 }
218 return newBlock;
219 }}
220 onReplace={onReplace}
221 onRemove={onRemove}
222 style={textStyle}
223 />
224 </div>
225 );
226 }
227
228 export function registerTextBlock() {
229 registerBlockType(metadata.name, {
230 ...(metadata as any),
231 icon: paragraph,
232 edit: TextEdit,
233 save: () => null,
234 });
235 }
236