Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
barthc committed Feb 22, 2019
1 parent 315f89a commit ed3da48
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 121 deletions.
2 changes: 2 additions & 0 deletions dev-test/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ collections: # A list of collections the CMS should be able to edit
guidelines that are specific to a collection.
folder: '_posts'
slug: '{{year}}-{{month}}-{{day}}-{{slug}}'
slug_field: 'slug'
create: true # Allow users to create new documents in this collection
fields: # The fields each document in this collection have
- { label: 'Title', name: 'title', widget: 'string', tagname: 'h1' }
- { label: 'Slug', name: 'slug', widget: 'string', tagname: 'h1' }
- {
label: 'Publish Date',
name: 'date',
Expand Down
12 changes: 1 addition & 11 deletions packages/netlify-cms-core/src/__tests__/backend.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { resolveBackend, generateUniqueSlug } from '../backend';
import { resolveBackend } from '../backend';
import registry from 'Lib/registry';
import { Map } from 'immutable';

jest.mock('Lib/registry');

Expand Down Expand Up @@ -109,13 +108,4 @@ describe('Backend', () => {
expect(result.length).toBe(1);
});
});

describe('uniqueSlug', () => {
it('generates unique slug', () => {
expect(generateUniqueSlug('title', Map(), ['title'])).toEqual('title-1');
expect(generateUniqueSlug('title', Map({ sanitize_replacement: '_' }), ['title'])).toEqual(
'title_1',
);
});
});
});
12 changes: 2 additions & 10 deletions packages/netlify-cms-core/src/actions/editorialWorkflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import { actions as notifActions } from 'redux-notifications';
import { BEGIN, COMMIT, REVERT } from 'redux-optimist';
import { serializeValues } from 'Lib/serializeEntryValues';
import { currentBackend } from 'src/backend';
import { getAsset, selectUnpublishedEntry } from 'Reducers';
import { getAsset, selectSlugs, selectUnpublishedEntry } from 'Reducers';
import { selectFields, selectSlugField } from 'Reducers/collections';
import { selectSlugEntries } from 'Reducers/entries';
import { selectUnpublishedSlugEntriesByCollection } from 'Reducers/editorialWorkflow';
import { EDITORIAL_WORKFLOW } from 'Constants/publishModes';
import { EDITORIAL_WORKFLOW_ERROR } from 'netlify-cms-lib-util';
import { loadEntry, deleteEntry } from './entries';
Expand Down Expand Up @@ -292,13 +290,7 @@ export function persistUnpublishedEntry(collection, existingUnpublishedEntry) {
const state = getState();
const entryDraft = state.entryDraft;
const fieldsErrors = entryDraft.get('fieldsErrors');
const unpublishedSlugs = selectUnpublishedSlugEntriesByCollection(
state.editorialWorkflow,
collection.get('name'),
);
const unavailableSlugs = selectSlugEntries(state.entries, collection.get('name')).concat(
unpublishedSlugs,
);
const unavailableSlugs = selectSlugs(state, collection.get('name'));

// Early return if draft contains validation errors
if (!fieldsErrors.isEmpty()) {
Expand Down
13 changes: 3 additions & 10 deletions packages/netlify-cms-core/src/actions/entries.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { fromJS, List, Map } from 'immutable';
import { actions as notifActions } from 'redux-notifications';
import { serializeValues } from 'Lib/serializeEntryValues';
import { currentBackend, slugFormatter } from 'src/backend';
import { currentBackend } from 'src/backend';
import { getIntegrationProvider } from 'Integrations';
import { getAsset, selectIntegration } from 'Reducers';
import { getAsset, selectSlugEntries, selectIntegration } from 'Reducers';
import { selectFields, selectSlugField } from 'Reducers/collections';
import { selectSlugEntries } from 'Reducers/entries';
import { selectCollectionEntriesCursor } from 'Reducers/cursors';
import { Cursor } from 'netlify-cms-lib-util';
import { createEntry } from 'ValueObjects/Entry';
Expand Down Expand Up @@ -410,7 +409,7 @@ export function persistEntry(collection) {
const state = getState();
const entryDraft = state.entryDraft;
const fieldsErrors = entryDraft.get('fieldsErrors');
const unavailableSlugs = selectSlugEntries(state.entries, collection.get('name'));
const unavailableSlugs = selectSlugEntries(state, collection.get('name'));

// Early return if draft contains validation errors
if (!fieldsErrors.isEmpty()) {
Expand Down Expand Up @@ -522,9 +521,3 @@ export function deleteEntry(collection, slug) {
});
};
}

export function getSlug(collection) {
return (entryData, config, unavailableSlugs) => {
return slugFormatter(collection, entryData, config, unavailableSlugs);
};
}
88 changes: 66 additions & 22 deletions packages/netlify-cms-core/src/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { createEntry } from 'ValueObjects/Entry';
import { sanitizeSlug } from 'Lib/urlHelper';
import { getBackend } from 'Lib/registry';
import { Cursor, CURSOR_COMPATIBILITY_SYMBOL } from 'netlify-cms-lib-util';
import { Cursor, EditorialWorkflowError, CURSOR_COMPATIBILITY_SYMBOL } from 'netlify-cms-lib-util';
import { EDITORIAL_WORKFLOW, status } from 'Constants/publishModes';

class LocalStorageAuthStore {
Expand Down Expand Up @@ -111,7 +111,7 @@ function compileSlug(template, date, identifier = '', data = Map(), processor) {
}
}

export function slugFormatter(collection, entryData, slugConfig, unavailableSlugs) {
export function slugFormatter(collection, entryData, slugConfig) {
const template = collection.get('slug') || '{{slug}}';

const identifier = entryData.get(selectIdentifier(collection));
Expand All @@ -129,6 +129,19 @@ export function slugFormatter(collection, entryData, slugConfig, unavailableSlug
return processSlug(template, new Date(), identifier, entryData);
}

const sanitizeEntrySlug = (slug, slugConfig) => {
return sanitizeSlug(
slug
// Convert slug to lower-case
.toLocaleLowerCase()
// Remove single quotes.
.replace(/[']/g, '')
// Replace periods with dashes.
.replace(/[.]/g, '-'),
slugConfig,
);
};

const commitMessageTemplates = Map({
create: 'Create {{collection}} “{{slug}}”',
update: 'Update {{collection}} “{{slug}}”',
Expand Down Expand Up @@ -157,24 +170,6 @@ const commitMessageFormatter = (type, config, { slug, path, collection }) => {
});
};

export const generateUniqueSlug = (slug, slugConfig, publishedOrDraftSlugs) => {
let i = 1;
let sanitizedSlug = sanitizeSlug(slug, slugConfig);
let uniqueSlug = sanitizedSlug;
while (publishedOrDraftSlugs.includes(uniqueSlug)) {
uniqueSlug = sanitizeSlug(`${sanitizedSlug} ${i++}`, slugConfig)
// Remove single quotes.
.replace(/[']/g, '')

// Replace periods with dashes.
.replace(/[.]/g, '-')

// Convert slug to lower-case
.toLocaleLowerCase();
}
return uniqueSlug;
};

const extractSearchFields = searchFields => entry =>
searchFields.reduce((acc, field) => {
const f = entry.data[field];
Expand Down Expand Up @@ -320,6 +315,55 @@ class Backend {

getToken = () => this.implementation.getToken();

async entryExist(collection, path, slug) {
const unpublishedEntry =
this.implementation.unpublishedEntry &&
(await this.implementation.unpublishedEntry(collection, slug).catch(error => {
if (error instanceof EditorialWorkflowError && error.notUnderEditorialWorkflow) {
return Promise.resolve(false);
}
return Promise.reject(error);
}));

if (unpublishedEntry) return unpublishedEntry;

const publishedEntry = await this.implementation
.getEntry(collection, slug, path)
.then(({ data }) => data)
.catch(error => {
if (error.status === 404 || error.message.includes(404)) {
return Promise.resolve(false);
}
return Promise.reject(error);
});

return publishedEntry;
}

async getSlug(collection, entryData, slugConfig, unavailableSlugs) {
const slug = slugFormatter(collection, entryData, slugConfig);

return await this.generateUniqueSlug(collection, slug, slugConfig, unavailableSlugs);
}

async generateUniqueSlug(collection, slug, slugConfig, unavailableSlugs, availableSlugs = []) {
let i = 1;
let sanitizedSlug = sanitizeEntrySlug(slug, slugConfig);
let uniqueSlug = sanitizedSlug;

// Return if slug is the same as the current entry or parent slug.
if (availableSlugs && availableSlugs.includes(uniqueSlug)) return uniqueSlug;

// Check for duplicate slug in loaded entities store first before repo
while (
unavailableSlugs.includes(uniqueSlug) ||
(await this.entryExist(collection, selectEntryPath(collection, uniqueSlug), uniqueSlug))
) {
uniqueSlug = sanitizeEntrySlug(`${sanitizedSlug} ${i++}`, slugConfig);
}
return uniqueSlug;
}

processEntries(loadedEntries, collection) {
const collectionFilter = collection.get('filter');
const entries = loadedEntries.map(loadedEntry =>
Expand Down Expand Up @@ -579,7 +623,7 @@ class Backend {
};
}

persistEntry(
async persistEntry(
config,
collection,
entryDraft,
Expand All @@ -602,7 +646,7 @@ class Backend {
if (!selectAllowNewEntries(collection)) {
throw new Error('Not allowed to create new entries in this collection');
}
const autoSlug = slugFormatter(
const autoSlug = await this.getSlug(
collection,
entryDraft.getIn(['entry', 'data']),
config.get('slug'),
Expand Down
30 changes: 2 additions & 28 deletions packages/netlify-cms-core/src/components/Editor/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
discardDraft,
changeDraftField,
changeDraftFieldValidation,
getSlug,
persistEntry,
deleteEntry,
} from 'Actions/entries';
Expand All @@ -25,15 +24,8 @@ import {
} from 'Actions/editorialWorkflow';
import { loadDeployPreview } from 'Actions/deploys';
import { deserializeValues } from 'Lib/serializeEntryValues';
import {
selectEntry,
selectUnpublishedEntry,
selectDeployPreview,
getAsset,
selectSlugEntries,
selectUnpublishedSlugEntriesByCollection,
} from 'Reducers';
import { selectFields, selectIdentifier, selectSlugField } from 'Reducers/collections';
import { selectEntry, selectUnpublishedEntry, selectDeployPreview, getAsset } from 'Reducers';
import { selectFields } from 'Reducers/collections';
import { status } from 'Constants/publishModes';
import { EDITORIAL_WORKFLOW } from 'Constants/publishModes';
import EditorInterface from './EditorInterface';
Expand Down Expand Up @@ -313,10 +305,7 @@ class Editor extends React.Component {
entry,
entryDraft,
fields,
indentifierField,
slugField,
boundGetAsset,
getAutoSlug,
collection,
changeDraftField,
changeDraftFieldValidation,
Expand All @@ -328,7 +317,6 @@ class Editor extends React.Component {
newEntry,
isModification,
currentStatus,
unavailableSlugs,
logoutUser,
deployPreview,
loadDeployPreview,
Expand Down Expand Up @@ -360,9 +348,6 @@ class Editor extends React.Component {
fields={fields}
fieldsMetaData={entryDraft.get('fieldsMetaData')}
fieldsErrors={entryDraft.get('fieldsErrors')}
indentifierField={indentifierField}
slugField={slugField}
getAutoSlug={getAutoSlug}
onChange={changeDraftField}
onValidate={changeDraftFieldValidation}
onPersist={this.handlePersistEntry}
Expand All @@ -379,7 +364,6 @@ class Editor extends React.Component {
isNewEntry={newEntry}
isModification={isModification}
currentStatus={currentStatus}
unavailableSlugs={unavailableSlugs}
onLogoutClick={logoutUser}
deployPreview={deployPreview}
loadDeployPreview={opts => loadDeployPreview(collection, slug, entry, isPublished, opts)}
Expand All @@ -395,8 +379,6 @@ function mapStateToProps(state, ownProps) {
const collectionName = collection.get('name');
const newEntry = ownProps.newRecord === true;
const fields = selectFields(collection, slug);
const indentifierField = selectIdentifier(collection, slug);
const slugField = selectSlugField(collection);
const entry = newEntry ? null : selectEntry(state, collectionName, slug);
const boundGetAsset = getAsset.bind(null, state);
const user = auth && auth.get('user');
Expand All @@ -408,20 +390,13 @@ function mapStateToProps(state, ownProps) {
const unpublishedEntry = selectUnpublishedEntry(state, collectionName, slug);
const currentStatus = unpublishedEntry && unpublishedEntry.getIn(['metaData', 'status']);
const deployPreview = selectDeployPreview(state, collectionName, slug);
const unpublishedSlugs = selectUnpublishedSlugEntriesByCollection(state, collectionName);
const publishedSlugs = selectSlugEntries(state, collectionName);
const unavailableSlugs = hasWorkflow ? publishedSlugs.concat(unpublishedSlugs) : publishedSlugs;
const getAutoSlug = getSlug(collection);
return {
collection,
collections,
newEntry,
entryDraft,
boundGetAsset,
getAutoSlug,
fields,
indentifierField,
slugField,
slug,
entry,
user,
Expand All @@ -432,7 +407,6 @@ function mapStateToProps(state, ownProps) {
collectionEntriesLoaded,
currentStatus,
deployPreview,
unavailableSlugs,
};
}

Expand Down
Loading

0 comments on commit ed3da48

Please sign in to comment.