import { compile } from 'path-to-regexp' import { ALLOWED_LINK_PROTOCOLS, PAGE_PARAM_TYPE_VALIDATION_FUNCTIONS, } from '@baserow/modules/builder/enums' import { ensureString } from '@baserow/modules/core/utils/validator' /** * Responsible for generating the data necessary to resolve an application builder * element URL. Used by LinkElement and LinkField to generate URLs, based on the * designer's navigation type / page / url choices. * * @param {Object} element The element's properties we'll use for generating the URL. * @param {Object} builder A builder application. * @param {Array} pages A list of pages of this application. * @param {Function} resolveFormula A resolveFormula function we'll use if the * element has page parameters which need to be resolved. * @param {String} editorMode A builder application's editor mode. * @returns {String} A resolved URL. */ export default function resolveElementUrl( element, builder, pages, resolveFormula, editorMode ) { let resolvedUrl = '' if (element.navigation_type === 'page') { if (!isNaN(element.navigate_to_page_id)) { const page = pages.find(({ id }) => id === element.navigate_to_page_id) // The builder page list might be empty or the page has been deleted if (!page) { return resolvedUrl } const paramTypeMap = Object.fromEntries( page.path_params.map(({ name, type }) => [name, type]) ) const toPath = compile(page.path, { encode: encodeURIComponent }) const pageParams = Object.fromEntries( element.page_parameters .filter(({ name }) => name in paramTypeMap) .map(({ name, value }) => [ name, PAGE_PARAM_TYPE_VALIDATION_FUNCTIONS[paramTypeMap[name]]( resolveFormula(value) ), ]) ) resolvedUrl = toPath(pageParams) } } else { resolvedUrl = ensureString(resolveFormula(element.navigate_to_url)) } resolvedUrl = prefixInternalResolvedUrl( resolvedUrl, builder, element.navigation_type, editorMode ) // If the protocol is a supported one, return early. const protocolRegex = /^[A-Za-z]+:/ if (protocolRegex.test(resolvedUrl)) { for (const protocol of ALLOWED_LINK_PROTOCOLS) { if (resolvedUrl.toLowerCase().startsWith(protocol)) { return resolvedUrl } } // Disallow unsupported protocols, e.g. `javascript:` return '' } return resolvedUrl } /** * Responsible for prefixing a resolvedUrl with the builder application's preview * URI, if it meets certain conditions. * * @param {String} resolvedUrl A URL which `resolveElementUrl` has generated. * @param {Object} builder A builder application. * @param {String} navigationType An element's `navigation_type` (custom / page). * @param {String} editorMode A builder application's editor mode. * @returns {String} A URL we may have prefixed with the application's preview URI. */ export function prefixInternalResolvedUrl( resolvedUrl, builder, navigationType, editorMode ) { if ( resolvedUrl && editorMode === 'preview' && (navigationType === 'page' || (navigationType === 'custom' && resolvedUrl.startsWith('/'))) ) { // Add prefix in preview mode for page navigation or custom URL starting with `/` return `/builder/${builder.id}/preview${resolvedUrl}` } else { return resolvedUrl } }