Files
coopgo/node_modules/decap-cms-core/dist/esm/backend.js
sgauthier 6e64e138e2
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s
planning
2024-10-14 09:15:30 +02:00

1104 lines
43 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.currentBackend = exports.LocalStorageAuthStore = exports.Backend = void 0;
exports.expandSearchEntries = expandSearchEntries;
exports.extractSearchFields = extractSearchFields;
exports.mergeExpandedEntries = mergeExpandedEntries;
exports.resolveBackend = resolveBackend;
exports.slugFromCustomPath = slugFromCustomPath;
var _set2 = _interopRequireDefault(require("lodash/set"));
var _get2 = _interopRequireDefault(require("lodash/get"));
var _sortBy2 = _interopRequireDefault(require("lodash/sortBy"));
var _trim2 = _interopRequireDefault(require("lodash/trim"));
var _uniq2 = _interopRequireDefault(require("lodash/uniq"));
var _isError2 = _interopRequireDefault(require("lodash/isError"));
var _flatten2 = _interopRequireDefault(require("lodash/flatten"));
var _attempt2 = _interopRequireDefault(require("lodash/attempt"));
var _immutable = require("immutable");
var fuzzy = _interopRequireWildcard(require("fuzzy"));
var _decapCmsLibUtil = require("decap-cms-lib-util");
var _path = require("path");
var _decapCmsLibWidgets = require("decap-cms-lib-widgets");
var _formats = require("./formats/formats");
var _config = require("./reducers/config");
var _entries = require("./reducers/entries");
var _integrations = require("./reducers/integrations");
var _collections = require("./reducers/collections");
var _Entry = require("./valueObjects/Entry");
var _urlHelper = require("./lib/urlHelper");
var _registry = require("./lib/registry");
var _formatters = require("./lib/formatters");
var _publishModes = require("./constants/publishModes");
var _collectionTypes = require("./constants/collectionTypes");
var _entryDraft = require("./reducers/entryDraft");
var _i18n = require("./lib/i18n");
const _excluded = ["field"];
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
const {
extractTemplateVars,
dateParsers,
expandPath
} = _decapCmsLibWidgets.stringTemplate;
function updateAssetProxies(assetProxies, config, collection, entryDraft, path) {
assetProxies.map(asset => {
// update media files path based on entry path
const oldPath = asset.path;
const newPath = (0, _entries.selectMediaFilePath)(config, collection, entryDraft.get('entry').set('path', path), oldPath, asset.field);
asset.path = newPath;
});
}
class LocalStorageAuthStore {
constructor() {
_defineProperty(this, "storageKey", 'decap-cms-user');
}
retrieve() {
const data = window.localStorage.getItem(this.storageKey);
return data && JSON.parse(data);
}
store(userData) {
window.localStorage.setItem(this.storageKey, JSON.stringify(userData));
}
logout() {
window.localStorage.removeItem(this.storageKey);
}
}
exports.LocalStorageAuthStore = LocalStorageAuthStore;
function getEntryBackupKey(collectionName, slug) {
const baseKey = 'backup';
if (!collectionName) {
return baseKey;
}
const suffix = slug ? `.${slug}` : '';
return `${baseKey}.${collectionName}${suffix}`;
}
function getEntryField(field, entry) {
const value = (0, _get2.default)(entry.data, field);
if (value) {
return String(value);
} else {
const firstFieldPart = field.split('.')[0];
if (entry[firstFieldPart]) {
// allows searching using entry.slug/entry.path etc.
return entry[firstFieldPart];
} else {
return '';
}
}
}
function extractSearchFields(searchFields) {
return entry => searchFields.reduce((acc, field) => {
const value = getEntryField(field, entry);
if (value) {
return `${acc} ${value}`;
} else {
return acc;
}
}, '');
}
function expandSearchEntries(entries, searchFields) {
// expand the entries for the purpose of the search
const expandedEntries = entries.reduce((acc, e) => {
const expandedFields = searchFields.reduce((acc, f) => {
const fields = expandPath({
data: e.data,
path: f
});
acc.push(...fields);
return acc;
}, []);
for (let i = 0; i < expandedFields.length; i++) {
acc.push(_objectSpread(_objectSpread({}, e), {}, {
field: expandedFields[i]
}));
}
return acc;
}, []);
return expandedEntries;
}
function mergeExpandedEntries(entries) {
// merge the search results by slug and only keep data that matched the search
const fields = entries.map(f => f.field);
const arrayPaths = {};
const merged = entries.reduce((acc, e) => {
if (!acc[e.slug]) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {
field
} = e,
rest = _objectWithoutProperties(e, _excluded);
acc[e.slug] = rest;
arrayPaths[e.slug] = (0, _immutable.Set)();
}
const nestedFields = e.field.split('.');
let value = acc[e.slug].data;
for (let i = 0; i < nestedFields.length; i++) {
value = value[nestedFields[i]];
if (Array.isArray(value)) {
const path = nestedFields.slice(0, i + 1).join('.');
arrayPaths[e.slug] = arrayPaths[e.slug].add(path);
}
}
return acc;
}, {});
// this keeps the search score sorting order designated by the order in entries
// and filters non matching items
Object.keys(merged).forEach(slug => {
const data = merged[slug].data;
for (const path of arrayPaths[slug].toArray()) {
const array = (0, _get2.default)(data, path);
const filtered = array.filter((_, index) => {
return fields.some(f => `${f}.`.startsWith(`${path}.${index}.`));
});
filtered.sort((a, b) => {
const indexOfA = array.indexOf(a);
const indexOfB = array.indexOf(b);
const pathOfA = `${path}.${indexOfA}.`;
const pathOfB = `${path}.${indexOfB}.`;
const matchingFieldIndexA = fields.findIndex(f => `${f}.`.startsWith(pathOfA));
const matchingFieldIndexB = fields.findIndex(f => `${f}.`.startsWith(pathOfB));
return matchingFieldIndexA - matchingFieldIndexB;
});
(0, _set2.default)(data, path, filtered);
}
});
return Object.values(merged);
}
function sortByScore(a, b) {
if (a.score > b.score) return -1;
if (a.score < b.score) return 1;
return 0;
}
function slugFromCustomPath(collection, customPath) {
const folderPath = collection.get('folder', '');
const entryPath = customPath.toLowerCase().replace(folderPath.toLowerCase(), '');
const slug = (0, _path.join)((0, _path.dirname)((0, _trim2.default)(entryPath, '/')), (0, _path.basename)(entryPath, (0, _path.extname)(customPath)));
return slug;
}
function prepareMetaPath(path, collection) {
if (!(0, _collections.selectHasMetaPath)(collection)) {
return path;
}
const dir = (0, _path.dirname)(path);
return dir.slice(collection.get('folder').length + 1) || '/';
}
function collectionDepth(collection) {
var _collection$get;
let depth;
depth = ((_collection$get = collection.get('nested')) === null || _collection$get === void 0 ? void 0 : _collection$get.get('depth')) || (0, _decapCmsLibUtil.getPathDepth)(collection.get('path', ''));
if ((0, _i18n.hasI18n)(collection)) {
depth = (0, _i18n.getI18nFilesDepth)(collection, depth);
}
return depth;
}
function i18nRulestring(ruleString, {
defaultLocale,
structure
}) {
if (structure === _i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS) {
return `${defaultLocale}\\/${ruleString}`;
}
if (structure === _i18n.I18N_STRUCTURE.MULTIPLE_FILES) {
return `${ruleString}\\.${defaultLocale}\\..*`;
}
return ruleString;
}
function collectionRegex(collection) {
let ruleString = '';
if (collection.get('path')) {
ruleString = `${collection.get('folder')}/${collection.get('path')}`.replace(/{{.*}}/gm, '(.*)');
}
if ((0, _i18n.hasI18n)(collection)) {
ruleString = i18nRulestring(ruleString, (0, _i18n.getI18nInfo)(collection));
}
return ruleString ? new RegExp(ruleString) : undefined;
}
class Backend {
constructor(implementation, {
backendName,
authStore,
config
}) {
_defineProperty(this, "implementation", void 0);
_defineProperty(this, "backendName", void 0);
_defineProperty(this, "config", void 0);
_defineProperty(this, "authStore", void 0);
_defineProperty(this, "user", void 0);
_defineProperty(this, "backupSync", void 0);
_defineProperty(this, "updateUserCredentials", updatedCredentials => {
const storedUser = this.authStore.retrieve();
if (storedUser && storedUser.backendName === this.backendName) {
this.user = _objectSpread(_objectSpread({}, storedUser), updatedCredentials);
this.authStore.store(this.user);
return this.user;
}
});
_defineProperty(this, "getToken", () => this.implementation.getToken());
// We can't reliably run this on exit, so we do cleanup on load.
this.deleteAnonymousBackup();
this.config = config;
this.implementation = implementation.init(this.config, {
useWorkflow: (0, _config.selectUseWorkflow)(this.config),
updateUserCredentials: this.updateUserCredentials,
initialWorkflowStatus: _publishModes.status.first()
});
this.backendName = backendName;
this.authStore = authStore;
if (this.implementation === null) {
throw new Error('Cannot instantiate a Backend with no implementation');
}
this.backupSync = (0, _decapCmsLibUtil.asyncLock)();
}
async status() {
const attempts = 3;
let status = {
auth: {
status: true
},
api: {
status: true,
statusPage: ''
}
};
for (let i = 1; i <= attempts; i++) {
status = await this.implementation.status();
// return on first success
if (Object.values(status).every(s => s.status === true)) {
return status;
} else {
await new Promise(resolve => setTimeout(resolve, i * 1000));
}
}
return status;
}
currentUser() {
if (this.user) {
return this.user;
}
const stored = this.authStore.retrieve();
if (stored && stored.backendName === this.backendName) {
return Promise.resolve(this.implementation.restoreUser(stored)).then(user => {
this.user = _objectSpread(_objectSpread({}, user), {}, {
backendName: this.backendName
});
// return confirmed/rehydrated user object instead of stored
this.authStore.store(this.user);
return this.user;
});
}
return Promise.resolve(null);
}
isGitBackend() {
var _this$implementation$, _this$implementation;
return ((_this$implementation$ = (_this$implementation = this.implementation).isGitBackend) === null || _this$implementation$ === void 0 ? void 0 : _this$implementation$.call(_this$implementation)) || false;
}
authComponent() {
return this.implementation.authComponent();
}
authenticate(credentials) {
return this.implementation.authenticate(credentials).then(user => {
this.user = _objectSpread(_objectSpread({}, user), {}, {
backendName: this.backendName
});
if (this.authStore) {
this.authStore.store(this.user);
}
return this.user;
});
}
async logout() {
try {
await this.implementation.logout();
} catch (e) {
console.warn('Error during logout', e.message);
} finally {
this.user = null;
if (this.authStore) {
this.authStore.logout();
}
}
}
async entryExist(collection, path, slug, useWorkflow) {
const unpublishedEntry = useWorkflow && (await this.implementation.unpublishedEntry({
collection: collection.get('name'),
slug
}).catch(error => {
if (error.name === _decapCmsLibUtil.EDITORIAL_WORKFLOW_ERROR && error.notUnderEditorialWorkflow) {
return Promise.resolve(false);
}
return Promise.reject(error);
}));
if (unpublishedEntry) return unpublishedEntry;
const publishedEntry = await this.implementation.getEntry(path).then(({
data
}) => data).catch(() => {
return Promise.resolve(false);
});
return publishedEntry;
}
async generateUniqueSlug(collection, entryData, config, usedSlugs, customPath) {
const slugConfig = config.slug;
let slug;
if (customPath) {
slug = slugFromCustomPath(collection, customPath);
} else {
slug = (0, _formatters.slugFormatter)(collection, entryData, slugConfig);
}
let i = 1;
let uniqueSlug = slug;
// Check for duplicate slug in loaded entities store first before repo
while (usedSlugs.includes(uniqueSlug) || (await this.entryExist(collection, (0, _collections.selectEntryPath)(collection, uniqueSlug), uniqueSlug, (0, _config.selectUseWorkflow)(config)))) {
uniqueSlug = `${slug}${(0, _urlHelper.sanitizeChar)(' ', slugConfig)}${i++}`;
}
return uniqueSlug;
}
processEntries(loadedEntries, collection) {
const entries = loadedEntries.map(loadedEntry => (0, _Entry.createEntry)(collection.get('name'), (0, _collections.selectEntrySlug)(collection, loadedEntry.file.path), loadedEntry.file.path, {
raw: loadedEntry.data || '',
label: loadedEntry.file.label,
author: loadedEntry.file.author,
updatedOn: loadedEntry.file.updatedOn,
meta: {
path: prepareMetaPath(loadedEntry.file.path, collection)
}
}));
const formattedEntries = entries.map(this.entryWithFormat(collection));
// If this collection has a "filter" property, filter entries accordingly
const collectionFilter = collection.get('filter');
const filteredEntries = collectionFilter ? this.filterEntries({
entries: formattedEntries
}, collectionFilter) : formattedEntries;
if ((0, _i18n.hasI18n)(collection)) {
const extension = (0, _collections.selectFolderEntryExtension)(collection);
const groupedEntries = (0, _i18n.groupEntries)(collection, extension, filteredEntries);
return groupedEntries;
}
return filteredEntries;
}
async listEntries(collection) {
var _cursor$meta;
const extension = (0, _collections.selectFolderEntryExtension)(collection);
let listMethod;
const collectionType = collection.get('type');
if (collectionType === _collectionTypes.FOLDER) {
listMethod = () => {
const depth = collectionDepth(collection);
return this.implementation.entriesByFolder(collection.get('folder'), extension, depth);
};
} else if (collectionType === _collectionTypes.FILES) {
const files = collection.get('files').map(collectionFile => ({
path: collectionFile.get('file'),
label: collectionFile.get('label')
})).toArray();
listMethod = () => this.implementation.entriesByFiles(files);
} else {
throw new Error(`Unknown collection type: ${collectionType}`);
}
const loadedEntries = await listMethod();
/*
Wrap cursors so we can tell which collection the cursor is
from. This is done to prevent traverseCursor from requiring a
`collection` argument.
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const cursor = _decapCmsLibUtil.Cursor.create(loadedEntries[_decapCmsLibUtil.CURSOR_COMPATIBILITY_SYMBOL]).wrapData({
cursorType: 'collectionEntries',
collection
});
return {
entries: this.processEntries(loadedEntries, collection),
pagination: (_cursor$meta = cursor.meta) === null || _cursor$meta === void 0 ? void 0 : _cursor$meta.get('page'),
cursor
};
}
// The same as listEntries, except that if a cursor with the "next"
// action available is returned, it calls "next" on the cursor and
// repeats the process. Once there is no available "next" action, it
// returns all the collected entries. Used to retrieve all entries
// for local searches and queries.
async listAllEntries(collection) {
if (collection.get('folder') && this.implementation.allEntriesByFolder) {
const depth = collectionDepth(collection);
const extension = (0, _collections.selectFolderEntryExtension)(collection);
return this.implementation.allEntriesByFolder(collection.get('folder'), extension, depth, collectionRegex(collection)).then(entries => this.processEntries(entries, collection));
}
const response = await this.listEntries(collection);
const {
entries
} = response;
let {
cursor
} = response;
while (cursor && cursor.actions.includes('next')) {
const {
entries: newEntries,
cursor: newCursor
} = await this.traverseCursor(cursor, 'next');
entries.push(...newEntries);
cursor = newCursor;
}
return entries;
}
async search(collections, searchTerm) {
// Perform a local search by requesting all entries. For each
// collection, load it, search, and call onCollectionResults with
// its results.
const errors = [];
const collectionEntriesRequests = collections.map(async collection => {
const summary = collection.get('summary', '');
const summaryFields = extractTemplateVars(summary);
// TODO: pass search fields in as an argument
let searchFields = [];
if (collection.get('type') === _collectionTypes.FILES) {
var _collection$get2;
(_collection$get2 = collection.get('files')) === null || _collection$get2 === void 0 ? void 0 : _collection$get2.forEach(f => {
const topLevelFields = f.get('fields').map(f => f.get('name')).toArray();
searchFields = [...searchFields, ...topLevelFields];
});
} else {
searchFields = [(0, _collections.selectInferredField)(collection, 'title'), (0, _collections.selectInferredField)(collection, 'shortTitle'), (0, _collections.selectInferredField)(collection, 'author'), ...summaryFields.map(elem => {
if (dateParsers[elem]) {
return (0, _collections.selectInferredField)(collection, 'date');
}
return elem;
})];
}
const filteredSearchFields = searchFields.filter(Boolean);
const collectionEntries = await this.listAllEntries(collection);
return fuzzy.filter(searchTerm, collectionEntries, {
extract: extractSearchFields((0, _uniq2.default)(filteredSearchFields))
});
}).map(p => p.catch(err => {
errors.push(err);
return [];
}));
const entries = await Promise.all(collectionEntriesRequests).then(arrays => (0, _flatten2.default)(arrays));
if (errors.length > 0) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
throw new Error({
message: 'Errors occurred while searching entries locally!',
errors
});
}
const hits = entries.filter(({
score
}) => score > 5).sort(sortByScore).map(f => f.original);
return {
entries: hits
};
}
async query(collection, searchFields, searchTerm, file, limit) {
let entries = await this.listAllEntries(collection);
if (file) {
entries = entries.filter(e => e.slug === file);
}
const expandedEntries = expandSearchEntries(entries, searchFields);
let hits = fuzzy.filter(searchTerm, expandedEntries, {
extract: entry => {
return getEntryField(entry.field, entry);
}
}).sort(sortByScore).map(f => f.original);
if (limit !== undefined && limit > 0) {
hits = hits.slice(0, limit);
}
const merged = mergeExpandedEntries(hits);
return {
query: searchTerm,
hits: merged
};
}
traverseCursor(cursor, action) {
const [data, unwrappedCursor] = cursor.unwrapData();
// TODO: stop assuming all cursors are for collections
const collection = data.get('collection');
return this.implementation.traverseCursor(unwrappedCursor, action).then(async ({
entries,
cursor: newCursor
}) => ({
entries: this.processEntries(entries, collection),
cursor: _decapCmsLibUtil.Cursor.create(newCursor).wrapData({
cursorType: 'collectionEntries',
collection
})
}));
}
async getLocalDraftBackup(collection, slug) {
const key = getEntryBackupKey(collection.get('name'), slug);
const backup = await _decapCmsLibUtil.localForage.getItem(key);
if (!backup || !backup.raw.trim()) {
return {};
}
const {
raw,
path
} = backup;
let {
mediaFiles = []
} = backup;
mediaFiles = mediaFiles.map(file => {
// de-serialize the file object
if (file.file) {
return _objectSpread(_objectSpread({}, file), {}, {
url: URL.createObjectURL(file.file)
});
}
return file;
});
const label = (0, _collections.selectFileEntryLabel)(collection, slug);
const formatRawData = raw => {
return this.entryWithFormat(collection)((0, _Entry.createEntry)(collection.get('name'), slug, path, {
raw,
label,
mediaFiles,
meta: {
path: prepareMetaPath(path, collection)
}
}));
};
const entry = formatRawData(raw);
if ((0, _i18n.hasI18n)(collection) && backup.i18n) {
const i18n = (0, _i18n.formatI18nBackup)(backup.i18n, formatRawData);
entry.i18n = i18n;
}
return {
entry
};
}
async persistLocalDraftBackup(entry, collection) {
try {
await this.backupSync.acquire();
const key = getEntryBackupKey(collection.get('name'), entry.get('slug'));
const raw = this.entryToRaw(collection, entry);
if (!raw.trim()) {
return;
}
const mediaFiles = await Promise.all(entry.get('mediaFiles').toJS().map(async file => {
var _file$url;
// make sure to serialize the file
if ((_file$url = file.url) !== null && _file$url !== void 0 && _file$url.startsWith('blob:')) {
const blob = await fetch(file.url).then(res => res.blob());
return _objectSpread(_objectSpread({}, file), {}, {
file: (0, _decapCmsLibUtil.blobToFileObj)(file.name, blob)
});
}
return file;
}));
let i18n;
if ((0, _i18n.hasI18n)(collection)) {
i18n = (0, _i18n.getI18nBackup)(collection, entry, entry => this.entryToRaw(collection, entry));
}
await _decapCmsLibUtil.localForage.setItem(key, _objectSpread({
raw,
path: entry.get('path'),
mediaFiles
}, i18n && {
i18n
}));
const result = await _decapCmsLibUtil.localForage.setItem(getEntryBackupKey(), raw);
return result;
} catch (e) {
console.warn('persistLocalDraftBackup', e);
} finally {
this.backupSync.release();
}
}
async deleteLocalDraftBackup(collection, slug) {
try {
await this.backupSync.acquire();
await _decapCmsLibUtil.localForage.removeItem(getEntryBackupKey(collection.get('name'), slug));
// delete new entry backup if not deleted
slug && (await _decapCmsLibUtil.localForage.removeItem(getEntryBackupKey(collection.get('name'))));
const result = await this.deleteAnonymousBackup();
return result;
} catch (e) {
console.warn('deleteLocalDraftBackup', e);
} finally {
this.backupSync.release();
}
}
// Unnamed backup for use in the global error boundary, should always be
// deleted on cms load.
deleteAnonymousBackup() {
return _decapCmsLibUtil.localForage.removeItem(getEntryBackupKey());
}
async getEntry(state, collection, slug) {
const path = (0, _collections.selectEntryPath)(collection, slug);
const label = (0, _collections.selectFileEntryLabel)(collection, slug);
const extension = (0, _collections.selectFolderEntryExtension)(collection);
const getEntryValue = async path => {
const loadedEntry = await this.implementation.getEntry(path);
let entry = (0, _Entry.createEntry)(collection.get('name'), slug, loadedEntry.file.path, {
raw: loadedEntry.data,
label,
mediaFiles: [],
meta: {
path: prepareMetaPath(loadedEntry.file.path, collection)
}
});
entry = this.entryWithFormat(collection)(entry);
entry = await this.processEntry(state, collection, entry);
return entry;
};
let entryValue;
if ((0, _i18n.hasI18n)(collection)) {
entryValue = await (0, _i18n.getI18nEntry)(collection, extension, path, slug, getEntryValue);
} else {
entryValue = await getEntryValue(path);
}
return entryValue;
}
getMedia() {
return this.implementation.getMedia();
}
getMediaFile(path) {
return this.implementation.getMediaFile(path);
}
getMediaDisplayURL(displayURL) {
if (this.implementation.getMediaDisplayURL) {
return this.implementation.getMediaDisplayURL(displayURL);
}
const err = new Error('getMediaDisplayURL is not implemented by the current backend, but the backend returned a displayURL which was not a string!');
err.displayURL = displayURL;
return Promise.reject(err);
}
entryWithFormat(collection) {
return entry => {
const format = (0, _formats.resolveFormat)(collection, entry);
if (entry && entry.raw !== undefined) {
const data = format && (0, _attempt2.default)(format.fromFile.bind(format, entry.raw)) || {};
if ((0, _isError2.default)(data)) console.error(data);
return Object.assign(entry, {
data: (0, _isError2.default)(data) ? {} : data
});
}
return format.fromFile(entry);
};
}
async processUnpublishedEntry(collection, entryData, withMediaFiles) {
const {
slug
} = entryData;
let extension;
if (collection.get('type') === _collectionTypes.FILES) {
const file = collection.get('files').find(f => (f === null || f === void 0 ? void 0 : f.get('name')) === slug);
extension = (0, _path.extname)(file.get('file'));
} else {
extension = (0, _collections.selectFolderEntryExtension)(collection);
}
const mediaFiles = [];
if (withMediaFiles) {
const nonDataFiles = entryData.diffs.filter(d => !d.path.endsWith(extension));
const files = await Promise.all(nonDataFiles.map(f => this.implementation.unpublishedEntryMediaFile(collection.get('name'), slug, f.path, f.id)));
mediaFiles.push(...files.map(f => _objectSpread(_objectSpread({}, f), {}, {
draft: true
})));
}
const dataFiles = (0, _sortBy2.default)(entryData.diffs.filter(d => d.path.endsWith(extension)), f => f.path.length);
const formatData = (data, path, newFile) => {
const entry = (0, _Entry.createEntry)(collection.get('name'), slug, path, {
raw: data,
isModification: !newFile,
label: collection && (0, _collections.selectFileEntryLabel)(collection, slug),
mediaFiles,
updatedOn: entryData.updatedAt,
author: entryData.pullRequestAuthor,
status: entryData.status,
meta: {
path: prepareMetaPath(path, collection)
}
});
const entryWithFormat = this.entryWithFormat(collection)(entry);
return entryWithFormat;
};
const readAndFormatDataFile = async dataFile => {
const data = await this.implementation.unpublishedEntryDataFile(collection.get('name'), entryData.slug, dataFile.path, dataFile.id);
const entryWithFormat = formatData(data, dataFile.path, dataFile.newFile);
return entryWithFormat;
};
// if the unpublished entry has no diffs, return the original
if (dataFiles.length <= 0) {
const loadedEntry = await this.implementation.getEntry((0, _collections.selectEntryPath)(collection, slug));
return formatData(loadedEntry.data, loadedEntry.file.path, false);
} else if ((0, _i18n.hasI18n)(collection)) {
// we need to read all locales files and not just the changes
const path = (0, _collections.selectEntryPath)(collection, slug);
const i18nFiles = (0, _i18n.getI18nDataFiles)(collection, extension, path, slug, dataFiles);
let entries = await Promise.all(i18nFiles.map(dataFile => readAndFormatDataFile(dataFile).catch(() => null)));
entries = entries.filter(Boolean);
const grouped = await (0, _i18n.groupEntries)(collection, extension, entries);
return grouped[0];
} else {
const entryWithFormat = await readAndFormatDataFile(dataFiles[0]);
return entryWithFormat;
}
}
async unpublishedEntries(collections) {
const ids = await this.implementation.unpublishedEntries();
const entries = (await Promise.all(ids.map(async id => {
const entryData = await this.implementation.unpublishedEntry({
id
});
const collectionName = entryData.collection;
const collection = collections.find(c => c.get('name') === collectionName);
if (!collection) {
console.warn(`Missing collection '${collectionName}' for unpublished entry '${id}'`);
return null;
}
const entry = await this.processUnpublishedEntry(collection, entryData, false);
return entry;
}))).filter(Boolean);
return {
pagination: 0,
entries
};
}
async processEntry(state, collection, entry) {
const integration = (0, _integrations.selectIntegration)(state.integrations, null, 'assetStore');
const mediaFolders = (0, _collections.selectMediaFolders)(state.config, collection, (0, _immutable.fromJS)(entry));
if (mediaFolders.length > 0 && !integration) {
const files = await Promise.all(mediaFolders.map(folder => this.implementation.getMedia(folder)));
entry.mediaFiles = entry.mediaFiles.concat(...files);
} else {
entry.mediaFiles = entry.mediaFiles.concat(state.mediaLibrary.get('files') || []);
}
return entry;
}
async unpublishedEntry(state, collection, slug) {
const entryData = await this.implementation.unpublishedEntry({
collection: collection.get('name'),
slug
});
let entry = await this.processUnpublishedEntry(collection, entryData, true);
entry = await this.processEntry(state, collection, entry);
return entry;
}
/**
* Creates a URL using `site_url` from the config and `preview_path` from the
* entry's collection. Does not currently make a request through the backend,
* but likely will in the future.
*/
getDeploy(collection, slug, entry) {
/**
* If `site_url` is undefined or `show_preview_links` in the config is set to false, do nothing.
*/
const baseUrl = this.config.site_url;
if (!baseUrl || this.config.show_preview_links === false) {
return;
}
return {
url: (0, _formatters.previewUrlFormatter)(baseUrl, collection, slug, entry, this.config.slug),
status: 'SUCCESS'
};
}
/**
* Requests a base URL from the backend for previewing a specific entry.
* Supports polling via `maxAttempts` and `interval` options, as there is
* often a delay before a preview URL is available.
*/
async getDeployPreview(collection, slug, entry, {
maxAttempts = 1,
interval = 5000
} = {}) {
/**
* If the registered backend does not provide a `getDeployPreview` method, or
* `show_preview_links` in the config is set to false, do nothing.
*/
if (!this.implementation.getDeployPreview || this.config.show_preview_links === false) {
return;
}
/**
* Poll for the deploy preview URL (defaults to 1 attempt, so no polling by
* default).
*/
let deployPreview,
count = 0;
while (!deployPreview && count < maxAttempts) {
count++;
deployPreview = await this.implementation.getDeployPreview(collection.get('name'), slug);
if (!deployPreview) {
await new Promise(resolve => setTimeout(() => resolve(undefined), interval));
}
}
/**
* If there's no deploy preview, do nothing.
*/
if (!deployPreview) {
return;
}
return {
/**
* Create a URL using the collection `preview_path`, if provided.
*/
url: (0, _formatters.previewUrlFormatter)(deployPreview.url, collection, slug, entry, this.config.slug),
/**
* Always capitalize the status for consistency.
*/
status: deployPreview.status ? deployPreview.status.toUpperCase() : ''
};
}
async persistEntry({
config,
collection,
entryDraft: draft,
assetProxies,
usedSlugs,
unpublished = false,
status
}) {
const updatedEntity = await this.invokePreSaveEvent(draft.get('entry'));
let entryDraft;
if (updatedEntity.get('data') === undefined) {
entryDraft = updatedEntity && draft.setIn(['entry', 'data'], updatedEntity) || draft;
} else {
entryDraft = updatedEntity && draft.setIn(['entry'], updatedEntity) || draft;
}
const newEntry = entryDraft.getIn(['entry', 'newRecord']) || false;
const useWorkflow = (0, _config.selectUseWorkflow)(config);
const customPath = (0, _entryDraft.selectCustomPath)(collection, entryDraft);
let dataFile;
if (newEntry) {
if (!(0, _collections.selectAllowNewEntries)(collection)) {
throw new Error('Not allowed to create new entries in this collection');
}
const slug = await this.generateUniqueSlug(collection, entryDraft.getIn(['entry', 'data']), config, usedSlugs, customPath);
const path = customPath || (0, _collections.selectEntryPath)(collection, slug);
dataFile = {
path,
slug,
raw: this.entryToRaw(collection, entryDraft.get('entry'))
};
updateAssetProxies(assetProxies, config, collection, entryDraft, path);
} else {
const slug = entryDraft.getIn(['entry', 'slug']);
dataFile = {
path: entryDraft.getIn(['entry', 'path']),
// for workflow entries we refresh the slug on publish
slug: customPath && !useWorkflow ? slugFromCustomPath(collection, customPath) : slug,
raw: this.entryToRaw(collection, entryDraft.get('entry')),
newPath: customPath
};
}
const {
slug,
path,
newPath
} = dataFile;
let dataFiles = [dataFile];
if ((0, _i18n.hasI18n)(collection)) {
const extension = (0, _collections.selectFolderEntryExtension)(collection);
dataFiles = (0, _i18n.getI18nFiles)(collection, extension, entryDraft.get('entry'), draftData => this.entryToRaw(collection, draftData), path, slug, newPath);
}
const user = await this.currentUser();
const commitMessage = (0, _formatters.commitMessageFormatter)(newEntry ? 'create' : 'update', config, {
collection,
slug,
path,
authorLogin: user.login,
authorName: user.name
}, user.useOpenAuthoring);
const collectionName = collection.get('name');
const updatedOptions = {
unpublished,
status
};
const opts = _objectSpread({
newEntry,
commitMessage,
collectionName,
useWorkflow
}, updatedOptions);
if (!useWorkflow) {
await this.invokePrePublishEvent(entryDraft.get('entry'));
}
await this.implementation.persistEntry({
dataFiles,
assets: assetProxies
}, opts);
await this.invokePostSaveEvent(entryDraft.get('entry'));
if (!useWorkflow) {
await this.invokePostPublishEvent(entryDraft.get('entry'));
}
return slug;
}
async invokeEventWithEntry(event, entry) {
const {
login,
name
} = await this.currentUser();
return await (0, _registry.invokeEvent)({
name: event,
data: {
entry,
author: {
login,
name
}
}
});
}
async invokePrePublishEvent(entry) {
await this.invokeEventWithEntry('prePublish', entry);
}
async invokePostPublishEvent(entry) {
await this.invokeEventWithEntry('postPublish', entry);
}
async invokePreUnpublishEvent(entry) {
await this.invokeEventWithEntry('preUnpublish', entry);
}
async invokePostUnpublishEvent(entry) {
await this.invokeEventWithEntry('postUnpublish', entry);
}
async invokePreSaveEvent(entry) {
return await this.invokeEventWithEntry('preSave', entry);
}
async invokePostSaveEvent(entry) {
await this.invokeEventWithEntry('postSave', entry);
}
async persistMedia(config, file) {
const user = await this.currentUser();
const options = {
commitMessage: (0, _formatters.commitMessageFormatter)('uploadMedia', config, {
path: file.path,
authorLogin: user.login,
authorName: user.name
}, user.useOpenAuthoring)
};
return this.implementation.persistMedia(file, options);
}
async deleteEntry(state, collection, slug) {
const config = state.config;
const path = (0, _collections.selectEntryPath)(collection, slug);
const extension = (0, _collections.selectFolderEntryExtension)(collection);
if (!(0, _collections.selectAllowDeletion)(collection)) {
throw new Error('Not allowed to delete entries in this collection');
}
const user = await this.currentUser();
const commitMessage = (0, _formatters.commitMessageFormatter)('delete', config, {
collection,
slug,
path,
authorLogin: user.login,
authorName: user.name
}, user.useOpenAuthoring);
const entry = (0, _entries.selectEntry)(state.entries, collection.get('name'), slug);
await this.invokePreUnpublishEvent(entry);
let paths = [path];
if ((0, _i18n.hasI18n)(collection)) {
paths = (0, _i18n.getFilePaths)(collection, extension, path, slug);
}
await this.implementation.deleteFiles(paths, commitMessage);
await this.invokePostUnpublishEvent(entry);
}
async deleteMedia(config, path) {
const user = await this.currentUser();
const commitMessage = (0, _formatters.commitMessageFormatter)('deleteMedia', config, {
path,
authorLogin: user.login,
authorName: user.name
}, user.useOpenAuthoring);
return this.implementation.deleteFiles([path], commitMessage);
}
persistUnpublishedEntry(args) {
return this.persistEntry(_objectSpread(_objectSpread({}, args), {}, {
unpublished: true
}));
}
updateUnpublishedEntryStatus(collection, slug, newStatus) {
return this.implementation.updateUnpublishedEntryStatus(collection, slug, newStatus);
}
async publishUnpublishedEntry(entry) {
const collection = entry.get('collection');
const slug = entry.get('slug');
await this.invokePrePublishEvent(entry);
await this.implementation.publishUnpublishedEntry(collection, slug);
await this.invokePostPublishEvent(entry);
}
deleteUnpublishedEntry(collection, slug) {
return this.implementation.deleteUnpublishedEntry(collection, slug);
}
entryToRaw(collection, entry) {
const format = (0, _formats.resolveFormat)(collection, entry.toJS());
const fieldsOrder = this.fieldsOrder(collection, entry);
const fieldsComments = (0, _collections.selectFieldsComments)(collection, entry);
let content = format.toFile(entry.get('data').toJS(), fieldsOrder, fieldsComments);
if (content.slice(-1) != '\n') {
// add the EOL if it does not exist.
content += '\n';
}
return content;
}
fieldsOrder(collection, entry) {
const fields = collection.get('fields');
if (fields) {
return collection.get('fields').map(f => f.get('name')).toArray();
}
const files = collection.get('files');
const file = (files || (0, _immutable.List)()).filter(f => f.get('name') === entry.get('slug')).get(0);
if (file == null) {
throw new Error(`No file found for ${entry.get('slug')} in ${collection.get('name')}`);
}
return file.get('fields').map(f => f.get('name')).toArray();
}
filterEntries(collection, filterRule) {
return collection.entries.filter(entry => {
const fieldValue = entry.data[filterRule.get('field')];
if (Array.isArray(fieldValue)) {
return fieldValue.includes(filterRule.get('value'));
}
return fieldValue === filterRule.get('value');
});
}
}
exports.Backend = Backend;
function resolveBackend(config) {
if (!config.backend.name) {
throw new Error('No backend defined in configuration');
}
const {
name
} = config.backend;
const authStore = new LocalStorageAuthStore();
const backend = (0, _registry.getBackend)(name);
if (!backend) {
throw new Error(`Backend not found: ${name}`);
} else {
return new Backend(backend, {
backendName: name,
authStore,
config
});
}
}
const currentBackend = exports.currentBackend = function () {
let backend;
return config => {
if (backend) {
return backend;
}
return backend = resolveBackend(config);
};
}();