@gitlab/ui
Version:
GitLab UI Components
173 lines (159 loc) • 5.48 kB
JavaScript
import has from 'lodash/has';
import isString from 'lodash/isString';
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
//
const PREFIX = '%{';
const SUFFIX = '}';
const START_SUFFIX = 'Start';
const END_SUFFIX = 'End';
const PLACE_HOLDER_REGEX = new RegExp(`(${PREFIX}[a-z]+[\\w-]*[a-z0-9]+${SUFFIX})`, 'gi');
function groupPlaceholdersByStartTag() {
let placeholders = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
return Object.entries(placeholders).reduce((acc, _ref) => {
let [slotName, [startTag, endTag]] = _ref;
acc[startTag] = {
slotName,
endTag
};
return acc;
}, {});
}
function getPlaceholderDefinition(chunk, placeholdersByStartTag) {
const tagName = chunk.slice(PREFIX.length, -SUFFIX.length);
if (has(placeholdersByStartTag, tagName)) {
// Use provided custom placeholder definition
return {
...placeholdersByStartTag[tagName],
tagName
};
}
if (tagName.endsWith(START_SUFFIX)) {
// Tag conforms to default start/end tag naming convention
const slotName = tagName.slice(0, -START_SUFFIX.length);
return {
slotName,
endTag: `${slotName}${END_SUFFIX}`,
tagName
};
}
return {
slotName: tagName,
endTag: undefined,
tagName
};
}
var script = {
name: 'GlSprintf',
functional: true,
props: {
/**
* A translated string with named placeholders, e.g., "Written by %{author}".
*/
message: {
type: String,
required: true
},
/**
* An object mapping slot names to custom start/end placeholders. Use this
* to avoid changing an existing message, and in turn invalidating existing
* translations, in the case it uses non-default placeholders.
*/
placeholders: {
type: Object,
required: false,
default: undefined,
validator: value => Object.values(value).every(
// eslint-disable-next-line unicorn/no-array-callback-reference
tagPair => Array.isArray(tagPair) && tagPair.length === 2 && tagPair.every(isString))
}
},
/**
* Available slots are determined by the placeholders in the provided
* message prop. For example, a message of "Written by %{author}" has
* a slot called "author", and its content is used to replace "%{author}"
* in the rendered output. When two placeholders indicate a start and an
* end region in the message, e.g., "%{linkStart}foo%{linkEnd}", the common
* base name can be used as a scoped slot, where the content between the
* placeholders is passed via the `content` scoped slot prop.
* @slot * (arbitrary)
* @binding {string} content The content to place between start and end placeholders.
*/
render(createElement, context) {
// While a functional style is generally preferred, an imperative style is
// used here, as it lends itself better to the message parsing algorithm.
// This approach is also more performant, as it minimizes (relatively) object
// creation/garbage collection, which is important given how frequently this
// code may run on a given page.
let i = 0;
const vnodes = [];
const slots = context.scopedSlots;
const chunks = context.props.message.split(PLACE_HOLDER_REGEX);
const placeholdersByStartTag = groupPlaceholdersByStartTag(context.props.placeholders);
while (i < chunks.length) {
const chunk = chunks[i];
// Skip past this chunk now we have it
i += 1;
if (!PLACE_HOLDER_REGEX.test(chunk)) {
// Not a placeholder, so pass through as-is
vnodes.push(chunk);
continue;
}
const {
slotName,
endTag,
tagName
} = getPlaceholderDefinition(chunk, placeholdersByStartTag);
if (endTag) {
// Peek ahead to find end placeholder, if any
const indexOfEnd = chunks.indexOf(`${PREFIX}${endTag}${SUFFIX}`, i);
if (indexOfEnd > -1) {
// We have a valid start/end placeholder pair! Extract the content
// between them and skip past the end placeholder
const content = chunks.slice(i, indexOfEnd);
i = indexOfEnd + 1;
if (!has(slots, slotName)) {
// Slot hasn't been provided; return placeholders and content as-is
vnodes.push(chunk, ...content, chunks[indexOfEnd]);
continue;
}
// Provide content to provided scoped slot
vnodes.push(slots[slotName]({
content: content.join('')
}));
continue;
}
}
// By process of elimination, chunk must be a plain placeholder
vnodes.push(has(slots, tagName) ? slots[tagName]() : chunk);
continue;
}
return vnodes;
}
};
/* script */
const __vue_script__ = script;
/* template */
/* style */
const __vue_inject_styles__ = undefined;
/* scoped */
const __vue_scope_id__ = undefined;
/* module identifier */
const __vue_module_identifier__ = undefined;
/* functional template */
const __vue_is_functional_template__ = undefined;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
const __vue_component__ = /*#__PURE__*/__vue_normalize__(
{},
__vue_inject_styles__,
__vue_script__,
__vue_scope_id__,
__vue_is_functional_template__,
__vue_module_identifier__,
false,
undefined,
undefined,
undefined
);
export { __vue_component__ as default };