diff --git a/src/components/utilities/sprintf/examples/index.js b/src/components/utilities/sprintf/examples/index.js index 97820eb9840086466e21653dcca9843576531279..9d489c9718b8d4b8194a6e552685d24c4c85d3b0 100644 --- a/src/components/utilities/sprintf/examples/index.js +++ b/src/components/utilities/sprintf/examples/index.js @@ -5,18 +5,18 @@ export default [ { name: 'Basic', items: [ - { - id: 'sprintf-basic', - name: 'Basic', - description: 'Basic sprintf', - component: SprintfBasicExample, - }, { id: 'sprintf-interpolated-content', name: 'Interpolated content', description: 'Interpolated content passed to scoped slots', component: SprintfInterpolatedExample, }, + { + id: 'sprintf-basic', + name: 'Basic placeholders', + description: 'Basic sprintf placeholders', + component: SprintfBasicExample, + }, ], }, ]; diff --git a/src/components/utilities/sprintf/examples/sprintf.basic.example.vue b/src/components/utilities/sprintf/examples/sprintf.basic.example.vue index e49168fbe0bf752b8fad8c8f80087dd8588bc9be..b4c962d2fb5371b3f561c4614e347643624000b2 100644 --- a/src/components/utilities/sprintf/examples/sprintf.basic.example.vue +++ b/src/components/utilities/sprintf/examples/sprintf.basic.example.vue @@ -1,8 +1,19 @@ + + @@ -94,8 +79,103 @@ cannot easily be used. In addition, a JS-only solution is more likely to be prone to XSS attacks, as the Vue compiler isn't available to help protect against them. +## Displaying components within a message + +Use slots to replace placeholders in the message with the slots' contents. +There is a slot for every placeholder in the message. For example, the `author` +slot name can be used when there is an `%{author}` placeholder in the message: + +```html + + + +``` + +The example above renders to this HTML: + +```html +
Written by Some author
+``` + ## Usage caveats +### White space + +`GlSprintf` does not handle white space in scoped slots specially; it is passed +through and rendered just like regular text. This means that white space in the +scoped slot templates *themselves*, including newlines and indentation, are +passed through untouched (assuming the template compiler you're using doesn't +trim text nodes at compile time; `vue-template-compiler` preserves white space +by default, for instance). + +Most of the time you don't need to worry about this, since +[browsers normalize white space][1] automatically, but here's an example, using +punctuation, where you might want to be conscious of the white space in the +template: + +```html +
+ + + +
+``` + +As written, the literal markup rendered would be: + +```html +
Foo + bar + ! +
+``` + +where the white space (including newlines) before and after `bar` is exactly +the newlines and indentation in the source template. The browser will render +this as: + +
Foo + bar + ! +
+ +Note the single space between `bar` and `!`. To avoid that, remove the +white space in the template, or use `v-text`: + +```html +
+ + + +
+``` + +### Miscellaneous + While there are a lot of caveats here, you don't need to worry about reading them _unless_ you find `GlSprintf` isn't rendering what you'd expect. @@ -103,25 +183,27 @@ them _unless_ you find `GlSprintf` isn't rendering what you'd expect. a component's root, it must be wrapped with at least one other root element, otherwise Vue will throw a `Multiple root nodes returned from render function` error. -- If a slot for a given named interpolation _isn't_ provided, the interpolation +- If a slot for a given placeholder _isn't_ provided, the placeholder will be rendered as-is, e.g., literally `Written by %{author}` if the `author` slot _isn't_ provided, or literally `%{linkStart}foo%{linkEnd}` if the `link` slot isn't provided. - Content between `Start` and `End` placeholders is effectively thrown away if the scoped slot of the correct name doesn't consume the `content` property in some way, though the slot's components should still be rendered. -- If there's no named interpolation in the message for a provided named slot, - the content of that slot is silently thrown away. +- If there's no placeholder in the message for a provided named slot, the + content of that slot is silently thrown away. - If only one of the `Start` or `End` placeholders is in the message, or they - are in the wrong order, they are treated as plain slots. This allows you to + are in the wrong order, they are treated as plain slots, i.e., it is assumed + there is no text to extract and pass to the scoped slot. This allows you to use plain slots whose names end in `Start` or `End`, e.g., `backEnd`, or - `fromStart`, without interpolating content into them. -- Interpolation between `Start` and `End` placeholders is only done one level + `fromStart` in isolation, without their `Start`/`End` counterparts. +- Text extraction between `Start` and `End` placeholders is only done one level deep. This is intentional, so as to avoid building complex sprintf messages that would better be implemented in components. As an example, `${linkStart}test%{icon}%{linkEnd}`, if provided both the `link` and `icon` slots, would pass `test%{icon}` as a literal string as content to the `link` scoped slot. +- For more examples and edge cases, please see the test suite for `GlSprintf`. - To be successfully used in `GlSprintf`, slot names should: * start with a letter (`[A-Za-z]`) * only contain alpha-numeric characters (`[A-Za-z0-9]`), underscore (`_`) and @@ -135,3 +217,5 @@ them _unless_ you find `GlSprintf` isn't rendering what you'd expect. This component uses [`String.prototype.startsWith()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith) and [`String.prototype.endsWith()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith) under the hood. Make sure those methods are polyfilled if you plan on using the component on IE11. > NOTE: These methods are already polyfilled in GitLab: [`app/assets/javascripts/commons/polyfills.js#L15-16`](https://gitlab.com/gitlab-org/gitlab/blob/dc60dee6ed6234dda9f032195577cd8fad9646d8/app/assets/javascripts/commons/polyfills.js#L15-16) + +[1]: https://www.w3.org/TR/css-text-3/#white-space-phase-1 diff --git a/src/components/utilities/sprintf/sprintf.stories.js b/src/components/utilities/sprintf/sprintf.stories.js index 999215e9f5dd969f9c22fa9f4b0f10058e82b773..8af651c4c26025c7d752a7641aff168321931b48 100644 --- a/src/components/utilities/sprintf/sprintf.stories.js +++ b/src/components/utilities/sprintf/sprintf.stories.js @@ -20,42 +20,44 @@ function generateProps({ message = 'Written by %{author}' } = {}) { documentedStoriesOf('utilities|sprintf', readme) .addDecorator(withKnobs) - .add('default', () => ({ - props: generateProps(), + .add('sentence with link', () => ({ + props: generateProps({ + message: 'Click %{linkStart}here%{linkEnd} to reticulate splines.', + }), components, template: `
-