PluginProbe
Extendify / trunk
Extendify vtrunk
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 0.6.0 All 127 releases
← All changes | src/AutoLaunch/components/DescriptionGathering.jsx +5 -195 3.0.6 → trunk View file →
@@ -1,196 +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 - autoComplete="off"
139 - data-1p-ignore
140 - value={input}
141 - onChange={(e) => {
142 - setInput(e.target.value);
143 - }}
144 - placeholder={__(
145 - 'E.g., A personal photography portfolio featuring a collection of landscape, portrait, and street photography, capturing moments from around the world.',
146 - 'extendify-local',
147 - )}
148 - />
149 - <div className="flex justify-between items-end gap-4 p-6">
150 - <div>
151 - <ImprovePrompt
152 - disabled={
153 - input.trim().length === 0 || input.trim() === lastImproved
154 - }
155 - onClick={handleImprove}
156 - />
157 - </div>
158 - <SubmitButton disabled={input.trim().length === 0} />
159 - </div>
160 - </>
161 - )}
162 - </div>
163 - </form>
164 - <div
165 - 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"
166 - dangerouslySetInnerHTML={{ __html: consentTerms }}
167 - />
168 - </>
169 - );
170 -};
171 -
172 -const SubmitButton = forwardRef((props, ref) => (
173 - <button
174 - ref={ref}
175 - type="submit"
176 - 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"
177 - {...props}
178 - >
179 - <span className="px-1">{__('Next', 'extendify-local')}</span>
180 - <Icon fill="currentColor" icon={chevronRight} size={24} />
181 - </button>
182 -));
183 -
184 -const ImprovePrompt = (props) => {
185 - return (
186 - <button
187 - type="button"
188 - 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"
189 - {...props}
190 - >
191 - <Icon icon={pencil} size={24} />
192 - {/* translators: "Enhance with AI" refers to improving the current input using AI. */}
193 - <span className="px-1">{__('Enhance with AI', 'extendify-local')}</span>
194 - </button>
195 - );
196 -};
4 +export const DescriptionGathering = ({ autoFocus }) => (
5 + <DescriptionForm {...useDescriptionForm()} autoFocus={autoFocus} />
6 +);