UNPKG

@gitlab/ui

Version:
137 lines (127 loc) 5.03 kB
import { RX_QUERY_START, RX_PLUS, RX_ENCODE_REVERSE, RX_ENCODED_COMMA } from '../constants/regex'; import { isTag } from './dom'; import { isPlainObject, isUndefined, isNull, isArray, isString } from './inspect'; import { keys } from './object'; import { safeVueInstance } from './safe-vue-instance'; import { toString } from './string'; const ANCHOR_TAG = 'a'; // Method to replace reserved chars const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16); // Fixed encodeURIComponent which is more conformant to RFC3986: // - escapes [!'()*] // - preserve commas const encode = str => encodeURIComponent(toString(str)).replace(RX_ENCODE_REVERSE, encodeReserveReplacer).replace(RX_ENCODED_COMMA, ','); const decode = decodeURIComponent; // Stringifies an object of query parameters // See: https://github.com/vuejs/vue-router/blob/dev/src/util/query.js const stringifyQueryObj = obj => { if (!isPlainObject(obj)) { return ''; } const query = keys(obj).map(key => { const value = obj[key]; if (isUndefined(value)) { return ''; } else if (isNull(value)) { return encode(key); } else if (isArray(value)) { return value.reduce((results, value2) => { if (isNull(value2)) { results.push(encode(key)); } else if (!isUndefined(value2)) { // Faster than string interpolation results.push(encode(key) + '=' + encode(value2)); } return results; }, []).join('&'); } // Faster than string interpolation return encode(key) + '=' + encode(value); }) /* must check for length, as we only want to filter empty strings, not things that look falsey! */.filter(x => x.length > 0).join('&'); return query ? `?${query}` : ''; }; const parseQuery = query => { const parsed = {}; query = toString(query).trim().replace(RX_QUERY_START, ''); if (!query) { return parsed; } query.split('&').forEach(param => { const parts = param.replace(RX_PLUS, ' ').split('='); const key = decode(parts.shift()); const value = parts.length > 0 ? decode(parts.join('=')) : null; if (isUndefined(parsed[key])) { parsed[key] = value; } else if (isArray(parsed[key])) { parsed[key].push(value); } else { parsed[key] = [parsed[key], value]; } }); return parsed; }; const isLink = props => !!(props.href || props.to); const isRouterLink = tag => !!(tag && !isTag(tag, 'a')); const computeTag = (_ref, thisOrParent) => { let { to, disabled, routerComponentName } = _ref; const hasRouter = !!safeVueInstance(thisOrParent).$router; const hasNuxt = !!safeVueInstance(thisOrParent).$nuxt; if (!hasRouter || hasRouter && (disabled || !to)) { return ANCHOR_TAG; } // TODO: // Check registered components for existence of user supplied router link component name // We would need to check PascalCase, kebab-case, and camelCase versions of name: // const name = routerComponentName // const names = [name, PascalCase(name), KebabCase(name), CamelCase(name)] // exists = names.some(name => !!thisOrParent.$options.components[name]) // And may want to cache the result for performance or we just let the render fail // if the component is not registered return routerComponentName || (hasNuxt ? 'nuxt-link' : 'router-link'); }; const computeRel = function () { let { target, rel } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return target === '_blank' && isNull(rel) ? 'noopener' : rel || null; }; const computeHref = function () { let { href, to } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; let tag = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ANCHOR_TAG; let fallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '#'; let toFallback = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '/'; // Return `href` when explicitly provided if (href) { return href; } // We've checked for `$router` in `computeTag()`, so `isRouterLink()` indicates a live router // When deferring to Vue Router's `<router-link>`, don't use the `href` attribute at all // We return `null`, and then remove `href` from the attributes passed to `<router-link>` if (isRouterLink(tag)) { return null; } // Fallback to `to` prop (if `to` is a string) if (isString(to)) { return to || toFallback; } // Fallback to `to.path' + `to.query` + `to.hash` prop (if `to` is an object) if (isPlainObject(to) && (to.path || to.query || to.hash)) { const path = toString(to.path); const query = stringifyQueryObj(to.query); let hash = toString(to.hash); hash = !hash || hash.charAt(0) === '#' ? hash : `#${hash}`; return `${path}${query}${hash}` || toFallback; } // If nothing is provided return the fallback return fallback; }; export { computeHref, computeRel, computeTag, isLink, isRouterLink, parseQuery, stringifyQueryObj };