strapi-plugin-copy-any-component 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALLATION.md +179 -0
- package/QUICK_START.md +88 -0
- package/README.md +165 -0
- package/TESTING.md +193 -0
- package/USAGE.md +121 -0
- package/admin/src/components/ComponentCopyField.jsx +3 -0
- package/admin/src/components/Initializer.jsx +22 -0
- package/admin/src/components/PluginIcon.jsx +22 -0
- package/admin/src/index.js +27 -0
- package/admin/src/pages/HomePage.jsx +995 -0
- package/admin/src/pluginId.js +2 -0
- package/package.json +62 -0
- package/server/src/controllers/controller.ts +22 -0
- package/server/src/controllers/index.ts +5 -0
- package/server/src/index.ts +13 -0
- package/server/src/routes/admin.ts +11 -0
- package/server/src/routes/content-api.ts +19 -0
- package/server/src/routes/index.ts +13 -0
- package/server/src/services/component-copy.js +439 -0
- package/strapi-admin.js +2 -0
- package/strapi-server.js +685 -0
package/strapi-server.js
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const contentApiRoutes = [
|
|
4
|
+
{
|
|
5
|
+
method: "GET",
|
|
6
|
+
path: "/hello",
|
|
7
|
+
handler: "controller.hello",
|
|
8
|
+
config: {
|
|
9
|
+
policies: [],
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
method: "GET",
|
|
14
|
+
path: "/greet/:name",
|
|
15
|
+
handler: "controller.greet",
|
|
16
|
+
config: {
|
|
17
|
+
policies: [],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
method: "GET",
|
|
22
|
+
path: "/pages/:pageId/sections",
|
|
23
|
+
handler: "controller.getPageSections",
|
|
24
|
+
config: {
|
|
25
|
+
policies: [],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
method: "POST",
|
|
30
|
+
path: "/pages/:sourcePageId/copy-to/:targetPageId",
|
|
31
|
+
handler: "controller.copySections",
|
|
32
|
+
config: {
|
|
33
|
+
policies: [],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
method: "POST",
|
|
38
|
+
path: "/pages/:sourcePageId/move-to/:targetPageId",
|
|
39
|
+
handler: "controller.moveSections",
|
|
40
|
+
config: {
|
|
41
|
+
policies: [],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const componentCopyService = require("./server/src/services/component-copy");
|
|
47
|
+
|
|
48
|
+
// Helper: Content type'dan display field'ı bul
|
|
49
|
+
const getDisplayField = (page) => {
|
|
50
|
+
const possibleFields = ['title', 'name', 'heading', 'label', 'displayName', 'slug'];
|
|
51
|
+
for (const field of possibleFields) {
|
|
52
|
+
if (page && page[field]) return page[field];
|
|
53
|
+
}
|
|
54
|
+
return page ? `ID: ${page.id}` : 'Unknown';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const controller = ({ strapi }) => ({
|
|
58
|
+
async hello(ctx) {
|
|
59
|
+
ctx.body = {
|
|
60
|
+
message: "Hello from my plugin! 👋",
|
|
61
|
+
timestamp: new Date().toISOString(),
|
|
62
|
+
plugin: "copy-any-component",
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async greet(ctx) {
|
|
67
|
+
const { name } = ctx.params;
|
|
68
|
+
ctx.body = {
|
|
69
|
+
message: `Merhaba, ${name}! 🎉`,
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// 🔍 Tüm content type'ları ve dynamic zone'larını listele
|
|
75
|
+
async getContentTypes(ctx) {
|
|
76
|
+
try {
|
|
77
|
+
const contentTypes = [];
|
|
78
|
+
|
|
79
|
+
// Strapi content type registry'sini tara
|
|
80
|
+
for (const [uid, contentType] of Object.entries(strapi.contentTypes)) {
|
|
81
|
+
// Sadece api:: ile başlayan content type'ları al (custom content types)
|
|
82
|
+
if (!uid.startsWith('api::')) continue;
|
|
83
|
+
|
|
84
|
+
const dynamicZones = [];
|
|
85
|
+
const attributes = contentType.attributes || {};
|
|
86
|
+
|
|
87
|
+
// Dynamic zone attribute'larını bul
|
|
88
|
+
for (const [attrName, attrConfig] of Object.entries(attributes)) {
|
|
89
|
+
if (attrConfig.type === 'dynamiczone') {
|
|
90
|
+
dynamicZones.push({
|
|
91
|
+
name: attrName,
|
|
92
|
+
components: attrConfig.components || [],
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Sadece dynamic zone içeren content type'ları ekle
|
|
98
|
+
if (dynamicZones.length > 0) {
|
|
99
|
+
contentTypes.push({
|
|
100
|
+
uid,
|
|
101
|
+
kind: contentType.kind, // 'collectionType' veya 'singleType'
|
|
102
|
+
displayName: contentType.info?.displayName || uid,
|
|
103
|
+
singularName: contentType.info?.singularName || uid,
|
|
104
|
+
pluralName: contentType.info?.pluralName || uid,
|
|
105
|
+
dynamicZones,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Önce Strapi Store'dan kaydedilmiş ayarları oku
|
|
111
|
+
const pluginStore = strapi.store({
|
|
112
|
+
environment: '',
|
|
113
|
+
type: 'plugin',
|
|
114
|
+
name: 'copy-any-component',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const savedSettings = await pluginStore.get({ key: 'settings' });
|
|
118
|
+
|
|
119
|
+
// Config dosyasından varsayılanları al
|
|
120
|
+
const pluginConfig = strapi.config.get('plugin::copy-any-component') || {};
|
|
121
|
+
|
|
122
|
+
// Öncelik: 1. Store'dan kaydedilmiş, 2. Config dosyasından, 3. Varsayılan
|
|
123
|
+
const currentConfig = {
|
|
124
|
+
contentType: savedSettings?.contentType || pluginConfig.contentType || 'api::page.page',
|
|
125
|
+
dynamicZoneField: savedSettings?.dynamicZoneField || pluginConfig.dynamicZoneField || 'sections',
|
|
126
|
+
savedInStore: !!savedSettings, // Kullanıcıya bilgi vermek için
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
ctx.body = {
|
|
130
|
+
data: {
|
|
131
|
+
contentTypes,
|
|
132
|
+
currentConfig,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
strapi.log.error("Error getting content types:", error);
|
|
137
|
+
ctx.status = 500;
|
|
138
|
+
ctx.body = { error: error.message };
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
// 🔧 Yapılandırmayı güncelle (kalıcı olarak Strapi Store'a kaydet)
|
|
143
|
+
async updateConfig(ctx) {
|
|
144
|
+
try {
|
|
145
|
+
const { contentType, dynamicZoneField } = ctx.request.body;
|
|
146
|
+
|
|
147
|
+
if (!contentType || !dynamicZoneField) {
|
|
148
|
+
ctx.status = 400;
|
|
149
|
+
ctx.body = { error: 'contentType and dynamicZoneField are required' };
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Content type'ın var olduğunu doğrula
|
|
154
|
+
if (!strapi.contentTypes[contentType]) {
|
|
155
|
+
ctx.status = 400;
|
|
156
|
+
ctx.body = { error: `Content type "${contentType}" not found` };
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Dynamic zone field'ın var olduğunu doğrula
|
|
161
|
+
const attributes = strapi.contentTypes[contentType].attributes || {};
|
|
162
|
+
if (!attributes[dynamicZoneField] || attributes[dynamicZoneField].type !== 'dynamiczone') {
|
|
163
|
+
ctx.status = 400;
|
|
164
|
+
ctx.body = { error: `Dynamic zone field "${dynamicZoneField}" not found in ${contentType}` };
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Strapi Store API kullanarak kalıcı kaydet (veritabanına)
|
|
169
|
+
const pluginStore = strapi.store({
|
|
170
|
+
environment: '',
|
|
171
|
+
type: 'plugin',
|
|
172
|
+
name: 'copy-any-component',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await pluginStore.set({
|
|
176
|
+
key: 'settings',
|
|
177
|
+
value: {
|
|
178
|
+
contentType,
|
|
179
|
+
dynamicZoneField,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Runtime config'i de güncelle
|
|
184
|
+
strapi.config.set('plugin::copy-any-component.contentType', contentType);
|
|
185
|
+
strapi.config.set('plugin::copy-any-component.dynamicZoneField', dynamicZoneField);
|
|
186
|
+
|
|
187
|
+
strapi.log.info(`[CopyAnyComponent] Config saved: ${contentType} / ${dynamicZoneField}`);
|
|
188
|
+
|
|
189
|
+
ctx.body = {
|
|
190
|
+
data: {
|
|
191
|
+
message: 'Yapılandırma başarıyla kaydedildi!',
|
|
192
|
+
contentType,
|
|
193
|
+
dynamicZoneField,
|
|
194
|
+
note: 'Bu ayar kalıcı olarak kaydedildi. Strapi yeniden başlatıldığında da geçerli olacak.',
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
strapi.log.error("Error updating config:", error);
|
|
199
|
+
ctx.status = 500;
|
|
200
|
+
ctx.body = { error: error.message };
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
async getPages(ctx) {
|
|
205
|
+
try {
|
|
206
|
+
const pluginConfig = strapi.config.get('plugin::copy-any-component') || {};
|
|
207
|
+
const contentType = pluginConfig.contentType || 'api::page.page';
|
|
208
|
+
const dynamicZoneField = pluginConfig.dynamicZoneField || 'sections';
|
|
209
|
+
|
|
210
|
+
const pages = await strapi.entityService.findMany(contentType, {
|
|
211
|
+
populate: [dynamicZoneField],
|
|
212
|
+
});
|
|
213
|
+
const formattedPages = pages.map((page) => ({
|
|
214
|
+
...page,
|
|
215
|
+
documentId: page.documentId || page.id,
|
|
216
|
+
}));
|
|
217
|
+
ctx.body = { data: formattedPages };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
ctx.status = 500;
|
|
220
|
+
ctx.body = { error: error.message };
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
async getPageSections(ctx) {
|
|
225
|
+
let { pageId } = ctx.params;
|
|
226
|
+
pageId = decodeURIComponent(pageId);
|
|
227
|
+
|
|
228
|
+
const pluginConfig = strapi.config.get('plugin::copy-any-component') || {};
|
|
229
|
+
const contentType = pluginConfig.contentType || 'api::page.page';
|
|
230
|
+
const dynamicZoneField = pluginConfig.dynamicZoneField || 'sections';
|
|
231
|
+
|
|
232
|
+
let page;
|
|
233
|
+
const numericId = parseInt(pageId);
|
|
234
|
+
if (!isNaN(numericId)) {
|
|
235
|
+
try {
|
|
236
|
+
page = await strapi.entityService.findOne(contentType, numericId, {
|
|
237
|
+
populate: [dynamicZoneField],
|
|
238
|
+
});
|
|
239
|
+
} catch (error) {
|
|
240
|
+
// Try with documentId
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!page) {
|
|
245
|
+
try {
|
|
246
|
+
const pages = await strapi.entityService.findMany(contentType, {
|
|
247
|
+
filters: { documentId: pageId },
|
|
248
|
+
populate: [dynamicZoneField],
|
|
249
|
+
});
|
|
250
|
+
page = pages[0];
|
|
251
|
+
} catch (err) {
|
|
252
|
+
ctx.status = 404;
|
|
253
|
+
ctx.body = { error: "Page not found: " + pageId, data: null };
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!page) {
|
|
259
|
+
ctx.status = 404;
|
|
260
|
+
ctx.body = { error: "Page not found: " + pageId, data: null };
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
ctx.body = {
|
|
265
|
+
error: null,
|
|
266
|
+
data: {
|
|
267
|
+
pageId: page.id,
|
|
268
|
+
documentId: page.documentId,
|
|
269
|
+
pageTitle: getDisplayField(page),
|
|
270
|
+
sections: page[dynamicZoneField] || [],
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
async copySections(ctx) {
|
|
276
|
+
let { sourcePageId, targetPageId } = ctx.params;
|
|
277
|
+
const { sectionIndices, insertIndex } = ctx.request.body || {};
|
|
278
|
+
|
|
279
|
+
strapi.log.info(`[CopySections] Request: sourcePageId=${sourcePageId}, targetPageId=${targetPageId}, sectionIndices=${JSON.stringify(sectionIndices)}, insertIndex=${insertIndex}`);
|
|
280
|
+
|
|
281
|
+
// Input validation
|
|
282
|
+
if (!sourcePageId || !targetPageId) {
|
|
283
|
+
ctx.status = 400;
|
|
284
|
+
ctx.body = { error: "Source and target page IDs are required", data: null };
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Validate sectionIndices if provided
|
|
289
|
+
if (sectionIndices !== undefined && sectionIndices !== null) {
|
|
290
|
+
if (!Array.isArray(sectionIndices)) {
|
|
291
|
+
ctx.status = 400;
|
|
292
|
+
ctx.body = { error: `sectionIndices must be an array, got: ${typeof sectionIndices}`, data: null };
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const invalidIdx = sectionIndices.find(idx => typeof idx !== 'number' || idx < 0 || !Number.isInteger(idx));
|
|
296
|
+
if (invalidIdx !== undefined) {
|
|
297
|
+
ctx.status = 400;
|
|
298
|
+
ctx.body = { error: `sectionIndices contains invalid value: ${invalidIdx} (type: ${typeof invalidIdx})`, data: null };
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
sourcePageId = decodeURIComponent(sourcePageId);
|
|
304
|
+
targetPageId = decodeURIComponent(targetPageId);
|
|
305
|
+
|
|
306
|
+
const pluginConfig = strapi.config.get('plugin::copy-any-component') || {};
|
|
307
|
+
const contentType = pluginConfig.contentType || 'api::page.page';
|
|
308
|
+
const dynamicZoneField = pluginConfig.dynamicZoneField || 'sections';
|
|
309
|
+
|
|
310
|
+
const findPage = async (id) => {
|
|
311
|
+
const numericId = parseInt(id);
|
|
312
|
+
if (!isNaN(numericId)) {
|
|
313
|
+
try {
|
|
314
|
+
return await strapi.entityService.findOne(contentType, numericId, {
|
|
315
|
+
populate: [dynamicZoneField],
|
|
316
|
+
});
|
|
317
|
+
} catch (error) {
|
|
318
|
+
// Try with documentId
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const pages = await strapi.entityService.findMany(contentType, {
|
|
324
|
+
filters: { documentId: id },
|
|
325
|
+
populate: [dynamicZoneField],
|
|
326
|
+
});
|
|
327
|
+
return pages[0];
|
|
328
|
+
} catch (err) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const sourcePage = await findPage(sourcePageId);
|
|
334
|
+
const targetPage = await findPage(targetPageId);
|
|
335
|
+
|
|
336
|
+
if (!sourcePage) {
|
|
337
|
+
ctx.status = 404;
|
|
338
|
+
ctx.body = { error: "Source page not found: " + sourcePageId, data: null };
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!targetPage) {
|
|
343
|
+
ctx.status = 404;
|
|
344
|
+
ctx.body = { error: "Target page not found: " + targetPageId, data: null };
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const service = strapi.plugin("copy-any-component").service("component-copy");
|
|
350
|
+
|
|
351
|
+
if (!service) {
|
|
352
|
+
ctx.status = 500;
|
|
353
|
+
ctx.body = { error: "Service not found", data: null };
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const result = await service.copySectionsToPage(
|
|
358
|
+
sourcePage.id,
|
|
359
|
+
targetPage.id,
|
|
360
|
+
sectionIndices,
|
|
361
|
+
insertIndex
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
strapi.log.info(`[CopySections] Service result: ${JSON.stringify(result?.error || 'success')}`);
|
|
365
|
+
|
|
366
|
+
if (result && result.error) {
|
|
367
|
+
strapi.log.error(`[CopySections] Service error: ${result.error}`);
|
|
368
|
+
ctx.status = 400;
|
|
369
|
+
ctx.body = result;
|
|
370
|
+
} else if (result) {
|
|
371
|
+
ctx.body = result;
|
|
372
|
+
} else {
|
|
373
|
+
ctx.status = 500;
|
|
374
|
+
ctx.body = { error: "Service returned null", data: null };
|
|
375
|
+
}
|
|
376
|
+
} catch (error) {
|
|
377
|
+
ctx.status = 500;
|
|
378
|
+
ctx.body = { error: error.message || "An error occurred", data: null };
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
async moveSections(ctx) {
|
|
383
|
+
const { sourcePageId, targetPageId } = ctx.params;
|
|
384
|
+
const { sectionIndices } = ctx.request.body || {};
|
|
385
|
+
|
|
386
|
+
const service = strapi.plugin("my-simple-plugin").service("component-copy");
|
|
387
|
+
const result = await service.moveSectionsToPage(
|
|
388
|
+
parseInt(sourcePageId),
|
|
389
|
+
parseInt(targetPageId),
|
|
390
|
+
sectionIndices
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (result.error) {
|
|
394
|
+
ctx.status = 400;
|
|
395
|
+
ctx.body = result;
|
|
396
|
+
} else {
|
|
397
|
+
ctx.body = result;
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
async updatePageSections(ctx) {
|
|
402
|
+
let { pageId } = ctx.params;
|
|
403
|
+
const { sections } = ctx.request.body || {};
|
|
404
|
+
|
|
405
|
+
// Input validation
|
|
406
|
+
if (!pageId) {
|
|
407
|
+
ctx.status = 400;
|
|
408
|
+
ctx.body = { error: "Page ID is required", data: null };
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!sections || !Array.isArray(sections)) {
|
|
413
|
+
ctx.status = 400;
|
|
414
|
+
ctx.body = { error: "Sections array is required", data: null };
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Validate sections structure
|
|
419
|
+
for (let i = 0; i < sections.length; i++) {
|
|
420
|
+
const section = sections[i];
|
|
421
|
+
if (!section || typeof section !== 'object') {
|
|
422
|
+
ctx.status = 400;
|
|
423
|
+
ctx.body = { error: `Section at index ${i} must be an object`, data: null };
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (!section.__component) {
|
|
427
|
+
ctx.status = 400;
|
|
428
|
+
ctx.body = { error: `Section at index ${i} must have a __component property`, data: null };
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
pageId = decodeURIComponent(pageId);
|
|
434
|
+
|
|
435
|
+
const pluginConfig = strapi.config.get('plugin::copy-any-component') || {};
|
|
436
|
+
const contentType = pluginConfig.contentType || 'api::page.page';
|
|
437
|
+
const dynamicZoneField = pluginConfig.dynamicZoneField || 'sections';
|
|
438
|
+
|
|
439
|
+
const findPage = async (id) => {
|
|
440
|
+
const numericId = parseInt(id);
|
|
441
|
+
if (!isNaN(numericId)) {
|
|
442
|
+
try {
|
|
443
|
+
return await strapi.entityService.findOne(contentType, numericId, {
|
|
444
|
+
populate: [dynamicZoneField],
|
|
445
|
+
});
|
|
446
|
+
} catch (error) {
|
|
447
|
+
// Try with documentId
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const pages = await strapi.entityService.findMany(contentType, {
|
|
453
|
+
filters: { documentId: id },
|
|
454
|
+
populate: [dynamicZoneField],
|
|
455
|
+
});
|
|
456
|
+
return pages[0];
|
|
457
|
+
} catch (err) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const page = await findPage(pageId);
|
|
463
|
+
|
|
464
|
+
if (!page) {
|
|
465
|
+
ctx.status = 404;
|
|
466
|
+
ctx.body = { error: "Page not found: " + pageId, data: null };
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
const updatedPage = await strapi.entityService.update(contentType, page.id, {
|
|
472
|
+
data: {
|
|
473
|
+
[dynamicZoneField]: sections,
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
ctx.body = {
|
|
478
|
+
error: null,
|
|
479
|
+
data: {
|
|
480
|
+
pageId: updatedPage.id,
|
|
481
|
+
pageTitle: getDisplayField(updatedPage),
|
|
482
|
+
sectionsCount: sections.length,
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
} catch (error) {
|
|
486
|
+
ctx.status = 500;
|
|
487
|
+
ctx.body = { error: error.message || "An error occurred", data: null };
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
|
|
491
|
+
async publishPage(ctx) {
|
|
492
|
+
let { pageId } = ctx.params;
|
|
493
|
+
pageId = decodeURIComponent(pageId);
|
|
494
|
+
|
|
495
|
+
const pluginConfig = strapi.config.get('plugin::copy-any-component') || {};
|
|
496
|
+
const contentType = pluginConfig.contentType || 'api::page.page';
|
|
497
|
+
|
|
498
|
+
const findPage = async (id) => {
|
|
499
|
+
const numericId = parseInt(id);
|
|
500
|
+
if (!isNaN(numericId)) {
|
|
501
|
+
try {
|
|
502
|
+
return await strapi.entityService.findOne(contentType, numericId);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
// Try with documentId
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
const pages = await strapi.entityService.findMany(contentType, {
|
|
510
|
+
filters: { documentId: id },
|
|
511
|
+
});
|
|
512
|
+
return pages[0];
|
|
513
|
+
} catch (err) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const page = await findPage(pageId);
|
|
519
|
+
|
|
520
|
+
if (!page) {
|
|
521
|
+
ctx.status = 404;
|
|
522
|
+
ctx.body = { error: "Page not found: " + pageId, data: null };
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
// Strapi 5'te documentService kullanılıyor
|
|
528
|
+
const documentService = strapi.documents(contentType);
|
|
529
|
+
const publishedPage = await documentService.publish(page.documentId || page.id);
|
|
530
|
+
|
|
531
|
+
ctx.body = {
|
|
532
|
+
error: null,
|
|
533
|
+
data: {
|
|
534
|
+
pageId: publishedPage.id,
|
|
535
|
+
documentId: publishedPage.documentId,
|
|
536
|
+
pageTitle: publishedPage.title,
|
|
537
|
+
publishedAt: publishedPage.publishedAt,
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
} catch (error) {
|
|
541
|
+
strapi.log.error("Publish error:", error);
|
|
542
|
+
ctx.status = 500;
|
|
543
|
+
ctx.body = { error: error.message || "An error occurred while publishing", data: null };
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
module.exports = {
|
|
549
|
+
register({ strapi }) {
|
|
550
|
+
strapi.log.info("📦 Copy Any Component Plugin registered!");
|
|
551
|
+
},
|
|
552
|
+
async bootstrap({ strapi }) {
|
|
553
|
+
strapi.log.info("🚀 Copy Any Component Plugin bootstrapped!");
|
|
554
|
+
|
|
555
|
+
// Strapi Store'dan kaydedilmiş ayarları oku ve runtime config'e uygula
|
|
556
|
+
try {
|
|
557
|
+
const pluginStore = strapi.store({
|
|
558
|
+
environment: '',
|
|
559
|
+
type: 'plugin',
|
|
560
|
+
name: 'copy-any-component',
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const savedSettings = await pluginStore.get({ key: 'settings' });
|
|
564
|
+
|
|
565
|
+
if (savedSettings) {
|
|
566
|
+
strapi.config.set('plugin::copy-any-component.contentType', savedSettings.contentType);
|
|
567
|
+
strapi.config.set('plugin::copy-any-component.dynamicZoneField', savedSettings.dynamicZoneField);
|
|
568
|
+
strapi.log.info(`[CopyAnyComponent] Loaded saved config: ${savedSettings.contentType} / ${savedSettings.dynamicZoneField}`);
|
|
569
|
+
}
|
|
570
|
+
} catch (error) {
|
|
571
|
+
strapi.log.warn("[CopyAnyComponent] Could not load saved settings:", error.message);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const actions = [
|
|
575
|
+
{
|
|
576
|
+
section: "plugins",
|
|
577
|
+
displayName: "Access Component Copy pages",
|
|
578
|
+
uid: "pages.read",
|
|
579
|
+
pluginName: "copy-any-component",
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
section: "plugins",
|
|
583
|
+
displayName: "Copy components",
|
|
584
|
+
uid: "copy",
|
|
585
|
+
pluginName: "copy-any-component",
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
section: "plugins",
|
|
589
|
+
displayName: "Update page sections",
|
|
590
|
+
uid: "sections.update",
|
|
591
|
+
pluginName: "copy-any-component",
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
section: "plugins",
|
|
595
|
+
displayName: "Publish pages",
|
|
596
|
+
uid: "publish",
|
|
597
|
+
pluginName: "copy-any-component",
|
|
598
|
+
},
|
|
599
|
+
];
|
|
600
|
+
|
|
601
|
+
strapi.admin.services.permission.actionProvider.registerMany(actions);
|
|
602
|
+
},
|
|
603
|
+
controllers: {
|
|
604
|
+
controller,
|
|
605
|
+
},
|
|
606
|
+
services: {
|
|
607
|
+
"component-copy": componentCopyService,
|
|
608
|
+
},
|
|
609
|
+
routes: {
|
|
610
|
+
"content-api": {
|
|
611
|
+
type: "content-api",
|
|
612
|
+
routes: contentApiRoutes,
|
|
613
|
+
},
|
|
614
|
+
admin: {
|
|
615
|
+
type: "admin",
|
|
616
|
+
routes: [
|
|
617
|
+
{
|
|
618
|
+
method: "GET",
|
|
619
|
+
path: "/content-types",
|
|
620
|
+
handler: "controller.getContentTypes",
|
|
621
|
+
config: {
|
|
622
|
+
policies: [],
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
method: "PUT",
|
|
627
|
+
path: "/config",
|
|
628
|
+
handler: "controller.updateConfig",
|
|
629
|
+
config: {
|
|
630
|
+
policies: [],
|
|
631
|
+
},
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
method: "GET",
|
|
635
|
+
path: "/pages",
|
|
636
|
+
handler: "controller.getPages",
|
|
637
|
+
config: {
|
|
638
|
+
policies: [],
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
method: "GET",
|
|
643
|
+
path: "/pages/:pageId/sections",
|
|
644
|
+
handler: "controller.getPageSections",
|
|
645
|
+
config: {
|
|
646
|
+
policies: [],
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
method: "POST",
|
|
651
|
+
path: "/pages/:sourcePageId/copy-to/:targetPageId",
|
|
652
|
+
handler: "controller.copySections",
|
|
653
|
+
config: {
|
|
654
|
+
policies: [],
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
method: "POST",
|
|
659
|
+
path: "/pages/:sourcePageId/move-to/:targetPageId",
|
|
660
|
+
handler: "controller.moveSections",
|
|
661
|
+
config: {
|
|
662
|
+
policies: [],
|
|
663
|
+
},
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
method: "PUT",
|
|
667
|
+
path: "/pages/:pageId/sections",
|
|
668
|
+
handler: "controller.updatePageSections",
|
|
669
|
+
config: {
|
|
670
|
+
policies: [],
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
method: "POST",
|
|
675
|
+
path: "/pages/:pageId/publish",
|
|
676
|
+
handler: "controller.publishPage",
|
|
677
|
+
config: {
|
|
678
|
+
policies: [],
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
],
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
|