@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
314 lines (312 loc) • 11.4 kB
JavaScript
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import queryString from 'query-string';
import { toQueryString } from './object';
import { v4 as uuid } from 'uuid';
import { ensureSingleSlash } from './string';
// Originally from ComponentPanel.getPreviewPagePath
export function getPathFromPreviewURL(previewURL) {
let pagePath = previewURL;
if (pagePath.indexOf('?') > 0) {
pagePath = pagePath.split('?')[0];
}
if (pagePath.indexOf('#') > 0) {
pagePath = pagePath.split('#')[0];
}
if (pagePath.indexOf(';') > 0) {
pagePath = pagePath.split(';')[0];
}
pagePath = pagePath.replace('.html', '.xml');
if (pagePath.indexOf('.xml') === -1) {
if (pagePath.substring(pagePath.length - 1) !== '/') {
pagePath += '/';
}
pagePath += 'index.xml';
}
return `/site/website${pagePath}`;
}
/**
* Computes and returns the preview URL from a path. Notice non-previewable paths will not be transformed.
* @param path {string} The path to compute the preview URL from
* @returns {string} The preview URL
*/
export function getPreviewURLFromPath(path) {
// Transform only paths that start with `/site/website`. Preview url and path for static assets is the same.
// Non-previewable paths are not transformed by this function.
// It is known that for some existing platform users, paths do not end with `/index.xml`. For example in
// `/site/website/folder/article.xml` previewUrl should be `/folder/article`. In these cases, the file
// name cannot be striped off.
return /^\/site\/website/.test(path)
? path
.replace(/^\/site\/website/, '')
.replace(/\/(index|default).xml$/, '')
.replace(/(.xml|\/)$/, '') || '/'
: path;
}
export function getFileNameFromPath(path) {
return path.substring(path.lastIndexOf('/') + 1);
}
export function getQueryVariable(query, variable) {
let qs = queryString.parse(query);
return qs[variable] ?? null;
}
export function parseQueryString() {
return queryString.parse(window.location.search);
}
export function withoutIndex(path) {
return path?.replace('/index.xml', '');
}
export function withIndex(path) {
return `${withoutIndex(path)}/index.xml`;
}
/**
* Takes in a path and if it ends with a file, strips the file off the path (returns the parent
* path of the file). Returns same path if not file is present in the path.
**/
export function withoutFile(path) {
const pieces = path.replace('/$', '').split('/');
const lastPieceSplitByDot = pieces[pieces.length - 1].split('.');
if (lastPieceSplitByDot.length > 1) {
return `/${pieces.slice(1, pieces.length - 1).join('/')}`;
} else {
return path;
}
}
/**
* Takes in a path (or file name) and extracts its extension (e.g. "/files/names.txt" => "txt")
**/
export function getFileExtension(path) {
return (path ?? '').match(/\.[0-9a-z]+$/i)?.[0].substr(1) ?? '';
}
export function hasExtension(path) {
return getFileExtension(path) !== '';
}
export function removeExtension(name) {
return name.replace(/\.[^/.]+$/, '');
}
export function getParentPath(path) {
let splitPath = withoutIndex(path).split('/');
splitPath.pop();
return splitPath.join('/') || '/';
}
export function isRootPath(path) {
return getRootPath(path) === withoutIndex(path);
}
export function getRootPath(path) {
return withoutIndex(path)
.split('/')
.slice(0, path.startsWith('/site') ? 3 : 2)
.join('/');
}
export function getParentsFromPath(path, rootPath) {
let splitPath = withoutIndex(path).replace(rootPath, '').split('/');
splitPath.pop();
return [rootPath, ...splitPath.map((value, i) => `${rootPath}/${splitPath.slice(1, i + 1).join('/')}`).splice(1)];
}
export function getIndividualPaths(path, rootPath = '') {
// `/` is not valid path in CrafterCMS
rootPath === '/' && (rootPath = '');
let rootWithoutIndex = withoutIndex(rootPath);
let paths = (rootPath ? path.replace(new RegExp(`^${withoutIndex(rootPath)}`), '') : path).split('/');
paths[0] === '' && paths.shift();
let length = paths.length;
let individualPaths = paths.map((p, i) => `${rootWithoutIndex}/${paths.slice(0, length - i).join('/')}`).reverse();
rootWithoutIndex && individualPaths.unshift(rootWithoutIndex);
if (
individualPaths.length > 1 &&
individualPaths[individualPaths.length - 1] === `${individualPaths[individualPaths.length - 2]}/index.xml`
) {
individualPaths.pop();
individualPaths[individualPaths.length - 1] += '/index.xml';
}
return individualPaths;
}
export function getPasteItemFromPath(path, paths) {
// create PasteItem with base path
let pasteItem = {
path,
children: []
};
paths.forEach((path) => addToPasteItem(pasteItem, path));
return pasteItem;
}
function addToPasteItem(pasteItem, path) {
const parentPath = getParentPath(path);
if (withoutIndex(pasteItem.path) === parentPath) {
// if current path is direct children of pasteItem's root path
pasteItem.children.push({
path,
children: []
});
} else if (pasteItem.path !== path) {
// neither root nor direct children - look in which of the children the item belongs to
const pathWithoutIndex = withoutIndex(path);
const pasteItemParent = pasteItem.children.find((item) =>
// includes parameter ends with a '/' to make it sure that it's a complete path and not part of a name in a path
// (it may match with another path that starts with the same chars)
pathWithoutIndex.includes(`${withoutIndex(item.path)}/`)
);
addToPasteItem(pasteItemParent, path);
}
}
export function isValidCopyPastePath(targetPath, sourcePath) {
return !getIndividualPaths(targetPath).includes(sourcePath);
}
export function isValidCutPastePath(targetPath, sourcePath) {
return isValidCopyPastePath(targetPath, sourcePath) && withoutIndex(targetPath) !== getParentPath(sourcePath);
}
export function getEditFormSrc({
path,
selectedFields,
site,
authoringBase,
readonly,
isHidden,
modelId,
changeTemplate,
contentTypeId,
isNewContent,
iceGroupId,
newEmbedded,
canEdit,
fieldsIndexes
}) {
const qs = toQueryString({
site,
path,
selectedFields,
readonly,
isHidden,
modelId,
changeTemplate,
contentTypeId,
isNewContent,
iceId: iceGroupId,
newEmbedded,
canEdit,
fieldsIndexes
});
return `${authoringBase}/legacy/form${qs}`;
}
export function getCodeEditorSrc({ path, site, type, contentType, authoringBase, readonly }) {
const qs = toQueryString({
site,
path,
type,
contentType,
readonly
});
return `${authoringBase}/legacy/form${qs}`;
}
export function stripDuplicateSlashes(str) {
return str.replace(/\/+/g, '/');
}
export function getItemGroovyPath(item) {
const contentTypeName = /[^/]*$/.exec(item.contentTypeId)[0];
return `${getControllerPath(item.systemType)}/${contentTypeName}.groovy`;
}
export function getItemTemplatePath(item, contentTypes) {
return contentTypes[item.contentTypeId].displayTemplate;
}
export function getControllerPath(type) {
return `/scripts/${type === 'page' ? 'pages' : 'components'}`;
}
const availableMacrosRegex = /{(objectId|objectGroupId|objectGroupId2|year|month|yyyy|mm|dd|parentPath(\[[0-9]+])?)}/;
export function processPathMacros(dependencies) {
let { path, objectId, objectGroupId, useUUID, fullParentPath } = dependencies;
if (!path) return path;
let processedPath = path;
// Remove unrecognized macros.
const pathMacros = processedPath.match(/\{(.+)}/g) ?? [];
pathMacros.forEach((macro) => {
if (!availableMacrosRegex.test(macro)) {
processedPath = processedPath.replace(macro, '');
}
});
processedPath = ensureSingleSlash(processedPath);
// The objectGroupId is the first 4 characters of the objectId, so compute if not provided.
if (objectGroupId === undefined && objectId) {
objectGroupId = objectId.substring(0, 4);
}
if (processedPath.includes('{objectId}')) {
if (useUUID) {
processedPath = processedPath.replace('{objectId}', uuid());
} else {
processedPath = processedPath.replace('{objectId}', objectId);
}
}
if (processedPath.includes('{objectGroupId}')) {
processedPath = processedPath.replace('{objectGroupId}', objectGroupId);
}
if (processedPath.includes('{objectGroupId2}')) {
processedPath = processedPath.replace('{objectGroupId2}', objectGroupId.substring(0, 2));
}
const currentDate = new Date();
if (processedPath.includes('{year}')) {
processedPath = processedPath.replace('{year}', `${currentDate.getFullYear()}`);
}
if (processedPath.includes('{month}')) {
processedPath = processedPath.replace('{month}', ('0' + (currentDate.getMonth() + 1)).slice(-2));
}
if (processedPath.includes('{yyyy}')) {
processedPath = processedPath.replace('{yyyy}', `${currentDate.getFullYear()}`);
}
if (processedPath.includes('{mm}')) {
processedPath = processedPath.replace('{mm}', ('0' + (currentDate.getMonth() + 1)).slice(-2));
}
if (processedPath.includes('{dd}')) {
processedPath = processedPath.replace('{dd}', ('0' + currentDate.getDate()).slice(-2));
}
if (fullParentPath) {
const parentPathPieces = fullParentPath.substr(1).split('/');
processedPath = processedPath.replace(/{parentPath(\[\s*?(\d+)\s*?])?}/g, function (fullMatch, indexExp, index) {
if (indexExp === void 0) {
// Handle simple exp `{parentPath}`
return fullParentPath.replace(/\/[^/]*\/[^/]*\/([^.]*)(\/[^/]*\.xml)?$/, '$1');
} else {
// Handle indexed exp `{parentPath[i]}`
return parentPathPieces[parseInt(index) + 2];
}
});
}
return processedPath;
}
export const pickExtensionForItemType = (systemType, name) => {
if (systemType === 'asset') {
return getFileExtension(name);
} else {
return systemType === 'controller' ? `groovy` : `ftl`;
}
};
export const getFileNameWithExtensionForItemType = (type, name) =>
`${name}.${pickExtensionForItemType(type)}`
.replace(/(\.groovy)(\.groovy)|(\.ftl)(\.ftl)/g, '$1$3')
.replace(/\.{2,}/g, '.');