diff options
author | Paul Gascou-Vaillancourt <paul.gascvail@gmail.com> | 2019-04-23 16:04:14 -0400 |
---|---|---|
committer | Paul Gascou-Vaillancourt <paul.gascvail@gmail.com> | 2019-04-25 10:30:46 -0400 |
commit | f7a498ccc3bcbf92cc1043b4d9ecf2f3091aae17 (patch) | |
tree | 65f9bfea4a3f46dc1e96d5204883e304244e964d | |
parent | 1e2d4a217a06da4fc92efa5c6f2e13873b2f2067 (diff) | |
download | gitlab-ce-10450-friendly-wrap-component.tar.gz |
Add FriendlyWrap component10450-friendly-wrap-component
The FriendlyWrap component wraps text at specific places by
inserting a <wbr> element after each symbols occurence
5 files changed, 114 insertions, 0 deletions
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index cc1d85fd97d..09d9ae35e70 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -192,3 +192,11 @@ export const truncateNamespace = (string = '') => { return namespace; }; + +/** + * Escapes RegExp special characters + * + * @param {String} str + * @returns String + */ +export const escapeRegExp = str => str.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&'); diff --git a/app/assets/javascripts/vue_shared/components/friendly_wrap.vue b/app/assets/javascripts/vue_shared/components/friendly_wrap.vue new file mode 100644 index 00000000000..dfebd67c54a --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/friendly_wrap.vue @@ -0,0 +1,31 @@ +<script> +import _ from 'underscore'; +import { escapeRegExp } from '../../lib/utils/text_utility'; + +export default { + props: { + text: { + type: String, + required: true, + }, + symbols: { + type: Array, + required: false, + default: () => ['/'], + }, + }, + computed: { + displayText() { + const appendWordBreak = (str, symbol) => + str.replace(new RegExp(`(${symbol})`, 'g'), `$1<wbr>`); + return _.uniq(this.symbols) + .map(escapeRegExp) + .reduce(appendWordBreak, _.escape(this.text)); + }, + }, +}; +</script> + +<template> + <span class="text-break" v-html="displayText"></span> +</template> diff --git a/changelogs/unreleased/10450-friendly-wrap-component.yml b/changelogs/unreleased/10450-friendly-wrap-component.yml new file mode 100644 index 00000000000..f10c15a2931 --- /dev/null +++ b/changelogs/unreleased/10450-friendly-wrap-component.yml @@ -0,0 +1,5 @@ +--- +title: Add FriendlyWrap component +merge_request: 27600 +author: +type: other diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index 0878c1de095..e31cbbaf99e 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -176,4 +176,11 @@ describe('text_utility', () => { }); }); }); + + describe('escapeRegExp', () => { + it('escapes regexp special characters', () => { + const str = '[\\^$.*+?()[]{}|]'; + expect(textUtils.escapeRegExp(str)).toBe('\\[\\\\\\^\\$\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\]'); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/friendly_wrap_spec.js b/spec/frontend/vue_shared/components/friendly_wrap_spec.js new file mode 100644 index 00000000000..170820dfadc --- /dev/null +++ b/spec/frontend/vue_shared/components/friendly_wrap_spec.js @@ -0,0 +1,63 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import FriendlyWrap from '~/vue_shared/components/friendly_wrap'; + +const localVue = createLocalVue(); + +describe('Friendly wrap component', () => { + let wrapper; + + const createComponent = props => { + wrapper = shallowMount(FriendlyWrap, { + localVue, + propsData: props, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('wraps text on slashes by default', () => { + const text = '/some/file/path'; + const textWrapped = '/<wbr>some/<wbr>file/<wbr>path'; + createComponent({ + text, + }); + + expect(wrapper.text()).toBe(text); + expect(wrapper.html()).toMatch(textWrapped); + }); + + it('supports backslashes', () => { + const text = '\\some\\long\\file\\path'; + const textWrapped = '\\<wbr>some\\<wbr>long\\<wbr>file\\<wbr>path'; + createComponent({ + text, + symbols: ['\\'], + }); + expect(wrapper.text()).toBe(text); + expect(wrapper.html()).toMatch(textWrapped); + }); + + it('accepts multiple symbols', () => { + const text = 'some;text-that.needs;to-be.wrapped'; + const textWrapped = 'some;<wbr>text-<wbr>that.<wbr>needs;<wbr>to-<wbr>be.<wbr>wrapped'; + createComponent({ + text, + symbols: [';', '-', '.'], + }); + expect(wrapper.text()).toBe(text); + expect(wrapper.html()).toMatch(textWrapped); + }); + + it('works with words', () => { + const text = 'it goes on and on and on and on'; + const textWrapped = 'it goes on and<wbr> on and<wbr> on and<wbr> on'; + createComponent({ + text, + symbols: ['and'], + }); + expect(wrapper.text()).toBe(text); + expect(wrapper.html()).toMatch(textWrapped); + }); +}); |