diff --git a/src/backends/backend.js b/src/backends/backend.js index f79bc856..19d02097 100644 --- a/src/backends/backend.js +++ b/src/backends/backend.js @@ -5,7 +5,8 @@ import GitGatewayBackend from "./git-gateway/implementation"; import { resolveFormat } from "../formats/formats"; import { selectListMethod, selectEntrySlug, selectEntryPath, selectAllowNewEntries, selectFolderEntryExtension } from "../reducers/collections"; import { createEntry } from "../valueObjects/Entry"; -import sanitize from 'sanitize-filename'; +import { sanitizeIRI } from "../lib/urlHelper"; +import sanitizeFilename from 'sanitize-filename'; class LocalStorageAuthStore { storageKey = "netlify-cms-user"; @@ -57,7 +58,19 @@ const slugFormatter = (template = "{{slug}}", entryData) => { } }); - return sanitize(slug, {replacement: "-"}).replace(/[.]/g, '-'); + // Convert slug to lower-case; + slug = slug.toLocaleLowerCase(); + + // Replace periods and spaces with dashes. + slug = slug.replace(/[.\s]/g, '-'); + // Sanitize as IRI (i18n URI) and as filename. + slug = sanitizeIRI(slug, {replacement: "-"}); + slug = sanitizeFilename(slug, {replacement: "-"}); + + // Remove any doubled or trailing replacement characters (that were added in the sanitizers). + slug = slug.replace(/-+/g, '-').replace(/-$/, ''); + + return slug; }; class Backend { diff --git a/src/lib/urlHelper.js b/src/lib/urlHelper.js index 62dfb588..c3a90939 100644 --- a/src/lib/urlHelper.js +++ b/src/lib/urlHelper.js @@ -12,6 +12,22 @@ export function getNewEntryUrl(collectionName, direct) { return getUrl(`/collections/${ collectionName }/entries/new`, direct); } +// Unreserved chars from RFC3987. +const uriChars = /[\w\-.~]/i; +const ucsChars = /[\xA0-\u{D7FF}]|[\u{F900}-\u{FDCF}]|[\u{FDF0}-\u{FFEF}]|[\u{10000}-\u{1FFFD}]|[\u{20000}-\u{2FFFD}]|[\u{30000}-\u{3FFFD}]|[\u{40000}-\u{4FFFD}]|[\u{50000}-\u{5FFFD}]|[\u{60000}-\u{6FFFD}]|[\u{70000}-\u{7FFFD}]|[\u{80000}-\u{8FFFD}]|[\u{90000}-\u{9FFFD}]|[\u{A0000}-\u{AFFFD}]|[\u{B0000}-\u{BFFFD}]|[\u{C0000}-\u{CFFFD}]|[\u{D0000}-\u{DFFFD}]|[\u{E1000}-\u{EFFFD}]/u; +export function sanitizeIRI(str, { replacement }) { + let result = ""; + // We cannot use a `map` function here because `string.split()` splits things like emojis into surrogate pairs. + for (const char of str) { + if (uriChars.test(char) || ucsChars.test(char)) { + result += char; + } else { + result += replacement; + } + } + return result; +} + export function urlize(string) { const sanitized = makePathSanitized(string); const parsedURL = url.parse(sanitized);