@retikolo/drag-drop-content-types 1.3.11 → 1.4.1

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/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "useTabs": false,
4
+ "singleQuote": true,
5
+ "semi": true,
6
+ "trailingComma": "es5"
7
+ }
package/README.md CHANGED
@@ -37,6 +37,9 @@ Go to `Settings` -> `Drag Drop Content Type` -> `Configuration`:
37
37
  #### Hints
38
38
  * Add "Default sort attribute" `rank`, "Default sort order" `ASC` and remove the `rank` attribute from the view using "Configure the view" button.
39
39
  * You can also set a `title` value that is displayed in the menu instead of the default.
40
+ * If you want a second field to be displayed in the drag and drop menu, you can add s `subtitle` in the settings. The subtitle should either be:
41
+ - A field containing a string or number or something like that.
42
+ - It can be an object (like a relation), but it must have the `title` field specified in the settings (it won't recognize automatically!).
40
43
 
41
44
  ### In your frontend
42
45
  Assuming you go with the default settings, you can make a request on the following url to get the ordered items:
@@ -47,3 +50,7 @@ http://localhost:1337/api/foo?sort=rank:asc
47
50
 
48
51
  ## 🤝 Contribute
49
52
  Feel free to fork and make pull requests to this plugin. All input is welcome - thanks for all contributions so far!
53
+
54
+
55
+ ## ⭐️ Support
56
+ I you like this project, please give it a star. Maybe this will help it getting integrated to strapi's core some day 😊.
@@ -65,7 +65,8 @@ const SortModal = () => {
65
65
  );
66
66
  let fetchedSettings = {
67
67
  rank: data.body.rank,
68
- title: data.body.title.length > 0 ? data.body.title : mainField
68
+ title: data.body.title.length > 0 ? data.body.title : mainField,
69
+ subtitle: data.body.subtitle.length > 0 ? data.body.subtitle : null
69
70
  }
70
71
  setSettings(fetchedSettings);
71
72
  } catch (e) {
@@ -121,11 +122,32 @@ const SortModal = () => {
121
122
  }
122
123
  };
123
124
 
125
+ // Get subtitle from entry if defined in settings
126
+ const getSubtitle = (entry) => {
127
+ try {
128
+ if (settings.subtitle && entry[settings.subtitle]) {
129
+ if (entry[settings.subtitle].constructor.name == "Array") {
130
+ if (entry[settings.subtitle].length > 0)
131
+ return `- ${entry[settings.subtitle][0][settings.title]}`;
132
+ } else if (typeof entry[settings.subtitle] === "object") {
133
+ return `- ${entry[settings.subtitle][settings.title]}`;
134
+ } else {
135
+ return `- ${entry[settings.subtitle]}`;
136
+ }
137
+ }
138
+ return "";
139
+ } catch (e) {
140
+ console.log("Unsupported subtitle field value.", e);
141
+ return "";
142
+ }
143
+ }
144
+
124
145
  // Update all ranks via put request.
