fix(gitlab): fetch media library images through API (#1433)
This commit is contained in:
committed by
Shawn Erquhart
parent
a4ba66e1a6
commit
83d2adc0be
@ -1,6 +1,6 @@
|
||||
import { localForage, unsentRequest, then, APIError, Cursor } from 'netlify-cms-lib-util';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { List, Map } from 'immutable';
|
||||
import { fromJS, List, Map } from 'immutable';
|
||||
import { flow, partial, result } from 'lodash';
|
||||
|
||||
export default class API {
|
||||
@ -29,22 +29,48 @@ export default class API {
|
||||
p => p.catch(err => Promise.reject(new APIError(err.message, null, 'GitLab'))),
|
||||
])(req);
|
||||
|
||||
parseResponse = async (res, { expectingOk = true, expectingFormat = false }) => {
|
||||
const contentType = res.headers.get('Content-Type');
|
||||
const isJSON = contentType === 'application/json';
|
||||
catchFormatErrors = (format, formatter) => res => {
|
||||
try {
|
||||
return formatter(res);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Response cannot be parsed into the expected format (${format}): ${err.message}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
responseFormats = fromJS({
|
||||
json: async res => {
|
||||
const contentType = res.headers.get('Content-Type');
|
||||
if (contentType !== 'application/json' && contentType !== 'text/json') {
|
||||
throw new Error(`${contentType} is not a valid JSON Content-Type`);
|
||||
}
|
||||
return res.json();
|
||||
},
|
||||
text: async res => res.text(),
|
||||
blob: async res => res.blob(),
|
||||
}).mapEntries(([format, formatter]) => [format, this.catchFormatErrors(format, formatter)]);
|
||||
|
||||
parseResponse = async (res, { expectingOk = true, expectingFormat = 'text' }) => {
|
||||
let body;
|
||||
try {
|
||||
body = await (expectingFormat === 'json' || isJSON ? res.json() : res.text());
|
||||
const formatter = this.responseFormats.get(expectingFormat, false);
|
||||
if (!formatter) {
|
||||
throw new Error(`${expectingFormat} is not a supported response format.`);
|
||||
}
|
||||
body = await formatter(res);
|
||||
} catch (err) {
|
||||
throw new APIError(err.message, res.status, 'GitLab');
|
||||
}
|
||||
if (expectingOk && !res.ok) {
|
||||
const isJSON = expectingFormat === 'json';
|
||||
throw new APIError(isJSON && body.message ? body.message : body, res.status, 'GitLab');
|
||||
}
|
||||
return body;
|
||||
};
|
||||
|
||||
responseToJSON = res => this.parseResponse(res, { expectingFormat: 'json' });
|
||||
responseToBlob = res => this.parseResponse(res, { expectingFormat: 'blob' });
|
||||
responseToText = res => this.parseResponse(res, { expectingFormat: 'text' });
|
||||
requestJSON = req => this.request(req).then(this.responseToJSON);
|
||||
requestText = req => this.request(req).then(this.responseToText);
|
||||
@ -64,30 +90,23 @@ export default class API {
|
||||
return false;
|
||||
});
|
||||
|
||||
readFile = async (path, sha, ref = this.branch) => {
|
||||
const cachedFile = sha ? await localForage.getItem(`gl.${sha}`) : null;
|
||||
readFile = async (path, sha, { ref = this.branch, parseText = true } = {}) => {
|
||||
const cacheKey = parseText ? `gl.${sha}` : `gl.${sha}.blob`;
|
||||
const cachedFile = sha ? await localForage.getItem(cacheKey) : null;
|
||||
if (cachedFile) {
|
||||
return cachedFile;
|
||||
}
|
||||
const result = await this.requestText({
|
||||
const result = await this.request({
|
||||
url: `${this.repoURL}/repository/files/${encodeURIComponent(path)}/raw`,
|
||||
params: { ref },
|
||||
cache: 'no-store',
|
||||
});
|
||||
}).then(parseText ? this.responseToText : this.responseToBlob);
|
||||
if (sha) {
|
||||
localForage.setItem(`gl.${sha}`, result);
|
||||
localForage.setItem(cacheKey, result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
fileDownloadURL = (path, ref = this.branch) =>
|
||||
unsentRequest.toURL(
|
||||
this.buildRequest({
|
||||
url: `${this.repoURL}/repository/files/${encodeURIComponent(path)}/raw`,
|
||||
params: { ref },
|
||||
}),
|
||||
);
|
||||
|
||||
getCursorFromHeaders = headers => {
|
||||
// indices and page counts are assumed to be zero-based, but the
|
||||
// indices and page counts returned from GitLab are one-based
|
||||
|
@ -132,13 +132,21 @@ export default class GitLab {
|
||||
}
|
||||
|
||||
getMedia() {
|
||||
const sem = semaphore(MAX_CONCURRENT_DOWNLOADS);
|
||||
|
||||
return this.api.listAllFiles(this.config.get('media_folder')).then(files =>
|
||||
files.map(({ id, name, path }) => {
|
||||
const url = new URL(this.api.fileDownloadURL(path));
|
||||
if (url.pathname.match(/.svg$/)) {
|
||||
url.search += (url.search.slice(1) === '' ? '?' : '&') + 'sanitize=true';
|
||||
}
|
||||
return { id, name, url: url.href, path };
|
||||
const getBlobPromise = () =>
|
||||
new Promise((resolve, reject) =>
|
||||
sem.take(() =>
|
||||
this.api
|
||||
.readFile(path, id, { parseText: false })
|
||||
.then(resolve, reject)
|
||||
.finally(() => sem.leave()),
|
||||
),
|
||||
);
|
||||
|
||||
return { id, name, getBlobPromise, path };
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -150,8 +158,8 @@ export default class GitLab {
|
||||
async persistMedia(mediaFile, options = {}) {
|
||||
await this.api.persistFiles([mediaFile], options);
|
||||
const { value, path, fileObj } = mediaFile;
|
||||
const url = this.api.fileDownloadURL(path);
|
||||
return { name: value, size: fileObj.size, url, path: trimStart(path, '/') };
|
||||
const getBlobPromise = () => Promise.resolve(fileObj);
|
||||
return { name: value, size: fileObj.size, getBlobPromise, path: trimStart(path, '/') };
|
||||
}
|
||||
|
||||
deleteFile(path, commitMessage, options) {
|
||||
|
Reference in New Issue
Block a user