@gitlab/ui
Version:
GitLab UI Components
137 lines (127 loc) • 5.03 kB
JavaScript
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 };