125
146
  const updateContentType = async ({ oldIndex, newIndex }) => {
126
147
  try {
127
148
  // Increase performance by breaking loop after last element having a rank change is updated
128
149
  const sortedList = arrayMoveImmutable(data, oldIndex, newIndex);
150
+ const rankUpdates = [];
129
151
  let rankHasChanged = false;
130
152
  // Iterate over all results and append them to the list
131
153
  for (let i = 0; i < sortedList.length; i++) {
@@ -133,20 +155,23 @@ const SortModal = () => {
133
155
  if (sortedList[i].id != data[i].id) {
134
156
  const newRank =
135
157
  parseInt(pageSize * (currentPage - 1) + i) || 0;
136
- // Update rank via put request
137
- await axiosInstance.put(
138
- `/drag-drop-content-types/sort-update/${sortedList[i].id}`,
139
- {
140
- contentType: contentTypePath,
141
- rankFieldName: settings.rank,
142
- rank: newRank,
143
- }
144
- );
158
+ const update = {
159
+ id: sortedList[i].id,
160
+ rank: newRank,
161
+ };
162
+ rankUpdates.push(update);
145
163
  rankHasChanged = true;
146
164
  } else if (rankHasChanged) {
147
165
  break;
148
166
  }
149
167
  }
168
+
169
+ // Batch Update DB with new ranks
170
+ await axiosInstance.put("/drag-drop-content-types/batch-update", {
171
+ contentType: contentTypePath,
172
+ updates: rankUpdates,
173
+ });
174
+
150
175
  // distinguish last page from full/first page
151
176
  let sortedListViewEntries =
152
177
  currentPage == 1
@@ -199,6 +224,7 @@ const SortModal = () => {
199
224
  &nbsp;
200
225
  <span title={value[settings.title] ? value[settings.title] : value[mainField]}>
201
226
  {value[settings.title] ? value[settings.title] : value[mainField]}
227
+ {getSubtitle(value)}
202
228
  </span>
203
229
  </div>
204
230
  </MenuItem>
@@ -133,6 +133,33 @@ const Settings = () => {
133
133
  } />
134
134
  </Box>
135
135
  </GridItem>
136
+ <GridItem col={6} s={12}>
137
+ <Box padding={0}>
138
+ <TextInput
139
+ placeholder="Subtitle"
140
+ label="Subitle Field Name"
141
+ hint="Optionally select a second subtitle field to show up attached to the title in the drag drop menu. Leave blank to not show a subtitle."
142
+ name="content"
143
+ onChange={e => {
144
+ setSettings({
145
+ ...settings,
146
+ subtitle: e.target.value,
147
+ })
148
+ }}
149
+ value={settings.subtitle}
150
+ labelAction={
151
+ <Tooltip description="Field that will show up in the drag drop menu attached to title">
152
+ <button aria-label="Information about the email" style={{
153
+ border: 'none',
154
+ padding: 0,
155
+ background: 'transparent'
156
+ }}>
157
+ <Information aria-hidden={true} />
158
+ </button>
159
+ </Tooltip>
160
+ } />
161
+ </Box>
162
+ </GridItem>
136
163
  </Grid>
137
164
  </Stack>
138
165
  </Box>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retikolo/drag-drop-content-types",
3
- "version": "1.3.11",
3
+ "version": "1.4.1",
4
4
  "description": "This plugin add a drag and droppable list that allows you to sort content type entries.",
5
5
  "strapi": {
6
6
  "name": "drag-drop-content-types",
@@ -17,7 +17,8 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "array-move": "^4.0.0",
20
- "react-sortable-hoc": "^2.0.0"
20
+ "react-sortable-hoc": "^2.0.0",
21
+ "zod": "^3.20.2"
21
22
  },
22
23
  "author": {
23
24
  "name": "Aeneas Meier",
@@ -1,5 +1,8 @@
1
+ // @ts-check
1
2
  'use strict';
2
3
 
4
+ const { z } = require('zod');
5
+
3
6
  // Get the store from the drag-drop plugin
4
7
  function getPluginStore() {
5
8
  return strapi.store({
@@ -16,7 +19,9 @@ async function createDefaultConfig() {
16
19
  body: {
17
20
  rank: 'rank',
18
21
  title: '',
22
+ subtitle: '',
19
23
  }
24
+
20
25
  };
21
26
  await pluginStore.set({ key: 'settings', value });
22
27
  return pluginStore.get({ key: 'settings' });
@@ -43,7 +48,7 @@ async function setSettings(settings) {
43
48
  // Search for entries ordered by rank
44
49
  async function index(contentType, start, limit, locale, rankFieldName) {
45
50
  let indexData = {
46
- sort: { },
51
+ sort: {},
47
52
  populate: '*',
48
53
  start: start,
49
54
  limit: limit,
@@ -51,7 +56,7 @@ async function index(contentType, start, limit, locale, rankFieldName) {
51
56
  }
52
57
  indexData.sort[rankFieldName] = 'asc'
53
58
  try {
54
- return await strapi.entityService.findMany(contentType, indexData );
59
+ return await strapi.entityService.findMany(contentType, indexData);
55
60
  } catch (err) {
56
61
  return {};
57
62
  }
@@ -67,6 +72,40 @@ async function update(id, contentType, rank, rankFieldName) {
67
72
  return await strapi.query(contentType).update(updateData);
68
73
  }
69
74
 
75
+ /**
76
+ *
77
+ * @param {RankUpdate[]} updates
78
+ * @param {string} contentType
79
+ */
80
+ async function batchUpdate(updates, contentType) {
81
+ const config = await getSettings();
82
+ const sortFieldName = config.body.rank;
83
+ const results = [];
84
+
85
+ for (const update of updates) {
86
+ // update entry's rank in db
87
+ try {
88
+ const updatedEntry = await strapi.db.query(contentType).update({
89
+ where: { id: update.id },
90
+ data: {
91
+ [sortFieldName]: update.rank,
92
+ },
93
+ });
94
+ updatedEntry?.id && results.push(updatedEntry);
95
+ } catch (err) {
96
+ console.log(err);
97
+ }
98
+ }
99
+ if (results?.length !== updates?.length) {
100
+ throw new Error('Error updating rank entries.');
101
+ } else {
102
+ return results.map((entry) => ({
103
+ id: entry.id,
104
+ rank: entry[sortFieldName],
105
+ }));
106
+ }
107
+ }
108
+
70
109
  module.exports = {
71
110
  async getSettings(ctx) {
72
111
  try {
@@ -98,4 +137,33 @@ module.exports = {
98
137
  ctx.throw(500, err);
99
138
  }
100
139
  },
101
- };
140
+ async batchUpdate(ctx) {
141
+ try {
142
+ const payload = await BatchUpdateRequestSchema.parseAsync(
143
+ ctx.request.body
144
+ );
145
+ try {
146
+ ctx.body = await batchUpdate(payload.updates, payload.contentType);
147
+ } catch (err) {
148
+ ctx.throw(500, err);
149
+ }
150
+ } catch (err) {
151
+ ctx.throw(400, err);
152
+ }
153
+ },
154
+ };
155
+
156
+ // TYPE-SAFETY AND VALIDATION
157
+
158
+ // Request schema used for parsing/validating incoming API requests. Uses Zod which works well with Typescript if used in the future.
159
+ const RankUpdateSchema = z.object({
160
+ id: z.number(),
161
+ rank: z.number(),
162
+ });
163
+ const BatchUpdateRequestSchema = z.object({
164
+ contentType: z.string(),
165
+ updates: z.array(RankUpdateSchema),
166
+ });
167
+
168
+ // Try to enforce Typescript types in Javascript using JSDoc
169
+ /** @typedef { z.infer<typeof RankUpdateSchema> } RankUpdate */
@@ -1,5 +1,3 @@
1
-
2
-
3
1
  module.exports = {
4
2
  type: 'admin',
5
3
  routes: [
@@ -22,22 +20,31 @@ module.exports = {
22
20
  },
23
21
  },
24
22
  {
25
- method: "POST",
26
- path: "/sort-index",
27
- handler: "sort.index",
23
+ method: 'POST',
24
+ path: '/sort-index',
25
+ handler: 'sort.index',
26
+ config: {
27
+ policies: [],
28
+ auth: false,
29
+ },
30
+ },
31
+ {
32
+ method: 'PUT',
33
+ path: '/batch-update',
34
+ handler: 'sort.batchUpdate',
28
35
  config: {
29
36
  policies: [],
30
37
  auth: false,
31
38
  },
32
39
  },
33
40
  {
34
- method: "PUT",
35
- path: "/sort-update/:id",
36
- handler: "sort.update",
41
+ method: 'PUT',
42
+ path: '/sort-update/:id',
43
+ handler: 'sort.update',
37
44
  config: {
38
45
  policies: [],
39
46
  auth: false,
40
47
  },
41
48
  },
42
49
  ],
43
- };
50
+ };