PluginProbe
Extendify / 2.3.1
Extendify v2.3.1
3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 0.7.0 All 126 releases
extendify / src / Draft / components / GenerateImageButtons.jsx

GenerateImageButtons.jsx in Extendify 2.3.1, at src/Draft/components/GenerateImageButtons.jsx

182 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { BlockControls } from '@wordpress/block-editor';
2 import {
3 Button,
4 MenuItem,
5 ToolbarGroup,
6 ToolbarButton,
7 } from '@wordpress/components';
8 import { useDispatch } from '@wordpress/data';
9 import { store as editPostStore } from '@wordpress/edit-post';
10 import { useEffect } from '@wordpress/element';
11 import { __ } from '@wordpress/i18n';
12 import { render } from '@shared/lib/dom';
13 import { magic } from '@draft/svg';
14
15 const supportedBlocks = [
16 'core/image',
17 'core/media-text',
18 'core/gallery',
19 'core/cover',
20 ];
21
22 export const GenerateImageButtons = (CurrentComponents, props) => {
23 const { openGeneralSidebar } = useDispatch(editPostStore);
24 const { clientId: blockId, name: name } = props;
25
26 useEffect(() => {
27 if (!supportedBlocks.includes(name)) return;
28
29 const frameSelector = 'iframe[name="editor-canvas"]';
30 const frame = document.querySelector(frameSelector)?.contentDocument;
31
32 const block = frame
33 ? frame.querySelector(`[data-block="${blockId}"]`)
34 : document.querySelector(`[data-block="${blockId}"]`);
35 if (!block) return;
36
37 const parentSelector =
38 '.block-editor-media-placeholder .components-form-file-upload';
39 const placeHolder = Object.assign(document.createElement('div'), {
40 className: 'components-form-file-upload',
41 });
42 block.querySelector(parentSelector)?.after(placeHolder);
43
44 let root;
45 const component = (
46 <>
47 <Button
48 variant="primary"
49 __next40pxDefaultSize
50 onClick={async () => {
51 openGeneralSidebar('extendify-draft/draft');
52 await new Promise((r) => requestAnimationFrame(r));
53 const btn = document.getElementById(
54 'extendify-draft-image-gen-button',
55 );
56 btn?.focus();
57 btn?.classList.add('animate-pulse-flash');
58 }}>
59 {__('Get Personalized Image', 'extendify-local')}
60 </Button>
61 {/* layout placeholder */}
62 <span aria-hidden="true" />
63 </>
64 );
65 const id = requestAnimationFrame(() => {
66 root = render(component, placeHolder);
67 });
68 return () => {
69 cancelAnimationFrame(id);
70 root?.unmount();
71 placeHolder?.remove();
72 };
73 }, [blockId, openGeneralSidebar, name]);
74
75 return (
76 <>
77 <CurrentComponents {...props} />
78 <BlockControls>
79 <ToolbarButtons {...props} />
80 </BlockControls>
81 </>
82 );
83 };
84
85 const GetPersonalizedImage = () => {
86 const { openGeneralSidebar } = useDispatch(editPostStore);
87 return (
88 <MenuItem
89 icon={magic}
90 onClick={async () => {
91 openGeneralSidebar('extendify-draft/draft');
92 await new Promise((r) => requestAnimationFrame(r));
93 const btn = document.getElementById('extendify-draft-image-gen-button');
94 btn?.focus();
95 btn?.classList.add('animate-pulse-flash');
96 }}>
97 {__('Get Personalized Image', 'extendify-local')}
98 </MenuItem>
99 );
100 };
101 const GetPersonalizedImageToolbar = () => {
102 const { openGeneralSidebar } = useDispatch(editPostStore);
103 return (
104 <ToolbarGroup className="extendify-draft">
105 <ToolbarButton
106 className="py-1.5 pl-2 pr-3 text-white before:bg-editor-main before:content-[''] hover:before:bg-editor-main-darker"
107 icon={magic}
108 onClick={async () => {
109 openGeneralSidebar('extendify-draft/draft');
110 await new Promise((r) => requestAnimationFrame(r));
111 const btn = document.getElementById(
112 'extendify-draft-image-gen-button',
113 );
114 btn?.focus();
115 btn?.classList.add('animate-pulse-flash');
116 }}>
117 {__('Ask AI', 'extendify-local')}
118 </ToolbarButton>
119 </ToolbarGroup>
120 );
121 };
122
123 const ToolbarButtons = ({ name, attributes }) => {
124 useEffect(() => {
125 if (!supportedBlocks.includes(name)) return;
126
127 let placeholder, root, rafInsert, rafOuter, observer;
128 // use async iife to allow frame delays
129 (async () => {
130 await new Promise((r) => (rafOuter = requestAnimationFrame(r)));
131 // Find a button on the toolbar that says replace or add
132 const replaceBtn = Array.from(
133 document.querySelectorAll('[data-toolbar-item="true"]'),
134 )?.find(
135 (btn) =>
136 btn.textContent === __('Replace') || btn.textContent === __('Add'),
137 );
138 if (!replaceBtn) return;
139
140 observer = new MutationObserver((mutations) => {
141 // Button is open
142 if (mutations[0].target.getAttribute('aria-expanded') === 'true') {
143 // Find the popover section we want to attach to
144 const pClass = '.block-editor-media-replace-flow__media-upload-menu';
145 const popover = document.querySelector(pClass);
146 if (!popover) return;
147
148 // Attach the placeholder to the popover then render
149 placeholder = document.createElement('div');
150 popover.prepend(placeholder);
151 rafInsert = requestAnimationFrame(() => {
152 root = render(<GetPersonalizedImage />, placeholder);
153 });
154 return;
155 }
156 // Replace button is closed
157 cancelAnimationFrame(rafInsert);
158 root?.unmount();
159 placeholder?.remove();
160 });
161
162 // Watch for aria-expanded attribute only
163 observer.observe(replaceBtn, {
164 attributes: true,
165 childList: false,
166 subtree: false,
167 });
168 })();
169
170 return () => {
171 [rafInsert, rafOuter].forEach(cancelAnimationFrame);
172 root?.unmount();
173 placeholder?.remove();
174 observer?.disconnect();
175 };
176 }, [name, attributes]);
177
178 if (!supportedBlocks.includes(name)) return null;
179
180 return <GetPersonalizedImageToolbar />;
181 };
182