static-cms/packages/core/src/lib/formatters.ts
2023-04-04 15:12:32 -04:00

165 lines
5.0 KiB
TypeScript

import flow from 'lodash/flow';
import get from 'lodash/get';
import partialRight from 'lodash/partialRight';
import { COMMIT_AUTHOR, COMMIT_DATE } from '../constants/commitProps';
import { sanitizeSlug } from './urlHelper';
import { selectIdentifier, selectInferredField } from './util/collection.util';
import { selectField } from './util/field.util';
import { set } from './util/object.util';
import {
addFileTemplateFields,
compileStringTemplate,
keyToPathArray,
parseDateFromEntry,
} from './widgets/stringTemplate';
import type { BaseField, Collection, Config, Entry, EntryData, Slug } from '../interface';
const commitMessageTemplates = {
create: 'Create {{collection}} “{{slug}}”',
update: 'Update {{collection}} “{{slug}}”',
delete: 'Delete {{collection}} “{{slug}}”',
uploadMedia: 'Upload “{{path}}”',
deleteMedia: 'Delete “{{path}}”',
} as const;
const variableRegex = /\{\{([^}]+)\}\}/g;
type Options<EF extends BaseField> = {
slug?: string;
path?: string;
collection?: Collection<EF>;
authorLogin?: string;
authorName?: string;
};
export function commitMessageFormatter<EF extends BaseField>(
type: keyof typeof commitMessageTemplates,
config: Config<EF>,
{ slug, path, collection, authorLogin, authorName }: Options<EF>,
) {
const templates = { ...commitMessageTemplates, ...(config.backend.commit_messages || {}) };
return templates[type].replace(variableRegex, (_, variable) => {
switch (variable) {
case 'slug':
return slug || '';
case 'path':
return path || '';
case 'collection':
return collection ? collection.label_singular || collection.label : '';
case 'author-login':
return authorLogin || '';
case 'author-name':
return authorName || '';
default:
console.warn(`Ignoring unknown variable “${variable}” in commit message template.`);
return '';
}
});
}
export function prepareSlug(slug: string) {
return (
slug
.trim()
// Convert slug to lower-case
.toLocaleLowerCase()
// Remove single quotes.
.replace(/[']/g, '')
// Replace periods with dashes.
.replace(/[.]/g, '-')
);
}
export function getProcessSegment(slugConfig?: Slug, ignoreValues?: string[]) {
return (value: string) =>
ignoreValues && ignoreValues.includes(value)
? value
: flow([value => String(value), prepareSlug, partialRight(sanitizeSlug, slugConfig)])(value);
}
export function slugFormatter(collection: Collection, entryData: EntryData, slugConfig?: Slug) {
const slugTemplate = collection.slug || '{{slug}}';
const identifier = get(entryData, keyToPathArray(selectIdentifier(collection)));
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 (!('path' in collection)) {
return slug;
} else {
const pathTemplate = prepareSlug(collection.path as string);
return compileStringTemplate(pathTemplate, date, slug, entryData, (value: string) =>
value === slug ? value : processSegment(value),
);
}
}
export function summaryFormatter<EF extends BaseField>(
summaryTemplate: string,
entry: Entry,
collection: Collection<EF>,
) {
let entryData = entry.data;
const date = parseDateFromEntry(entry, selectInferredField(collection, 'date')) || null;
const identifier = get(entryData, keyToPathArray(selectIdentifier(collection)));
entryData =
addFileTemplateFields(entry.path, entryData, 'folder' in collection ? collection.folder : '') ??
{};
// allow commit information in summary template
if (entry.author && !selectField(collection, COMMIT_AUTHOR)) {
entryData = set(entryData, COMMIT_AUTHOR, entry.author);
}
if (entry.updatedOn && !selectField(collection, COMMIT_DATE)) {
entryData = set(entryData, COMMIT_DATE, entry.updatedOn);
}
const summary = compileStringTemplate(summaryTemplate, date, identifier, entryData);
return summary;
}
export function folderFormatter<EF extends BaseField>(
folderTemplate: string,
entry: Entry | null | undefined,
collection: Collection<EF>,
defaultFolder: string,
folderKey: string,
slugConfig?: Slug,
) {
if (!entry || !entry.data) {
return folderTemplate;
}
let fields = set(entry.data, folderKey, defaultFolder) as EntryData;
fields = addFileTemplateFields(
entry.path,
fields,
'folder' in collection ? collection.folder : '',
);
const date = parseDateFromEntry(entry, selectInferredField(collection, 'date')) || null;
const identifier = get(fields, keyToPathArray(selectIdentifier(collection)));
const processSegment = getProcessSegment(slugConfig, [defaultFolder, fields?.dirname as string]);
const mediaFolder = compileStringTemplate(
folderTemplate,
date,
identifier,
fields,
processSegment,
);
return mediaFolder;
}