2017-08-14 09:00:47 -04:00
|
|
|
import url from 'url';
|
2021-04-29 10:57:43 +01:00
|
|
|
import urlJoin from 'url-join';
|
2018-03-27 16:56:10 -06:00
|
|
|
import diacritics from 'diacritics';
|
2017-10-03 09:01:06 -06:00
|
|
|
import sanitizeFilename from 'sanitize-filename';
|
2017-10-03 09:48:43 -06:00
|
|
|
import { isString, escapeRegExp, flow, partialRight } from 'lodash';
|
2021-05-31 16:46:41 +02:00
|
|
|
|
2021-05-31 14:23:16 +02:00
|
|
|
import type { CmsSlug } from '../types/redux';
|
2017-09-05 13:24:16 -07:00
|
|
|
|
2020-12-23 14:18:41 +02:00
|
|
|
function getUrl(urlString: string, direct?: boolean) {
|
2018-08-07 14:46:54 -06:00
|
|
|
return `${direct ? '/#' : ''}${urlString}`;
|
2017-03-15 14:32:41 -04:00
|
|
|
}
|
|
|
|
|
2020-12-23 14:18:41 +02:00
|
|
|
export function getCollectionUrl(collectionName: string, direct?: boolean) {
|
2018-08-07 14:46:54 -06:00
|
|
|
return getUrl(`/collections/${collectionName}`, direct);
|
2017-03-15 14:32:41 -04:00
|
|
|
}
|
|
|
|
|
2020-12-23 14:18:41 +02:00
|
|
|
export function getNewEntryUrl(collectionName: string, direct?: boolean) {
|
2018-08-07 14:46:54 -06:00
|
|
|
return getUrl(`/collections/${collectionName}/new`, direct);
|
2017-03-15 14:32:41 -04:00
|
|
|
}
|
2017-09-05 13:24:16 -07:00
|
|
|
|
2021-03-11 12:08:46 +02:00
|
|
|
export function addParams(urlString: string, params: Record<string, string>) {
|
2017-08-14 09:00:47 -04:00
|
|
|
const parsedUrl = url.parse(urlString, true);
|
|
|
|
parsedUrl.query = { ...parsedUrl.query, ...params };
|
|
|
|
return url.format(parsedUrl);
|
|
|
|
}
|
|
|
|
|
2019-12-18 18:16:02 +02:00
|
|
|
export function stripProtocol(urlString: string) {
|
2018-03-27 16:56:10 -06:00
|
|
|
const protocolEndIndex = urlString.indexOf('//');
|
|
|
|
return protocolEndIndex > -1 ? urlString.slice(protocolEndIndex + 2) : urlString;
|
WIP - Global UI (#785)
* update top bar and collections sidebar UI
* update collection entries UI
* improve global layout
* merge search page into collection page
* enable new entry button
* search fixup
* wip -initial editor update
* update editor scrolling and markdown toolbar position
* wip
* editor toolbar progress
* editor toolbar wip
* finished basic editor toolbar
* add standalone toggle component
* improve markdown toolbar spacing
* add user avatar placeholder
* finish markdown toggle styling
* refactor icon setup, add new icons
* add new icons to markdown editor toolbar
* remove extra app container
* add markdown active mark style
* relation and text widget styling
* widget design updates, basic list/object design update
* widget style updates, image widget improvements
* refactor widget directory, fix file removal
* widget focus styles
* finish editor widget focus styles
* migrate media library modal to react-modal
* wip - migrate editor component form to modal
* wip - move editor component form to modal
* wip - embed plugin forms in the editor
* inline shortcode forms working
* disable react hot loading, its breaking things
* improve shortcode form styles
* make shortcode form collapsible, improve styling
* add close functionality to shortcode blocks
* improve base media library styling
* fix shortcode label
* migrate unstyled workflow to new UI
* wip - reorganizing everything
* more work moving everything
* finish more moving and eliminating stuff
* restructure, remove react-toolbox
* wip - removing old stuff, more restructure
* finish restructure
* wip - css arch
* switch back to test repo
* update react-datetime to ^2.11.0
* remove leftover react-toolbox button
* more restructuring clean-up
* fix UI component directory case
* wip -css editor control style
* wip - consolidate widget styles
* wip - use a single control renderer
* fixed object values breaking
* wip - editor control active styles
* pass control wrapper to widgets
* ensure branch name is trimmed
* wip - improve widget authoring support
* import Map to Widget component
* refactor toolbar buttons
* wip - more widget active styles
* break out editor toggle component
* add local scroll sync back
* update editor toggle icons
* limit editor control pane content width
* fix editor control spacing
* migrate markdown toolbar stickiness to css
* fix markdown toolbar border radius
* temporarily use test backend
* stop markdown toolbar from going to bottom
* restore disabled markdown toolbar buttons for raw
* test markdown widget without focus styles
* more widget updates
* remove card visuals from editor
* disable dragging editor split off screen
* use editorControl component for shortcode fields
* make header site link configurable
* add configurable collection descriptions
* temporarily add example assets
* add basic list view
* remove outdated css mixins
* add and implement search icon
* activate quick add menu
* visualize usable space in editor view
* fix entry close, other improvements
* wip - editorial workflow updates
* some dropshadow and other CSS tweaks
* workflow ui updates
* add worfklow card buttons
* fix workflow card button handlers
* some dropshadow and other CSS tweaks
* make workflow board wider
* center workflow and collection views
* add basic responsiveness
* a bunch of fun UI fixes! a BUNCH! (#875)
* give `.nc-entryEditor-toolbar-mainSection` left and right child divs
* a bunch of fun UI fixes! a BUNCH!
* remove obscure --buttonShadow
* revert to test repo
* fix not found page styling
* allow workflow publishing from any column
* disallow publishing from all columns, with feedback
* fix new entry button
* fix markdown state persisting across entries
* enable simple workflow save and new from editor
* update slug in address bar when saving new entry
* wip - workflow updates, deletion working
* add status change functionality to editor
* wip - improving status change from editor
* editor toolbar back button improvements, loading improvements, cleanup
* progress on the media library UI cleanup
* remove font smothing css
* a quick fix for these buttons
* tweaks
* progress on media library modal— broken FYI
* fix media library functionality, finish migrating footer
* remove media library footer files
* remove leftover css import
* fix media library
* editor publishing functionality complete (unstyled)
* remove leftover loader var from media library
* wip - editor publishing styles
* add status dropdown styling
* editor toolbar style updates
* editor toolbar state improvements
* progress on the media library UI cleanup, style improvements
* finish editorial workflow editor styling
* finish media library styling
* fix config
* add what-input to optimize focus styling
* fix button
* fix navigation blocking for simple workflow
* improve simple workflow publishing
* add avatar dropdown to editor top bar
* style github and test-repo auth pages
* add git gateway auth page styles
* improve editor error styling
2017-12-07 12:37:10 -05:00
|
|
|
}
|
|
|
|
|
2017-10-03 10:07:20 -06:00
|
|
|
/* See https://www.w3.org/International/articles/idn-and-iri/#path.
|
|
|
|
* According to the new IRI (Internationalized Resource Identifier) spec, RFC 3987,
|
|
|
|
* ASCII chars should be kept the same way as in standard URIs (letters digits _ - . ~).
|
|
|
|
* Non-ASCII chars (unless they are not in the allowed "ucschars" list) should be percent-encoded.
|
|
|
|
* If the string is not encoded in Unicode, it should be converted to UTF-8 and normalized first,
|
2019-09-24 22:16:09 +02:00
|
|
|
* but JS stores strings as UTF-16/UCS-2 internally, so we should not normalize or re-encode.
|
2017-10-03 10:07:20 -06:00
|
|
|
*/
|
2017-09-30 17:27:07 -06:00
|
|
|
const uriChars = /[\w\-.~]/i;
|
2021-05-19 14:39:35 +02:00
|
|
|
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;
|
2021-02-08 20:01:21 +02:00
|
|
|
|
|
|
|
function validURIChar(char: string) {
|
|
|
|
return uriChars.test(char);
|
|
|
|
}
|
|
|
|
|
|
|
|
function validIRIChar(char: string) {
|
|
|
|
return uriChars.test(char) || ucsChars.test(char);
|
|
|
|
}
|
2018-08-07 14:46:54 -06:00
|
|
|
|
2019-12-18 18:16:02 +02:00
|
|
|
export function getCharReplacer(encoding: string, replacement: string) {
|
|
|
|
let validChar: (char: string) => boolean;
|
2019-11-28 05:39:33 +02:00
|
|
|
|
2018-08-07 14:46:54 -06:00
|
|
|
if (encoding === 'unicode') {
|
2018-03-27 16:56:10 -06:00
|
|
|
validChar = validIRIChar;
|
2018-08-07 14:46:54 -06:00
|
|
|
} else if (encoding === 'ascii') {
|
2018-03-27 16:56:10 -06:00
|
|
|
validChar = validURIChar;
|
|
|
|
} else {
|
|
|
|
throw new Error('`options.encoding` must be "unicode" or "ascii".');
|
|
|
|
}
|
2017-10-03 19:21:58 -06:00
|
|
|
|
|
|
|
// Check and make sure the replacement character is actually a safe char itself.
|
2018-03-27 16:56:10 -06:00
|
|
|
if (!Array.from(replacement).every(validChar)) {
|
2018-08-07 14:46:54 -06:00
|
|
|
throw new Error('The replacement character(s) (options.replacement) is itself unsafe.');
|
2018-03-27 16:56:10 -06:00
|
|
|
}
|
2017-10-03 18:06:15 -06:00
|
|
|
|
2019-12-18 18:16:02 +02:00
|
|
|
return (char: string) => (validChar(char) ? char : replacement);
|
2019-11-28 05:39:33 +02:00
|
|
|
}
|
|
|
|
// `sanitizeURI` does not actually URI-encode the chars (that is the browser's and server's job), just removes the ones that are not allowed.
|
2021-03-11 12:08:46 +02:00
|
|
|
export function sanitizeURI(
|
|
|
|
str: string,
|
|
|
|
options?: { replacement: CmsSlug['sanitize_replacement']; encoding: CmsSlug['encoding'] },
|
|
|
|
) {
|
|
|
|
const { replacement = '', encoding = 'unicode' } = options || {};
|
|
|
|
|
2019-11-28 05:39:33 +02:00
|
|
|
if (!isString(str)) {
|
|
|
|
throw new Error('The input slug must be a string.');
|
|
|
|
}
|
|
|
|
if (!isString(replacement)) {
|
|
|
|
throw new Error('`options.replacement` must be a string.');
|
|
|
|
}
|
|
|
|
|
2017-10-03 20:21:56 -06:00
|
|
|
// `Array.from` must be used instead of `String.split` because
|
|
|
|
// `split` converts things like emojis into UTF-16 surrogate pairs.
|
2021-05-19 14:39:35 +02:00
|
|
|
return Array.from(str).map(getCharReplacer(encoding, replacement)).join('');
|
2017-09-30 17:27:07 -06:00
|
|
|
}
|
|
|
|
|
2021-03-11 12:08:46 +02:00
|
|
|
export function sanitizeChar(char: string, options?: CmsSlug) {
|
|
|
|
const { encoding = 'unicode', sanitize_replacement: replacement = '' } = options || {};
|
2019-11-28 05:39:33 +02:00
|
|
|
return getCharReplacer(encoding, replacement)(char);
|
|
|
|
}
|
|
|
|
|
2021-03-11 12:08:46 +02:00
|
|
|
export function sanitizeSlug(str: string, options?: CmsSlug) {
|
2018-08-07 14:46:54 -06:00
|
|
|
if (!isString(str)) {
|
|
|
|
throw new Error('The input slug must be a string.');
|
|
|
|
}
|
|
|
|
|
2021-05-19 14:39:35 +02:00
|
|
|
const {
|
|
|
|
encoding,
|
|
|
|
clean_accents: stripDiacritics,
|
|
|
|
sanitize_replacement: replacement,
|
|
|
|
} = options || {};
|
2019-12-18 18:16:02 +02:00
|
|
|
|
2018-03-27 16:56:10 -06:00
|
|
|
const sanitizedSlug = flow([
|
|
|
|
...(stripDiacritics ? [diacritics.remove] : []),
|
|
|
|
partialRight(sanitizeURI, { replacement, encoding }),
|
2017-10-03 09:48:43 -06:00
|
|
|
partialRight(sanitizeFilename, { replacement }),
|
2018-03-27 16:56:10 -06:00
|
|
|
])(str);
|
|
|
|
|
2018-12-27 05:45:06 +01:00
|
|
|
// Remove any doubled or leading/trailing replacement characters (that were added in the sanitizers).
|
2018-08-07 14:46:54 -06:00
|
|
|
const doubleReplacement = new RegExp(`(?:${escapeRegExp(replacement)})+`, 'g');
|
2019-12-18 18:16:02 +02:00
|
|
|
const trailingReplacement = new RegExp(`${escapeRegExp(replacement)}$`);
|
|
|
|
const leadingReplacement = new RegExp(`^${escapeRegExp(replacement)}`);
|
2018-12-27 05:45:06 +01:00
|
|
|
|
2019-12-18 18:16:02 +02:00
|
|
|
const normalizedSlug: string = sanitizedSlug
|
2017-10-03 18:46:12 -06:00
|
|
|
.replace(doubleReplacement, replacement)
|
2019-12-18 18:16:02 +02:00
|
|
|
.replace(leadingReplacement, '')
|
|
|
|
.replace(trailingReplacement, '');
|
2017-10-03 09:01:06 -06:00
|
|
|
|
2017-10-03 09:48:43 -06:00
|
|
|
return normalizedSlug;
|
2017-10-03 09:01:06 -06:00
|
|
|
}
|
2021-04-29 10:57:43 +01:00
|
|
|
|
|
|
|
export function joinUrlPath(base: string, ...path: string[]) {
|
|
|
|
return urlJoin(base, ...path);
|
|
|
|
}
|