├── .babelrc
├── .editorconfig
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── example
├── App.vue
└── index.js
├── package.json
├── src
├── highlighter.js
├── highlighter.test.js
├── index.js
└── index.test.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "poi"
4 | ],
5 | "env": {
6 | "test": {
7 | "plugins": [ "istanbul" ]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | yarn-error.log
4 |
5 | #coverage
6 | .nyc_output
7 | coverage*.lcov
8 | coverage
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 8
4 | install:
5 | - yarn install
6 | - yarn global add nyc
7 | - yarn global add codecov
8 | script:
9 | - yarn test:cov
10 | - codecov
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Giulio Fagioli
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-highlighter
2 |
3 | > **Warning** I love Alessandra {{ text }} {{ text }} {{ text }}
4 | > This project is no longer maintained and its use is not recommended
5 |
6 |
7 | [](https://npmjs.com/package/vue-highlighter) [](https://npmjs.com/package/vue-highlighter) [](https://travis-ci.org/Remeic/vue-highlighter)
8 | [](https://codecov.io/gh/Remeic/vue-highlighter)
9 |
10 | Vue directive for highlight multiple istances of a word
11 |
12 | 
13 |
14 | ## Install
15 |
16 | ```bash
17 | yarn add vue-highlighter
18 | ```
19 |
20 | CDN: [UNPKG](https://unpkg.com/vue-highlighter/) | [jsDelivr](https://cdn.jsdelivr.net/npm/vue-highlighter/) (available as `window.vueHighlighter`)
21 |
22 | ## Usage
23 |
24 | ***
25 |
26 | ### Version 1.1.2 (**Deprecated**)
27 | ```vue
28 |
29 |
93 | The live attribute is an optional attribute, is set to false by default
94 | ```js
95 | data: () => {
96 | return {
97 | text: 'I love Alessandra',
98 | word: 'Alessandra',
99 | live: true,
100 | }
101 | }
102 | ```
103 |
104 | **Color**: Allow to customize the color of text when highlighted
105 | The color attribute is optional and is set to #fff by default
106 | color can be HEX or String
107 |
108 | ```js
109 | data: () => {
110 | return {
111 | text: 'I love Alessandra',
112 | word: 'Alessandra',
113 | style: {
114 | color: '#ffddee'
115 | }
116 | }
117 | }
118 | ```
119 |
120 | **Background Color**: Allow to customize the background color of text when highlighted
121 | The bgColor attribute is optional and is set to #009688 by default
122 | bgColor can be HEX or String
123 |
124 | ```js
125 | data: () => {
126 | return {
127 | text: 'I love Alessandra',
128 | word: 'Alessandra',
129 | style: {
130 | bgColor: '#ffddee'
131 | }
132 | }
133 | }
134 | ```
135 |
136 | **Padding**: Allow to customize the padding of text when highlighted
137 | The padding attribute is optional and is set to 0px 5px by default
138 | padding is validate if match this requirments: there is at least one number followed by one of this unit of measure ['cm','mm','in','px','pt','pc','em','ex','ch','rem','vw','vh','vmin','vmax','%']
139 |
140 | ```js
141 | data: () => {
142 | return {
143 | text: 'I love Alessandra',
144 | word: 'Alessandra',
145 | style: {
146 | padding: '4rem 5%'
147 | }
148 | }
149 | }
150 | ```
151 |
152 | ## Works on
153 |
154 | * Paragraph
155 | * Ul
156 | * Ol
157 | * Button
158 | * A
159 |
160 | ### Contributor
161 |
162 | Thanks to [Andrea Stagi](https://github.com/astagi) to help me with troubleshooting ❤️.
163 |
164 |
165 | ## License
166 |
167 | MIT © [Giulio Fagioli](https://github.com/remeic)
168 |
--------------------------------------------------------------------------------
/example/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
Hello World
' 9 | } 10 | 11 | const localVue = createLocalVue() 12 | 13 | localVue.directive('highlight', vueHighlighter) 14 | 15 | const wrapper = shallow(Component, { 16 | localVue, 17 | data: { 18 | word: '', 19 | live: false 20 | }, 21 | }) 22 | 23 | it('Empty word', () => { 24 | wrapper.setData({ word: '' }) 25 | expect(wrapper.html()).toBe('Hello World
') 26 | }) 27 | 28 | it('Contained word', () => { 29 | wrapper.setData({ word: 'World' }) 30 | expect(wrapper.html()).toBe( 31 | 'Hello World
' 32 | ) 33 | }) 34 | 35 | it('Not contained word', () => { 36 | wrapper.setData({ word: 'Giulio' }) 37 | expect(wrapper.html()).toBe('Hello World
') 38 | }) 39 | }) 40 | 41 | describe('Live Behaviour',() => { 42 | const Component = { 43 | template: 'Hello World
' 44 | } 45 | 46 | const localVue = createLocalVue() 47 | 48 | localVue.directive('highlight', vueHighlighter) 49 | 50 | const wrapper = shallow(Component, { 51 | localVue, 52 | data: { 53 | word: '', 54 | live: true 55 | }, 56 | }) 57 | 58 | it('Empty word', () => { 59 | wrapper.setData({ word: '' }) 60 | expect(wrapper.html()).toBe('Hello World
') 61 | }) 62 | 63 | it('Contained word', () => { 64 | wrapper.setData({ word: 'World' }) 65 | expect(wrapper.html()).toBe( 66 | 'Hello World
' 67 | ) 68 | }) 69 | 70 | it('Not contained word', () => { 71 | wrapper.setData({ word: 'Giulio' }) 72 | expect(wrapper.html()).toBe('Hello World
') 73 | }) 74 | 75 | it('Contained substring', () => { 76 | wrapper.setData({ word: 'ell' }) 77 | expect(wrapper.html()).toBe('Hello World
') 78 | }) 79 | 80 | }) 81 | 82 | describe('Live Behaviour - False -> True', () => { 83 | const Component = { 84 | template: 'Hello World
' 85 | } 86 | 87 | const localVue = createLocalVue() 88 | 89 | localVue.directive('highlight', vueHighlighter) 90 | 91 | const wrapper = shallow(Component, { 92 | localVue, 93 | data: { 94 | word: '', 95 | live: false 96 | }, 97 | }) 98 | 99 | beforeEach(() => { 100 | wrapper.setData({ live: false }) 101 | }) 102 | 103 | it('Empty word', () => { 104 | wrapper.setData({ word: '' }) 105 | wrapper.setData({ live: true }) 106 | expect(wrapper.html()).toBe('Hello World
') 107 | }) 108 | 109 | it('Contained word', () => { 110 | wrapper.setData({ word: 'World' }) 111 | expect(wrapper.html()).toBe( 112 | 'Hello World
' 113 | ) 114 | wrapper.setData({ live: true }) 115 | expect(wrapper.html()).toBe( 116 | 'Hello World
' 117 | ) 118 | }) 119 | 120 | it('Not contained word', () => { 121 | wrapper.setData({ word: 'Giulio' }) 122 | expect(wrapper.html()).toBe('Hello World
') 123 | wrapper.setData({ live: true }) 124 | expect(wrapper.html()).toBe('Hello World
') 125 | }) 126 | 127 | it('Contained substring', () => { 128 | wrapper.setData({ word: 'ell' }) 129 | expect(wrapper.html()).toBe('Hello World
') 130 | wrapper.setData({ live: true }) 131 | expect(wrapper.html()).toBe('Hello World
') 132 | }) 133 | 134 | }) 135 | 136 | describe('Live Behaviour - True -> False', () => { 137 | const Component = { 138 | template: 'Hello World
' 139 | } 140 | 141 | const localVue = createLocalVue() 142 | 143 | localVue.directive('highlight', vueHighlighter) 144 | 145 | const wrapper = shallow(Component, { 146 | localVue, 147 | data: { 148 | word: '', 149 | live: true 150 | }, 151 | }) 152 | 153 | beforeEach(() => { 154 | wrapper.setData({ live: true }) 155 | }) 156 | 157 | it('Empty word', () => { 158 | wrapper.setData({ word: '' }) 159 | wrapper.setData({ live: true }) 160 | expect(wrapper.html()).toBe('Hello World
') 161 | }) 162 | 163 | it('Contained word', () => { 164 | wrapper.setData({ word: 'World' }) 165 | expect(wrapper.html()).toBe( 166 | 'Hello World
' 167 | ) 168 | wrapper.setData({ live: false }) 169 | expect(wrapper.html()).toBe( 170 | 'Hello World
' 171 | ) 172 | }) 173 | 174 | it('Not contained word', () => { 175 | wrapper.setData({ word: 'Giulio' }) 176 | expect(wrapper.html()).toBe('Hello World
') 177 | wrapper.setData({ live: false }) 178 | expect(wrapper.html()).toBe('Hello World
') 179 | }) 180 | 181 | it('Contained substring', () => { 182 | wrapper.setData({ word: 'ell' }) 183 | expect(wrapper.html()).toBe('Hello World
') 184 | wrapper.setData({ live: false }) 185 | expect(wrapper.html()).toBe('Hello World
') 186 | }) 187 | 188 | }) 189 | 190 | describe('Default Word List Behaviour',() => { 191 | const Component = { 192 | template: 'Hello World, highlight me!
' 193 | } 194 | 195 | const localVue = createLocalVue() 196 | 197 | localVue.directive('highlight', vueHighlighter) 198 | 199 | let wrapper 200 | beforeEach(() => { 201 | wrapper = shallow(Component, { 202 | localVue, 203 | data: { 204 | wordList: [], 205 | live: false 206 | } 207 | }) 208 | }) 209 | 210 | it('Empty list', () => { 211 | wrapper.setData({ wordList: [] }) 212 | expect(wrapper.html()).toBe('Hello World, highlight me!
') 213 | }) 214 | 215 | it('List with empty word', () => { 216 | wrapper.setData({ wordList: [''] }) 217 | expect(wrapper.html()).toBe('Hello World, highlight me!
') 218 | }) 219 | 220 | it('List of words', () => { 221 | wrapper.setData({ wordList: ['Hello', 'World', 'highlight me', 'I\'m not here'] }) 222 | expect(wrapper.html()).toBe( 223 | 'Hello World, highlight me!
' 224 | ) 225 | }) 226 | 227 | it('Not contained word', () => { 228 | wrapper.setData({ wordList: ['Not', 'Present', 'In', 'List'] }) 229 | expect(wrapper.html()).toBe('Hello World, highlight me!
') 230 | }) 231 | }) 232 | 233 | describe('Unbind Directive',() => { 234 | const Component = { 235 | template: 'Hello World
' 236 | } 237 | 238 | const localVue = createLocalVue() 239 | 240 | localVue.directive('highlight', vueHighlighter) 241 | const spy = sinon.stub() 242 | 243 | const wrapper = shallow(Component, { 244 | localVue, 245 | data: { 246 | word: 'Hello', 247 | live: true 248 | }, 249 | destroyed() { 250 | spy() 251 | } 252 | }).destroy() 253 | 254 | it('Method called on destroy', () => { 255 | expect(spy.calledOnce).toBe(true) 256 | }) 257 | 258 | }) 259 | 260 | describe('Custom color of text',() => { 261 | const Component = { 262 | template: 'Hello World
' 263 | } 264 | 265 | const localVue = createLocalVue() 266 | 267 | localVue.directive('highlight', vueHighlighter) 268 | 269 | const wrapper = shallow(Component, { 270 | localVue, 271 | data: { 272 | word: 'Hello', 273 | live: true, 274 | style: { 275 | color: '' 276 | } 277 | } 278 | }) 279 | 280 | it('Default color of text', () => { 281 | expect(wrapper.html()).toBe('Hello World
') 282 | }) 283 | 284 | it('Custom color of text as hex', () => { 285 | wrapper.setData({ 286 | style: { 287 | color: '#000000' 288 | } 289 | }) 290 | expect(wrapper.html()).toBe('Hello World
') 291 | }) 292 | 293 | it('Custom color of text as word', () => { 294 | wrapper.setData({ 295 | style: { 296 | color: 'white' 297 | } 298 | }) 299 | expect(wrapper.html()).toBe('Hello World
') 300 | }) 301 | 302 | it('Wrong custom text color', () => { 303 | wrapper.setData({ 304 | style: { 305 | color: 'e54%' 306 | } 307 | }) 308 | expect(wrapper.html()).toBe('Hello World
') 309 | }) 310 | 311 | it('Change color : wrong to correct', () => { 312 | wrapper.setData({ 313 | style: { 314 | color: 'e54%' 315 | } 316 | }) 317 | expect(wrapper.html()).toBe('Hello World
') 318 | wrapper.setData({ 319 | style: { 320 | color: '#ffddee' 321 | } 322 | }) 323 | expect(wrapper.html()).toBe('Hello World
') 324 | }) 325 | 326 | it('Change color : correct to wrong', () => { 327 | wrapper.setData({ 328 | style: { 329 | color: '#ffddee' 330 | } 331 | }) 332 | expect(wrapper.html()).toBe('Hello World
') 333 | wrapper.setData({ 334 | style: { 335 | color: 'e54%' 336 | } 337 | }) 338 | expect(wrapper.html()).toBe('Hello World
') 339 | }) 340 | }) 341 | 342 | 343 | describe('Custom color of background', () => { 344 | const Component = { 345 | template: 'Hello World
' 346 | } 347 | 348 | const localVue = createLocalVue() 349 | 350 | localVue.directive('highlight', vueHighlighter) 351 | 352 | const wrapper = shallow(Component, { 353 | localVue, 354 | data: { 355 | word: 'Hello', 356 | live: true, 357 | style: { 358 | bgColor: '' 359 | } 360 | } 361 | }) 362 | 363 | it('Default color of background', () => { 364 | expect(wrapper.html()).toBe('Hello World
') 365 | }) 366 | 367 | it('Custom color of background as hex', () => { 368 | wrapper.setData({ 369 | style: { 370 | bgColor: '#000000' 371 | } 372 | }) 373 | expect(wrapper.html()).toBe('Hello World
') 374 | }) 375 | 376 | it('Custom color of background as word', () => { 377 | wrapper.setData({ 378 | style: { 379 | bgColor: 'white' 380 | } 381 | }) 382 | expect(wrapper.html()).toBe('Hello World
') 383 | }) 384 | 385 | it('Wrong custom background color', () => { 386 | wrapper.setData({ 387 | style: { 388 | bgColor: 'e54%' 389 | } 390 | }) 391 | expect(wrapper.html()).toBe('Hello World
') 392 | }) 393 | 394 | it('Change color : wrong to correct', () => { 395 | wrapper.setData({ 396 | style: { 397 | bgColor: 'e54%' 398 | } 399 | }) 400 | expect(wrapper.html()).toBe('Hello World
') 401 | wrapper.setData({ 402 | style: { 403 | bgColor: '#ffddee' 404 | } 405 | }) 406 | expect(wrapper.html()).toBe('Hello World
') 407 | }) 408 | 409 | it('Change color : correct to wrong', () => { 410 | wrapper.setData({ 411 | style: { 412 | bgColor: '#ffddee' 413 | } 414 | }) 415 | expect(wrapper.html()).toBe('Hello World
') 416 | wrapper.setData({ 417 | style: { 418 | bgColor: 'e54%' 419 | } 420 | }) 421 | expect(wrapper.html()).toBe('Hello World
') 422 | }) 423 | }) 424 | 425 | describe('Custom Text and Background color', () => { 426 | const Component = { 427 | template: 'Hello World
' 428 | } 429 | 430 | const localVue = createLocalVue() 431 | 432 | localVue.directive('highlight', vueHighlighter) 433 | 434 | const wrapper = shallow(Component, { 435 | localVue, 436 | data: { 437 | word: 'Hello', 438 | live: true, 439 | style: { 440 | color: '', 441 | bgColor: '' 442 | } 443 | } 444 | }) 445 | 446 | it('Default color', () => { 447 | expect(wrapper.html()).toBe('Hello World
') 448 | }) 449 | 450 | it('Custom color hex', () => { 451 | wrapper.setData({ 452 | style: { 453 | color: '#111', 454 | bgColor: '#000000' 455 | } 456 | }) 457 | expect(wrapper.html()).toBe('Hello World
') 458 | }) 459 | 460 | it('Custom color as word', () => { 461 | wrapper.setData({ 462 | style: { 463 | color: 'white', 464 | bgColor: 'white' 465 | } 466 | }) 467 | expect(wrapper.html()).toBe('Hello World
') 468 | }) 469 | 470 | it('Wrong custom background color', () => { 471 | wrapper.setData({ 472 | style: { 473 | color: '#fee', 474 | bgColor: 'e54%' 475 | } 476 | }) 477 | expect(wrapper.html()).toBe('Hello World
') 478 | }) 479 | 480 | it('Wrong custom text color', () => { 481 | wrapper.setData({ 482 | style: { 483 | color: '#e54%', 484 | bgColor: '#fee' 485 | } 486 | }) 487 | expect(wrapper.html()).toBe('Hello World
') 488 | }) 489 | }) 490 | 491 | describe('Custom Padding', () => { 492 | const Component = { 493 | template: 'Hello World
' 494 | } 495 | 496 | const localVue = createLocalVue() 497 | 498 | localVue.directive('highlight', vueHighlighter) 499 | 500 | const wrapper = shallow(Component, { 501 | localVue, 502 | data: { 503 | word: 'Hello', 504 | live: true, 505 | style: { 506 | padding: '' 507 | } 508 | } 509 | }) 510 | 511 | it('Default padding', () => { 512 | expect(wrapper.html()).toBe('Hello World
') 513 | }) 514 | 515 | it('Custom padding - 1 value', () => { 516 | wrapper.setData({ 517 | style: { 518 | padding: '1px' 519 | } 520 | }) 521 | expect(wrapper.html()).toBe('Hello World
') 522 | }) 523 | 524 | it('Custom padding - 2 value', () => { 525 | wrapper.setData({ 526 | style: { 527 | padding: '1px 2px' 528 | } 529 | }) 530 | expect(wrapper.html()).toBe('Hello World
') 531 | }) 532 | 533 | it('Custom padding - 3 value', () => { 534 | wrapper.setData({ 535 | style: { 536 | padding: '1px 2px 3px' 537 | } 538 | }) 539 | expect(wrapper.html()).toBe('Hello World
') 540 | }) 541 | 542 | it('Custom padding - 4 value', () => { 543 | wrapper.setData({ 544 | style: { 545 | padding: '1px 2px 3px 4px' 546 | } 547 | }) 548 | expect(wrapper.html()).toBe('Hello World
') 549 | }) 550 | 551 | it('Wrong Padding', () => { 552 | wrapper.setData({ 553 | style: { 554 | padding: 'ooo' 555 | } 556 | }) 557 | expect(wrapper.html()).toBe('Hello World
') 558 | }) 559 | 560 | it('Custom Padding - %', () => { 561 | wrapper.setData({ 562 | style: { 563 | padding: '5%' 564 | } 565 | }) 566 | expect(wrapper.html()).toBe('Hello World
') 567 | }) 568 | 569 | it('Change padding : wrong to correct', () => { 570 | wrapper.setData({ 571 | style: { 572 | padding: 'ooo' 573 | } 574 | }) 575 | expect(wrapper.html()).toBe('Hello World
') 576 | wrapper.setData({ 577 | style:{ 578 | padding: '4rem' 579 | } 580 | }) 581 | expect(wrapper.html()).toBe('Hello World
') 582 | }) 583 | 584 | it('Change padding : correct to wrong', () => { 585 | wrapper.setData({ 586 | style: { 587 | padding: '4rem' 588 | } 589 | }) 590 | expect(wrapper.html()).toBe('Hello World
') 591 | 592 | wrapper.setData({ 593 | style:{ 594 | padding: 'ooo' 595 | } 596 | }) 597 | expect(wrapper.html()).toBe('Hello World
') 598 | 599 | }) 600 | 601 | }) 602 | 603 | describe('Empty Behaviuor',() => { 604 | const Component = { 605 | template: 'Hello World
' 606 | } 607 | 608 | const localVue = createLocalVue() 609 | 610 | localVue.directive('highlight', vueHighlighter) 611 | 612 | const wrapper = shallow(Component, { 613 | localVue, 614 | data: { 615 | } 616 | }) 617 | 618 | it('Without Attribute', () => { 619 | expect(wrapper.html()).toBe('Hello World
') 620 | }) 621 | }) 622 | 623 | 624 | describe('Without Directive', () => { 625 | const Component = { 626 | template: 'Hello World
' 627 | } 628 | 629 | const localVue = createLocalVue() 630 | 631 | localVue.directive('highlight', vueHighlighter) 632 | 633 | const wrapper = shallow(Component, { 634 | localVue, 635 | data: { 636 | } 637 | }) 638 | 639 | it('Without Attribute', () => { 640 | expect(wrapper.html()).toBe('Hello World
') 641 | }) 642 | }) 643 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 2 | import highlight from './highlighter' 3 | 4 | const Plugin = { 5 | install(Vue, options) { 6 | Vue.directive('highlight', highlight) 7 | } 8 | } 9 | 10 | export default Plugin 11 | 12 | export { 13 | highlight 14 | } 15 | 16 | // Install by default 17 | if (typeof window !== 'undefined' && window.Vue) { 18 | window.Vue.use(Plugin) 19 | } 20 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import { shallow, createLocalVue } from '@vue/test-utils' 2 | 3 | import VueHighlighter from './index' 4 | import { highlight } from './index' 5 | 6 | describe('Plugin', () => { 7 | 8 | const Component = { 9 | template: 'Hello World
' 10 | } 11 | 12 | it('Add plugin', () => { 13 | const localVue = createLocalVue() 14 | localVue.use(VueHighlighter) 15 | expect(localVue.options.directives).toHaveProperty('highlight') 16 | }) 17 | 18 | it('Add directive', () => { 19 | const wrapper = shallow(Component, { 20 | directives: { 21 | highlight 22 | }, 23 | data: { 24 | word: '', 25 | live: false 26 | } 27 | }) 28 | expect(wrapper.vm.$options.directives).toHaveProperty('highlight') 29 | }) 30 | 31 | }) 32 | --------------------------------------------------------------------------------