PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.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 All 128 releases
← All changes | src/AutoLaunch/components/DescriptionGathering.jsx +5 -193 3.0.4 → 3.2.2 View file →
@@ -1,194 +1,6 @@
1 -import { fetchWithTimeout } from '@auto-launch/functions/helpers';
2 -import { useInstallRequiredPlugins } from '@auto-launch/hooks/useInstallRequiredPlugins';
3 -import { loaderThreeDots } from '@auto-launch/icons';
4 -import { useLaunchDataStore } from '@auto-launch/state/launch-data';
5 -import { AI_HOST } from '@constants';
6 -import { reqDataBasics } from '@shared/lib/data';
7 -import { useAIConsentStore } from '@shared/state/ai-consent';
8 -import {
9 - forwardRef,
10 - useCallback,
11 - useEffect,
12 - useRef,
13 - useState,
14 -} from '@wordpress/element';
15 -import { __ } from '@wordpress/i18n';
16 -import { chevronRight, Icon, pencil } from '@wordpress/icons';
1 +import { DescriptionForm } from '@auto-launch/components/DescriptionForm';
2 +import { useDescriptionForm } from '@auto-launch/hooks/useDescriptionForm';
17 3
18 -export const DescriptionGathering = () => {
19 - const { setData, descriptionBackup, urlParams } = useLaunchDataStore();
20 - useInstallRequiredPlugins();
21 - const [input, setInput] = useState(
22 - urlParams.description || urlParams.title || descriptionBackup || '',
23 - );
24 - const [improving, setImproving] = useState(false);
25 - const [lastImproved, setLastImproved] = useState(null);
26 - const textareaRef = useRef(null);
27 - const { consentTerms } = useAIConsentStore();
28 -
29 - // resize the height of the textarea based on the content
30 - const adjustHeight = useCallback(() => {
31 - const el = textareaRef.current;
32 - if (!el) return;
33 - const bottomPadding = 120; // tweak as needed
34 - // Reset to measure natural height
35 - el.style.height = 'auto';
36 -
37 - const rect = el.getBoundingClientRect();
38 - const viewportHeight = window.innerHeight;
39 -
40 - const maxAvailable = Math.max(0, viewportHeight - rect.top - bottomPadding);
41 - const desired = el.scrollHeight;
42 - const nextHeight = Math.min(desired, maxAvailable);
43 -
44 - el.style.height = `${nextHeight}px`;
45 - el.style.overflowY = desired > maxAvailable ? 'auto' : 'hidden';
46 -
47 - // Notify others
48 - window.dispatchEvent(new Event('launch-textarea-resize'));
49 - }, []);
50 -
51 - const submitForm = (e) => {
52 - e.preventDefault();
53 - setData('descriptionRaw', input.trim());
54 - setData('go', true);
55 - };
56 -
57 - const handleImprove = async () => {
58 - setImproving(true);
59 - const url = `${AI_HOST}/api/prompt/improve`;
60 - const method = 'POST';
61 - const headers = { 'Content-Type': 'application/json' };
62 - const response = await fetchWithTimeout(url, {
63 - method,
64 - headers,
65 - body: JSON.stringify({
66 - ...reqDataBasics,
67 - description: input.trim(),
68 - title: window.extSharedData.siteTitle,
69 - }),
70 - })
71 - .then((res) => res.ok && res.json())
72 - .catch(() => null);
73 - const nextValue = response?.improvedPrompt;
74 - setImproving(false);
75 - if (nextValue) {
76 - setLastImproved(nextValue);
77 - const el = textareaRef.current;
78 - if (!el) return setInput(nextValue);
79 - requestAnimationFrame(() => {
80 - // Preserve undo ability by using native events instead of React state
81 - el.focus();
82 - el.select();
83 - const ok = document.execCommand('insertText', false, nextValue);
84 - if (!ok) setInput(nextValue);
85 - });
86 - }
87 - };
88 -
89 - useEffect(() => {
90 - setData('descriptionBackup', input.trim());
91 - const raf = requestAnimationFrame(() => {
92 - adjustHeight();
93 - });
94 - return () => cancelAnimationFrame(raf);
95 - }, [input, setData]);
96 -
97 - useEffect(() => {
98 - const controller = new AbortController();
99 - const { signal } = controller;
100 - const handleResize = () => {
101 - adjustHeight();
102 - const c = textareaRef.current;
103 - c?.scrollTo(0, c.scrollHeight);
104 - };
105 - window.addEventListener('resize', handleResize, { signal });
106 - window.addEventListener('orientationchange', handleResize, { signal });
107 - adjustHeight();
108 - return () => controller.abort();
109 - }, [adjustHeight]);
110 -
111 - return (
112 - <>
113 - {/* biome-ignore lint: allow onClick without keyboard */}
114 - <form
115 - onSubmit={submitForm}
116 - onClick={() => textareaRef.current?.focus()}
117 - className="relative flex w-full flex-col"
118 - >
119 - <div className="w-full rounded-3xl border border-gray-300 bg-gray-100/80 text-gray-900 backdrop-blur-2xl focus-within:border-gray-500 focus-within:ring-gray-500 shadow-md overflow-hidden">
120 - {improving ? (
121 - <div className="flex h-49 flex-col items-center justify-center gap-4">
122 - <div className="h-12 w-12 text-design-main">
123 - {loaderThreeDots}
124 - </div>
125 - <p className="m-0 text-base leading-6 text-center text-gray-800">
126 - {__('Enhancing the website description...', 'extendify-local')}
127 - </p>
128 - </div>
129 - ) : (
130 - <>
131 - <textarea
132 - ref={textareaRef}
133 - id="extendify-launch-chat-textarea"
134 - className="flex min-h-20 md:min-h-24 w-full resize-none bg-transparent text-base leading-6 placeholder:text-gray-700 focus:shadow-none focus:outline-hidden border-none text-gray-900 p-6 pb-0"
135 - rows="1"
136 - // biome-ignore lint: Allow autofocus here
137 - autoFocus
138 - value={input}
139 - onChange={(e) => {
140 - setInput(e.target.value);
141 - }}
142 - placeholder={__(
143 - 'E.g., A personal photography portfolio featuring a collection of landscape, portrait, and street photography, capturing moments from around the world.',
144 - 'extendify-local',
145 - )}
146 - />
147 - <div className="flex justify-between items-end gap-4 p-6">
148 - <div>
149 - <ImprovePrompt
150 - disabled={
151 - input.trim().length === 0 || input.trim() === lastImproved
152 - }
153 - onClick={handleImprove}
154 - />
155 - </div>
156 - <SubmitButton disabled={input.trim().length === 0} />
157 - </div>
158 - </>
159 - )}
160 - </div>
161 - </form>
162 - <div
163 - className="text-pretty mt-4 text-center text-xs leading-4 opacity-70 text-banner-text [&>a]:text-xs [&>a]:text-banner-text [&>a]:underline w-full"
164 - dangerouslySetInnerHTML={{ __html: consentTerms }}
165 - />
166 - </>
167 - );
168 -};
169 -
170 -const SubmitButton = forwardRef((props, ref) => (
171 - <button
172 - ref={ref}
173 - type="submit"
174 - className="inline-flex items-center justify-center rounded-full border-0 bg-design-main px-3 py-2 text-sm leading-5 font-normal text-design-text focus-visible:ring-design-main disabled:opacity-40 focus:outline-none focus-visible:ring-1 focus-visible:ring-offset-2 group hover:opacity-90 transition-opacity"
175 - {...props}
176 - >
177 - <span className="px-1">{__('Next', 'extendify-local')}</span>
178 - <Icon fill="currentColor" icon={chevronRight} size={24} />
179 - </button>
180 -));
181 -
182 -const ImprovePrompt = (props) => {
183 - return (
184 - <button
185 - type="button"
186 - className="inline-flex items-center rounded-full ring-1 ring-gray-800 px-3 py-2 text-sm leading-5 font-normal text-gray-800 transition-colors hover:bg-gray-600/5 disabled:opacity-40"
187 - {...props}
188 - >
189 - <Icon icon={pencil} size={24} />
190 - {/* translators: "Enhance with AI" refers to improving the current input using AI. */}
191 - <span className="px-1">{__('Enhance with AI', 'extendify-local')}</span>
192 - </button>
193 - );
194 -};
4 +export const DescriptionGathering = ({ autoFocus }) => (
5 + <DescriptionForm {...useDescriptionForm()} autoFocus={autoFocus} />
6 +);