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 / elements / custom-html / index.tsx

index.tsx in Tableberg – Simple Gutenberg Table Block 1.1.5, at src/elements/custom-html/index.tsx

235 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useRef, useState, useEffect, useMemo } from "react";
2 import {
3 PlainText,
4 store as blockEditorStore,
5 transformStyles,
6 } from "@wordpress/block-editor";
7 import { __ } from "@wordpress/i18n";
8 import { mergeAttrsWithDefaultsAndApplyBindings } from "@tableberg/shared/utils/merge-attrs-with-defaults-and-apply-bindings";
9
10 import { useTableStore } from "../../store";
11 import { useClickOutside } from "../../hooks/useClickOutside";
12 import { ElementRendererProps } from "../index";
13 import { CustomHtmlElementControls } from "./controls";
14 import { BindableAttribute, ElementBindings } from "../../dynamic-data/types";
15 import { useDynamicDataBindings } from "../../dynamic-data/hooks/useDynamicData";
16 import { useSelect } from "@wordpress/data";
17 import {
18 elementAlignmentToJustifyContent,
19 ElementAlignment,
20 } from "../../alignment";
21
22 const DEFAULT_STYLES = `
23 html,body,:root {
24 margin: 0 !important;
25 padding: 0 !important;
26 overflow: visible !important;
27 min-height: auto !important;
28 }
29 `;
30
31 export interface CustomHtmlElementAttributes {
32 content: string;
33 align: ElementAlignment;
34 }
35
36 export const customHtmlAttrDefaults: CustomHtmlElementAttributes = {
37 content: "",
38 align: "left",
39 };
40
41 export interface CustomHtmlElementType {
42 name: "custom-html";
43 attributes: CustomHtmlElementAttributes;
44 bindings?: ElementBindings;
45 }
46
47 export function CustomHtmlElement({
48 attributes,
49 bindings,
50 cellCoords,
51 elementIndex,
52 }: ElementRendererProps<CustomHtmlElementAttributes>) {
53 const wrapperRef = useRef<HTMLDivElement>(null);
54 const iframeRef = useRef<HTMLIFrameElement>(null);
55
56 const sortPreviewMode = useTableStore(state => state.sortPreviewMode);
57 const isElementSelected = useTableStore(state => state.isElementSelected);
58 const setSelectedElement = useTableStore(state => state.setSelectedElement);
59 const clearSelectedElement = useTableStore(
60 state => state.clearSelectedElement
61 );
62 const updateCellElement = useTableStore(state => state.updateCellElement);
63
64 const isSelected = isElementSelected(cellCoords, elementIndex);
65 const [isPreview, setIsPreview] = useState(false);
66
67 useClickOutside({
68 ref: wrapperRef,
69 onClickOutside: () => {
70 if (isSelected) {
71 clearSelectedElement();
72 }
73 },
74 });
75
76 const { values: previewValues, isLoading: isDynamicLoading } =
77 useDynamicDataBindings(bindings);
78
79 const mergedAttrs = mergeAttrsWithDefaultsAndApplyBindings(
80 attributes,
81 customHtmlAttrDefaults,
82 bindings,
83 previewValues,
84 __("(No data)", "tableberg")
85 );
86
87 const contentIsBound = !!bindings?.content;
88 const { content, align } = mergedAttrs;
89
90 const wrapperStyle: React.CSSProperties = {
91 display: "flex",
92 justifyContent: elementAlignmentToJustifyContent(align),
93 };
94
95 useEffect(() => {
96 if (contentIsBound) {
97 setIsPreview(true);
98 }
99 }, [contentIsBound]);
100
101 const settingStyles = useSelect(
102 select => (select(blockEditorStore) as any).getSettings()?.styles || [],
103 []
104 );
105
106 const styles = useMemo(
107 () =>
108 [
109 DEFAULT_STYLES,
110 ...transformStyles(
111 settingStyles.filter((style: any) => style.css)
112 ),
113 ].join(""),
114 [settingStyles]
115 );
116
117 const renderIframeContent = () => {
118 const iframe = iframeRef.current;
119 if (!iframe) {
120 return;
121 }
122 const iframeDocument = iframe.contentWindow?.document;
123 if (!iframeDocument) {
124 return;
125 }
126
127 iframeDocument.head.innerHTML = `<style>${styles}</style>`;
128 iframeDocument.body.innerHTML = content
129 ? `<div
130 style="width: max-content; overflow: hidden;"
131 class="tableberg-html-content editor-styles-wrapper"
132 >
133 ${content}
134 </div>`
135 : `<div
136 style="width: max-content; overflow: hidden; color: grey; padding: 8px;"
137 class="tableberg-html-content editor-styles-wrapper"
138 >
139 ${__("Empty custom HTML block", "tableberg")}
140 </div>`;
141
142 const contentEl = iframeDocument.querySelector(
143 ".tableberg-html-content"
144 );
145 if (contentEl) {
146 const contentRect = contentEl.getBoundingClientRect();
147 iframe.style.height = `${Math.ceil(contentRect.height) + 1}px`;
148 iframe.style.width = `${Math.ceil(contentRect.width) + 1}px`;
149 }
150 };
151
152 useEffect(() => {
153 renderIframeContent();
154 }, [styles, isPreview, content, isSelected]);
155
156 const handleClick = () => {
157 if (!sortPreviewMode) {
158 setSelectedElement(cellCoords, elementIndex);
159 }
160 };
161
162 const handleContentChange = (newContent: string) => {
163 updateCellElement(cellCoords, elementIndex, {
164 attributes: { content: newContent },
165 });
166 };
167
168 if (isDynamicLoading) {
169 return (
170 <div
171 ref={wrapperRef}
172 className="tableberg-custom-html"
173 style={wrapperStyle}
174 onClick={handleClick}
175 >
176 <p style={{ margin: 0 }}>{__("Loading...", "tableberg")}</p>
177 </div>
178 );
179 }
180
181 const shouldShowPreview = sortPreviewMode || contentIsBound || isPreview;
182
183 return (
184 <>
185 {isSelected && (
186 <CustomHtmlElementControls
187 attributes={mergedAttrs}
188 bindings={bindings}
189 isPreview={isPreview}
190 setIsPreview={setIsPreview}
191 cellCoords={cellCoords}
192 elementIndex={elementIndex}
193 />
194 )}
195 <div
196 ref={wrapperRef}
197 className="tableberg-custom-html"
198 onClick={handleClick}
199 >
200 {shouldShowPreview ? (
201 <iframe
202 ref={iframeRef}
203 title={__("Custom HTML Preview", "tableberg")}
204 tabIndex={-1}
205 sandbox="allow-same-origin"
206 onLoad={renderIframeContent}
207 style={{
208 display: "block",
209 border: "none",
210 minWidth: "100px",
211 minHeight: "20px",
212 pointerEvents: "none",
213 }}
214 />
215 ) : (
216 <PlainText
217 className="tableberg-custom-html-editor"
218 value={content}
219 onChange={handleContentChange}
220 placeholder={__("Write HTML...", "tableberg")}
221 aria-label={__("HTML", "tableberg")}
222 />
223 )}
224 </div>
225 </>
226 );
227 }
228
229 export const customHtmlBindableAttributes: BindableAttribute[] = [
230 {
231 path: "content",
232 label: __("HTML Content", "tableberg"),
233 },
234 ];
235