mirror of
https://github.com/strapi/strapi.git
synced 2025-11-09 06:40:42 +00:00
Merge pull request #13083 from strapi/ML-folder/folder-tree
Ml folder/folder tree
This commit is contained in:
commit
b4cd89646b
@ -178,7 +178,7 @@ Object {
|
|||||||
class=" css-1hb7zxy-IndicatorsContainer"
|
class=" css-1hb7zxy-IndicatorsContainer"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="sc-hGnimi sc-bPVzhA sc-evQXBP iaYaJy cDFemq cOhtSo"
|
class="sc-iuqRDJ sc-hqyYpk sc-imVSVl cuYSdT kzYvLH iYRxVH"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { useQuery } from 'react-query';
|
|||||||
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
|
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
|
||||||
import { useNotification, useQueryParams } from '@strapi/helper-plugin';
|
import { useNotification, useQueryParams } from '@strapi/helper-plugin';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { axiosInstance, getRequestUrl } from '../utils';
|
import { axiosInstance, getRequestUrl } from '../utils';
|
||||||
|
|
||||||
export const useFolders = ({ enabled = true }) => {
|
export const useFolders = ({ enabled = true }) => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
collectionName: 'folders',
|
collectionName: 'upload_folders',
|
||||||
info: {
|
info: {
|
||||||
singularName: 'folder',
|
singularName: 'folder',
|
||||||
pluralName: 'folders',
|
pluralName: 'folders',
|
||||||
|
|||||||
@ -73,4 +73,14 @@ module.exports = {
|
|||||||
data: deletedFolders,
|
data: deletedFolders,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getStructure(ctx) {
|
||||||
|
const { getStructure } = getService('folder');
|
||||||
|
|
||||||
|
const structure = await getStructure();
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
data: structure,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,13 +17,13 @@ const validateCreateFolderSchema = yup
|
|||||||
.required(),
|
.required(),
|
||||||
parent: yup.strapiID().nullable(),
|
parent: yup.strapiID().nullable(),
|
||||||
})
|
})
|
||||||
|
.noUnknown()
|
||||||
|
.required()
|
||||||
.test('is-folder-unique', 'name already taken', async folder => {
|
.test('is-folder-unique', 'name already taken', async folder => {
|
||||||
const { exists } = getService('folder');
|
const { exists } = getService('folder');
|
||||||
const doesExist = await exists({ parent: folder.parent || null, name: folder.name });
|
const doesExist = await exists({ parent: folder.parent || null, name: folder.name });
|
||||||
return !doesExist;
|
return !doesExist;
|
||||||
})
|
});
|
||||||
.noUnknown()
|
|
||||||
.required();
|
|
||||||
|
|
||||||
const validateDeleteManyFoldersSchema = yup
|
const validateDeleteManyFoldersSchema = yup
|
||||||
.object()
|
.object()
|
||||||
|
|||||||
@ -16,7 +16,9 @@ const fileInfoSchema = yup.object({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getService('folder').exists({ id: folderId });
|
const exists = await getService('folder').exists({ id: folderId });
|
||||||
|
|
||||||
|
return exists;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -139,5 +139,21 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
path: '/folder-structure',
|
||||||
|
handler: 'admin-folder.getStructure',
|
||||||
|
config: {
|
||||||
|
policies: [
|
||||||
|
'admin::isAuthenticatedAdmin',
|
||||||
|
{
|
||||||
|
name: 'admin::hasPermissions',
|
||||||
|
config: {
|
||||||
|
actions: ['plugin::upload.read'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const uuid = require('uuid').v4;
|
const uuid = require('uuid').v4;
|
||||||
|
const { keys, sortBy, omit } = require('lodash/fp');
|
||||||
const { joinBy } = require('@strapi/utils');
|
const { joinBy } = require('@strapi/utils');
|
||||||
|
|
||||||
const folderModel = 'plugin::upload.folder';
|
const folderModel = 'plugin::upload.folder';
|
||||||
@ -42,8 +43,41 @@ const exists = async (params = {}) => {
|
|||||||
return count > 0;
|
return count > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStructure = async () => {
|
||||||
|
const joinTable = strapi.db.metadata.get('plugin::upload.folder').attributes.parent.joinTable;
|
||||||
|
const qb = strapi.db.queryBuilder('plugin::upload.folder');
|
||||||
|
const alias = qb.getAlias();
|
||||||
|
const folders = await qb
|
||||||
|
.select(['id', 'name', `${alias}.${joinTable.inverseJoinColumn.name} as parent`])
|
||||||
|
.join({
|
||||||
|
alias,
|
||||||
|
referencedTable: joinTable.name,
|
||||||
|
referencedColumn: joinTable.joinColumn.name,
|
||||||
|
rootColumn: joinTable.joinColumn.referencedColumn,
|
||||||
|
rootTable: qb.alias,
|
||||||
|
})
|
||||||
|
.execute({ mapResults: false });
|
||||||
|
|
||||||
|
const folderMap = folders.reduce((map, f) => {
|
||||||
|
f.children = [];
|
||||||
|
map[f.id] = f;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
folderMap.null = { children: [] };
|
||||||
|
|
||||||
|
for (const id of keys(omit('null', folderMap))) {
|
||||||
|
const parentId = folderMap[id].parent;
|
||||||
|
folderMap[parentId].children.push(folderMap[id]);
|
||||||
|
folderMap[parentId].children = sortBy('name', folderMap[parentId].children);
|
||||||
|
delete folderMap[id].parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderMap.null.children;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
exists,
|
exists,
|
||||||
deleteByIds,
|
deleteByIds,
|
||||||
setPathAndUID,
|
setPathAndUID,
|
||||||
|
getStructure,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,77 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { createTestBuilder } = require('../../../../../test/helpers/builder');
|
||||||
|
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||||
|
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||||
|
|
||||||
|
let strapi;
|
||||||
|
let rq;
|
||||||
|
let data = {
|
||||||
|
folders: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const createFolder = async (name, parent = null) => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/folders',
|
||||||
|
body: { name, parent },
|
||||||
|
});
|
||||||
|
return res.body.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Folder structure', () => {
|
||||||
|
const builder = createTestBuilder();
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
strapi = await createStrapiInstance();
|
||||||
|
rq = await createAuthRequest({ strapi });
|
||||||
|
|
||||||
|
const rootFolder1 = await createFolder('folder1');
|
||||||
|
const rootFolder2 = await createFolder('folder2');
|
||||||
|
const nestedFolder1a = await createFolder('folder1A', rootFolder1.id);
|
||||||
|
const nestedFolder1b = await createFolder('folder1B', rootFolder1.id);
|
||||||
|
const nestedFolder1a1 = await createFolder('folder1A1', nestedFolder1a.id);
|
||||||
|
data.folders.push(rootFolder1, rootFolder2, nestedFolder1a, nestedFolder1b, nestedFolder1a1);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/folders/batch-delete',
|
||||||
|
body: {
|
||||||
|
ids: data.folders.map(f => f.id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await strapi.destroy();
|
||||||
|
await builder.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Read', () => {
|
||||||
|
test('get structure', async () => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/upload/folder-structure',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res.body).toMatchObject({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
children: [{ children: [], id: expect.anything(), name: 'folder1A1' }],
|
||||||
|
id: expect.anything(),
|
||||||
|
name: 'folder1A',
|
||||||
|
},
|
||||||
|
{ children: [], id: expect.anything(), name: 'folder1B' },
|
||||||
|
],
|
||||||
|
id: expect.anything(),
|
||||||
|
name: 'folder1',
|
||||||
|
},
|
||||||
|
{ children: [], id: expect.anything(), name: 'folder2' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -31,6 +31,14 @@ describe('Folder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
await rq({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/upload/folders/batch-delete',
|
||||||
|
body: {
|
||||||
|
ids: data.folders.map(f => f.id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await strapi.destroy();
|
await strapi.destroy();
|
||||||
await builder.cleanup();
|
await builder.cleanup();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -762,7 +762,7 @@ describe('Admin | containers | RoleCreatePage', () => {
|
|||||||
border: 1px solid #4945ff;
|
border: 1px solid #4945ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c44:hover:not([aria-disabled='true']) .sc-hrJiTw {
|
.c44:hover:not([aria-disabled='true']) .sc-jcneYm {
|
||||||
color: #271fe0;
|
color: #271fe0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -831,7 +831,7 @@ describe('Admin | containers | RoleEditPage', () => {
|
|||||||
border: 1px solid #4945ff;
|
border: 1px solid #4945ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c49:hover:not([aria-disabled='true']) .sc-gtPNqn {
|
.c49:hover:not([aria-disabled='true']) .sc-kGrBqp {
|
||||||
color: #271fe0;
|
color: #271fe0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user