2021-05-31 16:46:41 +02:00
|
|
|
import { stripIndent } from 'common-tags';
|
2022-09-28 20:04:00 -06:00
|
|
|
import { flow, partialRight, trimEnd, trimStart } from 'lodash';
|
2021-05-31 16:46:41 +02:00
|
|
|
|
2022-09-28 20:04:00 -06:00
|
|
|
import { FILES } from '../constants/collectionTypes';
|
|
|
|
import { COMMIT_AUTHOR, COMMIT_DATE } from '../constants/commitProps';
|
2022-09-30 06:13:47 -06:00
|
|
|
import { stringTemplate } from './widgets';
|
2020-01-22 20:42:24 +02:00
|
|
|
import {
|
2022-09-28 20:04:00 -06:00
|
|
|
getFileFromSlug,
|
2020-04-30 16:03:08 +03:00
|
|
|
selectField,
|
2022-09-28 20:04:00 -06:00
|
|
|
selectIdentifier,
|
2020-04-30 16:03:08 +03:00
|
|
|
selectInferedField,
|
|
|
|
} from '../reducers/collections';
|
2021-05-31 16:46:41 +02:00
|
|
|
import { sanitizeSlug } from './urlHelper';
|
2020-04-30 16:03:08 +03:00
|
|
|
|
2021-05-31 16:46:41 +02:00
|
|
|
import type { Map } from 'immutable';
|
2022-10-01 13:45:01 -04:00
|
|
|
import type { CmsConfig } from '../interface';
|
2022-09-28 20:04:00 -06:00
|
|
|
import type { CmsSlug, Collection, EntryMap } from '../types/redux';
|
2021-05-31 16:46:41 +02:00
|
|
|
|
2020-04-30 16:03:08 +03:00
|
|
|
const {
|
2020-01-22 20:42:24 +02:00
|
|
|
compileStringTemplate,
|
|
|
|
parseDateFromEntry,
|
|
|
|
SLUG_MISSING_REQUIRED_DATE,
|
2020-02-12 08:30:44 +02:00
|
|
|
keyToPathArray,
|
2020-04-30 16:03:08 +03:00
|
|
|
addFileTemplateFields,
|
|
|
|
} = stringTemplate;
|
2020-01-22 20:42:24 +02:00
|
|
|
|
2021-03-11 12:08:46 +02:00
|
|
|
const commitMessageTemplates = {
|
2020-01-22 20:42:24 +02:00
|
|
|
create: 'Create {{collection}} “{{slug}}”',
|
|
|
|
update: 'Update {{collection}} “{{slug}}”',
|
|
|
|
delete: 'Delete {{collection}} “{{slug}}”',
|
|
|
|
uploadMedia: 'Upload “{{path}}”',
|
|
|
|
deleteMedia: 'Delete “{{path}}”',
|
2021-03-11 12:08:46 +02:00
|
|
|
} as const;
|
2020-01-22 20:42:24 +02:00
|
|
|
|
|
|
|
const variableRegex = /\{\{([^}]+)\}\}/g;
|
|
|
|
|
|
|
|
type Options = {
|
|
|
|
slug?: string;
|
|
|
|
path?: string;
|
|
|
|
collection?: Collection;
|
|
|
|
authorLogin?: string;
|
|
|
|
authorName?: string;
|
|
|
|
};
|
|
|
|
|
2021-02-08 20:01:21 +02:00
|
|
|
export function commitMessageFormatter(
|
2021-03-11 12:08:46 +02:00
|
|
|
type: keyof typeof commitMessageTemplates,
|
|
|
|
config: CmsConfig,
|
2020-01-22 20:42:24 +02:00
|
|
|
{ slug, path, collection, authorLogin, authorName }: Options,
|
2021-02-08 20:01:21 +02:00
|
|
|
) {
|
2021-03-11 12:08:46 +02:00
|
|
|
const templates = { ...commitMessageTemplates, ...(config.backend.commit_messages || {}) };
|
2020-01-22 20:42:24 +02:00
|
|
|
|
2022-10-01 13:45:01 -04:00
|
|
|
return templates[type].replace(variableRegex, (_, variable) => {
|
2020-01-22 20:42:24 +02:00
|
|
|
switch (variable) {
|
|
|
|
case 'slug':
|
|
|
|
return slug || '';
|
|
|
|
case 'path':
|
|
|
|
return path || '';
|
|
|
|
case 'collection':
|
|
|
|
return collection ? collection.get('label_singular') || collection.get('label') : '';
|
2020-05-25 02:36:35 -04:00
|
|
|
case 'author-login':
|
|
|
|
return authorLogin || '';
|
|
|
|
case 'author-name':
|
|
|
|
return authorName || '';
|
2020-01-22 20:42:24 +02:00
|
|
|
default:
|
|
|
|
console.warn(`Ignoring unknown variable “${variable}” in commit message template.`);
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
});
|
2021-02-08 20:01:21 +02:00
|
|
|
}
|
2020-01-22 20:42:24 +02:00
|
|
|
|
2021-02-08 20:01:21 +02:00
|
|
|
export function prepareSlug(slug: string) {
|
2020-01-22 20:42:24 +02:00
|
|
|
return (
|
|
|
|
slug
|
|
|
|
.trim()
|
|
|
|
// Convert slug to lower-case
|
|
|
|
.toLocaleLowerCase()
|
|
|
|
|
|
|
|
// Remove single quotes.
|
|
|
|
.replace(/[']/g, '')
|
|
|
|
|
|
|
|
// Replace periods with dashes.
|
|
|
|
.replace(/[.]/g, '-')
|
|
|
|
);
|
2021-02-08 20:01:21 +02:00
|
|
|
}
|
2020-01-22 20:42:24 +02:00
|
|
|
|
2021-03-11 12:08:46 +02:00
|
|
|
export function getProcessSegment(slugConfig?: CmsSlug, ignoreValues?: string[]) {
|
2020-09-28 23:20:07 +10:00
|
|
|
return (value: string) =>
|
2021-03-11 12:08:46 +02:00
|
|
|
ignoreValues && ignoreValues.includes(value)
|
2020-09-28 23:20:07 +10:00
|
|
|
? value
|
|
|
|
: flow([value => String(value), prepareSlug, partialRight(sanitizeSlug, slugConfig)])(value);
|
2021-02-08 20:01:21 +02:00
|
|
|
}
|
2020-01-22 20:42:24 +02:00
|
|
|
|
2021-02-08 20:01:21 +02:00
|
|
|
export function slugFormatter(
|
2020-01-22 20:42:24 +02:00
|
|
|
collection: Collection,
|
|
|
|
entryData: Map<string, unknown>,
|
2021-03-11 12:08:46 +02:00
|
|
|
slugConfig?: CmsSlug,
|
2021-02-08 20:01:21 +02:00
|
|
|
) {
|
2020-01-22 20:42:24 +02:00
|
|
|
const slugTemplate = collection.get('slug') || '{{slug}}';
|
|
|
|
|
2020-02-12 08:30:44 +02:00
|
|
|
const identifier = entryData.getIn(keyToPathArray(selectIdentifier(collection) as string));
|
2020-01-22 20:42:24 +02:00
|
|
|
if (!identifier) {
|
|
|
|
throw new Error(
|
|
|
|
'Collection must have a field name that is a valid entry identifier, or must have `identifier_field` set',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const processSegment = getProcessSegment(slugConfig);
|
|
|
|
const date = new Date();
|
|
|
|
const slug = compileStringTemplate(slugTemplate, date, identifier, entryData, processSegment);
|
|
|
|
|
|
|
|
if (!collection.has('path')) {
|
|
|
|
return slug;
|
|
|
|
} else {
|
2020-04-01 16:40:14 +03:00
|
|
|
const pathTemplate = prepareSlug(collection.get('path') as string);
|
2020-01-22 20:42:24 +02:00
|
|
|
return compileStringTemplate(pathTemplate, date, slug, entryData, (value: string) =>
|
|
|
|
value === slug ? value : processSegment(value),
|
|
|
|
);
|
|
|
|
}
|
2021-02-08 20:01:21 +02:00
|
|
|
}
|
2020-01-22 20:42:24 +02:00
|
|
|
|
2021-02-08 20:01:21 +02:00
|
|
|
export function previewUrlFormatter(
|
2020-01-22 20:42:24 +02:00
|
|
|
baseUrl: string,
|
|
|
|
collection: Collection,
|
|
|
|
slug: string,
|
|
|
|
entry: EntryMap,
|
2021-03-11 12:08:46 +02:00
|
|
|
slugConfig?: CmsSlug,
|
2021-02-08 20:01:21 +02:00
|
|
|
) {
|
2020-01-22 20:42:24 +02:00
|
|
|
/**
|
|
|
|
* Preview URL can't be created without `baseUrl`. This makes preview URLs
|
|
|
|
* optional for backends that don't support them.
|
|
|
|
*/
|
|
|
|
if (!baseUrl) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-15 16:21:36 +02:00
|
|
|
const basePath = trimEnd(baseUrl, '/');
|
|
|
|
|
|
|
|
const isFileCollection = collection.get('type') === FILES;
|
|
|
|
const file = isFileCollection ? getFileFromSlug(collection, entry.get('slug')) : undefined;
|
|
|
|
|
2021-02-08 20:01:21 +02:00
|
|
|
function getPathTemplate() {
|
2020-10-15 16:21:36 +02:00
|
|
|
return file?.get('preview_path') ?? collection.get('preview_path');
|
2021-02-08 20:01:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function getDateField() {
|
2020-10-15 16:21:36 +02:00
|
|
|
return file?.get('preview_path_date_field') ?? collection.get('preview_path_date_field');
|
2021-02-08 20:01:21 +02:00
|
|
|
}
|
2020-10-15 16:21:36 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* If a `previewPath` is provided for the collection/file, use it to construct the
|
|
|
|
* URL path.
|
|
|
|
*/
|
|
|
|
const pathTemplate = getPathTemplate();
|
|
|
|
|
2020-01-22 20:42:24 +02:00
|
|
|
/**
|
2020-10-15 16:21:36 +02:00
|
|
|
* Without a `previewPath` for the collection/file (via config), the preview URL
|
2020-01-22 20:42:24 +02:00
|
|
|
* will be the URL provided by the backend.
|
|
|
|
*/
|
2020-10-15 16:21:36 +02:00
|
|
|
if (!pathTemplate) {
|
2020-01-22 20:42:24 +02:00
|
|
|
return baseUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
let fields = entry.get('data') as Map<string, string>;
|
2020-09-28 23:20:07 +10:00
|
|
|
fields = addFileTemplateFields(entry.get('path'), fields, collection.get('folder'));
|
2020-10-15 16:21:36 +02:00
|
|
|
const dateFieldName = getDateField() || selectInferedField(collection, 'date');
|
2021-05-19 14:39:35 +02:00
|
|
|
const date = parseDateFromEntry(entry as unknown as Map<string, unknown>, dateFieldName);
|
2020-01-22 20:42:24 +02:00
|
|
|
|
|
|
|
// Prepare and sanitize slug variables only, leave the rest of the
|
|
|
|
// `preview_path` template as is.
|
2020-09-28 23:20:07 +10:00
|
|
|
const processSegment = getProcessSegment(slugConfig, [fields.get('dirname')]);
|
2020-01-22 20:42:24 +02:00
|
|
|
let compiledPath;
|
|
|
|
|
|
|
|
try {
|
|
|
|
compiledPath = compileStringTemplate(pathTemplate, date, slug, fields, processSegment);
|
2022-09-28 20:04:00 -06:00
|
|
|
} catch (err: any) {
|
2020-01-22 20:42:24 +02:00
|
|
|
// Print an error and ignore `preview_path` if both:
|
|
|
|
// 1. Date is invalid (according to Moment), and
|
|
|
|
// 2. A date expression (eg. `{{year}}`) is used in `preview_path`
|
|
|
|
if (err.name === SLUG_MISSING_REQUIRED_DATE) {
|
|
|
|
console.error(stripIndent`
|
|
|
|
Collection "${collection.get('name')}" configuration error:
|
|
|
|
\`preview_path_date_field\` must be a field with a valid date. Ignoring \`preview_path\`.
|
|
|
|
`);
|
|
|
|
return basePath;
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
const previewPath = trimStart(compiledPath, ' /');
|
|
|
|
return `${basePath}/${previewPath}`;
|
2021-02-08 20:01:21 +02:00
|
|
|
}
|
2020-01-22 20:42:24 +02:00
|
|
|
|
2021-02-08 20:01:21 +02:00
|
|
|
export function summaryFormatter(summaryTemplate: string, entry: EntryMap, collection: Collection) {
|
2020-03-04 11:08:15 +01:00
|
|
|
let entryData = entry.get('data');
|
2020-04-30 16:03:08 +03:00
|
|
|
const date =
|
|
|
|
parseDateFromEntry(
|
2021-05-19 14:39:35 +02:00
|
|
|
entry as unknown as Map<string, unknown>,
|
2020-04-30 16:03:08 +03:00
|
|
|
selectInferedField(collection, 'date'),
|
|
|
|
) || null;
|
2020-02-12 08:30:44 +02:00
|
|
|
const identifier = entryData.getIn(keyToPathArray(selectIdentifier(collection) as string));
|
2020-03-04 11:08:15 +01:00
|
|
|
|
2020-09-28 23:20:07 +10:00
|
|
|
entryData = addFileTemplateFields(entry.get('path'), entryData, collection.get('folder'));
|
2020-04-01 06:13:27 +03:00
|
|
|
// allow commit information in summary template
|
|
|
|
if (entry.get('author') && !selectField(collection, COMMIT_AUTHOR)) {
|
|
|
|
entryData = entryData.set(COMMIT_AUTHOR, entry.get('author'));
|
|
|
|
}
|
|
|
|
if (entry.get('updatedOn') && !selectField(collection, COMMIT_DATE)) {
|
|
|
|
entryData = entryData.set(COMMIT_DATE, entry.get('updatedOn'));
|
|
|
|
}
|
2020-01-22 20:42:24 +02:00
|
|
|
const summary = compileStringTemplate(summaryTemplate, date, identifier, entryData);
|
|
|
|
return summary;
|
2021-02-08 20:01:21 +02:00
|
|
|
}
|
2020-01-22 20:42:24 +02:00
|
|
|
|
2021-02-08 20:01:21 +02:00
|
|
|
export function folderFormatter(
|
2020-01-22 20:42:24 +02:00
|
|
|
folderTemplate: string,
|
|
|
|
entry: EntryMap | undefined,
|
|
|
|
collection: Collection,
|
|
|
|
defaultFolder: string,
|
|
|
|
folderKey: string,
|
2021-03-11 12:08:46 +02:00
|
|
|
slugConfig?: CmsSlug,
|
2021-02-08 20:01:21 +02:00
|
|
|
) {
|
2020-01-22 20:42:24 +02:00
|
|
|
if (!entry || !entry.get('data')) {
|
|
|
|
return folderTemplate;
|
|
|
|
}
|
2020-02-14 22:31:33 +02:00
|
|
|
|
2020-01-22 20:42:24 +02:00
|
|
|
let fields = (entry.get('data') as Map<string, string>).set(folderKey, defaultFolder);
|
2020-09-28 23:20:07 +10:00
|
|
|
fields = addFileTemplateFields(entry.get('path'), fields, collection.get('folder'));
|
2020-01-22 20:42:24 +02:00
|
|
|
|
2020-04-30 16:03:08 +03:00
|
|
|
const date =
|
|
|
|
parseDateFromEntry(
|
2021-05-19 14:39:35 +02:00
|
|
|
entry as unknown as Map<string, unknown>,
|
2020-04-30 16:03:08 +03:00
|
|
|
selectInferedField(collection, 'date'),
|
|
|
|
) || null;
|
2020-02-12 08:30:44 +02:00
|
|
|
const identifier = fields.getIn(keyToPathArray(selectIdentifier(collection) as string));
|
2020-09-28 23:20:07 +10:00
|
|
|
const processSegment = getProcessSegment(slugConfig, [defaultFolder, fields.get('dirname')]);
|
2020-01-22 20:42:24 +02:00
|
|
|
|
|
|
|
const mediaFolder = compileStringTemplate(
|
|
|
|
folderTemplate,
|
|
|
|
date,
|
|
|
|
identifier,
|
|
|
|
fields,
|
2020-09-28 23:20:07 +10:00
|
|
|
processSegment,
|
2020-01-22 20:42:24 +02:00
|
|
|
);
|
2020-02-14 22:31:33 +02:00
|
|
|
|
2020-01-22 20:42:24 +02:00
|
|
|
return mediaFolder;
|
2021-02-08 20:01:21 +02:00
|
|
|
}
|