-
-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #165 from plone/customize-flattenToAppURL
Add temporary customization for flattenToAppUrl
- Loading branch information
Showing
1 changed file
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,364 @@ | ||
/** | ||
* Url helper. | ||
* @module helpers/Url | ||
*/ | ||
|
||
import { last, memoize, isArray, isObject, isString } from 'lodash'; | ||
import { | ||
urlRegex, | ||
telRegex, | ||
mailRegex, | ||
} from '@plone/volto/helpers/Url/urlRegex'; | ||
import prependHttp from 'prepend-http'; | ||
import config from '@plone/volto/registry'; | ||
import { matchPath } from 'react-router'; | ||
|
||
/** | ||
* Get base url. | ||
* @function getBaseUrl | ||
* @param {string} url Url to be parsed. | ||
* @return {string} Base url of content object. | ||
*/ | ||
export const getBaseUrl = memoize((url) => { | ||
const { settings } = config; | ||
if (url === undefined) return; | ||
|
||
// We allow settings.nonContentRoutes to have strings (that are supposed to match | ||
// ending strings of pathnames, so we are converting them to RegEx to match also | ||
const normalized_nonContentRoutes = settings.nonContentRoutes.map((item) => { | ||
if (item.test) { | ||
return item; | ||
} else { | ||
return new RegExp(item + '$'); | ||
} | ||
}); | ||
|
||
let adjustedUrl = normalized_nonContentRoutes.reduce( | ||
(acc, item) => acc.replace(item, ''), | ||
url, | ||
); | ||
|
||
adjustedUrl = adjustedUrl || '/'; | ||
return adjustedUrl === '/' ? '' : adjustedUrl; | ||
}); | ||
|
||
/** | ||
* Get parent url. | ||
* @function getParentUrl | ||
* @param {string} url Url to be parsed. | ||
* @return {string} Parent url of content object. | ||
*/ | ||
export const getParentUrl = memoize((url) => { | ||
return url.substring(0, url.lastIndexOf('/')); | ||
}); | ||
|
||
/** | ||
* Get id from url. | ||
* @function getId | ||
* @param {string} url Url to be parsed. | ||
* @return {string} Id of content object. | ||
*/ | ||
export function getId(url) { | ||
return last(url.replace(/\?.*$/, '').split('/')); | ||
} | ||
|
||
/** | ||
* Get view of an url. | ||
* @function getView | ||
* @param {string} url Url to be parsed. | ||
* @return {string} View of content object. | ||
*/ | ||
export function getView(url) { | ||
const view = last(url.replace(/\?.*$/, '').split('/')); | ||
if ( | ||
[ | ||
'add', | ||
'layout', | ||
'contents', | ||
'edit', | ||
'delete', | ||
'diff', | ||
'history', | ||
'sharing', | ||
'controlpanel', | ||
].indexOf(view) === -1 | ||
) { | ||
return 'view'; | ||
} | ||
return view === 'layout' ? 'edit' : view; | ||
} | ||
|
||
/** | ||
* Flatten to app server URL - Given a URL if it starts with the API server URL | ||
* this method flattens it (removes) the server part | ||
* TODO: Update it when implementing non-root based app location (on a | ||
* directory other than /, eg. /myapp) | ||
* @method flattenToAppURL | ||
* @param {string} url URL of the object | ||
* @returns {string} Flattened URL to the app server | ||
*/ | ||
export function flattenToAppURL(url) { | ||
const { settings } = config; | ||
// START CUSTOMIZATION | ||
return ( | ||
url && | ||
url | ||
.replace('http://backend:8080/Plone', '') | ||
.replace(settings.internalApiPath, '') | ||
.replace(settings.apiPath, '') | ||
.replace(settings.publicURL, '') | ||
); | ||
// END CUSTOMIZATION | ||
} | ||
/** | ||
* Given a URL it remove the querystring from the URL. | ||
* @method stripQuerystring | ||
* @param {string} url URL of the object | ||
* @returns {string} URL without querystring | ||
*/ | ||
export function stripQuerystring(url) { | ||
return url.replace(/\?.*$/, ''); | ||
} | ||
|
||
/** | ||
* Given a URL if it starts with the API server URL | ||
* this method removes the /api or the /Plone part. | ||
* @method toPublicURL | ||
* @param {string} url URL of the object | ||
* @returns {string} public URL | ||
*/ | ||
export function toPublicURL(url) { | ||
const { settings } = config; | ||
return settings.publicURL.concat(flattenToAppURL(url)); | ||
} | ||
|
||
/** | ||
* Returns true if the current view is a cms ui view | ||
* @method isCmsUi | ||
* @param {string} currentPathname pathname of the current view | ||
* @returns {boolean} true if the current view is a cms ui view | ||
*/ | ||
export const isCmsUi = memoize((currentPathname) => { | ||
const { settings } = config; | ||
const fullPath = currentPathname.replace(/\?.*$/, ''); | ||
// WARNING: | ||
// not working properly for paths like /editors or similar | ||
// because the regexp test does not take that into account | ||
// https://github.com/plone/volto/issues/870 | ||
return settings.nonContentRoutes.reduce( | ||
(acc, route) => acc || new RegExp(route).test(`/${fullPath}`), | ||
false, | ||
); | ||
}); | ||
|
||
/** | ||
* Flatten to app server HTML - Given a text if it contains some urls that starts | ||
* with the API server URL this method flattens it (removes) the server part. | ||
* TODO: Update it when implementing non-root based app location (on a | ||
* directory other than /, eg. /myapp) | ||
* @method flattenHTMLToAppURL | ||
* @param {string} html Some html snippet | ||
* @returns {string} Same HTML with Flattened URLs to the app server | ||
*/ | ||
export function flattenHTMLToAppURL(html) { | ||
const { settings } = config; | ||
return settings.internalApiPath | ||
? html | ||
.replace(new RegExp(settings.internalApiPath, 'g'), '') | ||
.replace(new RegExp(settings.apiPath, 'g'), '') | ||
: html.replace(new RegExp(settings.apiPath, 'g'), ''); | ||
} | ||
|
||
/** | ||
* Add the app url | ||
* @method addAppURL | ||
* @param {string} url URL of the object | ||
* @returns {string} New URL with app | ||
*/ | ||
export function addAppURL(url) { | ||
const { settings } = config; | ||
return url.indexOf(settings.apiPath) === 0 | ||
? url | ||
: `${settings.apiPath}${url}`; | ||
} | ||
|
||
/** | ||
* Given a URL expands it to the backend URL | ||
* Useful when you have to actually call the backend from the | ||
* frontend code (eg. ICS calendar download) | ||
* It is seamless mode aware | ||
* @method expandToBackendURL | ||
* @param {string} url URL or path of the object | ||
* @returns {string} New URL with the backend URL | ||
*/ | ||
export function expandToBackendURL(path) { | ||
const { settings } = config; | ||
const APISUFIX = settings.legacyTraverse ? '' : '/++api++'; | ||
let adjustedPath; | ||
if (path.startsWith('http://') || path.startsWith('https://')) { | ||
// flattenToAppURL first if we get a full URL | ||
adjustedPath = flattenToAppURL(path); | ||
} else { | ||
// Next adds a / in front if not a full URL to make sure it's a valid relative path | ||
adjustedPath = path[0] !== '/' ? `/${path}` : path; | ||
} | ||
|
||
let apiPath = ''; | ||
if (settings.internalApiPath && __SERVER__) { | ||
apiPath = settings.internalApiPath; | ||
} else if (settings.apiPath) { | ||
apiPath = settings.apiPath; | ||
} | ||
|
||
return `${apiPath}${APISUFIX}${adjustedPath}`; | ||
} | ||
|
||
/** | ||
* Check if internal url | ||
* @method isInternalURL | ||
* @param {string} url URL of the object | ||
* @returns {boolean} True if internal url | ||
*/ | ||
export function isInternalURL(url) { | ||
const { settings } = config; | ||
|
||
const isMatch = (config.settings.externalRoutes ?? []).find((route) => { | ||
if (typeof route === 'object') { | ||
return matchPath(flattenToAppURL(url), route.match); | ||
} | ||
return matchPath(flattenToAppURL(url), route); | ||
}); | ||
|
||
const isExcluded = isMatch && Object.keys(isMatch)?.length > 0; | ||
|
||
const internalURL = | ||
url && | ||
(url.indexOf(settings.publicURL) !== -1 || | ||
(settings.internalApiPath && | ||
url.indexOf(settings.internalApiPath) !== -1) || | ||
url.indexOf(settings.apiPath) !== -1 || | ||
url.charAt(0) === '/' || | ||
url.charAt(0) === '.' || | ||
url.startsWith('#')); | ||
|
||
if (internalURL && isExcluded) { | ||
return false; | ||
} | ||
|
||
return internalURL; | ||
} | ||
|
||
/** | ||
* Check if it's a valid url | ||
* @method isUrl | ||
* @param {string} url URL of the object | ||
* @returns {boolean} True if is a valid url | ||
*/ | ||
export function isUrl(url) { | ||
return urlRegex().test(url); | ||
} | ||
|
||
/** | ||
* Get field url | ||
* @method getFieldURL | ||
* @param {object} data | ||
* @returns {string | any} URL string value if field is of url type or any. | ||
*/ | ||
export const getFieldURL = (data) => { | ||
let url = data; | ||
const _isObject = data && isObject(data) && !isArray(data); | ||
if (_isObject && data['@type'] === 'URL') { | ||
url = data['value'] ?? data['url'] ?? data['href'] ?? data; | ||
} else if (_isObject) { | ||
url = data['@id'] ?? data['url'] ?? data['href'] ?? data; | ||
} | ||
if (isArray(data)) { | ||
url = data.map((item) => getFieldURL(item)); | ||
} | ||
if (isString(url) && isInternalURL(url)) return flattenToAppURL(url); | ||
return url; | ||
}; | ||
|
||
/** | ||
* Normalize URL, adds protocol (if required eg. user has not entered the protocol) | ||
* @method normalizeUrl | ||
* @param {string} url URL of the object | ||
* @returns {boolean} URL with the protocol | ||
*/ | ||
export function normalizeUrl(url) { | ||
return prependHttp(url); | ||
} | ||
|
||
/** | ||
* Removes protocol from URL (for display) | ||
* @method removeProtocol | ||
* @param {string} url URL of the object | ||
* @returns {string} URL without the protocol part | ||
*/ | ||
export function removeProtocol(url) { | ||
return url.replace('https://', '').replace('http://', ''); | ||
} | ||
|
||
export function isMail(text) { | ||
return mailRegex().test(text); | ||
} | ||
|
||
export function isTelephone(text) { | ||
return telRegex().test(text); | ||
} | ||
|
||
export function normaliseMail(email) { | ||
if (email?.toLowerCase()?.startsWith('mailto:')) { | ||
return email; | ||
} | ||
return `mailto:${email}`; | ||
} | ||
|
||
export function normalizeTelephone(tel) { | ||
if (tel?.toLowerCase()?.startsWith('tel:')) { | ||
return tel; | ||
} | ||
return `tel:${tel}`; | ||
} | ||
|
||
export function checkAndNormalizeUrl(url) { | ||
let res = { | ||
isMail: false, | ||
isTelephone: false, | ||
url: url, | ||
isValid: true, | ||
}; | ||
if (URLUtils.isMail(URLUtils.normaliseMail(url))) { | ||
res.isMail = true; | ||
res.url = URLUtils.normaliseMail(url); | ||
} else if (URLUtils.isTelephone(url)) { | ||
//Phone | ||
res.isTelephone = true; | ||
res.url = URLUtils.normalizeTelephone(url); | ||
} else { | ||
//url | ||
if ( | ||
res.url?.length >= 0 && | ||
!res.url.startsWith('/') && | ||
!res.url.startsWith('#') | ||
) { | ||
res.url = URLUtils.normalizeUrl(url); | ||
if (!URLUtils.isUrl(res.url)) { | ||
res.isValid = false; | ||
} | ||
} | ||
if (res.url === undefined || res.url === null) res.isValid = false; | ||
} | ||
return res; | ||
} | ||
|
||
export const URLUtils = { | ||
normalizeTelephone, | ||
normaliseMail, | ||
normalizeUrl, | ||
isTelephone, | ||
isMail, | ||
isUrl, | ||
checkAndNormalizeUrl, | ||
}; |