From 7608ed1a772f62e6646d27cb5dd0d3567af9a9f8 Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 27 Mar 2018 13:45:29 -0600 Subject: [PATCH 1/7] Update backend.js --- src/backends/backend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/backend.js b/src/backends/backend.js index 9c3ab44b4a35..f9f92ce0e929 100644 --- a/src/backends/backend.js +++ b/src/backends/backend.js @@ -53,7 +53,7 @@ const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => { 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"); + throw new Error(`Collection must have a field name that is a valid entry identifier. Please add one of these fields: ${ validIdentifierFields }`); } return identifier; From 6ea068f9023821f34a2577bcbf95dc28c3eae605 Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 27 Mar 2018 14:47:53 -0600 Subject: [PATCH 2/7] Verify slug field exists on config load. --- src/backends/backend.js | 10 +++------- src/reducers/collections.js | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/backends/backend.js b/src/backends/backend.js index f9f92ce0e929..60ee1b71973e 100644 --- a/src/backends/backend.js +++ b/src/backends/backend.js @@ -2,6 +2,7 @@ import { attempt, isError } from 'lodash'; import { resolveFormat } from "Formats/formats"; import { selectIntegration } from 'Reducers/integrations'; import { + selectIdentifier, selectListMethod, selectEntrySlug, selectEntryPath, @@ -45,15 +46,10 @@ const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => { const date = new Date(); const getIdentifier = (entryData) => { - const validIdentifierFields = ["title", "path"]; - const identifiers = validIdentifierFields.map((field) => - entryData.find((_, key) => key.toLowerCase().trim() === field) - ); - - const identifier = identifiers.find(ident => ident !== undefined); + const identifier = selectIdentifier(entryData.keys()); if (identifier === undefined) { - throw new Error(`Collection must have a field name that is a valid entry identifier. Please add one of these fields: ${ validIdentifierFields }`); + throw new Error("Collection must have a field name that is a valid entry identifier"); } return identifier; diff --git a/src/reducers/collections.js b/src/reducers/collections.js index 3a1abaf1f45b..590457993146 100644 --- a/src/reducers/collections.js +++ b/src/reducers/collections.js @@ -34,7 +34,8 @@ function validateCollection(configCollection) { files, format, extension, - frontmatter_delimiter: delimiter + frontmatter_delimiter: delimiter, + fields, } = configCollection.toJS(); if (!folder && !files) { @@ -51,8 +52,22 @@ function validateCollection(configCollection) { // 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 (selectIdentifier(fields.map(f => f.name)) === undefined) { + // Verify that the collection has a slug field. + // TODO: Verify only for folder-type collections. + throw new Error(`Collection "${name}" must have a field that is a valid entry identifier. Supported fields are ${validIdentifierFields.join(',')}`); + } } +const validIdentifierFields = ["title", "path"]; +export const selectIdentifier = (entryData, collectionName) => { + const identifiers = validIdentifierFields.map((field) => + entryData.find(key => key.toLowerCase().trim() === field) + ); + + return identifiers.find(ident => ident !== undefined); +}; + const selectors = { [FOLDER]: { entryExtension(collection) { From ce48fdd2c96db471a03c2382e3ed10674f62a102 Mon Sep 17 00:00:00 2001 From: Caleb Date: Wed, 28 Mar 2018 13:15:12 -0600 Subject: [PATCH 3/7] Only require title field for folder-type collections. --- src/reducers/collections.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/reducers/collections.js b/src/reducers/collections.js index 590457993146..ccf64cfc6b7d 100644 --- a/src/reducers/collections.js +++ b/src/reducers/collections.js @@ -52,15 +52,14 @@ function validateCollection(configCollection) { // 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 (selectIdentifier(fields.map(f => f.name)) === undefined) { - // Verify that the collection has a slug field. - // TODO: Verify only for folder-type collections. + if ((!!folder) && (selectIdentifier(fields.map(f => f.name)) === undefined)) { + // Verify that folder-type collections have a "slug"-type field. throw new Error(`Collection "${name}" must have a field that is a valid entry identifier. Supported fields are ${validIdentifierFields.join(',')}`); } } const validIdentifierFields = ["title", "path"]; -export const selectIdentifier = (entryData, collectionName) => { +export const selectIdentifier = (entryData) => { const identifiers = validIdentifierFields.map((field) => entryData.find(key => key.toLowerCase().trim() === field) ); From 4138a232067012ff76f28fd0e55e222f799c069f Mon Sep 17 00:00:00 2001 From: Caleb Date: Wed, 28 Mar 2018 13:33:31 -0600 Subject: [PATCH 4/7] Fix broken slugs. --- src/backends/backend.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backends/backend.js b/src/backends/backend.js index 60ee1b71973e..49ad110dc688 100644 --- a/src/backends/backend.js +++ b/src/backends/backend.js @@ -46,13 +46,13 @@ const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => { const date = new Date(); const getIdentifier = (entryData) => { - const identifier = selectIdentifier(entryData.keys()); + const identifier = selectIdentifier(entryData.keySeq()); if (identifier === undefined) { throw new Error("Collection must have a field name that is a valid entry identifier"); } - return identifier; + return entryData.get(identifier); }; const slug = template.replace(/\{\{([^\}]+)\}\}/g, (_, field) => { From bbae4b568d1a14fb5a9bdc5f3282d844e10ea700 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Tue, 15 May 2018 14:45:42 -0400 Subject: [PATCH 5/7] split config and value validation, move selector to reducer --- src/actions/config.js | 41 +++++++++++++++++++++++++++ src/backends/backend.js | 24 +++++++--------- src/constants/fieldInference.js | 2 ++ src/reducers/collections.js | 49 ++++----------------------------- 4 files changed, 59 insertions(+), 57 deletions(-) diff --git a/src/actions/config.js b/src/actions/config.js index c290b4fe67ff..d4b40c8173f7 100644 --- a/src/actions/config.js +++ b/src/actions/config.js @@ -2,6 +2,9 @@ import yaml from "js-yaml"; import { Map, List, fromJS } from "immutable"; import { trimStart, flow, isBoolean, get } from "lodash"; 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"; 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) { if (!config.get('backend')) { 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()) { 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; } diff --git a/src/backends/backend.js b/src/backends/backend.js index 49ad110dc688..c27136f832d0 100644 --- a/src/backends/backend.js +++ b/src/backends/backend.js @@ -2,13 +2,13 @@ import { attempt, isError } from 'lodash'; import { resolveFormat } from "Formats/formats"; import { selectIntegration } from 'Reducers/integrations'; import { - selectIdentifier, selectListMethod, selectEntrySlug, selectEntryPath, selectAllowNewEntries, selectAllowDeletion, - selectFolderEntryExtension + selectFolderEntryExtension, + selectIdentifier, } from "Reducers/collections"; import { createEntry } from "ValueObjects/Entry"; import { sanitizeSlug } from "Lib/urlHelper"; @@ -42,18 +42,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 getIdentifier = (entryData) => { - const identifier = selectIdentifier(entryData.keySeq()); - - if (identifier === undefined) { - throw new Error("Collection must have a field name that is a valid entry identifier"); - } - - return entryData.get(identifier); - }; + const identifier = entryData.get(selectIdentifier(collection)); + if (identifier === undefined) { + throw new Error("Collection must have a field name that is a valid entry identifier"); + } const slug = template.replace(/\{\{([^\}]+)\}\}/g, (_, field) => { switch (field) { @@ -70,7 +66,7 @@ const slugFormatter = (template = "{{slug}}", entryData, slugConfig) => { case "second": return (`0${ date.getSeconds() }`).slice(-2); case "slug": - return getIdentifier(entryData).trim(); + return identifier.trim(); default: return entryData.get(field, "").trim(); } @@ -244,7 +240,7 @@ class Backend { if (!selectAllowNewEntries(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); entryObj = { path, diff --git a/src/constants/fieldInference.js b/src/constants/fieldInference.js index 2a5163bfc8ef..bd6c6a9ed7e3 100644 --- a/src/constants/fieldInference.js +++ b/src/constants/fieldInference.js @@ -1,6 +1,8 @@ import React from 'react'; /* eslint-disable */ +export const IDENTIFIER_FIELDS = ['title', 'path']; + export const INFERABLE_FIELDS = { title: { type: 'string', diff --git a/src/reducers/collections.js b/src/reducers/collections.js index ccf64cfc6b7d..d5c7288b9972 100644 --- a/src/reducers/collections.js +++ b/src/reducers/collections.js @@ -3,14 +3,13 @@ import { has, get, escapeRegExp } from 'lodash'; import consoleError from 'Lib/consoleError'; import { CONFIG_SUCCESS } from 'Actions/config'; import { FILES, FOLDER } from 'Constants/collectionTypes'; -import { INFERABLE_FIELDS } from 'Constants/fieldInference'; -import { formatByExtension, formatToExtension, supportedFormats, frontmatterFormats } from 'Formats/formats'; +import { INFERABLE_FIELDS, IDENTIFIER_FIELDS } from 'Constants/fieldInference'; +import { formatToExtension } from 'Formats/formats'; const collections = (state = null, action) => { switch (action.type) { case CONFIG_SUCCESS: const configCollections = action.payload ? action.payload.get('collections') : List(); - configCollections.forEach(validateCollection) return configCollections .toOrderedMap() .map(collection => { @@ -27,46 +26,6 @@ const collections = (state = null, action) => { } }; -function validateCollection(configCollection) { - const { - name, - folder, - files, - format, - extension, - frontmatter_delimiter: delimiter, - fields, - } = 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.`); - } - if ((!!folder) && (selectIdentifier(fields.map(f => f.name)) === undefined)) { - // Verify that folder-type collections have a "slug"-type field. - throw new Error(`Collection "${name}" must have a field that is a valid entry identifier. Supported fields are ${validIdentifierFields.join(',')}`); - } -} - -const validIdentifierFields = ["title", "path"]; -export const selectIdentifier = (entryData) => { - const identifiers = validIdentifierFields.map((field) => - entryData.find(key => key.toLowerCase().trim() === field) - ); - - return identifiers.find(ident => ident !== undefined); -}; - const selectors = { [FOLDER]: { entryExtension(collection) { @@ -134,6 +93,10 @@ export const selectListMethod = collection => selectors[collection.get('type')]. export const selectAllowNewEntries = collection => selectors[collection.get('type')].allowNewEntries(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 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) => { const inferableField = INFERABLE_FIELDS[fieldName]; const fields = collection.get('fields'); From 78f0f505d8d22f86a60fe9bab6fe33df8f8e47f2 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Tue, 15 May 2018 17:34:22 -0400 Subject: [PATCH 6/7] fix test --- src/actions/__tests__/config.spec.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/actions/__tests__/config.spec.js b/src/actions/__tests__/config.spec.js index f32f2d441eb3..c17db99fe2f1 100644 --- a/src/actions/__tests__/config.spec.js +++ b/src/actions/__tests__/config.spec.js @@ -58,7 +58,17 @@ describe('config', () => { describe('validateConfig', () => { 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( validateConfig(config) ).toEqual(config); From 4044766fb486e9cb5d3592c5acd42352ecea385f Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Fri, 25 May 2018 12:05:36 -0400 Subject: [PATCH 7/7] improve conditionals --- src/actions/config.js | 2 +- src/backends/backend.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions/config.js b/src/actions/config.js index d4b40c8173f7..f563fa22f991 100644 --- a/src/actions/config.js +++ b/src/actions/config.js @@ -68,7 +68,7 @@ function validateCollection(collection) { // 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)) { + 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(', ')}.`); } diff --git a/src/backends/backend.js b/src/backends/backend.js index c27136f832d0..5f693a5322c5 100644 --- a/src/backends/backend.js +++ b/src/backends/backend.js @@ -47,7 +47,7 @@ const slugFormatter = (collection, entryData, slugConfig) => { const date = new Date(); const identifier = entryData.get(selectIdentifier(collection)); - if (identifier === undefined) { + if (!identifier) { throw new Error("Collection must have a field name that is a valid entry identifier"); }