PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 All 203 releases
betterdocs / react-src / admin / settings / fields / ImporterUpload.js

ImporterUpload.js in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.5, at react-src/admin/settings/fields/ImporterUpload.js

447 lines 18.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useState, useEffect } from "react";
2 import apiFetch from "@wordpress/api-fetch";
3 import { __ } from "@wordpress/i18n";
4 import { validFieldProps, isObject } from "quickbuilder";
5
6 // This component will send form data on submit
7 const ImporterUpload = (props) => {
8 const validProps = validFieldProps(props, [
9 "is_pro",
10 "visible",
11 "trigger",
12 "disable",
13 "parentIndex",
14 "context",
15 "copyOnClick",
16 "description",
17 "label",
18 "help",
19 "style",
20 ]);
21 const [selectedFile, setSelectedFile] = useState(null);
22 const [isLoading, setIsLoading] = useState(false);
23 const [existingSlugs, setExistingSlugs] = useState([]);
24 const [selectedAction, setSelectedAction] = useState("ignore");
25
26 // from withLabel field
27 let {
28 label,
29 label_subtitle,
30 id,
31 name,
32 type,
33 style: prevStyle,
34 is_pro,
35 badge,
36 value,
37 enable_disable_text_active = false,
38 ...rest
39 } = props;
40 // const instanceId = useInstanceId(withLabel);
41
42 if (id == undefined) {
43 id = name;
44 }
45
46 const styles = {
47 description: {
48 position: "right",
49 },
50 ...prevStyle,
51 };
52
53 const classNames = (...args) => args.filter(Boolean).join(" ");
54
55 const styleClasses = classNames({
56 [`wprf-style-${styles?.type}`]: styles?.type || false,
57 "wprf-label-none":
58 label === undefined || label === "" || label.length === 0,
59 [`wprf-${styles?.label?.position || "inline"}-label`]:
60 (styles?.label?.position ?? true) && label != undefined,
61 });
62
63 if (type === "hidden") {
64 return <WrappedComponent {...props} id={id} />;
65 }
66
67 const componentClasses = classNames(
68 "wprf-control-wrapper",
69 `wprf-type-${type}`,
70 styleClasses,
71 props?.classes,
72 {
73 [`wprf-name-${name}`]: name,
74 },
75 );
76
77 const handleRadioChange = (e) => {
78 setSelectedAction(e.target.value);
79 };
80
81 const handleFileChange = (e) => {
82 if (!e.target.files.length) {
83 return;
84 }
85 const file = e.target.files[0];
86
87 if (file?.size == 0) {
88 props.context.alerts.toast(
89 "error",
90 __('File can\'t be empty.', "betterdocs"),
91 );
92 return;
93 } else if (
94 file?.type !== "application/json" &&
95 file?.type !== "text/json" &&
96 file?.type !== "application/xml" && // MIME type for XML
97 file?.type !== "text/xml" &&
98 file?.type !== "text/csv" &&
99 file?.type !== "application/csv" &&
100 file?.type !== "application/vnd.ms-excel" // MIME types for CSV
101 ) {
102 props.context.alerts.toast(
103 "error",
104 __('Invalid file type.', "betterdocs"),
105 );
106 return;
107 }
108
109 setSelectedFile(file);
110
111 // Check file type and call respective functions
112 if (file.type === "application/xml" || file.type === "text/xml") {
113 let reader = new FileReader();
114
115 reader.onload = function (e) {
116 // e.target.result contains the file data
117 const fileData = e.target.result;
118
119 // Parse the XML string
120 const parser = new DOMParser();
121 const xmlDoc = parser.parseFromString(fileData, "text/xml");
122
123 // Define the WordPress namespace
124 const wpNamespace = "http://wordpress.org/export/1.2/";
125
126 // Use XPath to extract wp:post_name values
127 let data = {};
128 const postSlugs = [];
129 const nodes = xmlDoc.evaluate(
130 "//wp:post_name",
131 xmlDoc,
132 (prefix) => {
133 return prefix === "wp" ? wpNamespace : null;
134 },
135 XPathResult.ANY_TYPE,
136 null,
137 );
138
139 let node = nodes.iterateNext();
140 while (node) {
141 postSlugs.push(node.textContent);
142 node = nodes.iterateNext();
143 }
144 if (postSlugs) {
145 data["posts"] = postSlugs;
146 }
147
148 apiFetch({
149 path: "/betterdocs/v1/parse-xml",
150 method: "POST",
151 data: data,
152 })
153 .then((response) => {
154 if (response.status === "success" && response.data) {
155 setExistingSlugs(Object.values(response.data));
156 }
157 })
158 .catch((error) => {
159 console.log(error);
160 });
161 };
162
163 // Read the file as text or binary, depending on your needs
164 reader.readAsText(file);
165 } else if (
166 file.type === "text/csv" ||
167 file.type === "application/csv" ||
168 file.type === "application/vnd.ms-excel"
169 ) {
170 handleCSVFile(file);
171 }
172 };
173
174 const handleCSVFile = (file) => {
175 const formData = new FormData();
176 formData.append("file", file);
177 apiFetch({
178 path: "/betterdocs/v1/parse-csv",
179 method: "POST",
180 body: formData,
181 })
182 .then((response) => {
183 if (response.status === "success" && response.data) {
184 setExistingSlugs(Object.values(response.data));
185 }
186 })
187 .catch((error) => {
188 console.log(error);
189 });
190 };
191
192 const handleUpload = () => {
193 setIsLoading(true);
194 const formData = new FormData();
195 formData.append("file", selectedFile);
196 // Add other form data to the FormData object
197 {
198 existingSlugs.length > 0 &&
199 formData.append("existing_slug", existingSlugs.join(","));
200 }
201
202 const selectedAction = document.querySelector(
203 'input[name="action"]:checked',
204 );
205
206 if (selectedAction) {
207 formData.append("action", selectedAction.value);
208 }
209
210 apiFetch({
211 path: props.ajax.api,
212 method: "POST",
213 body: formData,
214 })
215 .then((response) => {
216 setIsLoading(false);
217
218 if (response.status === "failed") {
219 throw new Error();
220 }
221
222 if (response.status === "success") {
223 setSelectedFile(null);
224
225 if (!props.ajax?.hideSwal) {
226 const type = props.ajax?.swal?.icon || "success";
227 const message = props.ajax?.swal?.text || "Complete";
228 props.context.alerts.toast(type, message, {
229 autoClose: props.ajax?.swal?.autoClose,
230 });
231 }
232 if (props.ajax?.reload) {
233 setTimeout(() => window.location.reload(), 1000);
234 }
235 }
236 })
237 .catch((error) => {
238 setIsLoading(true);
239 if (!props.ajax?.hideSwal) {
240 props.context.alerts.toast(
241 "error",
242 error?.message ||
243 __("Something went wrong.", "betterdocs"),
244 );
245 }
246 });
247 };
248 const removeFile = () => {
249 setSelectedFile(null);
250 props.onChange({
251 target: {
252 type: "importerupload",
253 name: props.name,
254 value: null,
255 },
256 });
257 };
258 return (
259 <>
260 <div className={componentClasses}>
261 <div className="wprf-control-label">
262 {label && <label htmlFor={id}>{label}</label>}
263 {label_subtitle && (
264 <p
265 className="wprf-label-subtitle"
266 dangerouslySetInnerHTML={{
267 __html: label_subtitle,
268 }}
269 ></p>
270 )}
271 </div>
272 <div className="wprf-control-field wprf-importer-upload-field">
273 <span className="wprf-json-uploader">
274 {selectedFile === null ? (
275 <label className="wprf-importer-upload-button">
276 <span className="icon">
277 <i className="btd-icon btd-upload"></i>
278 </span>
279 <span>
280 {__("Click to upload", "betterdocs")}
281 </span>
282 <input
283 type="file"
284 accept={props.file_type}
285 onChange={handleFileChange}
286 />
287 </label>
288 ) : (
289 selectedFile.name && (
290 <>
291 <span className="wpfr-json-file-name-wrapper">
292 <span className="wpfr-json-file-name">
293 {selectedFile.name.length > 20
294 ? `${selectedFile.name.substr(
295 0,
296 9,
297 )}...${selectedFile.name.substr(
298 selectedFile.name.length -
299 7,
300 )}`
301 : selectedFile.name}
302 </span>
303 <span
304 className="wprf-json-file-delete-button"
305 onClick={removeFile}
306 >
307 <svg
308 xmlns="http://www.w3.org/2000/svg"
309 width="24"
310 height="24"
311 viewBox="0 0 24 24"
312 fill="none"
313 >
314 <mask
315 id="mask0_2475_21238"
316 mask-type="alpha"
317 maskUnits="userSpaceOnUse"
318 x="0"
319 y="0"
320 width="24"
321 height="24"
322 >
323 <rect
324 width="24"
325 height="24"
326 fill="#D9D9D9"
327 />
328 </mask>
329 <g mask="url(#mask0_2475_21238)">
330 <path
331 d="M6.4 19L5 17.6L10.6 12L5 6.4L6.4 5L12 10.6L17.6 5L19 6.4L13.4 12L19 17.6L17.6 19L12 13.4L6.4 19Z"
332 fill="#475467"
333 />
334 </g>
335 </svg>
336 </span>
337 </span>
338 {existingSlugs.length > 0 && (
339 <>
340 <div className="data-exists">
341 <input
342 type="hidden"
343 name="existing_slug"
344 id="existing_slug"
345 />
346 <div className="error">
347 {props?.text
348 ?.exists_notice && (
349 <p className="exists-notice">
350 {
351 props?.text
352 ?.exists_notice
353 }
354 </p>
355 )}
356 <label htmlFor="ignore-radio">
357 <input
358 type="radio"
359 name="action"
360 value="ignore"
361 id="ignore-radio"
362 checked={
363 selectedAction ===
364 "ignore"
365 }
366 onChange={
367 handleRadioChange
368 }
369 />
370 {__(
371 "Ignore",
372 "betterdocs",
373 )}
374 </label>
375 <label htmlFor="keepboth-radio">
376 <input
377 type="radio"
378 name="action"
379 value="keepboth"
380 id="keepboth-radio"
381 checked={
382 selectedAction ===
383 "keepboth"
384 }
385 onChange={
386 handleRadioChange
387 }
388 />
389 {__(
390 "Keep Both",
391 "betterdocs",
392 )}
393 </label>
394 <label htmlFor="replace-radio">
395 <input
396 type="radio"
397 name="action"
398 value="replace"
399 id="replace-radio"
400 checked={
401 selectedAction ===
402 "replace"
403 }
404 onChange={
405 handleRadioChange
406 }
407 />
408 {__(
409 "Replace",
410 "betterdocs",
411 )}
412 </label>
413 </div>
414 </div>
415 </>
416 )}
417 </>
418 )
419 )}
420 </span>
421 </div>
422 </div>
423 {selectedFile && (
424 <div className="wprf-submit wprf-importer-submit">
425 <button
426 {...validProps}
427 type="submit"
428 disabled={isLoading}
429 onClick={handleUpload}
430 className="wprf-submit-button"
431 >
432 {isObject(props?.text) && props?.ajax
433 ? isLoading
434 ? props?.text?.loading
435 : props.value
436 ? props?.text?.saved
437 : props?.text?.normal
438 : props?.text}
439 </button>
440 </div>
441 )}
442 </>
443 );
444 };
445
446 export default ImporterUpload;
447