@retikolo/drag-drop-content-types 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/README.md +26 -0
- package/admin/src/api/sort.js +18 -0
- package/admin/src/components/Initializer/index.js +26 -0
- package/admin/src/components/SortModal/index.js +150 -0
- package/admin/src/index.js +91 -0
- package/admin/src/pages/App/index.js +24 -0
- package/admin/src/pages/Settings/index.js +145 -0
- package/admin/src/pluginId.js +5 -0
- package/admin/src/translations/en.json +1 -0
- package/admin/src/translations/fr.json +1 -0
- package/admin/src/utils/axiosInstance.js +40 -0
- package/admin/src/utils/getTrad.js +5 -0
- package/package.json +38 -0
- package/server/bootstrap.js +5 -0
- package/server/config/index.js +6 -0
- package/server/content-types/index.js +4 -0
- package/server/controllers/index.js +7 -0
- package/server/controllers/sort.js +77 -0
- package/server/destroy.js +5 -0
- package/server/index.js +25 -0
- package/server/middlewares/index.js +3 -0
- package/server/policies/index.js +3 -0
- package/server/register.js +12 -0
- package/server/routes/index.js +7 -0
- package/server/routes/sort.js +34 -0
- package/server/services/index.js +3 -0
- package/strapi-admin.js +3 -0
- package/strapi-server.js +3 -0
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Strapi plugin drag-drop-content-types
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
## ⚠THIS PLUGIN IS IN ALPHA⚠
|
|
5
|
+
Be warned. Breaking changes can happen. Bad things can happen. Unexpected things can happen. Woosh!
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
1. Add the plugin to the `src/plugins` directory.
|
|
9
|
+
2. Add this to your `config/plugins.js` file (create it, if it doesn't exist yet):
|
|
10
|
+
```js
|
|
11
|
+
module.exports = {
|
|
12
|
+
// ...
|
|
13
|
+
'drag-drop-content-types': {
|
|
14
|
+
enabled: true,
|
|
15
|
+
resolve: './src/plugins/drag-drop-content-types'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
3. Run `npm run build` and (re)start the app
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
Go to `Settings` -> `Drag Drop Content Type` -> `Configuration`:
|
|
23
|
+
* Specify how the rank field and the corresponding title field are called in your content types. Default value are `rank` and `title`.
|
|
24
|
+
* Add the specified fields to your content type. With the default values this would be `title` (Text (Type: Short Text)) and `rank` (Number (Number format: integer))
|
|
25
|
+
* You will be rewared with the drag-dropable menu in the list view of all content types having the specified fields.
|
|
26
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import axiosInstance from '../utils/axiosInstance';
|
|
2
|
+
|
|
3
|
+
const sortRequests = {
|
|
4
|
+
getTaskCount: async () => {
|
|
5
|
+
const data = await axiosInstance.get(`/drag-drop-content-types/count`);
|
|
6
|
+
return data;
|
|
7
|
+
},
|
|
8
|
+
getSettings: async () => {
|
|
9
|
+
const data = await axiosInstance.get(`/drag-drop-content-types/settings`);
|
|
10
|
+
return data;
|
|
11
|
+
},
|
|
12
|
+
setSettings: async data => {
|
|
13
|
+
return await axiosInstance.post(`/drag-drop-content-types/settings`, {
|
|
14
|
+
body: data,
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
export default sortRequests;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Initializer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useEffect, useRef } from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import pluginId from '../../pluginId';
|
|
10
|
+
|
|
11
|
+
const Initializer = ({ setPlugin }) => {
|
|
12
|
+
const ref = useRef();
|
|
13
|
+
ref.current = setPlugin;
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
ref.current(pluginId);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Initializer.propTypes = {
|
|
23
|
+
setPlugin: PropTypes.func.isRequired,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default Initializer;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import axiosInstance from '../../utils/axiosInstance';
|
|
4
|
+
|
|
5
|
+
import DragSortableList from 'react-drag-sortable'
|
|
6
|
+
|
|
7
|
+
import { SimpleMenu, MenuItem } from '@strapi/design-system/SimpleMenu';
|
|
8
|
+
import { IconButton } from '@strapi/design-system/IconButton';
|
|
9
|
+
import { Icon } from '@strapi/design-system/Icon';
|
|
10
|
+
import Drag from '@strapi/icons/Drag';
|
|
11
|
+
import Layer from '@strapi/icons/Layer';
|
|
12
|
+
|
|
13
|
+
const SortModal = () => {
|
|
14
|
+
|
|
15
|
+
const [active, setActive] = useState(false);
|
|
16
|
+
const [contentType, setContentType] = useState([]);
|
|
17
|
+
const [status, setStatus] = useState('loading');
|
|
18
|
+
const [settings, setSettings] = useState();
|
|
19
|
+
|
|
20
|
+
// Shorten string to prevent line break
|
|
21
|
+
const shortenString = (string) => {
|
|
22
|
+
if (string.length > 40) {
|
|
23
|
+
return string.substring(0, 37) + "..."
|
|
24
|
+
}
|
|
25
|
+
return string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Fetch settings from configuration
|
|
29
|
+
const fetchSettings = async () => {
|
|
30
|
+
try {
|
|
31
|
+
const { data } = await axiosInstance.get(`drag-drop-content-types/settings`);
|
|
32
|
+
setSettings(data.body);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.log(e);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Check database if the desired fields are available
|
|
39
|
+
// TODO: check field integrity
|
|
40
|
+
const initializeContentType = async () => {
|
|
41
|
+
try {
|
|
42
|
+
if (settings){
|
|
43
|
+
const { data } = await axiosInstance.get(
|
|
44
|
+
`/content-manager/collection-types/${contentTypePath}?sort=rank:asc`
|
|
45
|
+
);
|
|
46
|
+
if (data.results.length > 0 && !!toString(data.results[0][settings.rank]) && !!data.results[0][settings.title]) {
|
|
47
|
+
setActive(true);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.log(e);
|
|
52
|
+
setStatus('error');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fetch data from the database via get request
|
|
57
|
+
const fetchContentType = async () => {
|
|
58
|
+
try {
|
|
59
|
+
const { data } = await axiosInstance.get(
|
|
60
|
+
`/content-manager/collection-types/${contentTypePath}?sort=rank:asc`
|
|
61
|
+
);
|
|
62
|
+
setStatus('success');
|
|
63
|
+
// Iterate over all results and append them to the list
|
|
64
|
+
let list = [];
|
|
65
|
+
for (let i = 0; i < data.results.length; i++) {
|
|
66
|
+
list.push({
|
|
67
|
+
content: (<MenuItem ><Icon height={"0.6rem"} as={Drag} /> <span title={data.results[i][settings.title]}>{shortenString(data.results[i][settings.title])}</span></MenuItem>), strapiId: data.results[i].id
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
setContentType(list);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.log(e);
|
|
73
|
+
setStatus('error');
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Update all ranks via put request.
|
|
78
|
+
const updateContentType = async (sortedList) => {
|
|
79
|
+
try {
|
|
80
|
+
// Increase performance by breaking loop after last element having a rank change is updated
|
|
81
|
+
let rankHasChanged = false
|
|
82
|
+
// Iterate over all results and append them to the list
|
|
83
|
+
for (let i = 0; i < sortedList.length; i++) {
|
|
84
|
+
// Only update changed values
|
|
85
|
+
if (previousSortedList.length == 0 || previousSortedList[i].strapiId != sortedList[i].strapiId) {
|
|
86
|
+
// Update rank via put request
|
|
87
|
+
await axiosInstance.put(`/drag-drop-content-types/sort-update/${sortedList[i].strapiId}`, {
|
|
88
|
+
contentType: contentTypePath,
|
|
89
|
+
rank: sortedList[i].rank,
|
|
90
|
+
});
|
|
91
|
+
rankHasChanged = true;
|
|
92
|
+
} else if (rankHasChanged) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Store the state of the list to increase update performance
|
|
97
|
+
// TODO: Currently on the first event all entries are updated,
|
|
98
|
+
// since there is no previous list available.
|
|
99
|
+
// Get an initial state when first loading page.
|
|
100
|
+
previousSortedList = sortedList;
|
|
101
|
+
setStatus('success');
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.log(e);
|
|
104
|
+
setStatus('error');
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Render the menu
|
|
109
|
+
const showMenu = () => {
|
|
110
|
+
if (!active) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
<SimpleMenu
|
|
116
|
+
as={IconButton}
|
|
117
|
+
icon={<Layer />}
|
|
118
|
+
onClick={() => { fetchContentType() }}
|
|
119
|
+
>
|
|
120
|
+
<DragSortableList items={contentType} moveTransitionDuration={0.3} onSort={updateContentType} type="vertical" />
|
|
121
|
+
</SimpleMenu>
|
|
122
|
+
</>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fetch settings on page render
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
fetchSettings();
|
|
129
|
+
}, [])
|
|
130
|
+
|
|
131
|
+
// Update view when settings change
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
initializeContentType();
|
|
134
|
+
}, [settings])
|
|
135
|
+
|
|
136
|
+
// Get content type from url
|
|
137
|
+
const paths = window.location.pathname.split('/')
|
|
138
|
+
const contentTypePath = paths[paths.length - 1]
|
|
139
|
+
|
|
140
|
+
// Store the drag and drop lists previous value to increase update performance
|
|
141
|
+
let previousSortedList = []
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
{showMenu()}
|
|
146
|
+
</>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export default SortModal;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
|
2
|
+
import pluginPkg from '../../package.json';
|
|
3
|
+
import pluginId from './pluginId';
|
|
4
|
+
import Initializer from './components/Initializer';
|
|
5
|
+
import SortModal from './components/SortModal';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const name = pluginPkg.strapi.name;
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
register(app) {
|
|
12
|
+
app.createSettingSection(
|
|
13
|
+
{
|
|
14
|
+
id: pluginId,
|
|
15
|
+
intlLabel: {
|
|
16
|
+
id: `${pluginId}.plugin.name`,
|
|
17
|
+
defaultMessage: 'Drag Drop Content Types',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
[
|
|
21
|
+
{
|
|
22
|
+
intlLabel: {
|
|
23
|
+
id: `${pluginId}.plugin.name`,
|
|
24
|
+
defaultMessage: 'Configuration',
|
|
25
|
+
},
|
|
26
|
+
id: 'settings',
|
|
27
|
+
to: `/settings/${pluginId}`,
|
|
28
|
+
Component: async () => {
|
|
29
|
+
return import('./pages/Settings');
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
);
|
|
34
|
+
app.registerPlugin({
|
|
35
|
+
id: pluginId,
|
|
36
|
+
initializer: Initializer,
|
|
37
|
+
isReady: false,
|
|
38
|
+
name,
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
bootstrap(app) {
|
|
43
|
+
app.injectContentManagerComponent("listView", "actions", {
|
|
44
|
+
name: "sort-component",
|
|
45
|
+
Component: SortModal,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
app.registerHook('Admin/CM/pages/ListView/inject-column-in-table', ({ displayedHeaders, layout }) => {
|
|
49
|
+
return {
|
|
50
|
+
layout,
|
|
51
|
+
displayedHeaders: [
|
|
52
|
+
...displayedHeaders,
|
|
53
|
+
{
|
|
54
|
+
key: '__locale_key__', // Needed for the table
|
|
55
|
+
fieldSchema: { type: 'string' }, // Schema of the attribute
|
|
56
|
+
metadatas: {
|
|
57
|
+
label: 'Raboo', // Label of the header,
|
|
58
|
+
sortable: true | false // Define if the column is sortable
|
|
59
|
+
}, // Metadatas for the label
|
|
60
|
+
// Name of the key in the data we will display
|
|
61
|
+
name: 'locales',
|
|
62
|
+
// Custom renderer: props => Object.keys(props).map(key => <p key={key}>key</p>)
|
|
63
|
+
},
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async registerTrads({ locales }) {
|
|
70
|
+
const importedTrads = await Promise.all(
|
|
71
|
+
locales.map((locale) => {
|
|
72
|
+
return import(
|
|
73
|
+
/* webpackChunkName: "translation-[request]" */ `./translations/${locale}.json`
|
|
74
|
+
)
|
|
75
|
+
.then(({ default: data }) => {
|
|
76
|
+
return {
|
|
77
|
+
data: prefixPluginTranslations(data, pluginId),
|
|
78
|
+
locale,
|
|
79
|
+
};
|
|
80
|
+
})
|
|
81
|
+
.catch(() => {
|
|
82
|
+
return {
|
|
83
|
+
data: {},
|
|
84
|
+
locale,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
return Promise.resolve(importedTrads);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* This component is the skeleton around the actual pages, and should only
|
|
4
|
+
* contain code that should be seen on all pages. (e.g. navigation bar)
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Switch, Route } from 'react-router-dom';
|
|
10
|
+
import { NotFound } from '@strapi/helper-plugin';
|
|
11
|
+
import pluginId from '../../pluginId';
|
|
12
|
+
|
|
13
|
+
const App = () => {
|
|
14
|
+
return (
|
|
15
|
+
<div>
|
|
16
|
+
<Switch>
|
|
17
|
+
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
|
18
|
+
<Route component={NotFound} />
|
|
19
|
+
</Switch>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default App;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import sortRequests from '../../api/sort';
|
|
4
|
+
|
|
5
|
+
import { LoadingIndicatorPage, useNotification } from '@strapi/helper-plugin';
|
|
6
|
+
|
|
7
|
+
import { Box } from '@strapi/design-system/Box';
|
|
8
|
+
import { Stack } from '@strapi/design-system/Stack';
|
|
9
|
+
import { Button } from '@strapi/design-system/Button';
|
|
10
|
+
import { Grid, GridItem } from '@strapi/design-system/Grid';
|
|
11
|
+
import { HeaderLayout } from '@strapi/design-system/Layout';
|
|
12
|
+
import { ContentLayout } from '@strapi/design-system/Layout';
|
|
13
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
14
|
+
import { TextInput } from '@strapi/design-system/TextInput';
|
|
15
|
+
import { Tooltip } from '@strapi/design-system/Tooltip';
|
|
16
|
+
|
|
17
|
+
import Information from '@strapi/icons/Information';
|
|
18
|
+
import Check from '@strapi/icons/Check';
|
|
19
|
+
|
|
20
|
+
const Settings = () => {
|
|
21
|
+
const [settings, setSettings] = useState({});
|
|
22
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
23
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
24
|
+
const toggleNotification = useNotification();
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
sortRequests.getSettings().then(res => {
|
|
28
|
+
setSettings(res.data.body);
|
|
29
|
+
setIsLoading(false);
|
|
30
|
+
});
|
|
31
|
+
}, [setSettings]);
|
|
32
|
+
|
|
33
|
+
const handleSubmit = async () => {
|
|
34
|
+
setIsSaving(true);
|
|
35
|
+
const res = await sortRequests.setSettings(settings);
|
|
36
|
+
setSettings(res.data.body);
|
|
37
|
+
setIsSaving(false);
|
|
38
|
+
toggleNotification({
|
|
39
|
+
type: 'success',
|
|
40
|
+
message: 'Settings successfully updated',
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
<HeaderLayout
|
|
47
|
+
id="title"
|
|
48
|
+
title="Drag Drop Content Type Config"
|
|
49
|
+
subtitle="Manage field values for drag-droppable entries"
|
|
50
|
+
primaryAction={
|
|
51
|
+
isLoading ? (
|
|
52
|
+
<></>
|
|
53
|
+
) : (
|
|
54
|
+
<Button
|
|
55
|
+
onClick={handleSubmit}
|
|
56
|
+
startIcon={<Check />}
|
|
57
|
+
size="L"
|
|
58
|
+
disabled={isSaving}
|
|
59
|
+
loading={isSaving}
|
|
60
|
+
>
|
|
61
|
+
Save
|
|
62
|
+
</Button>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
></HeaderLayout>
|
|
66
|
+
{isLoading ? (
|
|
67
|
+
<LoadingIndicatorPage />
|
|
68
|
+
) : (
|
|
69
|
+
<ContentLayout>
|
|
70
|
+
<Box
|
|
71
|
+
background="neutral0"
|
|
72
|
+
hasRadius
|
|
73
|
+
shadow="filterShadow"
|
|
74
|
+
paddingTop={6}
|
|
75
|
+
paddingBottom={6}
|
|
76
|
+
paddingLeft={7}
|
|
77
|
+
paddingRight={7}
|
|
78
|
+
>
|
|
79
|
+
<Stack size={3}>
|
|
80
|
+
<Typography>Field Names</Typography>
|
|
81
|
+
<Grid gap={6}>
|
|
82
|
+
<GridItem col={6} s={12}>
|
|
83
|
+
<Box padding={0}>
|
|
84
|
+
<TextInput
|
|
85
|
+
placeholder="Rank"
|
|
86
|
+
label="Rank Field Name"
|
|
87
|
+
hint="You must create a Number Field with this label and type integer in the Content-Type Builder"
|
|
88
|
+
name="content"
|
|
89
|
+
onChange={e => {
|
|
90
|
+
setSettings({
|
|
91
|
+
...settings,
|
|
92
|
+
rank: e.target.value,
|
|
93
|
+
})
|
|
94
|
+
}}
|
|
95
|
+
value={settings.rank}
|
|
96
|
+
labelAction={
|
|
97
|
+
<Tooltip description="Field which is used for ordering content-type entries">
|
|
98
|
+
<button aria-label="Information about the email" style={{
|
|
99
|
+
border: 'none',
|
|
100
|
+
padding: 0,
|
|
101
|
+
background: 'transparent'
|
|
102
|
+
}}>
|
|
103
|
+
<Information aria-hidden={true} />
|
|
104
|
+
</button>
|
|
105
|
+
</Tooltip>
|
|
106
|
+
} />
|
|
107
|
+
</Box>;
|
|
108
|
+
</GridItem>
|
|
109
|
+
<GridItem col={6} s={12}>
|
|
110
|
+
<Box padding={0}>
|
|
111
|
+
<TextInput
|
|
112
|
+
placeholder="Title"
|
|
113
|
+
label="Title Field Name"
|
|
114
|
+
hint="You must create a Short Text Field with this label in the Content-Type Builder"
|
|
115
|
+
name="content"
|
|
116
|
+
onChange={e => {
|
|
117
|
+
setSettings({
|
|
118
|
+
...settings,
|
|
119
|
+
title: e.target.value,
|
|
120
|
+
})
|
|
121
|
+
}}
|
|
122
|
+
value={settings.title}
|
|
123
|
+
labelAction={
|
|
124
|
+
<Tooltip description="Field that will show up in the drag drop menu">
|
|
125
|
+
<button aria-label="Information about the email" style={{
|
|
126
|
+
border: 'none',
|
|
127
|
+
padding: 0,
|
|
128
|
+
background: 'transparent'
|
|
129
|
+
}}>
|
|
130
|
+
<Information aria-hidden={true} />
|
|
131
|
+
</button>
|
|
132
|
+
</Tooltip>
|
|
133
|
+
} />
|
|
134
|
+
</Box>;
|
|
135
|
+
</GridItem>
|
|
136
|
+
</Grid>
|
|
137
|
+
</Stack>
|
|
138
|
+
</Box>
|
|
139
|
+
</ContentLayout>
|
|
140
|
+
)}
|
|
141
|
+
</>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default Settings;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* axios with a custom config.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import { auth } from '@strapi/helper-plugin';
|
|
7
|
+
|
|
8
|
+
const instance = axios.create({
|
|
9
|
+
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
instance.interceptors.request.use(
|
|
13
|
+
async (config) => {
|
|
14
|
+
config.headers = {
|
|
15
|
+
Authorization: `Bearer ${auth.getToken()}`,
|
|
16
|
+
Accept: 'application/json',
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return config;
|
|
21
|
+
},
|
|
22
|
+
(error) => {
|
|
23
|
+
Promise.reject(error);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
instance.interceptors.response.use(
|
|
28
|
+
(response) => response,
|
|
29
|
+
(error) => {
|
|
30
|
+
// whatever you want to do with the error
|
|
31
|
+
if (error.response?.status === 401) {
|
|
32
|
+
auth.clearAppStorage();
|
|
33
|
+
window.location.reload();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export default instance;
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@retikolo/drag-drop-content-types",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "This plugin add a drag and droppable list that allows you to sort content type entries.",
|
|
5
|
+
"strapi": {
|
|
6
|
+
"name": "drag-drop-content-types",
|
|
7
|
+
"description": "This plugin add a drag and droppable list that allows you to sort content type entries.",
|
|
8
|
+
"kind": "plugin",
|
|
9
|
+
"displayName": "Drag Drop Content Types"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/plantagoIT/strapi-drag-drop-content-type-plugin"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@strapi/strapi": "^4.0.0"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"react-drag-sortable": "^1.0.6"
|
|
20
|
+
},
|
|
21
|
+
"author": {
|
|
22
|
+
"name": "Aeneas Meier",
|
|
23
|
+
"email": "aeneas@retikolo.xyz",
|
|
24
|
+
"url": "https://rgb.retikolo.xyz"
|
|
25
|
+
},
|
|
26
|
+
"maintainers": [
|
|
27
|
+
{
|
|
28
|
+
"name": "Aeneas Meier",
|
|
29
|
+
"email": "aeneas@retikolo.xyz",
|
|
30
|
+
"url": "https://rgb.retikolo.xyz"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=14.19.1 <=18.x.x",
|
|
35
|
+
"npm": ">=6.0.0"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Get the store from the drag-drop plugin
|
|
4
|
+
function getPluginStore() {
|
|
5
|
+
return strapi.store({
|
|
6
|
+
environment: '',
|
|
7
|
+
type: 'plugin',
|
|
8
|
+
name: 'drag-drop-content-types',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Create a default config if there is none yet
|
|
13
|
+
async function createDefaultConfig() {
|
|
14
|
+
const pluginStore = getPluginStore();
|
|
15
|
+
const value = {
|
|
16
|
+
body:{
|
|
17
|
+
rank: 'rank',
|
|
18
|
+
title: 'title',
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
await pluginStore.set({ key: 'settings', value });
|
|
22
|
+
return pluginStore.get({ key: 'settings' });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Get settings from plugin store
|
|
26
|
+
async function getSettings() {
|
|
27
|
+
const pluginStore = getPluginStore();
|
|
28
|
+
let config = await pluginStore.get({ key: 'settings' });
|
|
29
|
+
if (!config) {
|
|
30
|
+
config = await createDefaultConfig();
|
|
31
|
+
}
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Update settings to plugin store
|
|
36
|
+
async function setSettings(settings) {
|
|
37
|
+
const value = settings;
|
|
38
|
+
const pluginStore = getPluginStore();
|
|
39
|
+
await pluginStore.set({ key: 'settings', value });
|
|
40
|
+
return pluginStore.get({ key: 'settings' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Update rank of specified content type
|
|
44
|
+
async function update(id, contentType, rank) {
|
|
45
|
+
return await strapi.query(contentType).update({
|
|
46
|
+
where: { id: id },
|
|
47
|
+
data: {
|
|
48
|
+
rank: rank,
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
async getSettings(ctx) {
|
|
55
|
+
try {
|
|
56
|
+
ctx.body = await getSettings();
|
|
57
|
+
} catch (err) {
|
|
58
|
+
ctx.throw(500, err);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
async setSettings(ctx) {
|
|
62
|
+
const { body } = ctx.request;
|
|
63
|
+
try {
|
|
64
|
+
await setSettings(body);
|
|
65
|
+
ctx.body = await getSettings();
|
|
66
|
+
} catch (err) {
|
|
67
|
+
ctx.throw(500, err);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
async update(ctx) {
|
|
71
|
+
try {
|
|
72
|
+
ctx.body = await update(ctx.params.id, ctx.request.body.contentType, ctx.request.body.rank);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
ctx.throw(500, err);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
package/server/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const register = require('./register');
|
|
4
|
+
const bootstrap = require('./bootstrap');
|
|
5
|
+
const destroy = require('./destroy');
|
|
6
|
+
const config = require('./config');
|
|
7
|
+
const contentTypes = require('./content-types');
|
|
8
|
+
const controllers = require('./controllers');
|
|
9
|
+
const routes = require('./routes');
|
|
10
|
+
const middlewares = require('./middlewares');
|
|
11
|
+
const policies = require('./policies');
|
|
12
|
+
const services = require('./services');
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
register,
|
|
16
|
+
bootstrap,
|
|
17
|
+
destroy,
|
|
18
|
+
config,
|
|
19
|
+
controllers,
|
|
20
|
+
routes,
|
|
21
|
+
services,
|
|
22
|
+
contentTypes,
|
|
23
|
+
policies,
|
|
24
|
+
middlewares,
|
|
25
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// server/register.js
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
module.exports = ({ strapi }) => {
|
|
5
|
+
// Iterating on every content-types
|
|
6
|
+
Object.values(strapi.contentTypes).forEach(contentType => {
|
|
7
|
+
// If this is an api content-type
|
|
8
|
+
if (contentType.uid.includes('api::')) {
|
|
9
|
+
// Add prconfigured content types here
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
type: 'admin',
|
|
5
|
+
routes: [
|
|
6
|
+
{
|
|
7
|
+
method: 'GET',
|
|
8
|
+
path: '/settings',
|
|
9
|
+
handler: 'sort.getSettings',
|
|
10
|
+
config: {
|
|
11
|
+
policies: [],
|
|
12
|
+
auth: false,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
method: 'POST',
|
|
17
|
+
path: '/settings',
|
|
18
|
+
handler: 'sort.setSettings',
|
|
19
|
+
config: {
|
|
20
|
+
policies: [],
|
|
21
|
+
auth: false,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
method: "PUT",
|
|
26
|
+
path: "/sort-update/:id",
|
|
27
|
+
handler: "sort.update",
|
|
28
|
+
config: {
|
|
29
|
+
policies: [],
|
|
30
|
+
auth: false,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
package/strapi-admin.js
ADDED
package/strapi-server.js
ADDED