feat: folder support in media library (#687)
This commit is contained in:
@ -188,7 +188,8 @@ export default class API {
|
||||
// doesn't.)
|
||||
...(file.commit && file.commit.hash ? { id: this.getFileId(file.commit.hash, file.path) } : {}),
|
||||
});
|
||||
processFiles = (files: BitBucketFile[]) => files.filter(this.isFile).map(this.processFile);
|
||||
processFiles = (files: BitBucketFile[], folderSupport?: boolean) =>
|
||||
files.filter(file => (!folderSupport ? this.isFile(file) : true)).map(this.processFile);
|
||||
|
||||
readFile = async (
|
||||
path: string,
|
||||
@ -294,7 +295,7 @@ export default class API {
|
||||
})),
|
||||
])((cursor.data?.links as Record<string, unknown>)[action]);
|
||||
|
||||
listAllFiles = async (path: string, depth: number, branch: string) => {
|
||||
listAllFiles = async (path: string, depth: number, branch: string, folderSupport?: boolean) => {
|
||||
const { cursor: initialCursor, entries: initialEntries } = await this.listFiles(
|
||||
path,
|
||||
depth,
|
||||
@ -311,7 +312,7 @@ export default class API {
|
||||
entries.push(...newEntries);
|
||||
currentCursor = newCursor;
|
||||
}
|
||||
return this.processFiles(entries);
|
||||
return this.processFiles(entries, folderSupport);
|
||||
};
|
||||
|
||||
async uploadFiles(
|
||||
|
@ -351,12 +351,18 @@ export default class BitbucketBackend implements BackendClass {
|
||||
}));
|
||||
}
|
||||
|
||||
async getMedia(mediaFolder = this.mediaFolder) {
|
||||
async getMedia(mediaFolder = this.mediaFolder, folderSupport?: boolean) {
|
||||
if (!mediaFolder) {
|
||||
return [];
|
||||
}
|
||||
return this.api!.listAllFiles(mediaFolder, 1, this.branch).then(files =>
|
||||
files.map(({ id, name, path }) => ({ id, name, path, displayURL: { id, path } })),
|
||||
return this.api!.listAllFiles(mediaFolder, 1, this.branch, folderSupport).then(files =>
|
||||
files.map(({ id, name, path, type }) => ({
|
||||
id,
|
||||
name,
|
||||
path,
|
||||
displayURL: { id, path },
|
||||
isDirectory: type === 'commit_directory',
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -391,8 +391,8 @@ export default class GitGateway implements BackendClass {
|
||||
return client.enabled && client.matchPath(path);
|
||||
}
|
||||
|
||||
getMedia(mediaFolder = this.mediaFolder) {
|
||||
return this.backend!.getMedia(mediaFolder);
|
||||
getMedia(mediaFolder = this.mediaFolder, folderSupport?: boolean) {
|
||||
return this.backend!.getMedia(mediaFolder, folderSupport);
|
||||
}
|
||||
|
||||
// this method memoizes this._getLargeMediaClient so that there can
|
||||
|
@ -323,6 +323,7 @@ export default class API {
|
||||
async listFiles(
|
||||
path: string,
|
||||
{ repoURL = this.repoURL, branch = this.branch, depth = 1 } = {},
|
||||
folderSupport?: boolean,
|
||||
): Promise<{ type: string; id: string; name: string; path: string; size: number }[]> {
|
||||
const folder = trim(path, '/');
|
||||
try {
|
||||
@ -336,10 +337,11 @@ export default class API {
|
||||
);
|
||||
return (
|
||||
result.tree
|
||||
// filter only files and up to the required depth
|
||||
// filter only files and/or folders up to the required depth
|
||||
.filter(
|
||||
file =>
|
||||
file.type === 'blob' && decodeURIComponent(file.path).split('/').length <= depth,
|
||||
(!folderSupport ? file.type === 'blob' : true) &&
|
||||
decodeURIComponent(file.path).split('/').length <= depth,
|
||||
)
|
||||
.map(file => ({
|
||||
type: file.type,
|
||||
|
@ -281,5 +281,49 @@ describe('gitea API', () => {
|
||||
params: { recursive: 1 },
|
||||
});
|
||||
});
|
||||
it('should get files and folders', async () => {
|
||||
const api = new API({ branch: 'master', repo: 'owner/repo' });
|
||||
|
||||
const tree = [
|
||||
{
|
||||
path: 'image.png',
|
||||
type: 'blob',
|
||||
},
|
||||
{
|
||||
path: 'dir1',
|
||||
type: 'tree',
|
||||
},
|
||||
{
|
||||
path: 'dir1/nested-image.png',
|
||||
type: 'blob',
|
||||
},
|
||||
{
|
||||
path: 'dir1/dir2',
|
||||
type: 'tree',
|
||||
},
|
||||
{
|
||||
path: 'dir1/dir2/nested-image.png',
|
||||
type: 'blob',
|
||||
},
|
||||
];
|
||||
api.request = jest.fn().mockResolvedValue({ tree });
|
||||
|
||||
await expect(api.listFiles('media', {}, true)).resolves.toEqual([
|
||||
{
|
||||
path: 'media/image.png',
|
||||
type: 'blob',
|
||||
name: 'image.png',
|
||||
},
|
||||
{
|
||||
path: 'media/dir1',
|
||||
type: 'tree',
|
||||
name: 'dir1',
|
||||
},
|
||||
]);
|
||||
expect(api.request).toHaveBeenCalledTimes(1);
|
||||
expect(api.request).toHaveBeenCalledWith('/repos/owner/repo/git/trees/master:media', {
|
||||
params: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -285,15 +285,13 @@ export default class Gitea implements BackendClass {
|
||||
.catch(() => ({ file: { path, id: null }, data: '' }));
|
||||
}
|
||||
|
||||
async getMedia(mediaFolder = this.mediaFolder) {
|
||||
async getMedia(mediaFolder = this.mediaFolder, folderSupport?: boolean) {
|
||||
if (!mediaFolder) {
|
||||
return [];
|
||||
}
|
||||
return this.api!.listFiles(mediaFolder).then(files =>
|
||||
files.map(({ id, name, size, path }) => {
|
||||
// load media using getMediaDisplayURL to avoid token expiration with Gitlab raw content urls
|
||||
// for private repositories
|
||||
return { id, name, size, displayURL: { id, path }, path };
|
||||
return this.api!.listFiles(mediaFolder, undefined, folderSupport).then(files =>
|
||||
files.map(({ id, name, size, path, type }) => {
|
||||
return { id, name, size, displayURL: { id, path }, path, isDirectory: type === 'tree' };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -338,6 +338,7 @@ export default class API {
|
||||
async listFiles(
|
||||
path: string,
|
||||
{ repoURL = this.repoURL, branch = this.branch, depth = 1 } = {},
|
||||
folderSupport?: boolean,
|
||||
): Promise<{ type: string; id: string; name: string; path: string; size: number }[]> {
|
||||
const folder = trim(path, '/');
|
||||
try {
|
||||
@ -351,8 +352,12 @@ export default class API {
|
||||
);
|
||||
return (
|
||||
result.tree
|
||||
// filter only files and up to the required depth
|
||||
.filter(file => file.type === 'blob' && file.path.split('/').length <= depth)
|
||||
// filter only files and/or folders up to the required depth
|
||||
.filter(
|
||||
file =>
|
||||
(!folderSupport ? file.type === 'blob' : true) &&
|
||||
file.path.split('/').length <= depth,
|
||||
)
|
||||
.map(file => ({
|
||||
type: file.type,
|
||||
id: file.sha,
|
||||
|
@ -314,5 +314,49 @@ describe('github API', () => {
|
||||
params: { recursive: 1 },
|
||||
});
|
||||
});
|
||||
it('should get files and folders', async () => {
|
||||
const api = new API({ branch: 'master', repo: 'owner/repo' });
|
||||
|
||||
const tree = [
|
||||
{
|
||||
path: 'image.png',
|
||||
type: 'blob',
|
||||
},
|
||||
{
|
||||
path: 'dir1',
|
||||
type: 'tree',
|
||||
},
|
||||
{
|
||||
path: 'dir1/nested-image.png',
|
||||
type: 'blob',
|
||||
},
|
||||
{
|
||||
path: 'dir1/dir2',
|
||||
type: 'tree',
|
||||
},
|
||||
{
|
||||
path: 'dir1/dir2/nested-image.png',
|
||||
type: 'blob',
|
||||
},
|
||||
];
|
||||
api.request = jest.fn().mockResolvedValue({ tree });
|
||||
|
||||
await expect(api.listFiles('media', {}, true)).resolves.toEqual([
|
||||
{
|
||||
path: 'media/image.png',
|
||||
type: 'blob',
|
||||
name: 'image.png',
|
||||
},
|
||||
{
|
||||
path: 'media/dir1',
|
||||
type: 'tree',
|
||||
name: 'dir1',
|
||||
},
|
||||
]);
|
||||
expect(api.request).toHaveBeenCalledTimes(1);
|
||||
expect(api.request).toHaveBeenCalledWith('/repos/owner/repo/git/trees/master:media', {
|
||||
params: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -314,15 +314,15 @@ export default class GitHub implements BackendClass {
|
||||
.catch(() => ({ file: { path, id: null }, data: '' }));
|
||||
}
|
||||
|
||||
async getMedia(mediaFolder = this.mediaFolder) {
|
||||
async getMedia(mediaFolder = this.mediaFolder, folderSupport?: boolean) {
|
||||
if (!mediaFolder) {
|
||||
return [];
|
||||
}
|
||||
return this.api!.listFiles(mediaFolder).then(files =>
|
||||
files.map(({ id, name, size, path }) => {
|
||||
return this.api!.listFiles(mediaFolder, undefined, folderSupport).then(files =>
|
||||
files.map(({ id, name, size, path, type }) => {
|
||||
// load media using getMediaDisplayURL to avoid token expiration with GitHub raw content urls
|
||||
// for private repositories
|
||||
return { id, name, size, displayURL: { id, path }, path };
|
||||
return { id, name, size, displayURL: { id, path }, path, isDirectory: type == 'tree' };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -318,7 +318,12 @@ export default class API {
|
||||
};
|
||||
};
|
||||
|
||||
listAllFiles = async (path: string, recursive = false, branch = this.branch) => {
|
||||
listAllFiles = async (
|
||||
path: string,
|
||||
folderSupport?: boolean,
|
||||
recursive = false,
|
||||
branch = this.branch,
|
||||
) => {
|
||||
const entries = [];
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { cursor, entries: initialEntries } = await this.fetchCursorAndEntries({
|
||||
@ -333,7 +338,7 @@ export default class API {
|
||||
entries.push(...newEntries);
|
||||
cursor = newCursor;
|
||||
}
|
||||
return entries.filter(({ type }) => type === 'blob');
|
||||
return entries.filter(({ type }) => (!folderSupport ? type === 'blob' : true));
|
||||
};
|
||||
|
||||
toBase64 = (str: string) => Promise.resolve(Base64.encode(str));
|
||||
@ -421,7 +426,7 @@ export default class API {
|
||||
for (const item of items.filter(i => i.oldPath && i.action === CommitAction.MOVE)) {
|
||||
const sourceDir = dirname(item.oldPath as string);
|
||||
const destDir = dirname(item.path);
|
||||
const children = await this.listAllFiles(sourceDir, true, branch);
|
||||
const children = await this.listAllFiles(sourceDir, undefined, true, branch);
|
||||
children
|
||||
.filter(f => f.path !== item.oldPath)
|
||||
.forEach(file => {
|
||||
|
@ -172,7 +172,7 @@ export default class GitLab implements BackendClass {
|
||||
}
|
||||
|
||||
async listAllFiles(folder: string, extension: string, depth: number) {
|
||||
const files = await this.api!.listAllFiles(folder, depth > 1);
|
||||
const files = await this.api!.listAllFiles(folder, undefined, depth > 1);
|
||||
const filtered = files.filter(file => this.filterFile(folder, file, extension, depth));
|
||||
return filtered;
|
||||
}
|
||||
@ -217,13 +217,13 @@ export default class GitLab implements BackendClass {
|
||||
}));
|
||||
}
|
||||
|
||||
async getMedia(mediaFolder = this.mediaFolder) {
|
||||
async getMedia(mediaFolder = this.mediaFolder, folderSupport?: boolean) {
|
||||
if (!mediaFolder) {
|
||||
return [];
|
||||
}
|
||||
return this.api!.listAllFiles(mediaFolder).then(files =>
|
||||
files.map(({ id, name, path }) => {
|
||||
return { id, name, path, displayURL: { id, name, path } };
|
||||
return this.api!.listAllFiles(mediaFolder, folderSupport).then(files =>
|
||||
files.map(({ id, name, path, type }) => {
|
||||
return { id, name, path, displayURL: { id, name, path }, isDirectory: type === 'tree' };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -146,17 +146,23 @@ export default class ProxyBackend implements BackendClass {
|
||||
});
|
||||
}
|
||||
|
||||
async getMedia(mediaFolder = this.mediaFolder, publicFolder = this.publicFolder) {
|
||||
const files: { path: string; url: string }[] = await this.request({
|
||||
async getMedia(
|
||||
mediaFolder = this.mediaFolder,
|
||||
folderSupport?: boolean,
|
||||
publicFolder = this.publicFolder,
|
||||
) {
|
||||
const files: { path: string; url: string; isDirectory: boolean }[] = await this.request({
|
||||
action: 'getMedia',
|
||||
params: { branch: this.branch, mediaFolder, publicFolder },
|
||||
});
|
||||
|
||||
return files.map(({ url, path }) => {
|
||||
const filteredFiles = folderSupport ? files : files.filter(f => !f.isDirectory);
|
||||
|
||||
return filteredFiles.map(({ url, path, isDirectory }) => {
|
||||
const id = url;
|
||||
const name = basename(path);
|
||||
|
||||
return { id, name, displayURL: { id, path: url }, path };
|
||||
return { id, name, displayURL: { id, path: url }, path, isDirectory };
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user