@oat-sa/tao-core-ui 1.60.2 → 1.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (783) hide show
  1. package/LICENSE +339 -339
  2. package/README.md +23 -18
  3. package/dist/actionbar.js +393 -410
  4. package/dist/adder.js +143 -156
  5. package/dist/animable/absorbable/absorbable.js +208 -211
  6. package/dist/animable/absorbable/css/absorb.css +7 -8
  7. package/dist/animable/absorbable/css/absorb.css.map +1 -1
  8. package/dist/animable/pulsable/css/pulse.css +7 -7
  9. package/dist/animable/pulsable/css/pulse.css.map +1 -1
  10. package/dist/animable/pulsable/pulsable.js +174 -174
  11. package/dist/areaBroker.js +59 -72
  12. package/dist/autocomplete/css/autocomplete.css +7 -8
  13. package/dist/autocomplete/css/autocomplete.css.map +1 -1
  14. package/dist/autocomplete.js +494 -604
  15. package/dist/autoscroll.js +23 -25
  16. package/dist/badge/badge.js +201 -199
  17. package/dist/badge/css/badge.css +7 -8
  18. package/dist/badge/css/badge.css.map +1 -1
  19. package/dist/breadcrumbs/css/breadcrumbs.css +7 -7
  20. package/dist/breadcrumbs/css/breadcrumbs.css.map +1 -1
  21. package/dist/breadcrumbs.js +286 -285
  22. package/dist/btngrouper.js +83 -95
  23. package/dist/bulkActionPopup/css/bulkActionPopup.css +7 -7
  24. package/dist/bulkActionPopup/css/bulkActionPopup.css.map +1 -1
  25. package/dist/bulkActionPopup.js +503 -503
  26. package/dist/button.js +300 -293
  27. package/dist/calculator/css/calculator.css +10 -10
  28. package/dist/calculator/css/calculator.css.map +1 -1
  29. package/dist/calculator.js +59 -72
  30. package/dist/cascadingComboBox.js +257 -256
  31. package/dist/ckeditor/ckConfigurator.js +130 -154
  32. package/dist/ckeditor/dtdHandler.js +305 -345
  33. package/dist/class/css/selector.css +7 -7
  34. package/dist/class/css/selector.css.map +1 -1
  35. package/dist/class/selector.js +437 -470
  36. package/dist/component/alignable.js +97 -121
  37. package/dist/component/containable.js +53 -58
  38. package/dist/component/css/components.css +7 -7
  39. package/dist/component/css/components.css.map +1 -1
  40. package/dist/component/css/windowComponent.css +7 -7
  41. package/dist/component/css/windowComponent.css.map +1 -1
  42. package/dist/component/draggable.js +34 -36
  43. package/dist/component/placeable.js +84 -102
  44. package/dist/component/resizable.js +79 -89
  45. package/dist/component/stackable.js +21 -27
  46. package/dist/component/windowed.js +284 -297
  47. package/dist/component.js +439 -467
  48. package/dist/container.js +79 -98
  49. package/dist/contextualPopup/css/contextualPopup.css +7 -7
  50. package/dist/contextualPopup/css/contextualPopup.css.map +1 -1
  51. package/dist/contextualPopup.js +417 -436
  52. package/dist/dashboard/css/dashboard.css +7 -7
  53. package/dist/dashboard/css/dashboard.css.map +1 -1
  54. package/dist/dashboard.js +302 -305
  55. package/dist/datalist/css/datalist.css +7 -7
  56. package/dist/datalist/css/datalist.css.map +1 -1
  57. package/dist/datalist.js +783 -791
  58. package/dist/datatable/css/datatable.css +11 -7
  59. package/dist/datatable/css/datatable.css.map +1 -1
  60. package/dist/datatable/filterStrategy/filterStrategy.js +28 -30
  61. package/dist/datatable/filterStrategy/multiple.js +26 -45
  62. package/dist/datatable/filterStrategy/single.js +28 -40
  63. package/dist/datatable.js +1591 -1616
  64. package/dist/dateRange/css/dateRange.css +7 -7
  65. package/dist/dateRange/css/dateRange.css.map +1 -1
  66. package/dist/dateRange/dateRange.js +398 -412
  67. package/dist/datetime/css/picker.css +7 -7
  68. package/dist/datetime/css/picker.css.map +1 -1
  69. package/dist/datetime/picker.js +670 -703
  70. package/dist/deleter.js +383 -398
  71. package/dist/destination/css/selector.css +7 -7
  72. package/dist/destination/css/selector.css.map +1 -1
  73. package/dist/destination/selector.js +295 -293
  74. package/dist/dialog/alert.js +30 -32
  75. package/dist/dialog/confirm.js +29 -32
  76. package/dist/dialog/confirmDelete.js +223 -221
  77. package/dist/dialog.js +651 -695
  78. package/dist/disabler.js +108 -119
  79. package/dist/documentViewer/css/documentViewer.css +7 -7
  80. package/dist/documentViewer/css/documentViewer.css.map +1 -1
  81. package/dist/documentViewer/providers/pdfViewer/fallback/viewer.js +172 -169
  82. package/dist/documentViewer/providers/pdfViewer/pdfjs/areaBroker.js +32 -29
  83. package/dist/documentViewer/providers/pdfViewer/pdfjs/findBar.js +523 -564
  84. package/dist/documentViewer/providers/pdfViewer/pdfjs/pageView.js +376 -393
  85. package/dist/documentViewer/providers/pdfViewer/pdfjs/pagesManager.js +53 -68
  86. package/dist/documentViewer/providers/pdfViewer/pdfjs/searchEngine.js +517 -567
  87. package/dist/documentViewer/providers/pdfViewer/pdfjs/textManager.js +73 -96
  88. package/dist/documentViewer/providers/pdfViewer/pdfjs/viewer.js +381 -392
  89. package/dist/documentViewer/providers/pdfViewer/pdfjs/wrapper.js +125 -152
  90. package/dist/documentViewer/providers/pdfViewer.js +189 -191
  91. package/dist/documentViewer/viewerFactory.js +76 -81
  92. package/dist/documentViewer.js +303 -311
  93. package/dist/dropdown/css/dropdown.css +7 -7
  94. package/dist/dropdown/css/dropdown.css.map +1 -1
  95. package/dist/dropdown.js +392 -412
  96. package/dist/durationer.js +77 -85
  97. package/dist/dynamicComponent/css/dynamicComponent.css +7 -7
  98. package/dist/dynamicComponent/css/dynamicComponent.css.map +1 -1
  99. package/dist/dynamicComponent.js +595 -630
  100. package/dist/feedback.js +365 -375
  101. package/dist/figure/FigureStateActive.js +246 -296
  102. package/dist/filesender.js +44 -54
  103. package/dist/filter.js +244 -240
  104. package/dist/form/css/dropdownForm.css +7 -7
  105. package/dist/form/css/dropdownForm.css.map +1 -1
  106. package/dist/form/css/form.css +7 -7
  107. package/dist/form/css/form.css.map +1 -1
  108. package/dist/form/dropdownForm.js +357 -362
  109. package/dist/form/form.js +703 -984
  110. package/dist/form/simpleForm.js +74 -77
  111. package/dist/form/validator/css/validator.css +7 -7
  112. package/dist/form/validator/css/validator.css.map +1 -1
  113. package/dist/form/validator/renderer.js +235 -241
  114. package/dist/form/validator/validator.js +181 -267
  115. package/dist/form/widget/css/widget.css +7 -7
  116. package/dist/form/widget/css/widget.css.map +1 -1
  117. package/dist/form/widget/definitions.js +27 -28
  118. package/dist/form/widget/loader.js +16 -16
  119. package/dist/form/widget/providers/checkBox.js +257 -264
  120. package/dist/form/widget/providers/comboBox.js +194 -193
  121. package/dist/form/widget/providers/default.js +44 -50
  122. package/dist/form/widget/providers/hidden.js +179 -176
  123. package/dist/form/widget/providers/hiddenBox.js +262 -272
  124. package/dist/form/widget/providers/radioBox.js +222 -223
  125. package/dist/form/widget/providers/textArea.js +197 -193
  126. package/dist/form/widget/providers/textBox.js +36 -37
  127. package/dist/form/widget/widget.js +480 -508
  128. package/dist/form.js +12 -17
  129. package/dist/formValidator/formValidator.js +74 -91
  130. package/dist/formValidator/highlighters/highlighter.js +43 -51
  131. package/dist/formValidator/highlighters/message.js +31 -32
  132. package/dist/formValidator/highlighters/tooltip.js +33 -35
  133. package/dist/generis/form/css/form.css +7 -7
  134. package/dist/generis/form/css/form.css.map +1 -1
  135. package/dist/generis/form/form.js +323 -331
  136. package/dist/generis/validator/css/validator.css +7 -7
  137. package/dist/generis/validator/css/validator.css.map +1 -1
  138. package/dist/generis/validator/validator.js +215 -216
  139. package/dist/generis/widget/checkBox/checkBox.js +227 -225
  140. package/dist/generis/widget/comboBox/comboBox.js +189 -183
  141. package/dist/generis/widget/css/widget.css +7 -7
  142. package/dist/generis/widget/css/widget.css.map +1 -1
  143. package/dist/generis/widget/hiddenBox/hiddenBox.js +231 -232
  144. package/dist/generis/widget/loader.js +21 -23
  145. package/dist/generis/widget/textBox/textBox.js +178 -172
  146. package/dist/generis/widget/widget.js +255 -266
  147. package/dist/groupedComboBox.js +231 -226
  148. package/dist/groupvalidator.js +30 -34
  149. package/dist/hider.js +43 -50
  150. package/dist/highlighter.js +946 -1057
  151. package/dist/image/ImgStateActive/extractLabel.js +20 -21
  152. package/dist/image/ImgStateActive/helper.js +21 -25
  153. package/dist/image/ImgStateActive/initHelper.js +133 -162
  154. package/dist/image/ImgStateActive/initMediaEditor.js +72 -92
  155. package/dist/image/ImgStateActive/mediaSizer.js +48 -58
  156. package/dist/image/ImgStateActive.js +185 -215
  157. package/dist/incrementer.js +91 -110
  158. package/dist/inplacer.js +108 -132
  159. package/dist/interactUtils.js +56 -67
  160. package/dist/itemButtonList/css/item-button-list.css +23 -24
  161. package/dist/itemButtonList/css/item-button-list.css.map +1 -1
  162. package/dist/itemButtonList.js +444 -469
  163. package/dist/keyNavigation/navigableDomElement.js +124 -158
  164. package/dist/keyNavigation/navigator.js +235 -312
  165. package/dist/listbox/css/listbox.css +7 -7
  166. package/dist/listbox/css/listbox.css.map +1 -1
  167. package/dist/listbox.js +463 -476
  168. package/dist/liststyler.js +75 -76
  169. package/dist/loadingButton/css/button.css +7 -7
  170. package/dist/loadingButton/css/button.css.map +1 -1
  171. package/dist/loadingButton/loadingButton.js +219 -218
  172. package/dist/lock.js +484 -509
  173. package/dist/login/login.js +471 -486
  174. package/dist/maths/calculator/basicCalculator.js +247 -242
  175. package/dist/maths/calculator/calculatorComponent.js +34 -36
  176. package/dist/maths/calculator/core/areaBroker.js +30 -28
  177. package/dist/maths/calculator/core/board.js +805 -872
  178. package/dist/maths/calculator/core/expression.js +500 -518
  179. package/dist/maths/calculator/core/labels.js +240 -235
  180. package/dist/maths/calculator/core/plugin.js +20 -20
  181. package/dist/maths/calculator/core/terms.js +27 -27
  182. package/dist/maths/calculator/core/tokenizer.js +119 -132
  183. package/dist/maths/calculator/core/tokens.js +157 -168
  184. package/dist/maths/calculator/css/calculator.css +7 -7
  185. package/dist/maths/calculator/css/calculator.css.map +1 -1
  186. package/dist/maths/calculator/defaultCalculator.js +24 -27
  187. package/dist/maths/calculator/plugins/core/degrad.js +22 -30
  188. package/dist/maths/calculator/plugins/core/history.js +55 -67
  189. package/dist/maths/calculator/plugins/core/remind.js +22 -25
  190. package/dist/maths/calculator/plugins/core/stepNavigation.js +33 -49
  191. package/dist/maths/calculator/plugins/keyboard/templateKeyboard/templateKeyboard.js +255 -258
  192. package/dist/maths/calculator/plugins/modifiers/pow10.js +47 -52
  193. package/dist/maths/calculator/plugins/modifiers/sign.js +103 -130
  194. package/dist/maths/calculator/plugins/screen/simpleScreen/simpleScreen.js +288 -293
  195. package/dist/maths/calculator/pluginsLoader.js +22 -22
  196. package/dist/maths/calculator/scientificCalculator.js +339 -334
  197. package/dist/mediaEditor/mediaEditorComponent.js +244 -249
  198. package/dist/mediaEditor/plugins/mediaAlignment/helper.js +28 -33
  199. package/dist/mediaEditor/plugins/mediaAlignment/mediaAlignmentComponent.js +228 -236
  200. package/dist/mediaEditor/plugins/mediaAlignment/style.css +7 -7
  201. package/dist/mediaEditor/plugins/mediaDimension/helper.js +81 -106
  202. package/dist/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +597 -635
  203. package/dist/mediaEditor/plugins/mediaDimension/style.css +141 -141
  204. package/dist/mediaplayer/css/player.css +7 -7
  205. package/dist/mediaplayer/css/player.css.map +1 -1
  206. package/dist/mediaplayer/players/html5.js +646 -706
  207. package/dist/mediaplayer/players/youtube.js +393 -438
  208. package/dist/mediaplayer/players.js +19 -19
  209. package/dist/mediaplayer/support.js +66 -79
  210. package/dist/mediaplayer/utils/reminder.js +117 -128
  211. package/dist/mediaplayer/utils/timeObserver.js +106 -109
  212. package/dist/mediaplayer/youtubeManager.js +142 -170
  213. package/dist/mediaplayer.js +1484 -1774
  214. package/dist/mediasizer.js +633 -676
  215. package/dist/modal.js +137 -153
  216. package/dist/movableComponent.js +37 -37
  217. package/dist/pageSizeSelector.js +231 -224
  218. package/dist/pageStatus.js +43 -46
  219. package/dist/pagination/css/pagination.css +7 -7
  220. package/dist/pagination/css/pagination.css.map +1 -1
  221. package/dist/pagination/paginationStrategy.js +24 -25
  222. package/dist/pagination/providers/pages.js +281 -291
  223. package/dist/pagination/providers/simple.js +202 -196
  224. package/dist/pagination.js +49 -72
  225. package/dist/previewer.js +113 -138
  226. package/dist/progressbar.js +70 -80
  227. package/dist/propertySelector/css/propertySelector.css +74 -0
  228. package/dist/propertySelector/css/propertySelector.css.map +1 -0
  229. package/dist/propertySelector/propertySelector.js +489 -0
  230. package/dist/report.js +356 -359
  231. package/dist/resource/css/selector.css +7 -7
  232. package/dist/resource/css/selector.css.map +1 -1
  233. package/dist/resource/filters.js +275 -289
  234. package/dist/resource/list.js +1589 -1371
  235. package/dist/resource/selectable.js +101 -136
  236. package/dist/resource/selector.js +855 -914
  237. package/dist/resource/tree.js +1796 -1612
  238. package/dist/resourcemgr/css/resourcemgr.css +7 -7
  239. package/dist/resourcemgr/css/resourcemgr.css.map +1 -1
  240. package/dist/resourcemgr/fileBrowser.js +540 -587
  241. package/dist/resourcemgr/filePreview.js +16 -25
  242. package/dist/resourcemgr/fileSelector.js +520 -547
  243. package/dist/resourcemgr/util/updatePermissions.js +10 -18
  244. package/dist/resourcemgr.js +325 -332
  245. package/dist/scroller.js +29 -33
  246. package/dist/searchModal/advancedSearch.js +949 -833
  247. package/dist/searchModal/css/advancedSearch.css +7 -14
  248. package/dist/searchModal/css/advancedSearch.css.map +1 -1
  249. package/dist/searchModal/css/searchModal.css +28 -8
  250. package/dist/searchModal/css/searchModal.css.map +1 -1
  251. package/dist/searchModal.js +616 -335
  252. package/dist/selecter.js +21 -28
  253. package/dist/stacker.js +52 -60
  254. package/dist/switch/css/switch.css +7 -7
  255. package/dist/switch/css/switch.css.map +1 -1
  256. package/dist/switch/switch.js +305 -311
  257. package/dist/tableModel.js +39 -49
  258. package/dist/tabs/css/tabs.css +12 -12
  259. package/dist/tabs/css/tabs.css.map +1 -1
  260. package/dist/tabs.js +588 -639
  261. package/dist/taskQueue/css/taskQueue.css +7 -7
  262. package/dist/taskQueue/css/taskQueue.css.map +1 -1
  263. package/dist/taskQueue/status.js +317 -322
  264. package/dist/taskQueue/table.js +383 -394
  265. package/dist/taskQueue/taskQueue.js +19 -19
  266. package/dist/taskQueue/taskQueueModel.js +459 -538
  267. package/dist/taskQueue.js +49 -67
  268. package/dist/taskQueueButton/css/taskable.css +7 -7
  269. package/dist/taskQueueButton/css/taskable.css.map +1 -1
  270. package/dist/taskQueueButton/css/treeButton.css +7 -7
  271. package/dist/taskQueueButton/css/treeButton.css.map +1 -1
  272. package/dist/taskQueueButton/standardButton.js +45 -43
  273. package/dist/taskQueueButton/taskable.js +267 -272
  274. package/dist/taskQueueButton/treeButton.js +200 -198
  275. package/dist/themeLoader.js +109 -129
  276. package/dist/themes.js +86 -100
  277. package/dist/toggler.js +76 -88
  278. package/dist/tooltip.js +296 -311
  279. package/dist/tooltipster.js +17 -17
  280. package/dist/transformer.js +140 -169
  281. package/dist/tristateCheckboxGroup/css/tristateCheckboxGroup.css +7 -7
  282. package/dist/tristateCheckboxGroup/css/tristateCheckboxGroup.css.map +1 -1
  283. package/dist/tristateCheckboxGroup.js +319 -325
  284. package/dist/uploader.js +691 -724
  285. package/dist/validator/Report.js +1 -2
  286. package/dist/validator/Validator.js +6 -25
  287. package/dist/validator/validators.js +66 -80
  288. package/dist/validator.js +218 -254
  289. package/dist/waitForMedia.js +45 -51
  290. package/dist/waitingDialog/css/waitingDialog.css +7 -7
  291. package/dist/waitingDialog/css/waitingDialog.css.map +1 -1
  292. package/dist/waitingDialog/waitingDialog.js +59 -77
  293. package/package.json +109 -107
  294. package/scss/basic.scss +16 -16
  295. package/scss/ckeditor/skins/tao/scss/inc/_ck-icons.scss +59 -59
  296. package/scss/ckeditor/skins/tao/scss/inc/_tao.scss +59 -59
  297. package/scss/font/tao/tao.svg +234 -234
  298. package/scss/inc/_base.scss +495 -495
  299. package/scss/inc/_bootstrap.scss +6 -6
  300. package/scss/inc/_buttons.scss +114 -114
  301. package/scss/inc/_colors.scss +93 -88
  302. package/scss/inc/_feedback.scss +150 -150
  303. package/scss/inc/_flex-grid.scss +15 -15
  304. package/scss/inc/_fonts.scss +4 -4
  305. package/scss/inc/_forms.scss +832 -832
  306. package/scss/inc/_functions.scss +283 -283
  307. package/scss/inc/_jquery.nouislider.scss +254 -254
  308. package/scss/inc/_normalize.scss +528 -528
  309. package/scss/inc/_report.scss +67 -67
  310. package/scss/inc/_secondary-properties.scss +89 -89
  311. package/scss/inc/_select2.scss +634 -634
  312. package/scss/inc/_toolbars.scss +155 -155
  313. package/scss/inc/_tooltip.scss +312 -312
  314. package/scss/inc/_variables.scss +21 -21
  315. package/scss/inc/base/_highlight.scss +5 -5
  316. package/scss/inc/base/_list-style.scss +58 -58
  317. package/scss/inc/base/_svg.scss +3 -3
  318. package/scss/inc/base/_table.scss +62 -62
  319. package/scss/inc/fonts/_source-sans-pro.scss +29 -29
  320. package/scss/inc/fonts/_tao-icon-classes.scss +226 -226
  321. package/scss/inc/fonts/_tao-icon-def.scss +12 -12
  322. package/scss/inc/fonts/_tao-icon-vars.scss +240 -240
  323. package/src/actionbar/tpl/main.tpl +8 -8
  324. package/src/actionbar.js +251 -251
  325. package/src/adder.js +250 -250
  326. package/src/animable/absorbable/absorbable.js +134 -134
  327. package/src/animable/absorbable/css/absorb.css +7 -8
  328. package/src/animable/absorbable/css/absorb.css.map +1 -1
  329. package/src/animable/absorbable/scss/absorb.scss +37 -37
  330. package/src/animable/pulsable/css/pulse.css +7 -7
  331. package/src/animable/pulsable/css/pulse.css.map +1 -1
  332. package/src/animable/pulsable/pulsable.js +90 -90
  333. package/src/animable/pulsable/scss/pulse.scss +22 -22
  334. package/src/areaBroker.js +160 -160
  335. package/src/autocomplete/css/autocomplete.css +7 -8
  336. package/src/autocomplete/css/autocomplete.css.map +1 -1
  337. package/src/autocomplete/scss/autocomplete.scss +37 -37
  338. package/src/autocomplete.js +1029 -1029
  339. package/src/autoscroll.js +57 -57
  340. package/src/badge/badge.js +119 -119
  341. package/src/badge/css/badge.css +7 -8
  342. package/src/badge/css/badge.css.map +1 -1
  343. package/src/badge/scss/badge.scss +92 -92
  344. package/src/badge/tpl/badge.tpl +4 -4
  345. package/src/breadcrumbs/css/breadcrumbs.css +7 -7
  346. package/src/breadcrumbs/css/breadcrumbs.css.map +1 -1
  347. package/src/breadcrumbs/scss/breadcrumbs.scss +52 -52
  348. package/src/breadcrumbs/tpl/breadcrumbs.tpl +20 -20
  349. package/src/breadcrumbs.js +99 -99
  350. package/src/btngrouper.js +213 -213
  351. package/src/bulkActionPopup/css/bulkActionPopup.css +7 -7
  352. package/src/bulkActionPopup/css/bulkActionPopup.css.map +1 -1
  353. package/src/bulkActionPopup/scss/bulkActionPopup.scss +63 -63
  354. package/src/bulkActionPopup/tpl/layout.tpl +76 -76
  355. package/src/bulkActionPopup/tpl/select.tpl +8 -8
  356. package/src/bulkActionPopup.js +274 -274
  357. package/src/button/tpl/button.tpl +4 -4
  358. package/src/button.js +135 -135
  359. package/src/calculator/css/calculator.css +10 -10
  360. package/src/calculator/css/calculator.css.map +1 -1
  361. package/src/calculator/scss/calculator.scss +139 -139
  362. package/src/calculator.js +188 -188
  363. package/src/cascadingComboBox.js +126 -126
  364. package/src/ckeditor/ckConfigurator.js +736 -736
  365. package/src/ckeditor/dtdHandler.js +1030 -1030
  366. package/src/class/css/selector.css +7 -7
  367. package/src/class/css/selector.css.map +1 -1
  368. package/src/class/scss/selector.scss +101 -101
  369. package/src/class/selector.js +329 -329
  370. package/src/class/tpl/listItem.tpl +9 -9
  371. package/src/class/tpl/selector.tpl +10 -10
  372. package/src/component/alignable.js +274 -274
  373. package/src/component/containable.js +122 -122
  374. package/src/component/css/components.css +7 -7
  375. package/src/component/css/components.css.map +1 -1
  376. package/src/component/css/windowComponent.css +7 -7
  377. package/src/component/css/windowComponent.css.map +1 -1
  378. package/src/component/draggable.js +104 -104
  379. package/src/component/placeable.js +233 -233
  380. package/src/component/resizable.js +195 -195
  381. package/src/component/scss/components.scss +507 -507
  382. package/src/component/scss/windowComponent.scss +62 -62
  383. package/src/component/stackable.js +67 -67
  384. package/src/component/tpl/window.tpl +7 -7
  385. package/src/component/windowed.js +206 -206
  386. package/src/component.js +401 -401
  387. package/src/container.js +200 -200
  388. package/src/contextualPopup/css/contextualPopup.css +7 -7
  389. package/src/contextualPopup/css/contextualPopup.css.map +1 -1
  390. package/src/contextualPopup/scss/contextualPopup.scss +78 -78
  391. package/src/contextualPopup/tpl/popup.tpl +10 -10
  392. package/src/contextualPopup.js +297 -297
  393. package/src/css/basic.css +103 -106
  394. package/src/css/basic.css.map +1 -1
  395. package/src/dashboard/css/dashboard.css +7 -7
  396. package/src/dashboard/css/dashboard.css.map +1 -1
  397. package/src/dashboard/scss/dashboard.scss +93 -93
  398. package/src/dashboard/tpl/dashboard.tpl +16 -16
  399. package/src/dashboard/tpl/dashboardMetricsList.tpl +15 -15
  400. package/src/dashboard.js +184 -184
  401. package/src/datalist/css/datalist.css +7 -7
  402. package/src/datalist/css/datalist.css.map +1 -1
  403. package/src/datalist/scss/datalist.scss +116 -116
  404. package/src/datalist/tpl/list.tpl +24 -24
  405. package/src/datalist/tpl/main.tpl +44 -44
  406. package/src/datalist.js +500 -500
  407. package/src/datatable/css/datatable.css +11 -7
  408. package/src/datatable/css/datatable.css.map +1 -1
  409. package/src/datatable/filterStrategy/filterStrategy.js +70 -70
  410. package/src/datatable/filterStrategy/multiple.js +126 -126
  411. package/src/datatable/filterStrategy/single.js +108 -108
  412. package/src/datatable/scss/datatable.scss +149 -146
  413. package/src/datatable/tpl/button.tpl +6 -6
  414. package/src/datatable/tpl/layout.tpl +158 -158
  415. package/src/datatable.js +1056 -1056
  416. package/src/dateRange/css/dateRange.css +7 -7
  417. package/src/dateRange/css/dateRange.css.map +1 -1
  418. package/src/dateRange/dateRange.js +341 -341
  419. package/src/dateRange/scss/dateRange.scss +7 -7
  420. package/src/dateRange/tpl/select.tpl +18 -18
  421. package/src/datetime/css/picker.css +7 -7
  422. package/src/datetime/css/picker.css.map +1 -1
  423. package/src/datetime/picker.js +576 -576
  424. package/src/datetime/scss/picker.scss +192 -192
  425. package/src/datetime/tpl/picker.tpl +18 -18
  426. package/src/deleter/undo.tpl +6 -6
  427. package/src/deleter.js +296 -296
  428. package/src/destination/css/selector.css +7 -7
  429. package/src/destination/css/selector.css.map +1 -1
  430. package/src/destination/scss/selector.scss +36 -36
  431. package/src/destination/selector.js +195 -195
  432. package/src/destination/tpl/selector.tpl +13 -13
  433. package/src/dialog/alert.js +70 -70
  434. package/src/dialog/confirm.js +85 -85
  435. package/src/dialog/confirmDelete.js +95 -95
  436. package/src/dialog/tpl/body.tpl +24 -24
  437. package/src/dialog/tpl/buttons.tpl +6 -6
  438. package/src/dialog/tpl/checkbox.tpl +5 -5
  439. package/src/dialog.js +517 -517
  440. package/src/disabler.js +230 -230
  441. package/src/documentViewer/css/documentViewer.css +7 -7
  442. package/src/documentViewer/css/documentViewer.css.map +1 -1
  443. package/src/documentViewer/providers/pdfViewer/fallback/viewer.js +69 -69
  444. package/src/documentViewer/providers/pdfViewer/pdfjs/areaBroker.js +41 -41
  445. package/src/documentViewer/providers/pdfViewer/pdfjs/findBar.js +475 -475
  446. package/src/documentViewer/providers/pdfViewer/pdfjs/findBar.tpl +20 -20
  447. package/src/documentViewer/providers/pdfViewer/pdfjs/match.tpl +1 -1
  448. package/src/documentViewer/providers/pdfViewer/pdfjs/page.tpl +4 -4
  449. package/src/documentViewer/providers/pdfViewer/pdfjs/pageView.js +318 -318
  450. package/src/documentViewer/providers/pdfViewer/pdfjs/pagesManager.js +167 -167
  451. package/src/documentViewer/providers/pdfViewer/pdfjs/searchEngine.js +451 -451
  452. package/src/documentViewer/providers/pdfViewer/pdfjs/textManager.js +252 -252
  453. package/src/documentViewer/providers/pdfViewer/pdfjs/viewer.js +299 -299
  454. package/src/documentViewer/providers/pdfViewer/pdfjs/viewer.tpl +16 -16
  455. package/src/documentViewer/providers/pdfViewer/pdfjs/wrapper.js +351 -351
  456. package/src/documentViewer/providers/pdfViewer.js +93 -93
  457. package/src/documentViewer/scss/documentViewer.scss +184 -184
  458. package/src/documentViewer/viewerFactory.js +191 -191
  459. package/src/documentViewer.js +238 -238
  460. package/src/dropdown/css/dropdown.css +7 -7
  461. package/src/dropdown/css/dropdown.css.map +1 -1
  462. package/src/dropdown/scss/dropdown.scss +99 -99
  463. package/src/dropdown/tpl/dropdown.tpl +8 -8
  464. package/src/dropdown/tpl/list-item.tpl +4 -4
  465. package/src/dropdown.js +255 -255
  466. package/src/durationer.js +222 -222
  467. package/src/dynamicComponent/css/dynamicComponent.css +7 -7
  468. package/src/dynamicComponent/css/dynamicComponent.css.map +1 -1
  469. package/src/dynamicComponent/scss/dynamicComponent.scss +98 -98
  470. package/src/dynamicComponent/tpl/layout.tpl +17 -17
  471. package/src/dynamicComponent.js +554 -554
  472. package/src/feedback/feedback.tpl +7 -7
  473. package/src/feedback.js +295 -295
  474. package/src/figure/FigureStateActive.js +174 -174
  475. package/src/filesender.js +114 -114
  476. package/src/filter/template.tpl +5 -5
  477. package/src/filter.js +135 -135
  478. package/src/form/css/dropdownForm.css +7 -7
  479. package/src/form/css/dropdownForm.css.map +1 -1
  480. package/src/form/css/form.css +7 -7
  481. package/src/form/css/form.css.map +1 -1
  482. package/src/form/dropdownForm.js +281 -281
  483. package/src/form/form.js +688 -688
  484. package/src/form/scss/dropdownForm.scss +60 -60
  485. package/src/form/scss/form.scss +25 -25
  486. package/src/form/simpleForm.js +125 -125
  487. package/src/form/tpl/dropdownForm.tpl +4 -4
  488. package/src/form/tpl/form.tpl +7 -7
  489. package/src/form/validator/css/validator.css +7 -7
  490. package/src/form/validator/css/validator.css.map +1 -1
  491. package/src/form/validator/renderer.js +118 -118
  492. package/src/form/validator/scss/validator.scss +14 -14
  493. package/src/form/validator/tpl/message.tpl +1 -1
  494. package/src/form/validator/tpl/validator.tpl +1 -1
  495. package/src/form/validator/validator.js +220 -220
  496. package/src/form/widget/css/widget.css +7 -7
  497. package/src/form/widget/css/widget.css.map +1 -1
  498. package/src/form/widget/definitions.js +51 -51
  499. package/src/form/widget/loader.js +40 -40
  500. package/src/form/widget/providers/checkBox.js +138 -138
  501. package/src/form/widget/providers/comboBox.js +63 -63
  502. package/src/form/widget/providers/default.js +90 -90
  503. package/src/form/widget/providers/hidden.js +62 -62
  504. package/src/form/widget/providers/hiddenBox.js +152 -152
  505. package/src/form/widget/providers/radioBox.js +99 -99
  506. package/src/form/widget/providers/textArea.js +52 -52
  507. package/src/form/widget/providers/textBox.js +48 -48
  508. package/src/form/widget/scss/widget.scss +55 -55
  509. package/src/form/widget/tpl/checkBox.tpl +25 -25
  510. package/src/form/widget/tpl/comboBox.tpl +13 -13
  511. package/src/form/widget/tpl/hidden.tpl +1 -1
  512. package/src/form/widget/tpl/hiddenBox.tpl +17 -17
  513. package/src/form/widget/tpl/label.tpl +6 -6
  514. package/src/form/widget/tpl/radioBox.tpl +25 -25
  515. package/src/form/widget/tpl/textArea.tpl +8 -8
  516. package/src/form/widget/tpl/widget.tpl +8 -8
  517. package/src/form/widget/widget.js +372 -372
  518. package/src/form.js +53 -53
  519. package/src/formValidator/formValidator.js +253 -253
  520. package/src/formValidator/highlighters/highlighter.js +102 -102
  521. package/src/formValidator/highlighters/message.js +70 -70
  522. package/src/formValidator/highlighters/tooltip.js +78 -78
  523. package/src/generis/form/css/form.css +7 -7
  524. package/src/generis/form/css/form.css.map +1 -1
  525. package/src/generis/form/form.js +239 -239
  526. package/src/generis/form/readme.md +70 -70
  527. package/src/generis/form/scss/form.scss +23 -23
  528. package/src/generis/form/tpl/form.tpl +16 -16
  529. package/src/generis/validator/css/validator.css +7 -7
  530. package/src/generis/validator/css/validator.css.map +1 -1
  531. package/src/generis/validator/readme.md +46 -46
  532. package/src/generis/validator/scss/validator.scss +13 -13
  533. package/src/generis/validator/validator.js +128 -128
  534. package/src/generis/widget/checkBox/checkBox.js +112 -112
  535. package/src/generis/widget/checkBox/checkBox.tpl +18 -18
  536. package/src/generis/widget/comboBox/comboBox.js +67 -67
  537. package/src/generis/widget/comboBox/comboBox.tpl +12 -12
  538. package/src/generis/widget/css/widget.css +7 -7
  539. package/src/generis/widget/css/widget.css.map +1 -1
  540. package/src/generis/widget/hiddenBox/hiddenBox.js +132 -132
  541. package/src/generis/widget/hiddenBox/hiddenBox.tpl +16 -16
  542. package/src/generis/widget/loader.js +49 -49
  543. package/src/generis/widget/readme.md +59 -59
  544. package/src/generis/widget/scss/widget.scss +61 -61
  545. package/src/generis/widget/textBox/textBox.js +65 -65
  546. package/src/generis/widget/textBox/textBox.tpl +7 -7
  547. package/src/generis/widget/widget.js +164 -164
  548. package/src/generis/widget/widget.tpl +5 -5
  549. package/src/groupedComboBox.js +99 -99
  550. package/src/groupvalidator.js +84 -84
  551. package/src/hider.js +88 -88
  552. package/src/highlighter.js +1166 -1166
  553. package/src/image/ImgStateActive/extractLabel.js +29 -29
  554. package/src/image/ImgStateActive/helper.js +36 -36
  555. package/src/image/ImgStateActive/initHelper.js +137 -137
  556. package/src/image/ImgStateActive/initMediaEditor.js +92 -92
  557. package/src/image/ImgStateActive/mediaSizer.js +63 -63
  558. package/src/image/ImgStateActive.js +115 -115
  559. package/src/incrementer.js +319 -319
  560. package/src/inplacer.js +316 -316
  561. package/src/interactUtils.js +140 -140
  562. package/src/itemButtonList/css/item-button-list.css +23 -24
  563. package/src/itemButtonList/css/item-button-list.css.map +1 -1
  564. package/src/itemButtonList/scss/item-button-list.scss +236 -236
  565. package/src/itemButtonList/tpl/itemButtonList.tpl +21 -21
  566. package/src/itemButtonList.js +274 -274
  567. package/src/keyNavigation/navigableDomElement.js +282 -282
  568. package/src/keyNavigation/navigator.js +543 -543
  569. package/src/listbox/css/listbox.css +7 -7
  570. package/src/listbox/css/listbox.css.map +1 -1
  571. package/src/listbox/scss/listbox.scss +116 -116
  572. package/src/listbox/tpl/list.tpl +14 -14
  573. package/src/listbox/tpl/main.tpl +9 -9
  574. package/src/listbox.js +251 -251
  575. package/src/liststyler.js +155 -155
  576. package/src/loadingButton/css/button.css +7 -7
  577. package/src/loadingButton/css/button.css.map +1 -1
  578. package/src/loadingButton/loadingButton.js +110 -110
  579. package/src/loadingButton/scss/button.scss +41 -41
  580. package/src/loadingButton/tpl/button.tpl +5 -5
  581. package/src/lock/lock.tpl +16 -16
  582. package/src/lock.js +395 -395
  583. package/src/login/login.js +322 -322
  584. package/src/login/tpl/login.tpl +29 -29
  585. package/src/login/tpl/passwordReveal.tpl +7 -7
  586. package/src/maths/calculator/basicCalculator.js +55 -55
  587. package/src/maths/calculator/calculatorComponent.js +128 -128
  588. package/src/maths/calculator/core/areaBroker.js +38 -38
  589. package/src/maths/calculator/core/board.js +841 -841
  590. package/src/maths/calculator/core/expression.js +430 -430
  591. package/src/maths/calculator/core/labels.js +116 -116
  592. package/src/maths/calculator/core/plugin.js +40 -40
  593. package/src/maths/calculator/core/terms.js +459 -459
  594. package/src/maths/calculator/core/tokenizer.js +245 -245
  595. package/src/maths/calculator/core/tokens.js +178 -178
  596. package/src/maths/calculator/core/tpl/board.tpl +4 -4
  597. package/src/maths/calculator/css/calculator.css +7 -7
  598. package/src/maths/calculator/css/calculator.css.map +1 -1
  599. package/src/maths/calculator/defaultCalculator.js +66 -66
  600. package/src/maths/calculator/plugins/core/degrad.js +90 -90
  601. package/src/maths/calculator/plugins/core/history.js +166 -166
  602. package/src/maths/calculator/plugins/core/remind.js +96 -96
  603. package/src/maths/calculator/plugins/core/stepNavigation.js +175 -175
  604. package/src/maths/calculator/plugins/keyboard/templateKeyboard/defaultTemplate.tpl +36 -36
  605. package/src/maths/calculator/plugins/keyboard/templateKeyboard/templateKeyboard.js +91 -91
  606. package/src/maths/calculator/plugins/modifiers/pow10.js +143 -143
  607. package/src/maths/calculator/plugins/modifiers/sign.js +339 -339
  608. package/src/maths/calculator/plugins/screen/simpleScreen/defaultTemplate.tpl +3 -3
  609. package/src/maths/calculator/plugins/screen/simpleScreen/history.tpl +3 -3
  610. package/src/maths/calculator/plugins/screen/simpleScreen/simpleScreen.js +191 -191
  611. package/src/maths/calculator/pluginsLoader.js +46 -46
  612. package/src/maths/calculator/scientificCalculator.js +74 -74
  613. package/src/maths/calculator/scss/calculator.scss +396 -396
  614. package/src/maths/calculator/tpl/basicKeyboard.tpl +37 -37
  615. package/src/maths/calculator/tpl/basicScreen.tpl +2 -2
  616. package/src/maths/calculator/tpl/scientificKeyboard.tpl +61 -61
  617. package/src/maths/calculator/tpl/scientificScreen.tpl +3 -3
  618. package/src/mediaEditor/mediaEditorComponent.js +141 -141
  619. package/src/mediaEditor/plugins/mediaAlignment/helper.js +62 -62
  620. package/src/mediaEditor/plugins/mediaAlignment/mediaAlignmentComponent.js +99 -99
  621. package/src/mediaEditor/plugins/mediaAlignment/style.css +7 -7
  622. package/src/mediaEditor/plugins/mediaAlignment/tpl/mediaAlignment.tpl +25 -25
  623. package/src/mediaEditor/plugins/mediaDimension/helper.js +189 -189
  624. package/src/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +561 -561
  625. package/src/mediaEditor/plugins/mediaDimension/style.css +141 -141
  626. package/src/mediaEditor/plugins/mediaDimension/tpl/mediaDimension.tpl +55 -55
  627. package/src/mediaEditor/tpl/editor.tpl +4 -4
  628. package/src/mediaplayer/css/player.css +7 -7
  629. package/src/mediaplayer/css/player.css.map +1 -1
  630. package/src/mediaplayer/players/html5.js +564 -564
  631. package/src/mediaplayer/players/youtube.js +323 -323
  632. package/src/mediaplayer/players.js +29 -29
  633. package/src/mediaplayer/readme.md +305 -305
  634. package/src/mediaplayer/scss/player.scss +569 -569
  635. package/src/mediaplayer/support.js +126 -126
  636. package/src/mediaplayer/tpl/audio.tpl +6 -6
  637. package/src/mediaplayer/tpl/player.tpl +37 -37
  638. package/src/mediaplayer/tpl/source.tpl +1 -1
  639. package/src/mediaplayer/tpl/video.tpl +6 -6
  640. package/src/mediaplayer/tpl/youtube.tpl +1 -1
  641. package/src/mediaplayer/utils/reminder.js +184 -184
  642. package/src/mediaplayer/utils/timeObserver.js +143 -143
  643. package/src/mediaplayer/youtubeManager.js +161 -161
  644. package/src/mediaplayer.js +1606 -1606
  645. package/src/mediasizer/mediasizer.tpl +55 -55
  646. package/src/mediasizer.js +635 -635
  647. package/src/modal.js +365 -365
  648. package/src/movableComponent.js +78 -78
  649. package/src/pageSizeSelector/tpl/pageSizeSelector.tpl +9 -9
  650. package/src/pageSizeSelector.js +107 -107
  651. package/src/pageStatus.js +147 -147
  652. package/src/pagination/css/pagination.css +7 -7
  653. package/src/pagination/css/pagination.css.map +1 -1
  654. package/src/pagination/paginationStrategy.js +53 -53
  655. package/src/pagination/providers/pages.js +161 -161
  656. package/src/pagination/providers/simple.js +74 -74
  657. package/src/pagination/providers/tpl/pages/page.tpl +1 -1
  658. package/src/pagination/providers/tpl/pages.tpl +8 -8
  659. package/src/pagination/providers/tpl/simple.tpl +7 -7
  660. package/src/pagination/scss/pagination.scss +111 -111
  661. package/src/pagination.js +237 -237
  662. package/src/previewer.js +300 -300
  663. package/src/progressbar.js +165 -165
  664. package/src/propertySelector/css/propertySelector.css +74 -0
  665. package/src/propertySelector/css/propertySelector.css.map +1 -0
  666. package/src/propertySelector/propertySelector.js +286 -0
  667. package/src/propertySelector/scss/propertySelector.scss +66 -0
  668. package/src/propertySelector/tpl/highlighted-text.tpl +1 -0
  669. package/src/propertySelector/tpl/property-description.tpl +13 -0
  670. package/src/propertySelector/tpl/property-selector.tpl +7 -0
  671. package/src/report/feedback.tpl +11 -11
  672. package/src/report/layout.tpl +10 -10
  673. package/src/report.js +184 -184
  674. package/src/resource/css/selector.css +7 -7
  675. package/src/resource/css/selector.css.map +1 -1
  676. package/src/resource/filters.js +208 -208
  677. package/src/resource/list.js +200 -200
  678. package/src/resource/scss/_filters.scss +26 -26
  679. package/src/resource/scss/_resource-list.scss +107 -107
  680. package/src/resource/scss/_resource-tree.scss +205 -205
  681. package/src/resource/scss/selector.scss +187 -187
  682. package/src/resource/selectable.js +322 -322
  683. package/src/resource/selector.js +871 -871
  684. package/src/resource/tpl/filters.tpl +2 -2
  685. package/src/resource/tpl/list.tpl +7 -7
  686. package/src/resource/tpl/listNode.tpl +4 -4
  687. package/src/resource/tpl/selector.tpl +46 -46
  688. package/src/resource/tpl/tree.tpl +4 -4
  689. package/src/resource/tpl/treeNode.tpl +30 -30
  690. package/src/resource/tree.js +400 -400
  691. package/src/resourcemgr/css/resourcemgr.css +7 -7
  692. package/src/resourcemgr/css/resourcemgr.css.map +1 -1
  693. package/src/resourcemgr/fileBrowser.js +381 -381
  694. package/src/resourcemgr/filePreview.js +73 -73
  695. package/src/resourcemgr/fileSelector.js +348 -348
  696. package/src/resourcemgr/scss/resourcemgr.scss +254 -254
  697. package/src/resourcemgr/tpl/fileSelect.tpl +39 -39
  698. package/src/resourcemgr/tpl/folder.tpl +11 -11
  699. package/src/resourcemgr/tpl/layout.tpl +84 -84
  700. package/src/resourcemgr/tpl/rootFolder.tpl +13 -13
  701. package/src/resourcemgr/util/updatePermissions.js +53 -53
  702. package/src/resourcemgr.js +216 -216
  703. package/src/scroller.js +94 -94
  704. package/src/scss/basic.scss +16 -16
  705. package/src/searchModal/advancedSearch.js +638 -601
  706. package/src/searchModal/css/advancedSearch.css +7 -14
  707. package/src/searchModal/css/advancedSearch.css.map +1 -1
  708. package/src/searchModal/css/searchModal.css +28 -8
  709. package/src/searchModal/css/searchModal.css.map +1 -1
  710. package/src/searchModal/scss/advancedSearch.scss +171 -177
  711. package/src/searchModal/scss/searchModal.scss +393 -375
  712. package/src/searchModal/tpl/advanced-search.tpl +9 -9
  713. package/src/searchModal/tpl/criteria-alias.tpl +1 -0
  714. package/src/searchModal/tpl/criteria-class-label.tpl +1 -0
  715. package/src/searchModal/tpl/criteria-label.tpl +1 -0
  716. package/src/searchModal/tpl/highlighted-text.tpl +1 -0
  717. package/src/searchModal/tpl/info-message.tpl +3 -3
  718. package/src/searchModal/tpl/invalid-criteria-warning.tpl +10 -10
  719. package/src/searchModal/tpl/layout.tpl +27 -25
  720. package/src/searchModal/tpl/list-checkbox-criterion.tpl +17 -12
  721. package/src/searchModal/tpl/list-select-criterion.tpl +12 -6
  722. package/src/searchModal/tpl/property-select-button.tpl +1 -0
  723. package/src/searchModal/tpl/results-container.tpl +1 -0
  724. package/src/searchModal/tpl/text-criterion.tpl +11 -6
  725. package/src/searchModal.js +761 -496
  726. package/src/selecter.js +43 -43
  727. package/src/stacker.js +133 -133
  728. package/src/switch/css/switch.css +7 -7
  729. package/src/switch/css/switch.css.map +1 -1
  730. package/src/switch/scss/switch.scss +83 -83
  731. package/src/switch/switch.js +195 -195
  732. package/src/switch/tpl/switch.tpl +7 -7
  733. package/src/tableModel.js +112 -112
  734. package/src/tabs/css/tabs.css +12 -12
  735. package/src/tabs/css/tabs.css.map +1 -1
  736. package/src/tabs/scss/tabs.scss +50 -50
  737. package/src/tabs/tpl/panel.tpl +3 -3
  738. package/src/tabs/tpl/tabs.tpl +10 -10
  739. package/src/tabs.js +528 -528
  740. package/src/taskQueue/css/taskQueue.css +7 -7
  741. package/src/taskQueue/css/taskQueue.css.map +1 -1
  742. package/src/taskQueue/scss/taskQueue.scss +47 -47
  743. package/src/taskQueue/status.js +228 -228
  744. package/src/taskQueue/table.js +350 -350
  745. package/src/taskQueue/taskQueue.js +33 -33
  746. package/src/taskQueue/taskQueueModel.js +548 -548
  747. package/src/taskQueue/tpl/statusMessage.tpl +7 -7
  748. package/src/taskQueue.js +218 -218
  749. package/src/taskQueueButton/css/taskable.css +7 -7
  750. package/src/taskQueueButton/css/taskable.css.map +1 -1
  751. package/src/taskQueueButton/css/treeButton.css +7 -7
  752. package/src/taskQueueButton/css/treeButton.css.map +1 -1
  753. package/src/taskQueueButton/scss/taskable.scss +4 -4
  754. package/src/taskQueueButton/scss/treeButton.scss +34 -34
  755. package/src/taskQueueButton/standardButton.js +108 -108
  756. package/src/taskQueueButton/taskable.js +202 -202
  757. package/src/taskQueueButton/tpl/report.tpl +5 -5
  758. package/src/taskQueueButton/tpl/treeButton.tpl +6 -6
  759. package/src/taskQueueButton/treeButton.js +109 -109
  760. package/src/themeLoader.js +252 -252
  761. package/src/themes.js +162 -162
  762. package/src/toggler.js +200 -200
  763. package/src/tooltip/default.tpl +3 -3
  764. package/src/tooltip.js +160 -160
  765. package/src/tooltipster.js +25 -25
  766. package/src/transformer.js +327 -327
  767. package/src/tristateCheckboxGroup/css/tristateCheckboxGroup.css +7 -7
  768. package/src/tristateCheckboxGroup/css/tristateCheckboxGroup.css.map +1 -1
  769. package/src/tristateCheckboxGroup/scss/tristateCheckboxGroup.scss +15 -15
  770. package/src/tristateCheckboxGroup/tpl/li.tpl +6 -6
  771. package/src/tristateCheckboxGroup.js +207 -207
  772. package/src/uploader/fileEntry.tpl +6 -6
  773. package/src/uploader/uploader.tpl +32 -32
  774. package/src/uploader.js +594 -594
  775. package/src/validator/Report.js +10 -10
  776. package/src/validator/Validator.js +108 -108
  777. package/src/validator/validators.js +220 -220
  778. package/src/validator.js +264 -264
  779. package/src/waitForMedia.js +82 -82
  780. package/src/waitingDialog/css/waitingDialog.css +7 -7
  781. package/src/waitingDialog/css/waitingDialog.css.map +1 -1
  782. package/src/waitingDialog/scss/waitingDialog.scss +34 -34
  783. package/src/waitingDialog/waitingDialog.js +240 -240
