PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.0.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.0.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / src / admin / components / content-brief / ExportOptions.js

ExportOptions.js in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.0.0, at src/admin/components/content-brief/ExportOptions.js

402 lines 14.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Export Options Component
3 *
4 * Provides export functionality for generated content briefs
5 *
6 * @package ThinkRank
7 * @since 1.0.0
8 */
9
10 import { useState } from '@wordpress/element';
11 import { Button, Card, CardBody, SelectControl, Notice } from '@wordpress/components';
12 import { __ } from '@wordpress/i18n';
13 import { download, copy } from '@wordpress/icons';
14 import apiFetch from '@wordpress/api-fetch';
15
16 const ExportOptions = ({ briefData }) => {
17 const [exportFormat, setExportFormat] = useState('txt');
18 const [isExporting, setIsExporting] = useState(false);
19 const [exportError, setExportError] = useState(null);
20
21 // Export format options
22 const formatOptions = [
23 { label: __('Plain Text (.txt)', 'thinkrank'), value: 'txt' },
24 { label: __('PDF Document (.pdf)', 'thinkrank'), value: 'pdf' }
25 ];
26
27 /**
28 * Copy brief to clipboard as formatted text
29 */
30 const copyToClipboard = () => {
31 const formattedText = formatBriefAsText(briefData);
32
33 // Try modern clipboard API first
34 if (navigator.clipboard && navigator.clipboard.writeText) {
35 navigator.clipboard.writeText(formattedText).then(() => {
36 console.log('Brief copied to clipboard');
37 // Show success message
38 setExportError(null);
39 }).catch((error) => {
40 console.error('Failed to copy to clipboard:', error);
41 fallbackCopyToClipboard(formattedText);
42 });
43 } else {
44 // Fallback for older browsers
45 fallbackCopyToClipboard(formattedText);
46 }
47 };
48
49 /**
50 * Fallback copy to clipboard method
51 */
52 const fallbackCopyToClipboard = (text) => {
53 const textArea = document.createElement('textarea');
54 textArea.value = text;
55 textArea.style.position = 'fixed';
56 textArea.style.left = '-999999px';
57 textArea.style.top = '-999999px';
58 document.body.appendChild(textArea);
59 textArea.focus();
60 textArea.select();
61
62 try {
63 const successful = document.execCommand('copy');
64 if (successful) {
65 console.log('Brief copied to clipboard (fallback)');
66 setExportError(null);
67 } else {
68 setExportError(__('Failed to copy to clipboard', 'thinkrank'));
69 }
70 } catch (error) {
71 console.error('Fallback copy failed:', error);
72 setExportError(__('Copy to clipboard not supported in this browser', 'thinkrank'));
73 } finally {
74 document.body.removeChild(textArea);
75 }
76 };
77
78 /**
79 * Export brief in selected format
80 */
81 const exportBrief = async () => {
82 setIsExporting(true);
83 setExportError(null);
84
85 try {
86 const formattedText = formatBriefAsText(briefData);
87
88 if (exportFormat === 'txt') {
89 // Download as plain text
90 downloadTextFile(formattedText, `content-brief-${briefData.id || 'generated'}.txt`);
91 } else if (exportFormat === 'pdf') {
92 // For PDF, we'll use the browser's print functionality
93 // Create a new window with formatted content
94 const printWindow = window.open('', '_blank');
95 printWindow.document.write(`
96 <html>
97 <head>
98 <title>Content Brief</title>
99 <style>
100 body {
101 font-family: Arial, sans-serif;
102 margin: 40px;
103 line-height: 1.6;
104 color: #333;
105 }
106 h1 {
107 color: #333;
108 border-bottom: 2px solid #333;
109 padding-bottom: 10px;
110 }
111 h2 {
112 color: #666;
113 margin-top: 30px;
114 border-bottom: 1px solid #ccc;
115 padding-bottom: 5px;
116 }
117 .section { margin-bottom: 20px; }
118 .outline-item { margin-left: 20px; }
119 pre {
120 white-space: pre-wrap;
121 font-family: Arial, sans-serif;
122 font-size: 12px;
123 }
124 @media print {
125 body { margin: 20px; }
126 @page { margin: 1in; }
127 }
128 </style>
129 </head>
130 <body>
131 <pre>${formattedText.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</pre>
132 </body>
133 </html>
134 `);
135 printWindow.document.close();
136 printWindow.print();
137 }
138 } catch (error) {
139 console.error('Export error:', error);
140 setExportError(error.message || __('An error occurred during export', 'thinkrank'));
141 } finally {
142 setIsExporting(false);
143 }
144 };
145
146 /**
147 * Format brief data as plain text
148 */
149 const formatBriefAsText = (data) => {
150 let text = '';
151
152 // Debug: Log the data structure to see what's available
153 console.log('Brief data for export:', data);
154
155 // Header
156 if (data.title && Array.isArray(data.title) && data.title[0]) {
157 text += `Content Brief: "${data.title[0]}"\n`;
158 text += '='.repeat(60) + '\n\n';
159 } else if (data.title && typeof data.title === 'string') {
160 text += `Content Brief: "${data.title}"\n`;
161 text += '='.repeat(60) + '\n\n';
162 }
163
164 // Generation parameters
165 if (data.generation_params) {
166 const params = data.generation_params;
167 text += 'BRIEF CONFIGURATION\n';
168 text += `-`.repeat(30) + '\n';
169 text += `Target Keywords: ${params.target_keywords?.join(', ') || 'N/A'}\n`;
170 text += `Content Type: ${params.content_type || 'N/A'}\n`;
171 text += `Target Audience: ${params.target_audience || 'N/A'}\n`;
172 text += `Content Length: ${params.content_length || 'N/A'}\n`;
173 text += `Tone: ${params.tone || 'N/A'}\n`;
174 if (params.competitor_urls && params.competitor_urls.length > 0) {
175 text += `Competitor URLs: ${params.competitor_urls.join(', ')}\n`;
176 }
177 if (params.additional_context) {
178 text += `Additional Context: ${params.additional_context}\n`;
179 }
180 text += '\n';
181 }
182
183 // Title suggestions
184 if (data.title && Array.isArray(data.title) && data.title.length > 1) {
185 text += 'TITLE SUGGESTIONS\n';
186 text += `-`.repeat(30) + '\n';
187 data.title.forEach((title, index) => {
188 text += `${index + 1}. ${title}\n`;
189 });
190 text += '\n';
191 }
192
193 // Meta description
194 if (data.meta_description) {
195 text += 'META DESCRIPTION\n';
196 text += `-`.repeat(30) + '\n';
197 text += `${data.meta_description}\n\n`;
198 }
199
200 // Content outline
201 if (data.outline && Array.isArray(data.outline) && data.outline.length > 0) {
202 text += 'CONTENT OUTLINE\n';
203 text += `-`.repeat(30) + '\n';
204 data.outline.forEach((item, index) => {
205 const indent = ' '.repeat((item.level || 1) - 1);
206 text += `${indent}${index + 1}. ${item.heading || item}`;
207 if (item.word_count && item.word_count > 0) {
208 text += ` (${item.word_count} words)`;
209 }
210 text += '\n';
211
212 if (item.key_points && Array.isArray(item.key_points) && item.key_points.length > 0) {
213 item.key_points.forEach((point) => {
214 text += `${indent}${point}\n`;
215 });
216 }
217
218 if (item.keywords && Array.isArray(item.keywords) && item.keywords.length > 0) {
219 text += `${indent} Keywords: ${item.keywords.join(', ')}\n`;
220 }
221 text += '\n';
222 });
223 }
224
225 // SEO recommendations
226 if (data.seo_recommendations) {
227 text += 'SEO Recommendations:\n';
228 text += `-`.repeat(20) + '\n';
229
230 const seo = data.seo_recommendations;
231 if (seo.related_keywords && seo.related_keywords.length > 0) {
232 text += `Related Keywords: ${seo.related_keywords.join(', ')}\n`;
233 }
234 if (seo.internal_links && seo.internal_links.length > 0) {
235 text += 'Internal Linking Opportunities:\n';
236 seo.internal_links.forEach((link) => {
237 text += `${link}\n`;
238 });
239 }
240 text += '\n';
241 }
242
243 // Content gaps
244 if (data.competitor_gaps && data.competitor_gaps.length > 0) {
245 text += 'Content Gaps & Opportunities:\n';
246 text += `-`.repeat(20) + '\n';
247 data.competitor_gaps.forEach((gap) => {
248 text += `${gap}\n`;
249 });
250 text += '\n';
251 }
252
253 // Call-to-actions
254 if (data.call_to_actions && data.call_to_actions.length > 0) {
255 text += 'Call-to-Action Suggestions:\n';
256 text += `-`.repeat(20) + '\n';
257 data.call_to_actions.forEach((cta, index) => {
258 text += `${index + 1}. ${cta}\n`;
259 });
260 text += '\n';
261 }
262
263 // Raw AI response (if available)
264 if (data.raw_response && data.raw_response.trim()) {
265 text += 'FULL AI RESPONSE\n';
266 text += `-`.repeat(30) + '\n';
267 text += data.raw_response + '\n\n';
268 }
269
270 // Footer
271 text += '='.repeat(60) + '\n';
272 if (data.created_at) {
273 text += `Generated on: ${data.created_at}\n`;
274 }
275 text += 'Powered by ThinkRank AI\n';
276 text += 'https://thinkrank.ai\n';
277
278 return text;
279 };
280
281 /**
282 * Download text as file
283 */
284 const downloadTextFile = (content, filename) => {
285 const blob = new Blob([content], { type: 'text/plain' });
286 const url = URL.createObjectURL(blob);
287 const link = document.createElement('a');
288 link.href = url;
289 link.download = filename;
290 document.body.appendChild(link);
291 link.click();
292 document.body.removeChild(link);
293 URL.revokeObjectURL(url);
294 };
295
296 /**
297 * Create new post with brief content
298 */
299 const createPostFromBrief = () => {
300 if (!briefData.outline || briefData.outline.length === 0) {
301 setExportError(__('No outline available to create post', 'thinkrank'));
302 return;
303 }
304
305 // Create basic post content from outline
306 let postContent = '';
307
308 briefData.outline.forEach((item) => {
309 const headingTag = `h${item.level}`;
310 postContent += `<${headingTag}>${item.heading}</${headingTag}>\n\n`;
311
312 if (item.key_points && item.key_points.length > 0) {
313 postContent += '<ul>\n';
314 item.key_points.forEach((point) => {
315 postContent += `<li>${point}</li>\n`;
316 });
317 postContent += '</ul>\n\n';
318 } else {
319 postContent += `<p>[Write ${item.word_count || 200} words about ${item.heading}]</p>\n\n`;
320 }
321 });
322
323 // Open new post editor with pre-filled content
324 const newPostUrl = `${window.location.origin}/wp-admin/post-new.php`;
325 const postData = {
326 post_title: briefData.title?.[0] || 'New Post from Brief',
327 content: postContent
328 };
329
330 // Store data in sessionStorage to be picked up by the editor
331 sessionStorage.setItem('thinkrank_brief_data', JSON.stringify(postData));
332
333 // Open new post editor
334 window.open(newPostUrl, '_blank');
335 };
336
337 if (!briefData) {
338 return null;
339 }
340
341 return (
342 <Card>
343 <CardBody>
344 <h3>{__('📤 Export Options', 'thinkrank')}</h3>
345
346 {exportError && (
347 <Notice status="error" isDismissible onRemove={() => setExportError(null)}>
348 {exportError}
349 </Notice>
350 )}
351
352 <div className="thinkrank-export-options">
353 <div className="export-format-selection">
354 <SelectControl
355 label={__('Export Format', 'thinkrank')}
356 value={exportFormat}
357 options={formatOptions}
358 onChange={setExportFormat}
359 __next40pxDefaultSize={true}
360 __nextHasNoMarginBottom={true}
361 />
362 </div>
363
364 <div className="export-actions">
365 <Button
366 isPrimary
367 onClick={exportBrief}
368 disabled={isExporting}
369 icon={download}
370 >
371 {isExporting ? __('Exporting...', 'thinkrank') : __('Download Brief', 'thinkrank')}
372 </Button>
373
374 <Button
375 isSecondary
376 onClick={copyToClipboard}
377 icon={copy}
378 >
379 {__('Copy to Clipboard', 'thinkrank')}
380 </Button>
381
382 <Button
383 isTertiary
384 onClick={createPostFromBrief}
385 >
386 {__('Create Post from Brief', 'thinkrank')}
387 </Button>
388 </div>
389 </div>
390
391 <div className="export-help">
392 <p className="description">
393 {__('Export your content brief for use in external tools or create a new WordPress post with the outline structure.', 'thinkrank')}
394 </p>
395 </div>
396 </CardBody>
397 </Card>
398 );
399 };
400
401 export default ExportOptions;
402