@nkhang1902/strapi-plugin-export-import-clsx 1.0.4 → 1.1.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/strapi-admin.js CHANGED
@@ -1,8 +1,7 @@
1
- import React from 'react';
2
- import pluginPkg from './package.json';
3
- import pluginId from './admin/src/pluginId';
4
- import Initializer from './admin/src/components/Initializer';
5
- import ExportImportButtons from './admin/src/components/ExportImportButtons';
1
+ import pluginPkg from "./package.json";
2
+ import pluginId from "./admin/src/pluginId";
3
+ import Initializer from "./admin/src/components/Initializer";
4
+ import ExportImportButtons from "./admin/src/components/ExportImportButtons";
6
5
 
7
6
  const name = pluginPkg.strapi.name;
8
7
 
@@ -19,70 +18,12 @@ export default {
19
18
  },
20
19
 
21
20
  bootstrap(app) {
22
- // Try different injection methods for Strapi v5
23
- try {
24
- // Method 1: Direct injection
25
- if (app.injectContentManagerComponent) {
26
- app.injectContentManagerComponent('listView', 'actions', {
27
- name: 'export-import-buttons',
28
- Component: ExportImportButtons,
29
- });
30
- }
31
- // Method 2: Plugin-based injection
32
- else if (app.getPlugin) {
33
- const contentManager = app.getPlugin('content-manager');
34
- if (contentManager && contentManager.injectComponent) {
35
- contentManager.injectComponent('listView', 'actions', {
36
- name: 'export-import-buttons',
37
- Component: ExportImportButtons,
38
- });
39
- }
40
- }
41
- // Method 3: Global injection
42
- else if (app.addComponent) {
43
- app.addComponent('content-manager.listView.actions', ExportImportButtons);
44
- }
45
- } catch (error) {
46
- console.warn('Failed to inject export-import buttons:', error);
47
-
48
- // Fallback: Add as menu item if injection fails
49
- app.addMenuLink({
50
- to: `/plugins/${pluginId}`,
51
- icon: () => React.createElement('span', null, '📊'),
52
- intlLabel: {
53
- id: `${pluginId}.plugin.name`,
54
- defaultMessage: 'Export Import',
55
- },
56
- Component: async () => {
57
- const component = await import('./admin/src/pages/App');
58
- return component;
59
- },
60
- permissions: [],
21
+ const contentManager = app.getPlugin("content-manager");
22
+ if (contentManager && contentManager.injectComponent) {
23
+ contentManager.injectComponent("listView", "actions", {
24
+ name: "export-import-buttons",
25
+ Component: ExportImportButtons,
61
26
  });
62
27
  }
63
28
  },
64
-
65
- async registerTrads(app) {
66
- const { locales } = app;
67
-
68
- const importedTrads = await Promise.all(
69
- locales.map((locale) => {
70
- return import(`./admin/src/translations/${locale}.json`)
71
- .then(({ default: data }) => {
72
- return {
73
- data: data,
74
- locale,
75
- };
76
- })
77
- .catch(() => {
78
- return {
79
- data: {},
80
- locale,
81
- };
82
- });
83
- })
84
- );
85
-
86
- return Promise.resolve(importedTrads);
87
- },
88
- };
29
+ };
@@ -1,70 +0,0 @@
1
- import React from 'react';
2
- import ExportButton from '../ExportButton';
3
- import ImportButton from '../ImportButton';
4
-
5
- const BulkActions = ({ layout }) => {
6
- const handleExportAll = async () => {
7
- try {
8
- const contentType = layout.uid;
9
-
10
- // Get current filters from URL if any
11
- const urlParams = new URLSearchParams(window.location.search);
12
- const filters = {};
13
-
14
- // Build filters from URL params
15
- for (const [key, value] of urlParams.entries()) {
16
- if (key.startsWith('filters[')) {
17
- filters[key] = value;
18
- }
19
- }
20
-
21
- const queryString = new URLSearchParams({
22
- format: 'excel',
23
- contentType: contentType,
24
- ...filters
25
- }).toString();
26
-
27
- const response = await fetch(`/export-import-clsx/export?${queryString}`);
28
-
29
- if (response.ok) {
30
- const blob = await response.blob();
31
- const url = window.URL.createObjectURL(blob);
32
- const a = document.createElement('a');
33
- a.href = url;
34
- a.download = `${contentType.replace('api::', '')}-export-${new Date().toISOString().split('T')[0]}.xlsx`;
35
- document.body.appendChild(a);
36
- a.click();
37
- window.URL.revokeObjectURL(url);
38
- document.body.removeChild(a);
39
- } else {
40
- throw new Error('Export failed');
41
- }
42
- } catch (error) {
43
- alert('Export failed: ' + error.message);
44
- }
45
- };
46
-
47
- return React.createElement('div', {
48
- style: {
49
- display: 'flex',
50
- gap: '8px',
51
- alignItems: 'center',
52
- marginLeft: '16px'
53
- }
54
- },
55
- React.createElement('button', {
56
- onClick: handleExportAll,
57
- style: {
58
- padding: '8px 16px',
59
- backgroundColor: '#4945ff',
60
- color: 'white',
61
- border: 'none',
62
- borderRadius: '4px',
63
- cursor: 'pointer'
64
- }
65
- }, 'Export All'),
66
- React.createElement(ImportButton)
67
- );
68
- };
69
-
70
- export default BulkActions;
@@ -1,48 +0,0 @@
1
- import React from 'react';
2
-
3
- const ExportButton = ({ layout, modifiedData }) => {
4
- const handleExport = async () => {
5
- try {
6
- const contentType = layout.uid;
7
- const entryId = modifiedData.id;
8
-
9
- if (!entryId) {
10
- alert('Please save the entry first');
11
- return;
12
- }
13
-
14
- const response = await fetch(`/export-import-clsx/export/${contentType}/${entryId}`);
15
-
16
- if (response.ok) {
17
- const blob = await response.blob();
18
- const url = window.URL.createObjectURL(blob);
19
- const a = document.createElement('a');
20
- a.href = url;
21
- a.download = `entry-${entryId}-${new Date().toISOString().split('T')[0]}.xlsx`;
22
- document.body.appendChild(a);
23
- a.click();
24
- window.URL.revokeObjectURL(url);
25
- document.body.removeChild(a);
26
- } else {
27
- throw new Error('Export failed');
28
- }
29
- } catch (error) {
30
- alert('Export failed: ' + error.message);
31
- }
32
- };
33
-
34
- return React.createElement('button', {
35
- onClick: handleExport,
36
- style: {
37
- padding: '8px 16px',
38
- backgroundColor: '#4945ff',
39
- color: 'white',
40
- border: 'none',
41
- borderRadius: '4px',
42
- cursor: 'pointer',
43
- marginLeft: '8px'
44
- }
45
- }, 'Export Entry');
46
- };
47
-
48
- export default ExportButton;
@@ -1,279 +0,0 @@
1
- import React, { useState } from 'react';
2
-
3
- const ExportImportButtons = (props) => {
4
- const [isExporting, setIsExporting] = useState(false);
5
- const [isImporting, setIsImporting] = useState(false);
6
-
7
- // Get current content type from props or URL
8
- const getContentType = () => {
9
- if (props.layout?.uid) {
10
- return props.layout.uid;
11
- }
12
- // Fallback: extract from URL
13
- const path = window.location.pathname;
14
- const match = path.match(/\/admin\/content-manager\/collection-types\/([^\/]+)/);
15
- return match ? match[1] : null;
16
- };
17
-
18
- // Get current filters from URL
19
- const getCurrentFilters = () => {
20
- const urlParams = new URLSearchParams(window.location.search);
21
- const filters = {};
22
-
23
- for (const [key, value] of urlParams.entries()) {
24
- if (key.startsWith('filters[') || key === 'sort' || key === 'page' || key === 'pageSize' || key === 'locale' || key === '_q') {
25
- filters[key] = value;
26
- }
27
- }
28
-
29
- return filters;
30
- };
31
-
32
- // Get selected entries from props
33
- const getSelectedEntries = () => {
34
- // Try to get selected entries from various possible props
35
- if (props.selectedEntries && props.selectedEntries.length > 0) {
36
- return props.selectedEntries;
37
- }
38
- if (props.selected && props.selected.length > 0) {
39
- return props.selected;
40
- }
41
- if (props.selection && props.selection.length > 0) {
42
- return props.selection;
43
- }
44
- const selectedIds = [];
45
- let field = '';
46
- const getHeaderKey = i => {
47
- const el = document.querySelector(`thead th:nth-child(${i}) button, thead th:nth-child(${i}) span`);
48
- if (!el) return '';
49
- const parts = el.textContent.trim().split(/\s+/);
50
- return parts.pop(); // last word
51
- };
52
-
53
- try {
54
- const rows = document.querySelectorAll('tbody tr');
55
- const allowedFields = [
56
- 'id', 'name', 'title', 'tickerCode',
57
- 'fullName', 'email', 'businessEmail',
58
- 'telephone', 'mobile'
59
- ];
60
-
61
- let foundIndex = null;
62
-
63
- for (let i = 1; i <= 10; i++) {
64
- const headerBtn = getHeaderKey(i);
65
- if (headerBtn !== '' && allowedFields.includes(headerBtn)) {
66
- field = headerBtn;
67
- foundIndex = i;
68
- break;
69
- }
70
- }
71
-
72
- if (!foundIndex) {
73
- console.warn('No valid header column found');
74
- return [[], ''];
75
- }
76
-
77
- // gather values for selected rows
78
- rows.forEach(row => {
79
- const checkbox = row.querySelector('td:nth-child(1) button[role="checkbox"]');
80
- if (checkbox?.getAttribute('aria-checked') === 'true') {
81
- const cellSpan = row.querySelector(`td:nth-child(${foundIndex}) span`);
82
- const text = cellSpan?.textContent.trim();
83
- if (text) selectedIds.push(text);
84
- }
85
- });
86
-
87
- return [selectedIds, field];
88
-
89
- } catch (e) {
90
- console.error(e);
91
- return [[], ''];
92
- }
93
- };
94
-
95
- const handleExport = async () => {
96
- const contentType = getContentType();
97
- if (!contentType) {
98
- alert('Could not determine content type');
99
- return;
100
- }
101
-
102
- setIsExporting(true);
103
- try {
104
- const filters = getCurrentFilters();
105
- const [selectedEntries, selectedField] = getSelectedEntries();
106
-
107
- const queryParams = new URLSearchParams({
108
- format: 'excel',
109
- contentType: contentType,
110
- ...filters
111
- });
112
-
113
- // Add selected IDs if any
114
- if (selectedEntries.length > 0) {
115
- queryParams.set('selectedIds', JSON.stringify(selectedEntries));
116
- queryParams.set('selectedField', selectedField);
117
- }
118
-
119
- const response = await fetch(`/export-import-clsx/export?${queryParams}`);
120
-
121
- if (response.ok) {
122
- const blob = await response.blob();
123
- const url = window.URL.createObjectURL(blob);
124
- const a = document.createElement('a');
125
- a.href = url;
126
-
127
- // Set filename based on selection
128
- const filename = selectedEntries.length > 0
129
- ? `${contentType.replace('api::', '')}-selected-${selectedEntries.length}-${new Date().toISOString().split('T')[0]}.xlsx`
130
- : `${contentType.replace('api::', '')}-export-${new Date().toISOString().split('T')[0]}.xlsx`;
131
-
132
- a.download = filename;
133
- document.body.appendChild(a);
134
- a.click();
135
- window.URL.revokeObjectURL(url);
136
- document.body.removeChild(a);
137
- } else {
138
- throw new Error('Export failed');
139
- }
140
- } catch (error) {
141
- alert('Export failed: ' + error.message);
142
- } finally {
143
- setIsExporting(false);
144
- }
145
- };
146
-
147
- const handleImport = async (event) => {
148
- const file = event.target.files[0];
149
- if (!file) return;
150
-
151
- const contentType = getContentType();
152
- if (!contentType) {
153
- alert('Could not determine content type');
154
- return;
155
- }
156
-
157
- setIsImporting(true);
158
- const formData = new FormData();
159
- formData.append('file', file);
160
- formData.append('contentType', contentType);
161
-
162
- try {
163
- const response = await fetch('/export-import-clsx/import', {
164
- method: 'POST',
165
- body: formData,
166
- });
167
-
168
- if (response.ok) {
169
- const result = await response.json();
170
-
171
- // Create simple, human message
172
- const created = result.summary?.created || result.result.created;
173
- const updated = result.summary?.updated || result.result.updated;
174
- const errors = result.result.errors?.length || 0;
175
-
176
- const total = created + updated;
177
- let message = 'Import completed!\n\n';
178
-
179
- if (total > 0) {
180
- message += `Processed ${total} ${total === 1 ? 'entry' : 'entries'}\n`;
181
- if (created > 0) {
182
- message += `• Created: ${created}\n`;
183
- }
184
- if (updated > 0) {
185
- message += `• Updated: ${updated}\n`;
186
- }
187
- } else if (errors === 0) {
188
- message += 'No changes were made\n';
189
- }
190
-
191
- if (errors > 0) {
192
- message += `\nFound ${errors} ${errors === 1 ? 'error' : 'errors'}:\n`;
193
- result.result.errors.slice(0, 2).forEach((error, index) => {
194
- message += `• ${error}\n`;
195
- });
196
- if (errors > 2) {
197
- message += `• ... and ${errors - 2} more\n`;
198
- }
199
- }
200
-
201
- alert(message);
202
-
203
- // Reload the page to show new data
204
- window.location.reload();
205
- } else {
206
- const error = await response.json();
207
- throw new Error(error.error || 'Import failed');
208
- }
209
- } catch (error) {
210
- alert('Import failed: ' + error.message);
211
- } finally {
212
- setIsImporting(false);
213
- event.target.value = '';
214
- }
215
- };
216
-
217
- const [selectedEntries, selectedField] = getSelectedEntries();
218
- const exportButtonText = isExporting
219
- ? 'Exporting...'
220
- : selectedEntries.length > 0
221
- ? `Export (${selectedEntries.length})`
222
- : 'Export';
223
-
224
- return React.createElement('div', {
225
- style: {
226
- display: 'flex',
227
- gap: '8px',
228
- alignItems: 'center',
229
- marginRight: '16px',
230
- order: -1 // This will place it before other elements
231
- }
232
- },
233
- // Export Button
234
- React.createElement('button', {
235
- onClick: handleExport,
236
- disabled: isExporting,
237
- style: {
238
- padding: '8px 16px',
239
- backgroundColor: isExporting ? '#dcdce4' : '#4945ff',
240
- color: 'white',
241
- border: 'none',
242
- borderRadius: '4px',
243
- fontSize: '14px',
244
- fontWeight: '500',
245
- cursor: isExporting ? 'not-allowed' : 'pointer',
246
- transition: 'background-color 0.2s'
247
- }
248
- }, exportButtonText),
249
-
250
- // Import Button - same color as Export
251
- React.createElement('div', { style: { position: 'relative' } },
252
- React.createElement('input', {
253
- type: 'file',
254
- accept: '.xlsx,.xls,.json',
255
- onChange: handleImport,
256
- disabled: isImporting,
257
- style: { display: 'none' },
258
- id: 'import-file-input'
259
- }),
260
- React.createElement('label', {
261
- htmlFor: 'import-file-input',
262
- style: {
263
- display: 'inline-block',
264
- padding: '8px 16px',
265
- backgroundColor: isImporting ? '#dcdce4' : '#4945ff', // Same color as Export
266
- color: 'white',
267
- border: 'none',
268
- borderRadius: '4px',
269
- fontSize: '14px',
270
- fontWeight: '500',
271
- cursor: isImporting ? 'not-allowed' : 'pointer',
272
- transition: 'background-color 0.2s'
273
- }
274
- }, isImporting ? 'Importing...' : 'Import')
275
- )
276
- );
277
- };
278
-
279
- export default ExportImportButtons;
@@ -1,54 +0,0 @@
1
- import React from 'react';
2
-
3
- const ImportButton = () => {
4
- const handleImport = async (event) => {
5
- const file = event.target.files[0];
6
- if (!file) return;
7
-
8
- const formData = new FormData();
9
- formData.append('file', file);
10
-
11
- try {
12
- const response = await fetch('/export-import-clsx/import', {
13
- method: 'POST',
14
- body: formData,
15
- });
16
-
17
- if (response.ok) {
18
- const result = await response.json();
19
- alert(`Import completed! Imported: ${result.result.imported}, Errors: ${result.result.errors.length}`);
20
- window.location.reload();
21
- } else {
22
- throw new Error('Import failed');
23
- }
24
- } catch (error) {
25
- alert('Import failed: ' + error.message);
26
- } finally {
27
- event.target.value = '';
28
- }
29
- };
30
-
31
- return React.createElement('div', { style: { display: 'inline-block', marginLeft: '8px' } },
32
- React.createElement('input', {
33
- type: 'file',
34
- accept: '.xlsx,.xls,.json',
35
- onChange: handleImport,
36
- style: { display: 'none' },
37
- id: 'import-file-input'
38
- }),
39
- React.createElement('label', {
40
- htmlFor: 'import-file-input',
41
- style: {
42
- display: 'inline-block',
43
- padding: '8px 16px',
44
- backgroundColor: '#328048',
45
- color: 'white',
46
- border: 'none',
47
- borderRadius: '4px',
48
- cursor: 'pointer'
49
- }
50
- }, 'Import Data')
51
- );
52
- };
53
-
54
- export default ImportButton;
@@ -1,14 +0,0 @@
1
- {
2
- "plugin.name": "Export Import CLSX",
3
- "plugin.description": "Export and import data with enhanced functionality",
4
- "export.title": "Export Data",
5
- "export.description": "Export all your content types data to a JSON file",
6
- "export.button": "Export Data",
7
- "export.success": "Export completed successfully",
8
- "export.error": "Export failed",
9
- "import.title": "Import Data",
10
- "import.description": "Import data from a JSON file to your Strapi instance",
11
- "import.button": "Import Data",
12
- "import.success": "Import completed successfully",
13
- "import.error": "Import failed"
14
- }