Check for title/slug field on config load. (#1203)
This commit is contained in:
parent
9fd0ff4a6a
commit
0022df57d2
@ -58,7 +58,17 @@ describe('config', () => {
|
|||||||
|
|
||||||
describe('validateConfig', () => {
|
describe('validateConfig', () => {
|
||||||
it('should return the config if no errors', () => {
|
it('should return the config if no errors', () => {
|
||||||
const config = fromJS({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz', collections: [{}] });
|
const collections = [{
|
||||||
|
name: 'posts',
|
||||||
|
folder: '_posts',
|
||||||
|
fields: [{ name: 'title', label: 'title' }],
|
||||||
|
}];
|
||||||
|
const config = fromJS({
|
||||||
|
foo: 'bar',
|
||||||
|
backend: { name: 'bar' },
|
||||||
|
media_folder: 'baz',
|
||||||
|
collections,
|
||||||
|
});
|
||||||
expect(
|
expect(
|
||||||
validateConfig(config)
|
validateConfig(config)
|
||||||
).toEqual(config);
|
).toEqual(config);
|
||||||
|
@ -2,6 +2,9 @@ import yaml from "js-yaml";
|
|||||||
import { Map, List, fromJS } from "immutable";
|
import { Map, List, fromJS } from "immutable";
|
||||||
import { trimStart, flow, isBoolean, get } from "lodash";
|
import { trimStart, flow, isBoolean, get } from "lodash";
|
||||||
import { authenticateUser } from "Actions/auth";
|
import { authenticateUser } from "Actions/auth";
|
||||||
|
import { formatByExtension, supportedFormats, frontmatterFormats } from "Formats/formats";
|
||||||
|
import { selectIdentifier } from "Reducers/collections";
|
||||||
|
import { IDENTIFIER_FIELDS } from "Constants/fieldInference";
|
||||||
import * as publishModes from "Constants/publishModes";
|
import * as publishModes from "Constants/publishModes";
|
||||||
|
|
||||||
export const CONFIG_REQUEST = "CONFIG_REQUEST";
|
export const CONFIG_REQUEST = "CONFIG_REQUEST";
|
||||||
@ -40,6 +43,38 @@ export function applyDefaults(config) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateCollection(collection) {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
folder,
|
||||||
|
files,
|
||||||
|
format,
|
||||||
|
extension,
|
||||||
|
frontmatter_delimiter: delimiter,
|
||||||
|
fields,
|
||||||
|
} = collection.toJS();
|
||||||
|
|
||||||
|
if (!folder && !files) {
|
||||||
|
throw new Error(`Unknown collection type for collection "${name}". Collections can be either Folder based or File based.`);
|
||||||
|
}
|
||||||
|
if (format && !supportedFormats.includes(format)) {
|
||||||
|
throw new Error(`Unknown collection format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
|
||||||
|
}
|
||||||
|
if (!format && extension && !formatByExtension(extension)) {
|
||||||
|
// Cannot infer format from extension.
|
||||||
|
throw new Error(`Please set a format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
|
||||||
|
}
|
||||||
|
if (delimiter && !frontmatterFormats.includes(format)) {
|
||||||
|
// Cannot set custom delimiter without explicit and proper frontmatter format declaration
|
||||||
|
throw new Error(`Please set a proper frontmatter format for collection "${name}" to use a custom delimiter. Supported frontmatter formats are yaml-frontmatter, toml-frontmatter, and json-frontmatter.`);
|
||||||
|
}
|
||||||
|
if (folder && !selectIdentifier(collection)) {
|
||||||
|
// Verify that folder-type collections have an identifier field for slug creation.
|
||||||
|
throw new Error(`Collection "${name}" must have a field that is a valid entry identifier. Supported fields are ${IDENTIFIER_FIELDS.join(', ')}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function validateConfig(config) {
|
export function validateConfig(config) {
|
||||||
if (!config.get('backend')) {
|
if (!config.get('backend')) {
|
||||||
throw new Error("Error in configuration file: A `backend` wasn't found. Check your config.yml file.");
|
throw new Error("Error in configuration file: A `backend` wasn't found. Check your config.yml file.");
|
||||||
@ -70,6 +105,12 @@ export function validateConfig(config) {
|
|||||||
if (!List.isList(collections) || collections.isEmpty() || !collections.first()) {
|
if (!List.isList(collections) || collections.isEmpty() || !collections.first()) {
|
||||||
throw new Error("Error in configuration file: Your `collections` must be an array with at least one element. Check your config.yml file.");
|
throw new Error("Error in configuration file: Your `collections` must be an array with at least one element. Check your config.yml file.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate Collections
|
||||||
|
*/
|
||||||
|
config.get('collections').forEach(validateCollection);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@ import {
|
|||||||
selectEntryPath,
|
selectEntryPath,
|
||||||
selectAllowNewEntries,
|
selectAllowNewEntries,
|
||||||
selectAllowDeletion,
|
selectAllowDeletion,
|
||||||
selectFolderEntryExtension
|
selectFolderEntryExtension,
|
||||||
|
selectIdentifier,
|
||||||
} from "Reducers/collections";
|
} from "Reducers/collections";
|
||||||
import { createEntry } from "ValueObjects/Entry";
|
import { createEntry } from "ValueObjects/Entry";
|
||||||
import { sanitizeSlug } from "Lib/urlHelper";
|
import { sanitizeSlug } from "Lib/urlHelper";
|
||||||
@ -42,23 +43,14 @@ class LocalStorageAuthStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => {
|
const slugFormatter = (collection, entryData, slugConfig) => {
|
||||||
|
const template = collection.get('slug') || "{{slug}}";
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
|
||||||
const getIdentifier = (entryData) => {
|
const identifier = entryData.get(selectIdentifier(collection));
|
||||||
const validIdentifierFields = ["title", "path"];
|
if (!identifier) {
|
||||||
const identifiers = validIdentifierFields.map((field) =>
|
throw new Error("Collection must have a field name that is a valid entry identifier");
|
||||||
entryData.find((_, key) => key.toLowerCase().trim() === field)
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const identifier = identifiers.find(ident => ident !== undefined);
|
|
||||||
|
|
||||||
if (identifier === undefined) {
|
|
||||||
throw new Error("Collection must have a field name that is a valid entry identifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifier;
|
|
||||||
};
|
|
||||||
|
|
||||||
const slug = template.replace(/\{\{([^\}]+)\}\}/g, (_, field) => {
|
const slug = template.replace(/\{\{([^\}]+)\}\}/g, (_, field) => {
|
||||||
switch (field) {
|
switch (field) {
|
||||||
@ -75,7 +67,7 @@ const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => {
|
|||||||
case "second":
|
case "second":
|
||||||
return (`0${ date.getSeconds() }`).slice(-2);
|
return (`0${ date.getSeconds() }`).slice(-2);
|
||||||
case "slug":
|
case "slug":
|
||||||
return getIdentifier(entryData).trim();
|
return identifier.trim();
|
||||||
default:
|
default:
|
||||||
return entryData.get(field, "").trim();
|
return entryData.get(field, "").trim();
|
||||||
}
|
}
|
||||||
@ -275,7 +267,7 @@ class Backend {
|
|||||||
if (!selectAllowNewEntries(collection)) {
|
if (!selectAllowNewEntries(collection)) {
|
||||||
throw (new Error("Not allowed to create new entries in this collection"));
|
throw (new Error("Not allowed to create new entries in this collection"));
|
||||||
}
|
}
|
||||||
const slug = slugFormatter(collection.get("slug"), entryDraft.getIn(["entry", "data"]), config.get("slug"));
|
const slug = slugFormatter(collection, entryDraft.getIn(["entry", "data"]), config.get("slug"));
|
||||||
const path = selectEntryPath(collection, slug);
|
const path = selectEntryPath(collection, slug);
|
||||||
entryObj = {
|
entryObj = {
|
||||||
path,
|
path,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
export const IDENTIFIER_FIELDS = ['title', 'path'];
|
||||||
|
|
||||||
export const INFERABLE_FIELDS = {
|
export const INFERABLE_FIELDS = {
|
||||||
title: {
|
title: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -3,14 +3,13 @@ import { has, get, escapeRegExp } from 'lodash';
|
|||||||
import consoleError from 'Lib/consoleError';
|
import consoleError from 'Lib/consoleError';
|
||||||
import { CONFIG_SUCCESS } from 'Actions/config';
|
import { CONFIG_SUCCESS } from 'Actions/config';
|
||||||
import { FILES, FOLDER } from 'Constants/collectionTypes';
|
import { FILES, FOLDER } from 'Constants/collectionTypes';
|
||||||
import { INFERABLE_FIELDS } from 'Constants/fieldInference';
|
import { INFERABLE_FIELDS, IDENTIFIER_FIELDS } from 'Constants/fieldInference';
|
||||||
import { formatByExtension, formatToExtension, supportedFormats, frontmatterFormats } from 'Formats/formats';
|
import { formatToExtension } from 'Formats/formats';
|
||||||
|
|
||||||
const collections = (state = null, action) => {
|
const collections = (state = null, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CONFIG_SUCCESS:
|
case CONFIG_SUCCESS:
|
||||||
const configCollections = action.payload ? action.payload.get('collections') : List();
|
const configCollections = action.payload ? action.payload.get('collections') : List();
|
||||||
configCollections.forEach(validateCollection)
|
|
||||||
return configCollections
|
return configCollections
|
||||||
.toOrderedMap()
|
.toOrderedMap()
|
||||||
.map(collection => {
|
.map(collection => {
|
||||||
@ -27,32 +26,6 @@ const collections = (state = null, action) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function validateCollection(configCollection) {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
folder,
|
|
||||||
files,
|
|
||||||
format,
|
|
||||||
extension,
|
|
||||||
frontmatter_delimiter: delimiter
|
|
||||||
} = configCollection.toJS();
|
|
||||||
|
|
||||||
if (!folder && !files) {
|
|
||||||
throw new Error(`Unknown collection type for collection "${name}". Collections can be either Folder based or File based.`);
|
|
||||||
}
|
|
||||||
if (format && !supportedFormats.includes(format)) {
|
|
||||||
throw new Error(`Unknown collection format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
|
|
||||||
}
|
|
||||||
if (!format && extension && !formatByExtension(extension)) {
|
|
||||||
// Cannot infer format from extension.
|
|
||||||
throw new Error(`Please set a format for collection "${name}". Supported formats are ${supportedFormats.join(',')}`);
|
|
||||||
}
|
|
||||||
if (delimiter && !frontmatterFormats.includes(format)) {
|
|
||||||
// Cannot set custom delimiter without explicit and proper frontmatter format declaration
|
|
||||||
throw new Error(`Please set a proper frontmatter format for collection "${name}" to use a custom delimiter. Supported frontmatter formats are yaml-frontmatter, toml-frontmatter, and json-frontmatter.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectors = {
|
const selectors = {
|
||||||
[FOLDER]: {
|
[FOLDER]: {
|
||||||
entryExtension(collection) {
|
entryExtension(collection) {
|
||||||
@ -120,6 +93,10 @@ export const selectListMethod = collection => selectors[collection.get('type')].
|
|||||||
export const selectAllowNewEntries = collection => selectors[collection.get('type')].allowNewEntries(collection);
|
export const selectAllowNewEntries = collection => selectors[collection.get('type')].allowNewEntries(collection);
|
||||||
export const selectAllowDeletion = collection => selectors[collection.get('type')].allowDeletion(collection);
|
export const selectAllowDeletion = collection => selectors[collection.get('type')].allowDeletion(collection);
|
||||||
export const selectTemplateName = (collection, slug) => selectors[collection.get('type')].templateName(collection, slug);
|
export const selectTemplateName = (collection, slug) => selectors[collection.get('type')].templateName(collection, slug);
|
||||||
|
export const selectIdentifier = collection => {
|
||||||
|
const fieldNames = collection.get('fields').map(field => field.get('name'));
|
||||||
|
return IDENTIFIER_FIELDS.find(id => fieldNames.find(name => name.toLowerCase().trim() === id));
|
||||||
|
};
|
||||||
export const selectInferedField = (collection, fieldName) => {
|
export const selectInferedField = (collection, fieldName) => {
|
||||||
const inferableField = INFERABLE_FIELDS[fieldName];
|
const inferableField = INFERABLE_FIELDS[fieldName];
|
||||||
const fields = collection.get('fields');
|
const fields = collection.get('fields');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user