@@ -1,1166 +1,1166 @@
1
- /**
2
- * This program is free software; you can redistribute it and/or
3
- * modify it under the terms of the GNU General Public License
4
- * as published by the Free Software Foundation; under version 2
5
- * of the License (non-upgradable).
6
- *
7
- * This program is distributed in the hope that it will be useful,
8
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
- * GNU General Public License for more details.
11
- *
12
- * You should have received a copy of the GNU General Public License
13
- * along with this program; if not, write to the Free Software
14
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15
- *
16
- * Copyright (c) 2016-2021 (original work) Open Assessment Technologies SA;
17
- */
18
- /**
19
- * Highlighter helper: wraps every text node found within a Range object.
20
- *
21
- * @author Christophe Noël <christophe@taotesting.com>
22
- */
23
- import _ from 'lodash';
24
- import $ from 'jquery';
25
-
26
- /**
27
- * Data attribute used to logically group the wrapping nodes into a single selection
28
- * @type {string}
29
- */
30
- var GROUP_ATTR = 'data-hl-group';
31
-
32
- /**
33
- * Children of those nodes types cannot be highlighted
34
- * @type {string[]}
35
- */
36
- var defaultBlackList = ['textarea', 'math', 'script', '.select2-container'];
37
-
38
- /**
39
- * @param {Object} options
40
- * @param {String} options.className - name of the class that will be used by the wrappers tags to highlight text
41
- * @param {String} options.containerSelector - allows to select the root Node in which highlighting is allowed
42
- * @param {Array<String>} [options.containersBlackList] - additional blacklist selectors to be added to module instance's blacklist
43
- * @param {Array<String>} [options.containersWhiteList] - whitelist selectors; supported only in `keepEmptyNodes` mode.
44
- * Priority of blacklist or whitelist is decided by which selector is closest to the node. If no match found, node is considered whitelisted.
45
- * @param {Boolean} [options.clearOnClick] - clear single highlight node on click
46
- * @param {Object} [options.colors] - keys is keeping as the "c" value of storing/restore the highlighters for indexing, values are wrappers class names
47
- * @param {Boolean} [options.keepEmptyNodes] - retain original dom structure as far as possible and do not remove empty nodes if they were not created by highlighter
48
- * @returns {Object} - the highlighter instance
49
- */
50
- export default function (options) {
51
- var className = options.className;
52
- var containerSelector = options.containerSelector;
53
- var keepEmptyNodes = options.keepEmptyNodes;
54
-
55
- let highlightingClasses = [className];
56
-
57
- // Multi-color mode
58
- if (options.colors) {
59
- highlightingClasses = Object.values(options.colors);
60
- }
61
-
62
- /**
63
- * list of node selectors which should NOT receive any highlighting from this instance
64
- * an optional passed-in blacklist is merged with local defaults
65
- * @type {Array}
66
- */
67
- var containersBlackList = _.union(defaultBlackList, options.containersBlackList);
68
- var containersBlackListSelector = containersBlackList.join(', ');
69
- var containersWhiteListSelector = null;
70
- var containersBlackAndWhiteListSelector = containersBlackListSelector;
71
- if (options.keepEmptyNodes && options.containersWhiteList) {
72
- containersWhiteListSelector = options.containersWhiteList.join(', ');
73
- containersBlackAndWhiteListSelector = _.union(containersBlackList, options.containersWhiteList).join(', ');
74
- }
75
-
76
- /**
77
- * used in recursive loops to decide if we should wrap or not the current node
78
- * @type {boolean}
79
- */
80
- var isWrapping = false;
81
-
82
- /**
83
- * performance improvement to break out of a potentially big recursive loop once the wrapping has ended
84
- * @type {boolean}
85
- */
86
- var hasWrapped = false;
87
-
88
- /**
89
- * used in recursive loops to assign a group Id to the current wrapped node
90
- * @type {number}
91
- */
92
- var currentGroupId;
93
-
94
- /**
95
- * used in recursive loops to build the index of text nodes
96
- * @type {number}
97
- */
98
- var textNodesIndex;
99
-
100
- /**
101
- * Returns the node in which highlighting is allowed
102
- * @returns {Element}
103
- */
104
- function getContainer() {
105
- return $(containerSelector).get(0);
106
- }
107
-
108
- /**
109
- * Returns all highlighted nodes, excluding any inside blacklisted elements
110
- * @returns {JQuery<HTMLElement>}
111
- */
112
- function getHighlightedNodes() {
113
- return $(containerSelector)
114
- .find(`.${highlightingClasses.join(',.')}`)
115
- .filter((i, node) => !isBlacklisted(node));
116
- }
117
-
118
- /**
119
- * Attach data to wrapper node.
120
- * Use it when deleting this highlight to know if highlight content should be merged with neighbour text nodes or not.
121
- * Use it when building/restoring index to know if restored highlight content should be split off neighbour text node or not.
122
- * Needed to keep markup the same as it was before highlighting.
123
- * @param {HTMLElement} node
124
- * @param {Boolean} beforeWasSplit
125
- * @param {Boolean} afterWasSplit
126
- */
127
- function addSplitData(node, beforeWasSplit, afterWasSplit) {
128
- node.dataset.beforeWasSplit = beforeWasSplit;
129
- node.dataset.afterWasSplit = afterWasSplit;
130
- }
131
-
132
- /**
133
- * Highlight all text nodes within each given range
134
- * @param {Range[]} ranges - array of ranges to highlight, may be given by the helper selector.getAllRanges()
135
- */
136
- function highlightRanges(ranges) {
137
- ranges.forEach(function (range) {
138
- var rangeInfos;
139
-
140
- if (isRangeValid(range)) {
141
- currentGroupId = getAvailableGroupId();
142
-
143
- // easy peasy: highlighting a plain text without any DOM nodes
144
- // NOTE: The condition checks the whole node content and not a selected content in a given range, that allows to wrap whitespace
145
- if (
146
- isWrappable(range.commonAncestorContainer) &&
147
- !isWrappingNode(range.commonAncestorContainer.parentNode)
148
- ) {
149
- const wrapperNode = getWrapper(currentGroupId);
150
- if (!keepEmptyNodes) {
151
- range.surroundContents(wrapperNode);
152
- } else {
153
- addSplitData(wrapperNode, range.startOffset > 0, range.endOffset < range.commonAncestorContainer.length);
154
- rangeSurroundContentsNoEmptyNodes(range, wrapperNode);
155
- }
156
- } else if (
157
- isWrappable(range.commonAncestorContainer) &&
158
- isWrappingNode(range.commonAncestorContainer.parentNode) &&
159
- range.commonAncestorContainer.parentNode !== className
160
- ) {
161
- highlightContainerNodes(range.commonAncestorContainer, className, range, currentGroupId);
162
-
163
- // now the fun stuff: highlighting a mix of text and DOM nodes
164
- } else {
165
- rangeInfos = {
166
- startNode: isElement(range.startContainer)
167
- ? range.startContainer.childNodes[range.startOffset]
168
- : range.startContainer,
169
- startNodeContainer: range.startContainer,
170
- startOffset: range.startOffset,
171
-
172
- endNode: isElement(range.endContainer) && range.endOffset > 0
173
- ? range.endContainer.childNodes[range.endOffset - 1]
174
- : range.endContainer,
175
- endNodeContainer: range.endContainer,
176
- endOffset: range.endOffset,
177
- commonRange: range
178
- };
179
-
180
- isWrapping = false;
181
- hasWrapped = false;
182
- wrapTextNodesInRange(range.commonAncestorContainer, rangeInfos);
183
- }
184
- }
185
-
186
- if (!keepEmptyNodes) {
187
- // clean up the markup after wrapping...
188
- range.commonAncestorContainer.normalize();
189
- }
190
-
191
- currentGroupId = 0;
192
- isWrapping = false;
193
- reindexGroups(getContainer());
194
- mergeAdjacentWrappingNodes(getContainer());
195
- unWrapEmptyHighlights();
196
- });
197
-
198
- if (options.clearOnClick) {
199
- $(containerSelector + ' .' + className)
200
- .off('click')
201
- .on('click', clearSingleHighlight);
202
- }
203
- }
204
-
205
- /**
206
- * Check if a range is valid
207
- * @param {Range} range
208
- * @returns {boolean}
209
- */
210
- function isRangeValid(range) {
211
- var rangeInContainer;
212
- try {
213
- rangeInContainer =
214
- $.contains(getContainer(), range.commonAncestorContainer) ||
215
- getContainer().isSameNode(range.commonAncestorContainer);
216
-
217
- return rangeInContainer && !range.collapsed;
218
- } catch (e) {
219
- return false;
220
- }
221
- }
222
-
223
- /**
224
- * Core wrapping function. Traverse the DOM tree and highlight (= wraps) all text nodes within the given range.
225
- * Recursive.
226
- *
227
- * @param {Node} rootNode - top of the node hierarchy in which text nodes will be searched
228
- * @param {Object} rangeInfos
229
- * @param {Node} rangeInfos.startNode - node on which the selection starts
230
- * @param {Node} rangeInfos.startNodeContainer - container of the startNode, or the start node itself in case of text nodes
231
- * @param {number} rangeInfos.startOffset - same as range.startOffset, but not read-only to allow override
232
- * @param {Node} rangeInfos.endNode - node on which the selection ends
233
- * @param {Node} rangeInfos.endNodeContainer - container of the endNode, or the end node itself in case of text nodes
234
- * @param {number} rangeInfos.endOffset - same as range.endOffset, but not read-only to allow override
235
- */
236
- function wrapTextNodesInRange(rootNode, rangeInfos) {
237
- var childNodes = rootNode.childNodes;
238
- var currentNode, i;
239
- var splitDatas = [];
240
-
241
- for (i = 0; i < childNodes.length; i++) {
242
- if (hasWrapped) {
243
- break;
244
- }
245
- currentNode = childNodes[i];
246
-
247
- if (isBlacklisted(currentNode)) {
248
- if (isElement(currentNode)) {
249
- //go deeper in case a descendant of the current blacklisted is whitelisted
250
- wrapTextNodesInRange(currentNode, rangeInfos);
251
- }
252
- } else {
253
- const isCurrentNodeTextInsideOfAnotherHighlightingWrapper =
254
- isText(currentNode) &&
255
- isWrappingNode(currentNode.parentNode) &&
256
- currentNode.parentNode.className !== className;
257
-
258
- if (isCurrentNodeTextInsideOfAnotherHighlightingWrapper) {
259
- const internalRange = new Range();
260
- internalRange.selectNodeContents(currentNode);
261
-
262
- if (rangeInfos.startNode === currentNode) {
263
- internalRange.setStart(currentNode, rangeInfos.startOffset);
264
- }
265
-
266
- if (rangeInfos.endNode === currentNode) {
267
- internalRange.setEnd(currentNode, rangeInfos.endOffset);
268
- }
269
-
270
- const isNodeInRange = rangeInfos.commonRange.isPointInRange(currentNode, internalRange.endOffset);
271
-
272
- // Apply new highlighting color only for selected nodes
273
- if (isNodeInRange) {
274
- isWrapping = true;
275
- highlightContainerNodes(currentNode, className, internalRange, currentGroupId);
276
- }
277
- } else {
278
- // split current node in case the wrapping start/ends on a partially selected text node
279
- if (currentNode.isSameNode(rangeInfos.startNode)) {
280
- if (isText(rangeInfos.startNodeContainer) && rangeInfos.startOffset !== 0) {
281
- // we defer the wrapping to the next iteration of the loop
282
- //end of node should be highlighted
283
- rangeInfos.startNode = currentNode.splitText(rangeInfos.startOffset);
284
- rangeInfos.startOffset = 0;
285
- splitDatas.push({ node: rangeInfos.startNode, beforeWasSplit: true, afterWasSplit: false });
286
- } else {
287
- //whole node should be highlighted
288
- isWrapping = true;
289
- splitDatas.push({ node: currentNode, beforeWasSplit: false, afterWasSplit: false });
290
- }
291
- }
292
-
293
- if (currentNode.isSameNode(rangeInfos.endNode) && isText(rangeInfos.endNodeContainer)) {
294
- if (rangeInfos.endOffset !== 0) {
295
- if (rangeInfos.endOffset < currentNode.textContent.length) {
296
- //start of node should be highlighted
297
- currentNode.splitText(rangeInfos.endOffset);
298
- splitDatas.push({ node: currentNode, beforeWasSplit: false, afterWasSplit: true });
299
- } else {
300
- //whole node should be highlighted
301
- splitDatas.push({ node: currentNode, beforeWasSplit: false, afterWasSplit: false });
302
- }
303
- } else {
304
- isWrapping = false;
305
- }
306
- }
307
-
308
- // wrap the current node...
309
- if (isText(currentNode)) {
310
- if (!keepEmptyNodes) {
311
- wrapTextNode(currentNode, currentGroupId);
312
- } else if (willHighlightNotBeEmptyAfterMerge(currentNode)) {
313
- const wrapperNode = wrapTextNode(currentNode, currentGroupId);
314
- if (wrapperNode) {
315
- const splitData = splitDatas.find(d => d.node === currentNode);
316
- addSplitData(wrapperNode,
317
- splitData ? splitData.beforeWasSplit : false,
318
- splitData ? splitData.afterWasSplit : false);
319
- }
320
- }
321
-
322
- // ... or continue deeper in the node tree
323
- } else if (isElement(currentNode)) {
324
- //some selections end at the very start of the next node, we should end wrapping when we reach such node
325
- if (!currentNode.isSameNode(rangeInfos.endNode) || rangeInfos.endOffset > 0) {
326
- wrapTextNodesInRange(currentNode, rangeInfos);
327
- }
328
- }
329
- }
330
- }
331
-
332
- // end wrapping ?
333
- if (currentNode.isSameNode(rangeInfos.endNode)) {
334
- isWrapping = false;
335
- hasWrapped = true;
336
- break;
337
- }
338
- }
339
- }
340
-
341
- /**
342
- * Restructure content of the highlighted wrapper according to the selectedRange
343
- * @param {Node} textNode
344
- * @param {string} activeClass
345
- * @param {Range} selectedRange
346
- * @param {number} currentGroupId
347
- */
348
- function highlightContainerNodes(textNode, activeClass, selectedRange, currentGroupId) {
349
- const container = textNode.parentNode;
350
- const range = new Range();
351
- range.selectNodeContents(textNode);
352
-
353
- const isSelectionCoversNodeStart = range.compareBoundaryPoints(Range.START_TO_START, selectedRange) === 0;
354
- const isSelectionCoversNodeEnd = range.compareBoundaryPoints(Range.END_TO_END, selectedRange) === 0;
355
-
356
- /*
357
- There are 4 possible cases selected area is intersected with already highlighted element.
358
- In examples below the border is represents the selection, "yellow" is class name of already highlighted
359
- container, "red" is class name of currently active highlighter
360
- **********************************************************************************************************
361
- 1. The container content is completely selected, so that we only have to change the highlighter class name
362
-
363
- Input:
364
- __________________________________________________
365
- | |
366
- |<span class="yellow"> Lorem ipsum dolor sit</span>|
367
- |__________________________________________________|
368
-
369
- Output:
370
- <span class="red"> Lorem ipsum dolor sit</span>
371
-
372
- **********************************************************************************************************
373
- 2. The container content is partially selected from the begging.
374
-
375
- Input:
376
- ______________________________
377
- | |
378
- |<span class="yellow"> Lorem ip|sum dolor sit</span>
379
- |______________________________|
380
-
381
- Output:
382
- <span class="red"> Lorem ip</span><span class="yellow">sum dolor sit</span>
383
-
384
- **********************************************************************************************************
385
- 3. The container content is partially selected at the end.
386
-
387
- Input:
388
- ____________________
389
- | |
390
- <span class="yellow"> Lorem ip|sum dolor sit</span>|
391
- |____________________|
392
-
393
- Output:
394
- <span class="yellow"> Lorem ip</span><span class="red">sum dolor sit</span>
395
-
396
- **********************************************************************************************************
397
- 4. The container content is partially selected in the middle.
398
-
399
- Input:
400
- ___________
401
- | |
402
- <span class="yellow"> Lorem |ipsum dolor| sit</span>
403
- |___________|
404
-
405
- Output:
406
- <span class="yellow"> Lorem </span><span class="red">ipsum dolor</span><span class="yellow"> sit</span>
407
- */
408
- if (isSelectionCoversNodeStart && isSelectionCoversNodeEnd) {
409
- textNode.parentNode.className = activeClass;
410
- } else if (isSelectionCoversNodeStart) {
411
- textNode.splitText(selectedRange.endOffset);
412
- wrapContainerChildNodes(container, 0, activeClass, currentGroupId);
413
- } else if (isSelectionCoversNodeEnd) {
414
- textNode.splitText(selectedRange.startOffset);
415
- wrapContainerChildNodes(container, 1, activeClass, currentGroupId);
416
- } else {
417
- textNode.splitText(selectedRange.startOffset).splitText(selectedRange.endOffset);
418
- wrapContainerChildNodes(container, 1, activeClass, currentGroupId);
419
- }
420
- }
421
-
422
- /**
423
- * Wraps all containers text nodes with highlighter element.
424
- * The child node with index given by indexToWrapNode parameter will be wrap with class given by activeClass parameter
425
- * @param {Element} container
426
- * @param {number} indexToWrapNode
427
- * @param {string} activeClass
428
- * @param {number} currentGroupId
429
- */
430
- function wrapContainerChildNodes(container, indexToWrapNode, activeClass, currentGroupId) {
431
- const containerClass = container.className;
432
- const fragment = new DocumentFragment();
433
- const childNodesLength = container.childNodes.length;
434
-
435
- container.childNodes.forEach((node, index) => {
436
- var wrapperNode;
437
- if (index === indexToWrapNode) {
438
- wrapperNode = wrapNode(node.cloneNode(), activeClass, currentGroupId);
439
- } else {
440
- wrapperNode = wrapNode(node.cloneNode(), containerClass, currentGroupId);
441
- }
442
- fragment.appendChild(wrapperNode);
443
-
444
- if (keepEmptyNodes) {
445
- addSplitData(wrapperNode,
446
- index === 0 ? container.dataset.beforeWasSplit : true,
447
- index === childNodesLength - 1 ? container.dataset.afterWasSplit : true);
448
- }
449
- });
450
-
451
- container.replaceWith(fragment);
452
- }
453
-
454
- /**
455
- * wraps a text node into the highlight span
456
- * @param {Node} node - the node to wrap
457
- * @param {number} groupId - the highlight group
458
- * @returns {Node|null} wrapper node, if it was created
459
- */
460
- function wrapTextNode(node, groupId) {
461
- if (isWrapping && !isWrappingNode(node.parentNode) && isWrappable(node)) {
462
- $(node).wrap(getWrapper(groupId));
463
- return node.parentNode;
464
- }
465
- return null;
466
- }
467
-
468
- /**
469
- * We need to re-index the groups after a user highlight: either to merge groups or to resolve inconsistencies
470
- * Recursive.
471
- *
472
- * @param {Node} rootNode
473
- */
474
- function reindexGroups(rootNode) {
475
- if (!rootNode) {
476
- return;
477
- }
478
-
479
- var childNodes = rootNode.childNodes;
480
- var i, currentNode, parent;
481
-
482
- for (i = 0; i < childNodes.length; i++) {
483
- currentNode = childNodes[i];
484
-
485
- if (isWrappable(currentNode)) {
486
- parent = currentNode.parentNode;
487
- if (isWrappingNode(parent)) {
488
- if (isWrapping === false) {
489
- currentGroupId++;
490
- }
491
- isWrapping = true;
492
- parent.setAttribute(GROUP_ATTR, currentGroupId); // set the new group Id
493
- } else {
494
- isWrapping = false;
495
- }
496
- } else if (isElement(currentNode)) {
497
- reindexGroups(currentNode);
498
- }
499
- }
500
- }
501
-
502
- /**
503
- * Some highlights may result in having adjacent wrapping nodes. We remove them here to get a cleaner markup.
504
- *
505
- * @param {Node} rootNode
506
- */
507
- function mergeAdjacentWrappingNodes(rootNode) {
508
- if (!rootNode) {
509
- return;
510
- }
511
-
512
- var childNodes = rootNode.childNodes;
513
- var i, currentNode;
514
-
515
- for (i = 0; i < childNodes.length; i++) {
516
- currentNode = childNodes[i];
517
-
518
- if (isWrappingNode(currentNode)) {
519
- if (keepEmptyNodes) {
520
- currentNode.normalize();
521
- }
522
- while (
523
- isWrappingNode(currentNode.nextSibling) &&
524
- currentNode.className === currentNode.nextSibling.className
525
- ) {
526
- if (keepEmptyNodes) {
527
- currentNode.nextSibling.normalize();
528
- }
529
- currentNode.firstChild.textContent += currentNode.nextSibling.firstChild.textContent;
530
- if (keepEmptyNodes) {
531
- addSplitData(currentNode, currentNode.dataset.beforeWasSplit, currentNode.nextSibling.dataset.afterWasSplit);
532
- }
533
- currentNode.parentNode.removeChild(currentNode.nextSibling);
534
- }
535
- } else if (isElement(currentNode)) {
536
- mergeAdjacentWrappingNodes(currentNode);
537
- }
538
- }
539
- }
540
-
541
- /**
542
- * Unwraps highlighted nodes with a line break or with an empty content
543
- */
544
- function unWrapEmptyHighlights() {
545
- getHighlightedNodes().each((index, node) => {
546
- const nodeContent = node.textContent;
547
-
548
- if (nodeContent.trim().length === 0) {
549
- if (nodeContent.length === 0 || /\r|\n/.exec(nodeContent)) {
550
- clearSingleHighlight({ target: node });
551
- }
552
- }
553
- });
554
- }
555
-
556
- /**
557
- * Check condition to avoid the work of `unwrapEmptyHighlights` ahead of time, before `mergeAdjacentNodes` runs,
558
- * because in `keepEmptyNodes` case we do not want to add nodes to dom unless necessary.
559
- * Also be more strict and don't allow to select nodes with spaces only, because they may appear in unexpected places in markup
560
- * (here it's not exactly same as `unwrapEmptyHighlights`).
561
- * @param {Node} node - node which will be wrapped (highlighted)
562
- * @returns {Boolean}
563
- */
564
- function willHighlightNotBeEmptyAfterMerge(node) {
565
- if (!node.textContent.length) {
566
- return false;
567
- }
568
- if (node.textContent.trim().length) {
569
- return true;
570
- }
571
- const prevNode = node.previousSibling;
572
- const canWrapperBeMergedWithPreviousSibling = prevNode && isWrappingNode(prevNode) && prevNode.className === className;
573
- if (canWrapperBeMergedWithPreviousSibling) {
574
- return true;
575
- }
576
- const nextNode = node.nextSibling;
577
- const canWrapperBeMergedWithNextSibling = nextNode && isWrappingNode(nextNode) && nextNode.className === className;
578
- if (canWrapperBeMergedWithNextSibling) {
579
- return true;
580
- }
581
- return false;
582
- }
583
-
584
- /**
585
- * `range.surroundContents` can create empty text nodes,
586
- * which will cause trouble in `mergeAdjacentNodes` later (in `keepEmptyNodes` case).
587
- * This method surrounds range, then removes those nodes
588
- * @param {Range} range
589
- * @param {Node} wrapperNode
590
- */
591
- function rangeSurroundContentsNoEmptyNodes(range, wrapperNode) {
592
- const containerPreviousSibling = range.commonAncestorContainer.previousSibling;
593
- const containerNextSibling = range.commonAncestorContainer.nextSibling;
594
-
595
- range.surroundContents(wrapperNode);
596
-
597
- removeEmptyTextNodeIfDifferent(wrapperNode.previousSibling, containerPreviousSibling);
598
- removeEmptyTextNodeIfDifferent(wrapperNode.nextSibling, containerNextSibling);
599
- }
600
-
601
- /**
602
- * Remove `node`, if it's an empty text node and is *not* the same node as `nodeToCompare`
603
- * @param {Node} node
604
- * @param {Node} nodeToCompare
605
- */
606
- function removeEmptyTextNodeIfDifferent(node, nodeToCompare) {
607
- if (node && node !== nodeToCompare && isText(node) && node.textContent.length === 0) {
608
- node.remove();
609
- }
610
- }
611
-
612
- /**
613
- * Remove all wrapping nodes from markup
614
- */
615
- function clearHighlights() {
616
- getHighlightedNodes().each(function (i, elem) {
617
- if (!keepEmptyNodes) {
618
- var $wrapped = $(this);
619
- $wrapped.replaceWith($wrapped.text());
620
- } else {
621
- clearSingleHighlight({ target: elem });
622
- }
623
- });
624
- }
625
-
626
- /**
627
- * Remove unwrap dom node
628
- */
629
- function clearSingleHighlight(e) {
630
- if (!keepEmptyNodes) {
631
- const $wrapped = $(e.target);
632
- const text = $wrapped.text();
633
-
634
- // NOTE: JQuery replaceWith is not working with empty string https://bugs.jquery.com/ticket/13401
635
- if (text === '') {
636
- $wrapped.remove();
637
- } else {
638
- $wrapped.replaceWith(text);
639
- }
640
- } else {
641
- const nodeToRemove = e.target;
642
- const nodeToRemoveText = nodeToRemove.textContent;
643
- const beforeWasSplit = nodeToRemove.dataset.beforeWasSplit === 'true';
644
- const afterWasSplit = nodeToRemove.dataset.afterWasSplit === 'true';
645
- const prevNode = nodeToRemove.previousSibling;
646
- const nextNode = nodeToRemove.nextSibling;
647
-
648
- if (beforeWasSplit && prevNode && isText(prevNode) && prevNode.textContent) {
649
- //append text to previous sibling
650
- prevNode.textContent += nodeToRemoveText;
651
- nodeToRemove.remove();
652
-
653
- if (afterWasSplit && prevNode.nextSibling && isText(prevNode.nextSibling) && prevNode.nextSibling.textContent) {
654
- //merge it with next sibling
655
- prevNode.textContent += prevNode.nextSibling.textContent;
656
- prevNode.nextSibling.remove();
657
- }
658
- }
659
- else if (afterWasSplit && nextNode && isText(nextNode) && nextNode.textContent) {
660
- //append text to next sibling
661
- nextNode.textContent = nodeToRemoveText + nextNode.textContent;
662
- nodeToRemove.remove();
663
- } else if (nodeToRemoveText) {
664
- //keep text in a separate text node
665
- nodeToRemove.replaceWith(document.createTextNode(nodeToRemoveText));
666
- } else {
667
- //text is empty, just remove it
668
- nodeToRemove.remove();
669
- }
670
- }
671
- }
672
-
673
- /**
674
- * Index-related functions:
675
- * ========================
676
- * To allow saving and restoring highlights on an equivalent, but different, DOM tree (for example if the markup is deleted and re-created)
677
- * we build an index containing the status of each text node:
678
- * - not highlighted
679
- * - fully highlighted
680
- * - partially highlighted (= with inline ranges)
681
- *
682
- * This index will be used to restore a selection on the new DOM tree
683
- */
684
-
685
- /**
686
- * Bootstrap the process of building the highlight index
687
- * @returns {Object[]|BuildModelResultKeepEmpty|null}
688
- */
689
- function getHighlightIndex() {
690
- var rootNode = getContainer();
691
- if (!keepEmptyNodes) {
692
- var highlightIndex = [];
693
- if (rootNode) {
694
- rootNode.normalize();
695
- textNodesIndex = 0;
696
- buildHighlightIndex(rootNode, highlightIndex);
697
- }
698
- return highlightIndex;
699
- } else {
700
- if (rootNode) {
701
- return buildHighlightModelKeepEmpty(rootNode);
702
- } else {
703
- return null;
704
- }
705
- }
706
- }
707
-
708
- /**
709
- * Traverse the DOM tree to create the text Nodes index. Recursive.
710
- * @param {Node} rootNode
711
- * @param {Object[]} highlightIndex
712
- */
713
- function buildHighlightIndex(rootNode, highlightIndex) {
714
- var childNodes = rootNode.childNodes;
715
- var i, currentNode;
716
- var nodeInfos, inlineRange, inlineOffset, nodesToSkip;
717
-
718
- for (i = 0; i < childNodes.length; i++) {
719
- currentNode = childNodes[i];
720
-
721
- // Skip blacklisted nodes
722
- if (isBlacklisted(currentNode)) {
723
- continue;
724
- }
725
- // A simple node not highlighted and isolated (= not followed by an wrapped text)
726
- else if (isWrappable(currentNode) && !isWrappingNode(currentNode.nextSibling)) {
727
- highlightIndex[textNodesIndex] = { highlighted: false };
728
- textNodesIndex++;
729
-
730
- // an isolated node (= not followed by a highlight table text) with its whole content highlighted
731
- } else if (
732
- isWrappingNode(currentNode) &&
733
- !isText(currentNode.nextSibling) &&
734
- (!isWrappingNode(currentNode.nextSibling) ||
735
- currentNode.className === currentNode.nextSibling.className)
736
- ) {
737
- highlightIndex[textNodesIndex] = {
738
- highlighted: true,
739
- groupId: currentNode.getAttribute(GROUP_ATTR),
740
- c: getColorByClassName(currentNode.className)
741
- };
742
- textNodesIndex++;
743
-
744
- // less straightforward: a succession of (at least) 1 wrapping node with 1 wrappable text node, in either order, and possibly more
745
- // the trick is to create a unique text node on which we will be able to re-apply multiple partial highlights
746
- // for this, we use 'inlineRanges'
747
- } else if (isHotNode(currentNode)) {
748
- nodeInfos = {
749
- highlighted: true,
750
- inlineRanges: []
751
- };
752
-
753
- nodesToSkip = -1;
754
- inlineOffset = 0;
755
-
756
- while (currentNode) {
757
- if (isWrappingNode(currentNode)) {
758
- inlineRange = {
759
- groupId: currentNode.getAttribute(GROUP_ATTR),
760
- c: getColorByClassName(currentNode.className)
761
- };
762
-
763
- if (isText(currentNode.previousSibling) || isWrappingNode(currentNode.previousSibling)) {
764
- inlineRange.startOffset = inlineOffset;
765
- }
766
- if (isText(currentNode.nextSibling) || isWrappingNode(currentNode.nextSibling)) {
767
- inlineRange.endOffset = inlineOffset + currentNode.textContent.length;
768
- }
769
- nodeInfos.inlineRanges.push(inlineRange);
770
- }
771
-
772
- inlineOffset += currentNode.textContent.length;
773
- currentNode =
774
- isHotNode(currentNode.nextSibling) || isText(currentNode.nextSibling)
775
- ? currentNode.nextSibling
776
- : null;
777
- nodesToSkip++;
778
- }
779
- i += nodesToSkip; // we increase the loop counter to avoid looping over the nodes that we just analyzed
780
-
781
- highlightIndex[textNodesIndex] = nodeInfos;
782
- textNodesIndex++;
783
-
784
- // go deeper in the node tree...
785
- } else if (isElement(currentNode)) {
786
- buildHighlightIndex(currentNode, highlightIndex);
787
- }
788
- }
789
- }
790
-
791
- /**
792
- * @typedef HighlightEntryKeepEmpty
793
- * @property {String} groupId
794
- * @property {String} c - color
795
- * @property {Number} offsetBefore
796
- * @property {Number} textLength
797
- * @property {String} beforeWasSplit
798
- * @property {String} afterWasSplit
799
- * @property {Array<Number>} path - on each level from root container to highlight, index among siblings
800
- */
801
- /**
802
- * @typedef BuildModelResultKeepEmpty
803
- * @property {HighlightEntryKeepEmpty[]} highlightModel
804
- * @property {NodeList} wrapperNodes
805
- */
806
- /**
807
- * For `keepEmptyNodes` option, creates data model of highlights.
808
- * Additionally returns array of highlight nodes. Traverses DOM tree.
809
- * @param {Node} rootNode
810
- * @returns {BuildModelResultKeepEmpty|null} result
811
- */
812
- function buildHighlightModelKeepEmpty(rootNode) {
813
- const classNames = options.colors ? Object.values(options.colors) : [className];
814
- const wrapperNodesSelector = classNames.map(cls => containerSelector + ' .' + cls).join(', ');
815
- const wrapperNodes = Array.from(document.querySelectorAll(wrapperNodesSelector))
816
- .filter(node => !isBlacklisted(node));
817
-
818
- if (!wrapperNodes.length) {
819
- return null;
820
- }
821
-
822
- var highlightModel = [];
823
- const indexCache = new Map();
824
- for (var k = 0; k < wrapperNodes.length; k++) {
825
- var wrapperNode = wrapperNodes[k];
826
-
827
- //get info about highlight itself
828
- var offsetBefore = 0;
829
- var prevNode = wrapperNode.previousSibling;
830
- if (prevNode && isText(prevNode)) {
831
- const beforeWasSplit = wrapperNode.dataset.beforeWasSplit === 'true';
832
- if (beforeWasSplit) {
833
- offsetBefore = prevNode.textContent.length;
834
- }
835
- }
836
- var highlightData = {
837
- groupId: wrapperNode.getAttribute(GROUP_ATTR),
838
- c: getColorByClassName(wrapperNode.className),
839
- offsetBefore,
840
- textLength: wrapperNode.textContent.length,
841
- beforeWasSplit: wrapperNode.dataset.beforeWasSplit,
842
- afterWasSplit: wrapperNode.dataset.afterWasSplit,
843
- path: []
844
- };
845
-
846
- //get info about its position in the tree: path through all parents from rootNode to highlight
847
- let currentNode = wrapperNode;
848
- while (currentNode && currentNode !== rootNode) {
849
- let indexInModel = indexCache.get(currentNode);
850
- if (!indexInModel && indexInModel !== 0) {
851
- //should be more reliable to ignore empty nodes when indexing
852
- const childNodes = Array.from(currentNode.parentNode.childNodes).filter(node => !(isText(node) && !node.textContent.length));
853
- //index among its non-empty siblings
854
- indexInModel = childNodes.indexOf(currentNode);
855
- indexCache.set(currentNode, indexInModel);
856
- }
857
- highlightData.path.unshift(indexInModel);
858
- currentNode = currentNode.parentNode;
859
- }
860
-
861
- //add info about highlight and its position to model
862
- highlightModel.push(highlightData);
863
- }
864
- return {
865
- highlightModel,
866
- wrapperNodes
867
- };
868
- }
869
-
870
- /**
871
- * Bootstrap the process of restoring the highlights from an index
872
- * @param {Object[]|HighlightEntryKeepEmpty[]|null} highlightIndex
873
- */
874
- function highlightFromIndex(highlightIndex) {
875
- var rootNode = getContainer();
876
- if (rootNode) {
877
- if (!keepEmptyNodes) {
878
- rootNode.normalize();
879
- textNodesIndex = 0;
880
- restoreHighlight(rootNode, highlightIndex);
881
- } else {
882
- restoreHighlightKeepEmpty(rootNode, highlightIndex);
883
- }
884
- }
885
- }
886
-
887
- /**
888
- * Traverse the DOM tree to wraps the text nodes according to the highlight index. Recursive.
889
- * @param {Node} rootNode
890
- * @param {Object[]} highlightIndex
891
- */
892
- function restoreHighlight(rootNode, highlightIndex) {
893
- var childNodes = rootNode.childNodes;
894
- var i, currentNode, parent;
895
- var nodeInfos, nodesToSkip, range, initialChildCount;
896
-
897
- for (i = 0; i < childNodes.length; i++) {
898
- currentNode = childNodes[i];
899
-
900
- if (isBlacklisted(currentNode)) {
901
- continue;
902
- } else if (isWrappable(currentNode)) {
903
- parent = currentNode.parentNode;
904
- initialChildCount = parent.childNodes.length;
905
-
906
- nodeInfos = highlightIndex[textNodesIndex];
907
-
908
- if (nodeInfos.highlighted === true) {
909
- if (_.isArray(nodeInfos.inlineRanges)) {
910
- nodeInfos.inlineRanges.reverse();
911
- nodeInfos.inlineRanges.forEach(function (inlineRange) {
912
- range = document.createRange();
913
- range.setStart(currentNode, inlineRange.startOffset || 0);
914
- range.setEnd(currentNode, inlineRange.endOffset || currentNode.textContent.length);
915
- range.surroundContents(getWrapper(inlineRange.groupId, getClassNameByColor(inlineRange.c)));
916
- });
917
-
918
- // fully highlighted text node
919
- } else {
920
- range = document.createRange();
921
- range.selectNodeContents(currentNode);
922
- range.surroundContents(getWrapper(nodeInfos.groupId, getClassNameByColor(nodeInfos.c)));
923
- }
924
- // we do want to loop over the nodes created by the wrapping operation
925
- nodesToSkip = parent.childNodes.length - initialChildCount;
926
- i += nodesToSkip;
927
- }
928
- textNodesIndex++;
929
- } else if (isElement(currentNode)) {
930
- restoreHighlight(currentNode, highlightIndex);
931
- }
932
- }
933
- }
934
-
935
- /**
936
- * For `keepEmptyNodes` option, wraps the text nodes according to highlights data model.
937
- * Traverses and updates DOM tree. Shouldn't throw errors in case of mismatches.
938
- * @param {Node} rootNode
939
- * @param {HighlightEntryKeepEmpty[]|null} highlightModel
940
- */
941
- function restoreHighlightKeepEmpty(rootNode, highlightModel) {
942
- if (!highlightModel) {
943
- return;
944
- }
945
-
946
- var currentModel;
947
- var range;
948
- for (var k = 0; k < highlightModel.length; k++) {
949
- currentModel = highlightModel[k];
950
-
951
- //find node to wrap - go through nodes until we reach level where node to wrap will be
952
- let childNodes;
953
- let indexInModel;
954
- let currentParentNode = rootNode;
955
- let pathNotFound = false;
956
- if (!currentModel.path || !currentModel.path.length) {
957
- continue; //something went wrong
958
- }
959
- for (var m = 0; m < currentModel.path.length; m++) {
960
- //path was counted among non-empty nodes
961
- childNodes = Array.from(currentParentNode.childNodes).filter(node => !(isText(node) && !node.textContent.length));
962
- indexInModel = currentModel.path[m];
963
- currentParentNode = childNodes[indexInModel];
964
- if (!currentParentNode && m < currentModel.path.length - 1) {
965
- //node on last level may not exist yet, no need to fail. See `nodeAtIndex`
966
- pathNotFound = true;
967
- break;
968
- }
969
- }
970
- if (pathNotFound) {
971
- continue; //something went wrong
972
- }
973
-
974
- //add single highlight
975
- var nodeAtIndex = null;
976
- if (!currentModel.offsetBefore) {
977
- //wrap starts on this node
978
- nodeAtIndex = childNodes[indexInModel];
979
- if (!nodeAtIndex || !isText(nodeAtIndex) || isBlacklisted(nodeAtIndex)) {
980
- continue; //something went wrong
981
- }
982
- } else {
983
- //split previousSibling to create a node for wrapping
984
- var nodeBefore = childNodes[indexInModel - 1];
985
- if (!nodeBefore || !isText(nodeBefore) ||
986
- nodeBefore.textContent.length <= currentModel.offsetBefore || isBlacklisted(nodeBefore)) {
987
- continue; //something went wrong
988
- }
989
- nodeAtIndex = nodeBefore.splitText(currentModel.offsetBefore);
990
- }
991
- //cut off its end
992
- if (nodeAtIndex.textContent.length > currentModel.textLength) {
993
- nodeAtIndex.splitText(currentModel.textLength);
994
- }
995
-
996
- //wrap
997
- const wrapperNode = getWrapper(currentModel.groupId, getClassNameByColor(currentModel.c));
998
- addSplitData(wrapperNode, currentModel.beforeWasSplit, currentModel.afterWasSplit);
999
- range = document.createRange();
1000
- range.selectNodeContents(nodeAtIndex);
1001
- rangeSurroundContentsNoEmptyNodes(range, wrapperNode);
1002
- }
1003
- }
1004
-
1005
- /**
1006
- * Set highlighter color
1007
- * @param {string} color Active highlighter color
1008
- */
1009
- const setActiveColor = color => {
1010
- if (options.colors[color]) {
1011
- className = options.colors[color];
1012
- }
1013
- };
1014
-
1015
- /**
1016
- * Helpers
1017
- */
1018
-
1019
- /**
1020
- * Return the object key contains the given value
1021
- * @param {Object} object
1022
- * @param {any} value
1023
- * @return {string|undefined}
1024
- */
1025
- const getKeyByValue = (object, value) => Object.keys(object).find(key => object[key] === value);
1026
-
1027
- /**
1028
- * Returns color identifier for the given class name
1029
- * @param {string} highlighterClassName Class name of highlighter classes
1030
- * @returns {string|number} Color identifier
1031
- */
1032
- const getColorByClassName = highlighterClassName => {
1033
- if (options.colors) {
1034
- return getKeyByValue(options.colors, highlighterClassName);
1035
- }
1036
-
1037
- return className;
1038
- };
1039
-
1040
- /**
1041
- * Returns class name for the given color identifier
1042
- * @param {string|number} color Color identifier
1043
- * @returns {string} Class name
1044
- */
1045
- const getClassNameByColor = color => {
1046
- if (options.colors && options.colors[color]) {
1047
- return options.colors[color];
1048
- }
1049
-
1050
- return className;
1051
- };
1052
-
1053
- /**
1054
- * Check if the given node is a wrapper
1055
- * @param {Node|Element} node
1056
- * @returns {boolean}
1057
- */
1058
- function isWrappingNode(node) {
1059
- return isElement(node) && node.tagName.toLowerCase() === 'span' && highlightingClasses.includes(node.className);
1060
- }
1061
-
1062
- /**
1063
- * Check if the given node can be wrapped
1064
- * @param {Node} node
1065
- * @returns {boolean}
1066
- */
1067
- function isWrappable(node) {
1068
- return isText(node) && !isBlacklisted(node);
1069
- }
1070
-
1071
- /**
1072
- * Check if the given node is, or is within, a blacklisted container.
1073
- * With `keepEmptyNodes` option, node inside blacklisted container can be whitelisted too.
1074
- * Priority of blacklist or whitelist is decided by which selector is closest to the node.
1075
- * If no match found, node is considered whitelisted.
1076
- * @param {Node} node
1077
- * @returns {boolean}
1078
- */
1079
- function isBlacklisted(node) {
1080
- const closest = $(node).closest(containersBlackAndWhiteListSelector);
1081
- if (!closest.length) {
1082
- return false;
1083
- } else if (!containersWhiteListSelector) {
1084
- return true;
1085
- } else {
1086
- return !closest.get(0).matches(containersWhiteListSelector);
1087
- }
1088
- }
1089
-
1090
- /**
1091
- * Wraps text node to the highlighter wrapper element
1092
- * @param {Node} textNode Text node to wrap
1093
- * @param {string} className Wrapper class name
1094
- * @param {number} groupId Group id
1095
- */
1096
- function wrapNode(textNode, className, groupId) {
1097
- const element = getWrapper(groupId, className);
1098
-
1099
- element.appendChild(textNode);
1100
- return element;
1101
- }
1102
-
1103
- /**
1104
- * Create a wrapping node
1105
- * @param {number} groupId
1106
- * @returns {Element}
1107
- */
1108
- function getWrapper(groupId, wrapperClass) {
1109
- const wrapper = document.createElement('span');
1110
-
1111
- wrapper.className = wrapperClass || className;
1112
- wrapper.setAttribute(GROUP_ATTR, `${groupId}`);
1113
- return wrapper;
1114
- }
1115
-
1116
- /**
1117
- * Returns the first unused group Id
1118
- * @returns {number}
1119
- */
1120
- function getAvailableGroupId() {
1121
- var id = currentGroupId || 1;
1122
- while ($(getContainer()).find('[' + GROUP_ATTR + '=' + id + ']').length !== 0) {
1123
- id++;
1124
- }
1125
- return id;
1126
- }
1127
-
1128
- /**
1129
- * Check if the given node is an element
1130
- * @param {Node} node
1131
- * @returns {boolean}
1132
- */
1133
- function isElement(node) {
1134
- return node && typeof node === 'object' && node.nodeType === window.Node.ELEMENT_NODE;
1135
- }
1136
-
1137
- /**
1138
- * Check if the given node is of type text
1139
- * @param {Node} node
1140
- * @returns {boolean}
1141
- */
1142
- function isText(node) {
1143
- return node && typeof node === 'object' && node.nodeType === window.Node.TEXT_NODE;
1144
- }
1145
-
1146
- /**
1147
- * a "Hot Node" is either wrappable text node or a wrapper
1148
- * @param {Node} node
1149
- * @returns {boolean}
1150
- */
1151
- function isHotNode(node) {
1152
- return isWrappingNode(node) || isWrappable(node);
1153
- }
1154
-
1155
- /**
1156
- * Public API of the highlighter helper
1157
- */
1158
- return {
1159
- highlightRanges: highlightRanges,
1160
- highlightFromIndex: highlightFromIndex,
1161
- getHighlightIndex: getHighlightIndex,
1162
- clearHighlights: clearHighlights,
1163
- clearSingleHighlight: clearSingleHighlight,
1164
- setActiveColor
1165
- };
1166
- }
1
+ /**
2
+ * This program is free software; you can redistribute it and/or
3
+ * modify it under the terms of the GNU General Public License
4
+ * as published by the Free Software Foundation; under version 2
5
+ * of the License (non-upgradable).
6
+ *
7
+ * This program is distributed in the hope that it will be useful,
8
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ * GNU General Public License for more details.
11
+ *
12
+ * You should have received a copy of the GNU General Public License
13
+ * along with this program; if not, write to the Free Software
14
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15
+ *
16
+ * Copyright (c) 2016-2021 (original work) Open Assessment Technologies SA;
17
+ */
18
+ /**
19
+ * Highlighter helper: wraps every text node found within a Range object.
20
+ *
21
+ * @author Christophe Noël <christophe@taotesting.com>
22
+ */
23
+ import _ from 'lodash';
24
+ import $ from 'jquery';
25
+
26
+ /**
27
+ * Data attribute used to logically group the wrapping nodes into a single selection
28
+ * @type {string}
29
+ */
30
+ var GROUP_ATTR = 'data-hl-group';
31
+
32
+ /**
33
+ * Children of those nodes types cannot be highlighted
34
+ * @type {string[]}
35
+ */
36
+ var defaultBlackList = ['textarea', 'math', 'script', '.select2-container'];
37
+
38
+ /**
39
+ * @param {Object} options
40
+ * @param {String} options.className - name of the class that will be used by the wrappers tags to highlight text
41
+ * @param {String} options.containerSelector - allows to select the root Node in which highlighting is allowed
42
+ * @param {Array<String>} [options.containersBlackList] - additional blacklist selectors to be added to module instance's blacklist
43
+ * @param {Array<String>} [options.containersWhiteList] - whitelist selectors; supported only in `keepEmptyNodes` mode.
44
+ * Priority of blacklist or whitelist is decided by which selector is closest to the node. If no match found, node is considered whitelisted.
45
+ * @param {Boolean} [options.clearOnClick] - clear single highlight node on click
46
+ * @param {Object} [options.colors] - keys is keeping as the "c" value of storing/restore the highlighters for indexing, values are wrappers class names
47
+ * @param {Boolean} [options.keepEmptyNodes] - retain original dom structure as far as possible and do not remove empty nodes if they were not created by highlighter
48
+ * @returns {Object} - the highlighter instance
49
+ */
50
+ export default function (options) {
51
+ var className = options.className;
52
+ var containerSelector = options.containerSelector;
53
+ var keepEmptyNodes = options.keepEmptyNodes;
54
+
55
+ let highlightingClasses = [className];
56
+
57
+ // Multi-color mode
58
+ if (options.colors) {
59
+ highlightingClasses = Object.values(options.colors);
60
+ }
61
+
62
+ /**
63
+ * list of node selectors which should NOT receive any highlighting from this instance
64
+ * an optional passed-in blacklist is merged with local defaults
65
+ * @type {Array}
66
+ */
67
+ var containersBlackList = _.union(defaultBlackList, options.containersBlackList);
68
+ var containersBlackListSelector = containersBlackList.join(', ');
69
+ var containersWhiteListSelector = null;
70
+ var containersBlackAndWhiteListSelector = containersBlackListSelector;
71
+ if (options.keepEmptyNodes && options.containersWhiteList) {
72
+ containersWhiteListSelector = options.containersWhiteList.join(', ');
73
+ containersBlackAndWhiteListSelector = _.union(containersBlackList, options.containersWhiteList).join(', ');
74
+ }
75
+
76
+ /**
77
+ * used in recursive loops to decide if we should wrap or not the current node
78
+ * @type {boolean}
79
+ */
80
+ var isWrapping = false;
81
+
82
+ /**
83
+ * performance improvement to break out of a potentially big recursive loop once the wrapping has ended
84
+ * @type {boolean}
85
+ */
86
+ var hasWrapped = false;
87
+
88
+ /**
89
+ * used in recursive loops to assign a group Id to the current wrapped node
90
+ * @type {number}
91
+ */
92
+ var currentGroupId;
93
+
94
+ /**
95
+ * used in recursive loops to build the index of text nodes
96
+ * @type {number}
97
+ */
98
+ var textNodesIndex;
99
+
100
+ /**
101
+ * Returns the node in which highlighting is allowed
102
+ * @returns {Element}
103
+ */
104
+ function getContainer() {
105
+ return $(containerSelector).get(0);
106
+ }
107
+
108
+ /**
109
+ * Returns all highlighted nodes, excluding any inside blacklisted elements
110
+ * @returns {JQuery<HTMLElement>}
111
+ */
112
+ function getHighlightedNodes() {
113
+ return $(containerSelector)
114
+ .find(`.${highlightingClasses.join(',.')}`)
115
+ .filter((i, node) => !isBlacklisted(node));
116
+ }
117
+
118
+ /**
119
+ * Attach data to wrapper node.
120
+ * Use it when deleting this highlight to know if highlight content should be merged with neighbour text nodes or not.
121
+ * Use it when building/restoring index to know if restored highlight content should be split off neighbour text node or not.
122
+ * Needed to keep markup the same as it was before highlighting.
123
+ * @param {HTMLElement} node
124
+ * @param {Boolean} beforeWasSplit
125
+ * @param {Boolean} afterWasSplit
126
+ */
127
+ function addSplitData(node, beforeWasSplit, afterWasSplit) {
128
+ node.dataset.beforeWasSplit = beforeWasSplit;
129
+ node.dataset.afterWasSplit = afterWasSplit;
130
+ }
131
+
132
+ /**
133
+ * Highlight all text nodes within each given range
134
+ * @param {Range[]} ranges - array of ranges to highlight, may be given by the helper selector.getAllRanges()
135
+ */
136
+ function highlightRanges(ranges) {
137
+ ranges.forEach(function (range) {
138
+ var rangeInfos;
139
+
140
+ if (isRangeValid(range)) {
141
+ currentGroupId = getAvailableGroupId();
142
+
143
+ // easy peasy: highlighting a plain text without any DOM nodes
144
+ // NOTE: The condition checks the whole node content and not a selected content in a given range, that allows to wrap whitespace
145
+ if (
146
+ isWrappable(range.commonAncestorContainer) &&
147
+ !isWrappingNode(range.commonAncestorContainer.parentNode)
148
+ ) {
149
+ const wrapperNode = getWrapper(currentGroupId);
150
+ if (!keepEmptyNodes) {
151
+ range.surroundContents(wrapperNode);
152
+ } else {
153
+ addSplitData(wrapperNode, range.startOffset > 0, range.endOffset < range.commonAncestorContainer.length);
154
+ rangeSurroundContentsNoEmptyNodes(range, wrapperNode);
155
+ }
156
+ } else if (
157
+ isWrappable(range.commonAncestorContainer) &&
158
+ isWrappingNode(range.commonAncestorContainer.parentNode) &&
159
+ range.commonAncestorContainer.parentNode !== className
160
+ ) {
161
+ highlightContainerNodes(range.commonAncestorContainer, className, range, currentGroupId);
162
+
163
+ // now the fun stuff: highlighting a mix of text and DOM nodes
164
+ } else {
165
+ rangeInfos = {
166
+ startNode: isElement(range.startContainer)
167
+ ? range.startContainer.childNodes[range.startOffset]
168
+ : range.startContainer,
169
+ startNodeContainer: range.startContainer,
170
+ startOffset: range.startOffset,
171
+
172
+ endNode: isElement(range.endContainer) && range.endOffset > 0
173
+ ? range.endContainer.childNodes[range.endOffset - 1]
174
+ : range.endContainer,
175
+ endNodeContainer: range.endContainer,
176
+ endOffset: range.endOffset,
177
+ commonRange: range
178
+ };
179
+
180
+ isWrapping = false;
181
+ hasWrapped = false;
182
+ wrapTextNodesInRange(range.commonAncestorContainer, rangeInfos);
183
+ }
184
+ }
185
+
186
+ if (!keepEmptyNodes) {
187
+ // clean up the markup after wrapping...
188
+ range.commonAncestorContainer.normalize();
189
+ }
190
+
191
+ currentGroupId = 0;
192
+ isWrapping = false;
193
+ reindexGroups(getContainer());
194
+ mergeAdjacentWrappingNodes(getContainer());
195
+ unWrapEmptyHighlights();
196
+ });
197
+
198
+ if (options.clearOnClick) {
199
+ $(containerSelector + ' .' + className)
200
+ .off('click')
201
+ .on('click', clearSingleHighlight);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Check if a range is valid
207
+ * @param {Range} range
208
+ * @returns {boolean}
209
+ */
210
+ function isRangeValid(range) {
211
+ var rangeInContainer;
212
+ try {
213
+ rangeInContainer =
214
+ $.contains(getContainer(), range.commonAncestorContainer) ||
215
+ getContainer().isSameNode(range.commonAncestorContainer);
216
+
217
+ return rangeInContainer && !range.collapsed;
218
+ } catch (e) {
219
+ return false;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Core wrapping function. Traverse the DOM tree and highlight (= wraps) all text nodes within the given range.
225
+ * Recursive.
226
+ *
227
+ * @param {Node} rootNode - top of the node hierarchy in which text nodes will be searched
228
+ * @param {Object} rangeInfos
229
+ * @param {Node} rangeInfos.startNode - node on which the selection starts
230
+ * @param {Node} rangeInfos.startNodeContainer - container of the startNode, or the start node itself in case of text nodes
231
+ * @param {number} rangeInfos.startOffset - same as range.startOffset, but not read-only to allow override
232
+ * @param {Node} rangeInfos.endNode - node on which the selection ends
233
+ * @param {Node} rangeInfos.endNodeContainer - container of the endNode, or the end node itself in case of text nodes
234
+ * @param {number} rangeInfos.endOffset - same as range.endOffset, but not read-only to allow override
235
+ */
236
+ function wrapTextNodesInRange(rootNode, rangeInfos) {
237
+ var childNodes = rootNode.childNodes;
238
+ var currentNode, i;
239
+ var splitDatas = [];
240
+
241
+ for (i = 0; i < childNodes.length; i++) {
242
+ if (hasWrapped) {
243
+ break;
244
+ }
245
+ currentNode = childNodes[i];
246
+
247
+ if (isBlacklisted(currentNode)) {
248
+ if (isElement(currentNode)) {
249
+ //go deeper in case a descendant of the current blacklisted is whitelisted
250
+ wrapTextNodesInRange(currentNode, rangeInfos);
251
+ }
252
+ } else {
253
+ const isCurrentNodeTextInsideOfAnotherHighlightingWrapper =
254
+ isText(currentNode) &&
255
+ isWrappingNode(currentNode.parentNode) &&
256
+ currentNode.parentNode.className !== className;
257
+
258
+ if (isCurrentNodeTextInsideOfAnotherHighlightingWrapper) {
259
+ const internalRange = new Range();
260
+ internalRange.selectNodeContents(currentNode);
261
+
262
+ if (rangeInfos.startNode === currentNode) {
263
+ internalRange.setStart(currentNode, rangeInfos.startOffset);
264
+ }
265
+
266
+ if (rangeInfos.endNode === currentNode) {
267
+ internalRange.setEnd(currentNode, rangeInfos.endOffset);
268
+ }
269
+
270
+ const isNodeInRange = rangeInfos.commonRange.isPointInRange(currentNode, internalRange.endOffset);
271
+
272
+ // Apply new highlighting color only for selected nodes
273
+ if (isNodeInRange) {
274
+ isWrapping = true;
275
+ highlightContainerNodes(currentNode, className, internalRange, currentGroupId);
276
+ }
277
+ } else {
278
+ // split current node in case the wrapping start/ends on a partially selected text node
279
+ if (currentNode.isSameNode(rangeInfos.startNode)) {
280
+ if (isText(rangeInfos.startNodeContainer) && rangeInfos.startOffset !== 0) {
281
+ // we defer the wrapping to the next iteration of the loop
282
+ //end of node should be highlighted
283
+ rangeInfos.startNode = currentNode.splitText(rangeInfos.startOffset);
284
+ rangeInfos.startOffset = 0;
285
+ splitDatas.push({ node: rangeInfos.startNode, beforeWasSplit: true, afterWasSplit: false });
286
+ } else {
287
+ //whole node should be highlighted
288
+ isWrapping = true;
289
+ splitDatas.push({ node: currentNode, beforeWasSplit: false, afterWasSplit: false });
290
+ }
291
+ }
292
+
293
+ if (currentNode.isSameNode(rangeInfos.endNode) && isText(rangeInfos.endNodeContainer)) {
294
+ if (rangeInfos.endOffset !== 0) {
295
+ if (rangeInfos.endOffset < currentNode.textContent.length) {
296
+ //start of node should be highlighted
297
+ currentNode.splitText(rangeInfos.endOffset);
298
+ splitDatas.push({ node: currentNode, beforeWasSplit: false, afterWasSplit: true });
299
+ } else {
300
+ //whole node should be highlighted
301
+ splitDatas.push({ node: currentNode, beforeWasSplit: false, afterWasSplit: false });
302
+ }
303
+ } else {
304
+ isWrapping = false;
305
+ }
306
+ }
307
+
308
+ // wrap the current node...
309
+ if (isText(currentNode)) {
310
+ if (!keepEmptyNodes) {
311
+ wrapTextNode(currentNode, currentGroupId);
312
+ } else if (willHighlightNotBeEmptyAfterMerge(currentNode)) {
313
+ const wrapperNode = wrapTextNode(currentNode, currentGroupId);
314
+ if (wrapperNode) {
315
+ const splitData = splitDatas.find(d => d.node === currentNode);
316
+ addSplitData(wrapperNode,
317
+ splitData ? splitData.beforeWasSplit : false,
318
+ splitData ? splitData.afterWasSplit : false);
319
+ }
320
+ }
321
+
322
+ // ... or continue deeper in the node tree
323
+ } else if (isElement(currentNode)) {
324
+ //some selections end at the very start of the next node, we should end wrapping when we reach such node
325
+ if (!currentNode.isSameNode(rangeInfos.endNode) || rangeInfos.endOffset > 0) {
326
+ wrapTextNodesInRange(currentNode, rangeInfos);
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ // end wrapping ?
333
+ if (currentNode.isSameNode(rangeInfos.endNode)) {
334
+ isWrapping = false;
335
+ hasWrapped = true;
336
+ break;
337
+ }
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Restructure content of the highlighted wrapper according to the selectedRange
343
+ * @param {Node} textNode
344
+ * @param {string} activeClass
345
+ * @param {Range} selectedRange
346
+ * @param {number} currentGroupId
347
+ */
348
+ function highlightContainerNodes(textNode, activeClass, selectedRange, currentGroupId) {
349
+ const container = textNode.parentNode;
350
+ const range = new Range();
351
+ range.selectNodeContents(textNode);
352
+
353
+ const isSelectionCoversNodeStart = range.compareBoundaryPoints(Range.START_TO_START, selectedRange) === 0;
354
+ const isSelectionCoversNodeEnd = range.compareBoundaryPoints(Range.END_TO_END, selectedRange) === 0;
355
+
356
+ /*
357
+ There are 4 possible cases selected area is intersected with already highlighted element.
358
+ In examples below the border is represents the selection, "yellow" is class name of already highlighted
359
+ container, "red" is class name of currently active highlighter
360
+ **********************************************************************************************************
361
+ 1. The container content is completely selected, so that we only have to change the highlighter class name
362
+
363
+ Input:
364
+ __________________________________________________
365
+ | |
366
+ |<span class="yellow"> Lorem ipsum dolor sit</span>|
367
+ |__________________________________________________|
368
+
369
+ Output:
370
+ <span class="red"> Lorem ipsum dolor sit</span>
371
+
372
+ **********************************************************************************************************
373
+ 2. The container content is partially selected from the begging.
374
+
375
+ Input:
376
+ ______________________________
377
+ | |
378
+ |<span class="yellow"> Lorem ip|sum dolor sit</span>
379
+ |______________________________|
380
+
381
+ Output:
382
+ <span class="red"> Lorem ip</span><span class="yellow">sum dolor sit</span>
383
+
384
+ **********************************************************************************************************
385
+ 3. The container content is partially selected at the end.
386
+
387
+ Input:
388
+ ____________________
389
+ | |
390
+ <span class="yellow"> Lorem ip|sum dolor sit</span>|
391
+ |____________________|
392
+
393
+ Output:
394
+ <span class="yellow"> Lorem ip</span><span class="red">sum dolor sit</span>
395
+
396
+ **********************************************************************************************************
397
+ 4. The container content is partially selected in the middle.
398
+
399
+ Input:
400
+ ___________
401
+ | |
402
+ <span class="yellow"> Lorem |ipsum dolor| sit</span>
403
+ |___________|
404
+
405
+ Output:
406
+ <span class="yellow"> Lorem </span><span class="red">ipsum dolor</span><span class="yellow"> sit</span>
407
+ */
408
+ if (isSelectionCoversNodeStart && isSelectionCoversNodeEnd) {
409
+ textNode.parentNode.className = activeClass;
410
+ } else if (isSelectionCoversNodeStart) {
411
+ textNode.splitText(selectedRange.endOffset);
412
+ wrapContainerChildNodes(container, 0, activeClass, currentGroupId);
413
+ } else if (isSelectionCoversNodeEnd) {
414
+ textNode.splitText(selectedRange.startOffset);
415
+ wrapContainerChildNodes(container, 1, activeClass, currentGroupId);
416
+ } else {
417
+ textNode.splitText(selectedRange.startOffset).splitText(selectedRange.endOffset);
418
+ wrapContainerChildNodes(container, 1, activeClass, currentGroupId);
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Wraps all containers text nodes with highlighter element.
424
+ * The child node with index given by indexToWrapNode parameter will be wrap with class given by activeClass parameter
425
+ * @param {Element} container
426
+ * @param {number} indexToWrapNode
427
+ * @param {string} activeClass
428
+ * @param {number} currentGroupId
429
+ */
430
+ function wrapContainerChildNodes(container, indexToWrapNode, activeClass, currentGroupId) {
431
+ const containerClass = container.className;
432
+ const fragment = new DocumentFragment();
433
+ const childNodesLength = container.childNodes.length;
434
+
435
+ container.childNodes.forEach((node, index) => {
436
+ var wrapperNode;
437
+ if (index === indexToWrapNode) {
438
+ wrapperNode = wrapNode(node.cloneNode(), activeClass, currentGroupId);
439
+ } else {
440
+ wrapperNode = wrapNode(node.cloneNode(), containerClass, currentGroupId);
441
+ }
442
+ fragment.appendChild(wrapperNode);
443
+
444
+ if (keepEmptyNodes) {
445
+ addSplitData(wrapperNode,
446
+ index === 0 ? container.dataset.beforeWasSplit : true,
447
+ index === childNodesLength - 1 ? container.dataset.afterWasSplit : true);
448
+ }
449
+ });
450
+
451
+ container.replaceWith(fragment);
452
+ }
453
+
454
+ /**
455
+ * wraps a text node into the highlight span
456
+ * @param {Node} node - the node to wrap
457
+ * @param {number} groupId - the highlight group
458
+ * @returns {Node|null} wrapper node, if it was created
459
+ */
460
+ function wrapTextNode(node, groupId) {
461
+ if (isWrapping && !isWrappingNode(node.parentNode) && isWrappable(node)) {
462
+ $(node).wrap(getWrapper(groupId));
463
+ return node.parentNode;
464
+ }
465
+ return null;
466
+ }
467
+
468
+ /**
469
+ * We need to re-index the groups after a user highlight: either to merge groups or to resolve inconsistencies
470
+ * Recursive.
471
+ *
472
+ * @param {Node} rootNode
473
+ */
474
+ function reindexGroups(rootNode) {
475
+ if (!rootNode) {
476
+ return;
477
+ }
478
+
479
+ var childNodes = rootNode.childNodes;
480
+ var i, currentNode, parent;
481
+
482
+ for (i = 0; i < childNodes.length; i++) {
483
+ currentNode = childNodes[i];
484
+
485
+ if (isWrappable(currentNode)) {
486
+ parent = currentNode.parentNode;
487
+ if (isWrappingNode(parent)) {
488
+ if (isWrapping === false) {
489
+ currentGroupId++;
490
+ }
491
+ isWrapping = true;
492
+ parent.setAttribute(GROUP_ATTR, currentGroupId); // set the new group Id
493
+ } else {
494
+ isWrapping = false;
495
+ }
496
+ } else if (isElement(currentNode)) {
497
+ reindexGroups(currentNode);
498
+ }
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Some highlights may result in having adjacent wrapping nodes. We remove them here to get a cleaner markup.
504
+ *
505
+ * @param {Node} rootNode
506
+ */
507
+ function mergeAdjacentWrappingNodes(rootNode) {
508
+ if (!rootNode) {
509
+ return;
510
+ }
511
+
512
+ var childNodes = rootNode.childNodes;
513
+ var i, currentNode;
514
+
515
+ for (i = 0; i < childNodes.length; i++) {
516
+ currentNode = childNodes[i];
517
+
518
+ if (isWrappingNode(currentNode)) {
519
+ if (keepEmptyNodes) {
520
+ currentNode.normalize();
521
+ }
522
+ while (
523
+ isWrappingNode(currentNode.nextSibling) &&
524
+ currentNode.className === currentNode.nextSibling.className
525
+ ) {
526
+ if (keepEmptyNodes) {
527
+ currentNode.nextSibling.normalize();
528
+ }
529
+ currentNode.firstChild.textContent += currentNode.nextSibling.firstChild.textContent;
530
+ if (keepEmptyNodes) {
531
+ addSplitData(currentNode, currentNode.dataset.beforeWasSplit, currentNode.nextSibling.dataset.afterWasSplit);
532
+ }
533
+ currentNode.parentNode.removeChild(currentNode.nextSibling);
534
+ }
535
+ } else if (isElement(currentNode)) {
536
+ mergeAdjacentWrappingNodes(currentNode);
537
+ }
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Unwraps highlighted nodes with a line break or with an empty content
543
+ */
544
+ function unWrapEmptyHighlights() {
545
+ getHighlightedNodes().each((index, node) => {
546
+ const nodeContent = node.textContent;
547
+
548
+ if (nodeContent.trim().length === 0) {
549
+ if (nodeContent.length === 0 || /\r|\n/.exec(nodeContent)) {
550
+ clearSingleHighlight({ target: node });
551
+ }
552
+ }
553
+ });
554
+ }
555
+
556
+ /**
557
+ * Check condition to avoid the work of `unwrapEmptyHighlights` ahead of time, before `mergeAdjacentNodes` runs,
558
+ * because in `keepEmptyNodes` case we do not want to add nodes to dom unless necessary.
559
+ * Also be more strict and don't allow to select nodes with spaces only, because they may appear in unexpected places in markup
560
+ * (here it's not exactly same as `unwrapEmptyHighlights`).
561
+ * @param {Node} node - node which will be wrapped (highlighted)
562
+ * @returns {Boolean}
563
+ */
564
+ function willHighlightNotBeEmptyAfterMerge(node) {
565
+ if (!node.textContent.length) {
566
+ return false;
567
+ }
568
+ if (node.textContent.trim().length) {
569
+ return true;
570
+ }
571
+ const prevNode = node.previousSibling;
572
+ const canWrapperBeMergedWithPreviousSibling = prevNode && isWrappingNode(prevNode) && prevNode.className === className;
573
+ if (canWrapperBeMergedWithPreviousSibling) {
574
+ return true;
575
+ }
576
+ const nextNode = node.nextSibling;
577
+ const canWrapperBeMergedWithNextSibling = nextNode && isWrappingNode(nextNode) && nextNode.className === className;
578
+ if (canWrapperBeMergedWithNextSibling) {
579
+ return true;
580
+ }
581
+ return false;
582
+ }
583
+
584
+ /**
585
+ * `range.surroundContents` can create empty text nodes,
586
+ * which will cause trouble in `mergeAdjacentNodes` later (in `keepEmptyNodes` case).
587
+ * This method surrounds range, then removes those nodes
588
+ * @param {Range} range
589
+ * @param {Node} wrapperNode
590
+ */
591
+ function rangeSurroundContentsNoEmptyNodes(range, wrapperNode) {
592
+ const containerPreviousSibling = range.commonAncestorContainer.previousSibling;
593
+ const containerNextSibling = range.commonAncestorContainer.nextSibling;
594
+
595
+ range.surroundContents(wrapperNode);
596
+
597
+ removeEmptyTextNodeIfDifferent(wrapperNode.previousSibling, containerPreviousSibling);
598
+ removeEmptyTextNodeIfDifferent(wrapperNode.nextSibling, containerNextSibling);
599
+ }
600
+
601
+ /**
602
+ * Remove `node`, if it's an empty text node and is *not* the same node as `nodeToCompare`
603
+ * @param {Node} node
604
+ * @param {Node} nodeToCompare
605
+ */
606
+ function removeEmptyTextNodeIfDifferent(node, nodeToCompare) {
607
+ if (node && node !== nodeToCompare && isText(node) && node.textContent.length === 0) {
608
+ node.remove();
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Remove all wrapping nodes from markup
614
+ */
615
+ function clearHighlights() {
616
+ getHighlightedNodes().each(function (i, elem) {
617
+ if (!keepEmptyNodes) {
618
+ var $wrapped = $(this);
619
+ $wrapped.replaceWith($wrapped.text());
620
+ } else {
621
+ clearSingleHighlight({ target: elem });
622
+ }
623
+ });
624
+ }
625
+
626
+ /**
627
+ * Remove unwrap dom node
628
+ */
629
+ function clearSingleHighlight(e) {
630
+ if (!keepEmptyNodes) {
631
+ const $wrapped = $(e.target);
632
+ const text = $wrapped.text();
633
+
634
+ // NOTE: JQuery replaceWith is not working with empty string https://bugs.jquery.com/ticket/13401
635
+ if (text === '') {
636
+ $wrapped.remove();
637
+ } else {
638
+ $wrapped.replaceWith(text);
639
+ }
640
+ } else {
641
+ const nodeToRemove = e.target;
642
+ const nodeToRemoveText = nodeToRemove.textContent;
643
+ const beforeWasSplit = nodeToRemove.dataset.beforeWasSplit === 'true';
644
+ const afterWasSplit = nodeToRemove.dataset.afterWasSplit === 'true';
645
+ const prevNode = nodeToRemove.previousSibling;
646
+ const nextNode = nodeToRemove.nextSibling;
647
+
648
+ if (beforeWasSplit && prevNode && isText(prevNode) && prevNode.textContent) {
649
+ //append text to previous sibling
650
+ prevNode.textContent += nodeToRemoveText;
651
+ nodeToRemove.remove();
652
+
653
+ if (afterWasSplit && prevNode.nextSibling && isText(prevNode.nextSibling) && prevNode.nextSibling.textContent) {
654
+ //merge it with next sibling
655
+ prevNode.textContent += prevNode.nextSibling.textContent;
656
+ prevNode.nextSibling.remove();
657
+ }
658
+ }
659
+ else if (afterWasSplit && nextNode && isText(nextNode) && nextNode.textContent) {
660
+ //append text to next sibling
661
+ nextNode.textContent = nodeToRemoveText + nextNode.textContent;
662
+ nodeToRemove.remove();
663
+ } else if (nodeToRemoveText) {
664
+ //keep text in a separate text node
665
+ nodeToRemove.replaceWith(document.createTextNode(nodeToRemoveText));
666
+ } else {
667
+ //text is empty, just remove it
668
+ nodeToRemove.remove();
669
+ }
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Index-related functions:
675
+ * ========================
676
+ * To allow saving and restoring highlights on an equivalent, but different, DOM tree (for example if the markup is deleted and re-created)
677
+ * we build an index containing the status of each text node:
678
+ * - not highlighted
679
+ * - fully highlighted
680
+ * - partially highlighted (= with inline ranges)
681
+ *
682
+ * This index will be used to restore a selection on the new DOM tree
683
+ */
684
+
685
+ /**
686
+ * Bootstrap the process of building the highlight index
687
+ * @returns {Object[]|BuildModelResultKeepEmpty|null}
688
+ */
689
+ function getHighlightIndex() {
690
+ var rootNode = getContainer();
691
+ if (!keepEmptyNodes) {
692
+ var highlightIndex = [];
693
+ if (rootNode) {
694
+ rootNode.normalize();
695
+ textNodesIndex = 0;
696
+ buildHighlightIndex(rootNode, highlightIndex);
697
+ }
698
+ return highlightIndex;
699
+ } else {
700
+ if (rootNode) {
701
+ return buildHighlightModelKeepEmpty(rootNode);
702
+ } else {
703
+ return null;
704
+ }
705
+ }
706
+ }
707
+
708
+ /**
709
+ * Traverse the DOM tree to create the text Nodes index. Recursive.
710
+ * @param {Node} rootNode
711
+ * @param {Object[]} highlightIndex
712
+ */
713
+ function buildHighlightIndex(rootNode, highlightIndex) {
714
+ var childNodes = rootNode.childNodes;
715
+ var i, currentNode;
716
+ var nodeInfos, inlineRange, inlineOffset, nodesToSkip;
717
+
718
+ for (i = 0; i < childNodes.length; i++) {
719
+ currentNode = childNodes[i];
720
+
721
+ // Skip blacklisted nodes
722
+ if (isBlacklisted(currentNode)) {
723
+ continue;
724
+ }
725
+ // A simple node not highlighted and isolated (= not followed by an wrapped text)
726
+ else if (isWrappable(currentNode) && !isWrappingNode(currentNode.nextSibling)) {
727
+ highlightIndex[textNodesIndex] = { highlighted: false };
728
+ textNodesIndex++;
729
+
730
+ // an isolated node (= not followed by a highlight table text) with its whole content highlighted
731
+ } else if (
732
+ isWrappingNode(currentNode) &&
733
+ !isText(currentNode.nextSibling) &&
734
+ (!isWrappingNode(currentNode.nextSibling) ||
735
+ currentNode.className === currentNode.nextSibling.className)
736
+ ) {
737
+ highlightIndex[textNodesIndex] = {
738
+ highlighted: true,
739
+ groupId: currentNode.getAttribute(GROUP_ATTR),
740
+ c: getColorByClassName(currentNode.className)
741
+ };
742
+ textNodesIndex++;
743
+
744
+ // less straightforward: a succession of (at least) 1 wrapping node with 1 wrappable text node, in either order, and possibly more
745
+ // the trick is to create a unique text node on which we will be able to re-apply multiple partial highlights
746
+ // for this, we use 'inlineRanges'
747
+ } else if (isHotNode(currentNode)) {
748
+ nodeInfos = {
749
+ highlighted: true,
750
+ inlineRanges: []
751
+ };
752
+
753
+ nodesToSkip = -1;
754
+ inlineOffset = 0;
755
+
756
+ while (currentNode) {
757
+ if (isWrappingNode(currentNode)) {
758
+ inlineRange = {
759
+ groupId: currentNode.getAttribute(GROUP_ATTR),
760
+ c: getColorByClassName(currentNode.className)
761
+ };
762
+
763
+ if (isText(currentNode.previousSibling) || isWrappingNode(currentNode.previousSibling)) {
764
+ inlineRange.startOffset = inlineOffset;
765
+ }
766
+ if (isText(currentNode.nextSibling) || isWrappingNode(currentNode.nextSibling)) {
767
+ inlineRange.endOffset = inlineOffset + currentNode.textContent.length;
768
+ }
769
+ nodeInfos.inlineRanges.push(inlineRange);
770
+ }
771
+
772
+ inlineOffset += currentNode.textContent.length;
773
+ currentNode =
774
+ isHotNode(currentNode.nextSibling) || isText(currentNode.nextSibling)
775
+ ? currentNode.nextSibling
776
+ : null;
777
+ nodesToSkip++;
778
+ }
779
+ i += nodesToSkip; // we increase the loop counter to avoid looping over the nodes that we just analyzed
780
+
781
+ highlightIndex[textNodesIndex] = nodeInfos;
782
+ textNodesIndex++;
783
+
784
+ // go deeper in the node tree...
785
+ } else if (isElement(currentNode)) {
786
+ buildHighlightIndex(currentNode, highlightIndex);
787
+ }
788
+ }
789
+ }
790
+
791
+ /**
792
+ * @typedef HighlightEntryKeepEmpty
793
+ * @property {String} groupId
794
+ * @property {String} c - color
795
+ * @property {Number} offsetBefore
796
+ * @property {Number} textLength
797
+ * @property {String} beforeWasSplit
798
+ * @property {String} afterWasSplit
799
+ * @property {Array<Number>} path - on each level from root container to highlight, index among siblings
800
+ */
801
+ /**
802
+ * @typedef BuildModelResultKeepEmpty
803
+ * @property {HighlightEntryKeepEmpty[]} highlightModel
804
+ * @property {NodeList} wrapperNodes
805
+ */
806
+ /**
807
+ * For `keepEmptyNodes` option, creates data model of highlights.
808
+ * Additionally returns array of highlight nodes. Traverses DOM tree.
809
+ * @param {Node} rootNode
810
+ * @returns {BuildModelResultKeepEmpty|null} result
811
+ */
812
+ function buildHighlightModelKeepEmpty(rootNode) {
813
+ const classNames = options.colors ? Object.values(options.colors) : [className];
814
+ const wrapperNodesSelector = classNames.map(cls => containerSelector + ' .' + cls).join(', ');
815
+ const wrapperNodes = Array.from(document.querySelectorAll(wrapperNodesSelector))
816
+ .filter(node => !isBlacklisted(node));
817
+
818
+ if (!wrapperNodes.length) {
819
+ return null;
820
+ }
821
+
822
+ var highlightModel = [];
823
+ const indexCache = new Map();
824
+ for (var k = 0; k < wrapperNodes.length; k++) {
825
+ var wrapperNode = wrapperNodes[k];
826
+
827
+ //get info about highlight itself
828
+ var offsetBefore = 0;
829
+ var prevNode = wrapperNode.previousSibling;
830
+ if (prevNode && isText(prevNode)) {
831
+ const beforeWasSplit = wrapperNode.dataset.beforeWasSplit === 'true';
832
+ if (beforeWasSplit) {
833
+ offsetBefore = prevNode.textContent.length;
834
+ }
835
+ }
836
+ var highlightData = {
837
+ groupId: wrapperNode.getAttribute(GROUP_ATTR),
838
+ c: getColorByClassName(wrapperNode.className),
839
+ offsetBefore,
840
+ textLength: wrapperNode.textContent.length,
841
+ beforeWasSplit: wrapperNode.dataset.beforeWasSplit,
842
+ afterWasSplit: wrapperNode.dataset.afterWasSplit,
843
+ path: []
844
+ };
845
+
846
+ //get info about its position in the tree: path through all parents from rootNode to highlight
847
+ let currentNode = wrapperNode;
848
+ while (currentNode && currentNode !== rootNode) {
849
+ let indexInModel = indexCache.get(currentNode);
850
+ if (!indexInModel && indexInModel !== 0) {
851
+ //should be more reliable to ignore empty nodes when indexing
852
+ const childNodes = Array.from(currentNode.parentNode.childNodes).filter(node => !(isText(node) && !node.textContent.length));
853
+ //index among its non-empty siblings
854
+ indexInModel = childNodes.indexOf(currentNode);
855
+ indexCache.set(currentNode, indexInModel);
856
+ }
857
+ highlightData.path.unshift(indexInModel);
858
+ currentNode = currentNode.parentNode;
859
+ }
860
+
861
+ //add info about highlight and its position to model
862
+ highlightModel.push(highlightData);
863
+ }
864
+ return {
865
+ highlightModel,
866
+ wrapperNodes
867
+ };
868
+ }
869
+
870
+ /**
871
+ * Bootstrap the process of restoring the highlights from an index
872
+ * @param {Object[]|HighlightEntryKeepEmpty[]|null} highlightIndex
873
+ */
874
+ function highlightFromIndex(highlightIndex) {
875
+ var rootNode = getContainer();
876
+ if (rootNode) {
877
+ if (!keepEmptyNodes) {
878
+ rootNode.normalize();
879
+ textNodesIndex = 0;
880
+ restoreHighlight(rootNode, highlightIndex);
881
+ } else {
882
+ restoreHighlightKeepEmpty(rootNode, highlightIndex);
883
+ }
884
+ }
885
+ }
886
+
887
+ /**
888
+ * Traverse the DOM tree to wraps the text nodes according to the highlight index. Recursive.
889
+ * @param {Node} rootNode
890
+ * @param {Object[]} highlightIndex
891
+ */
892
+ function restoreHighlight(rootNode, highlightIndex) {
893
+ var childNodes = rootNode.childNodes;
894
+ var i, currentNode, parent;
895
+ var nodeInfos, nodesToSkip, range, initialChildCount;
896
+
897
+ for (i = 0; i < childNodes.length; i++) {
898
+ currentNode = childNodes[i];
899
+
900
+ if (isBlacklisted(currentNode)) {
901
+ continue;
902
+ } else if (isWrappable(currentNode)) {
903
+ parent = currentNode.parentNode;
904
+ initialChildCount = parent.childNodes.length;
905
+
906
+ nodeInfos = highlightIndex[textNodesIndex];
907
+
908
+ if (nodeInfos.highlighted === true) {
909
+ if (_.isArray(nodeInfos.inlineRanges)) {
910
+ nodeInfos.inlineRanges.reverse();
911
+ nodeInfos.inlineRanges.forEach(function (inlineRange) {
912
+ range = document.createRange();
913
+ range.setStart(currentNode, inlineRange.startOffset || 0);
914
+ range.setEnd(currentNode, inlineRange.endOffset || currentNode.textContent.length);
915
+ range.surroundContents(getWrapper(inlineRange.groupId, getClassNameByColor(inlineRange.c)));
916
+ });
917
+
918
+ // fully highlighted text node
919
+ } else {
920
+ range = document.createRange();
921
+ range.selectNodeContents(currentNode);
922
+ range.surroundContents(getWrapper(nodeInfos.groupId, getClassNameByColor(nodeInfos.c)));
923
+ }
924
+ // we do want to loop over the nodes created by the wrapping operation
925
+ nodesToSkip = parent.childNodes.length - initialChildCount;
926
+ i += nodesToSkip;
927
+ }
928
+ textNodesIndex++;
929
+ } else if (isElement(currentNode)) {
930
+ restoreHighlight(currentNode, highlightIndex);
931
+ }
932
+ }
933
+ }
934
+
935
+ /**
936
+ * For `keepEmptyNodes` option, wraps the text nodes according to highlights data model.
937
+ * Traverses and updates DOM tree. Shouldn't throw errors in case of mismatches.
938
+ * @param {Node} rootNode
939
+ * @param {HighlightEntryKeepEmpty[]|null} highlightModel
940
+ */
941
+ function restoreHighlightKeepEmpty(rootNode, highlightModel) {
942
+ if (!highlightModel) {
943
+ return;
944
+ }
945
+
946
+ var currentModel;
947
+ var range;
948
+ for (var k = 0; k < highlightModel.length; k++) {
949
+ currentModel = highlightModel[k];
950
+
951
+ //find node to wrap - go through nodes until we reach level where node to wrap will be
952
+ let childNodes;
953
+ let indexInModel;
954
+ let currentParentNode = rootNode;
955
+ let pathNotFound = false;
956
+ if (!currentModel.path || !currentModel.path.length) {
957
+ continue; //something went wrong
958
+ }
959
+ for (var m = 0; m < currentModel.path.length; m++) {
960
+ //path was counted among non-empty nodes
961
+ childNodes = Array.from(currentParentNode.childNodes).filter(node => !(isText(node) && !node.textContent.length));
962
+ indexInModel = currentModel.path[m];
963
+ currentParentNode = childNodes[indexInModel];
964
+ if (!currentParentNode && m < currentModel.path.length - 1) {
965
+ //node on last level may not exist yet, no need to fail. See `nodeAtIndex`
966
+ pathNotFound = true;
967
+ break;
968
+ }
969
+ }
970
+ if (pathNotFound) {
971
+ continue; //something went wrong
972
+ }
973
+
974
+ //add single highlight
975
+ var nodeAtIndex = null;
976
+ if (!currentModel.offsetBefore) {
977
+ //wrap starts on this node
978
+ nodeAtIndex = childNodes[indexInModel];
979
+ if (!nodeAtIndex || !isText(nodeAtIndex) || isBlacklisted(nodeAtIndex)) {
980
+ continue; //something went wrong
981
+ }
982
+ } else {
983
+ //split previousSibling to create a node for wrapping
984
+ var nodeBefore = childNodes[indexInModel - 1];
985
+ if (!nodeBefore || !isText(nodeBefore) ||
986
+ nodeBefore.textContent.length <= currentModel.offsetBefore || isBlacklisted(nodeBefore)) {
987
+ continue; //something went wrong
988
+ }
989
+ nodeAtIndex = nodeBefore.splitText(currentModel.offsetBefore);
990
+ }
991
+ //cut off its end
992
+ if (nodeAtIndex.textContent.length > currentModel.textLength) {
993
+ nodeAtIndex.splitText(currentModel.textLength);
994
+ }
995
+
996
+ //wrap
997
+ const wrapperNode = getWrapper(currentModel.groupId, getClassNameByColor(currentModel.c));
998
+ addSplitData(wrapperNode, currentModel.beforeWasSplit, currentModel.afterWasSplit);
999
+ range = document.createRange();
1000
+ range.selectNodeContents(nodeAtIndex);
1001
+ rangeSurroundContentsNoEmptyNodes(range, wrapperNode);
1002
+ }
1003
+ }
1004
+
1005
+ /**
1006
+ * Set highlighter color
1007
+ * @param {string} color Active highlighter color
1008
+ */
1009
+ const setActiveColor = color => {
1010
+ if (options.colors[color]) {
1011
+ className = options.colors[color];
1012
+ }
1013
+ };
1014
+
1015
+ /**
1016
+ * Helpers
1017
+ */
1018
+
1019
+ /**
1020
+ * Return the object key contains the given value
1021
+ * @param {Object} object
1022
+ * @param {any} value
1023
+ * @return {string|undefined}
1024
+ */
1025
+ const getKeyByValue = (object, value) => Object.keys(object).find(key => object[key] === value);
1026
+
1027
+ /**
1028
+ * Returns color identifier for the given class name
1029
+ * @param {string} highlighterClassName Class name of highlighter classes
1030
+ * @returns {string|number} Color identifier
1031
+ */
1032
+ const getColorByClassName = highlighterClassName => {
1033
+ if (options.colors) {
1034
+ return getKeyByValue(options.colors, highlighterClassName);
1035
+ }
1036
+
1037
+ return className;
1038
+ };
1039
+
1040
+ /**
1041
+ * Returns class name for the given color identifier
1042
+ * @param {string|number} color Color identifier
1043
+ * @returns {string} Class name
1044
+ */
1045
+ const getClassNameByColor = color => {
1046
+ if (options.colors && options.colors[color]) {
1047
+ return options.colors[color];
1048
+ }
1049
+
1050
+ return className;
1051
+ };
1052
+
1053
+ /**
1054
+ * Check if the given node is a wrapper
1055
+ * @param {Node|Element} node
1056
+ * @returns {boolean}
1057
+ */
1058
+ function isWrappingNode(node) {
1059
+ return isElement(node) && node.tagName.toLowerCase() === 'span' && highlightingClasses.includes(node.className);
1060
+ }
1061
+
1062
+ /**
1063
+ * Check if the given node can be wrapped
1064
+ * @param {Node} node
1065
+ * @returns {boolean}
1066
+ */
1067
+ function isWrappable(node) {
1068
+ return isText(node) && !isBlacklisted(node);
1069
+ }
1070
+
1071
+ /**
1072
+ * Check if the given node is, or is within, a blacklisted container.
1073
+ * With `keepEmptyNodes` option, node inside blacklisted container can be whitelisted too.
1074
+ * Priority of blacklist or whitelist is decided by which selector is closest to the node.
1075
+ * If no match found, node is considered whitelisted.
1076
+ * @param {Node} node
1077
+ * @returns {boolean}
1078
+ */
1079
+ function isBlacklisted(node) {
1080
+ const closest = $(node).closest(containersBlackAndWhiteListSelector);
1081
+ if (!closest.length) {
1082
+ return false;
1083
+ } else if (!containersWhiteListSelector) {
1084
+ return true;
1085
+ } else {
1086
+ return !closest.get(0).matches(containersWhiteListSelector);
1087
+ }
1088
+ }
1089
+
1090
+ /**
1091
+ * Wraps text node to the highlighter wrapper element
1092
+ * @param {Node} textNode Text node to wrap
1093
+ * @param {string} className Wrapper class name
1094
+ * @param {number} groupId Group id
1095
+ */
1096
+ function wrapNode(textNode, className, groupId) {
1097
+ const element = getWrapper(groupId, className);
1098
+
1099
+ element.appendChild(textNode);
1100
+ return element;
1101
+ }
1102
+
1103
+ /**
1104
+ * Create a wrapping node
1105
+ * @param {number} groupId
1106
+ * @returns {Element}
1107
+ */
1108
+ function getWrapper(groupId, wrapperClass) {
1109
+ const wrapper = document.createElement('span');
1110
+
1111
+ wrapper.className = wrapperClass || className;
1112
+ wrapper.setAttribute(GROUP_ATTR, `${groupId}`);
1113
+ return wrapper;
1114
+ }
1115
+
1116
+ /**
1117
+ * Returns the first unused group Id
1118
+ * @returns {number}
1119
+ */
1120
+ function getAvailableGroupId() {
1121
+ var id = currentGroupId || 1;
1122
+ while ($(getContainer()).find('[' + GROUP_ATTR + '=' + id + ']').length !== 0) {
1123
+ id++;
1124
+ }
1125
+ return id;
1126
+ }
1127
+
1128
+ /**
1129
+ * Check if the given node is an element
1130
+ * @param {Node} node
1131
+ * @returns {boolean}
1132
+ */
1133
+ function isElement(node) {
1134
+ return node && typeof node === 'object' && node.nodeType === window.Node.ELEMENT_NODE;
1135
+ }
1136
+
1137
+ /**
1138
+ * Check if the given node is of type text
1139
+ * @param {Node} node
1140
+ * @returns {boolean}
1141
+ */
1142
+ function isText(node) {
1143
+ return node && typeof node === 'object' && node.nodeType === window.Node.TEXT_NODE;
1144
+ }
1145
+
1146
+ /**
1147
+ * a "Hot Node" is either wrappable text node or a wrapper
1148
+ * @param {Node} node
1149
+ * @returns {boolean}
1150
+ */
1151
+ function isHotNode(node) {
1152
+ return isWrappingNode(node) || isWrappable(node);
1153
+ }
1154
+
1155
+ /**
1156
+ * Public API of the highlighter helper
1157
+ */
1158
+ return {
1159
+ highlightRanges: highlightRanges,
1160
+ highlightFromIndex: highlightFromIndex,
1161
+ getHighlightIndex: getHighlightIndex,
1162
+ clearHighlights: clearHighlights,
1163
+ clearSingleHighlight: clearSingleHighlight,
1164
+ setActiveColor
1165
+ };
1166
+ }