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
extendify / src / Shared / api / wp.js

wp.js in Extendify 3.2.2, at src/Shared/api/wp.js

204 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { AI_HOST } from '@constants';
2 import { renderAiLabelPill, stampAiLabel } from '@shared/lib/ai-label';
3 import apiFetch from '@wordpress/api-fetch';
4 import { sprintf } from '@wordpress/i18n';
5 import { addQueryArgs } from '@wordpress/url';
6
7 export const getPlugin = async (slug) => {
8 const response = await apiFetch({
9 path: addQueryArgs('/wp/v2/plugins', { search: slug }),
10 });
11
12 const plugin = response?.[0];
13
14 if (!plugin) throw new Error('Plugin not found');
15
16 return plugin;
17 };
18
19 export const getAllPlugins = async () => {
20 const response = await apiFetch({
21 path: '/wp/v2/plugins',
22 });
23
24 if (!response) {
25 throw new Error('Failed to fetch installed plugins');
26 }
27
28 return response;
29 };
30
31 export const enableAutoUpdate = async (plugin) => {
32 if (!plugin) return;
33 try {
34 await apiFetch({
35 path: '/extendify/v1/shared/enable-auto-update',
36 method: 'POST',
37 data: { plugin },
38 });
39 } catch (_e) {
40 // Best-effort: the install already succeeded.
41 }
42 };
43
44 export const installPlugin = async (slug) => {
45 const plugin = await apiFetch({
46 path: '/wp/v2/plugins',
47 method: 'POST',
48 data: {
49 slug,
50 },
51 });
52
53 await enableAutoUpdate(plugin?.plugin);
54
55 return plugin;
56 };
57
58 export const activatePlugin = async (slug) => {
59 const plugin = await getPlugin(slug);
60
61 return await apiFetch({
62 path: `/wp/v2/plugins/${plugin.plugin}`,
63 method: 'POST',
64 data: {
65 status: 'active',
66 },
67 });
68 };
69
70 export const loadImage = (img) => {
71 return new Promise((resolve, reject) => {
72 img.onload = () => resolve(img);
73 img.onerror = (e) => reject(e);
74 });
75 };
76
77 const shouldDisclose = (metadata) =>
78 Boolean(metadata.aiGenerated) && Boolean(metadata.disclose);
79
80 const altText = (metadata) => {
81 if (!shouldDisclose(metadata)) return metadata.alt ?? '';
82 const pattern =
83 window.extSharedData?.aiImageAltPattern ?? '%s (AI-generated)';
84 return sprintf(pattern, metadata.alt ?? '').trim();
85 };
86
87 export const importImage = async (imageUrl, metadata = {}) => {
88 const image = new Image();
89 image.src = imageUrl;
90 image.crossOrigin = 'anonymous';
91 await loadImage(image);
92
93 const canvas = document.createElement('canvas');
94 canvas.width = image.width;
95 canvas.height = image.height;
96
97 const ctx = canvas.getContext('2d');
98 if (!ctx) return;
99 ctx.drawImage(image, 0, 0);
100 if (shouldDisclose(metadata)) {
101 stampAiLabel(ctx, canvas.width, canvas.height);
102 }
103
104 const blob = await new Promise((resolve) => {
105 canvas.toBlob((blob) => {
106 blob && resolve(blob);
107 }, 'image/jpeg');
108 });
109
110 const formData = new FormData();
111 formData.append('file', new File([blob], metadata.filename));
112 formData.append('alt_text', altText(metadata));
113 formData.append('caption', metadata.caption ?? '');
114 formData.append('status', 'publish');
115 if (metadata.aiGenerated) {
116 formData.append('meta[extendify_ai_generated]', '1');
117 }
118
119 return await apiFetch({
120 path: 'wp/v2/media',
121 method: 'POST',
122 body: formData,
123 });
124 };
125
126 export const importImageServer = async (src, metadata = {}) => {
127 const formData = new FormData();
128 formData.append('source', src);
129 // Fallback doesn't support custom file_name
130 formData.append('alt_text', altText(metadata));
131 formData.append('caption', metadata.caption ?? '');
132 if (metadata.aiGenerated) {
133 formData.append('ai_generated', '1');
134 if (shouldDisclose(metadata)) {
135 const pill = await renderAiLabelPill();
136 if (pill) {
137 formData.append(
138 'disclosure_label',
139 new File([pill], 'disclosure-label.png', { type: 'image/png' }),
140 );
141 }
142 }
143 }
144
145 return await apiFetch({
146 path: '/extendify/v1/draft/upload-image',
147 method: 'POST',
148 body: formData,
149 });
150 };
151
152 export const downloadImage = async (
153 id,
154 src,
155 source,
156 unsplashId,
157 metadata = { alt: '', caption: '' },
158 ) => {
159 let image;
160 const aiGenerated = source === 'ai-generated';
161 if (unsplashId) {
162 await downloadPing(id, source, { unsplashId });
163 } else if (aiGenerated && id) {
164 await downloadPing(id, source, { disclose: metadata.disclose });
165 }
166
167 let filename = metadata.filename ?? 'image.jpg';
168 if (aiGenerated && !filename.includes('ai-generated')) {
169 const parts = filename.split('.');
170 if (parts.length > 1) {
171 const ext = parts.pop();
172 filename = `${parts.join('.')}-ai-generated.${ext}`;
173 } else {
174 filename = `${filename}-ai-generated`;
175 }
176 }
177
178 try {
179 image = await importImage(src, {
180 alt: metadata.alt,
181 filename,
182 caption: metadata.caption,
183 aiGenerated,
184 disclose: metadata.disclose,
185 });
186 } catch (_e) {
187 image = await importImageServer(src, {
188 alt: metadata.alt,
189 caption: metadata.caption,
190 aiGenerated,
191 disclose: metadata.disclose,
192 });
193 }
194
195 return image;
196 };
197
198 export const downloadPing = (id, source, details = {}) =>
199 fetch(`${AI_HOST}/api/draft/image/download`, {
200 method: 'POST',
201 headers: { 'Content-Type': 'application/json' },
202 body: JSON.stringify({ id, source, ...details }),
203 });
204