@oat-sa/tao-core-ui 1.60.2 → 1.61.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 (780) hide show
  1. package/LICENSE +339 -339
  2. package/README.md +23 -18
  3. package/dist/actionbar.js +105 -105
  4. package/dist/adder.js +109 -109
  5. package/dist/animable/absorbable/absorbable.js +42 -42
  6. package/dist/animable/absorbable/css/absorb.css +7 -7
  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 +36 -36
  11. package/dist/areaBroker.js +51 -51
  12. package/dist/autocomplete/css/autocomplete.css +7 -7
  13. package/dist/autocomplete/css/autocomplete.css.map +1 -1
  14. package/dist/autocomplete.js +400 -400
  15. package/dist/autoscroll.js +22 -22
  16. package/dist/badge/badge.js +48 -48
  17. package/dist/badge/css/badge.css +7 -7
  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 +70 -70
  22. package/dist/btngrouper.js +64 -64
  23. package/dist/bulkActionPopup/css/bulkActionPopup.css +7 -7
  24. package/dist/bulkActionPopup/css/bulkActionPopup.css.map +1 -1
  25. package/dist/bulkActionPopup.js +104 -104
  26. package/dist/button.js +102 -102
  27. package/dist/calculator/css/calculator.css +10 -10
  28. package/dist/calculator/css/calculator.css.map +1 -1
  29. package/dist/calculator.js +51 -51
  30. package/dist/cascadingComboBox.js +47 -47
  31. package/dist/ckeditor/ckConfigurator.js +48 -48
  32. package/dist/ckeditor/dtdHandler.js +110 -110
  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 +111 -111
  36. package/dist/component/alignable.js +81 -81
  37. package/dist/component/containable.js +37 -37
  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 +25 -25
  43. package/dist/component/placeable.js +70 -70
  44. package/dist/component/resizable.js +61 -61
  45. package/dist/component/stackable.js +20 -20
  46. package/dist/component/windowed.js +59 -59
  47. package/dist/component.js +153 -153
  48. package/dist/container.js +76 -76
  49. package/dist/contextualPopup/css/contextualPopup.css +7 -7
  50. package/dist/contextualPopup/css/contextualPopup.css.map +1 -1
  51. package/dist/contextualPopup.js +113 -113
  52. package/dist/dashboard/css/dashboard.css +7 -7
  53. package/dist/dashboard/css/dashboard.css.map +1 -1
  54. package/dist/dashboard.js +75 -75
  55. package/dist/datalist/css/datalist.css +7 -7
  56. package/dist/datalist/css/datalist.css.map +1 -1
  57. package/dist/datalist.js +160 -160
  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 +27 -27
  61. package/dist/datatable/filterStrategy/multiple.js +25 -25
  62. package/dist/datatable/filterStrategy/single.js +25 -25
  63. package/dist/datatable.js +464 -431
  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 +110 -110
  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 +174 -174
  70. package/dist/deleter.js +92 -92
  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 +65 -65
  74. package/dist/dialog/alert.js +26 -26
  75. package/dist/dialog/confirm.js +27 -27
  76. package/dist/dialog/confirmDelete.js +44 -44
  77. package/dist/dialog.js +156 -156
  78. package/dist/disabler.js +90 -90
  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 +43 -43
  82. package/dist/documentViewer/providers/pdfViewer/pdfjs/areaBroker.js +25 -25
  83. package/dist/documentViewer/providers/pdfViewer/pdfjs/findBar.js +133 -133
  84. package/dist/documentViewer/providers/pdfViewer/pdfjs/pageView.js +102 -102
  85. package/dist/documentViewer/providers/pdfViewer/pdfjs/pagesManager.js +52 -52
  86. package/dist/documentViewer/providers/pdfViewer/pdfjs/searchEngine.js +125 -125
  87. package/dist/documentViewer/providers/pdfViewer/pdfjs/textManager.js +67 -67
  88. package/dist/documentViewer/providers/pdfViewer/pdfjs/viewer.js +94 -94
  89. package/dist/documentViewer/providers/pdfViewer/pdfjs/wrapper.js +111 -111
  90. package/dist/documentViewer/providers/pdfViewer.js +42 -42
  91. package/dist/documentViewer/viewerFactory.js +71 -71
  92. package/dist/documentViewer.js +99 -99
  93. package/dist/dropdown/css/dropdown.css +7 -7
  94. package/dist/dropdown/css/dropdown.css.map +1 -1
  95. package/dist/dropdown.js +97 -97
  96. package/dist/durationer.js +58 -58
  97. package/dist/dynamicComponent/css/dynamicComponent.css +7 -7
  98. package/dist/dynamicComponent/css/dynamicComponent.css.map +1 -1
  99. package/dist/dynamicComponent.js +116 -116
  100. package/dist/feedback.js +97 -97
  101. package/dist/figure/FigureStateActive.js +117 -117
  102. package/dist/filesender.js +26 -26
  103. package/dist/filter.js +47 -47
  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 +112 -112
  109. package/dist/form/form.js +245 -245
  110. package/dist/form/simpleForm.js +71 -71
  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 +65 -65
  114. package/dist/form/validator/validator.js +87 -87
  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 +24 -24
  118. package/dist/form/widget/loader.js +16 -16
  119. package/dist/form/widget/providers/checkBox.js +75 -75
  120. package/dist/form/widget/providers/comboBox.js +59 -59
  121. package/dist/form/widget/providers/default.js +35 -35
  122. package/dist/form/widget/providers/hidden.js +50 -50
  123. package/dist/form/widget/providers/hiddenBox.js +71 -71
  124. package/dist/form/widget/providers/radioBox.js +70 -70
  125. package/dist/form/widget/providers/textArea.js +48 -48
  126. package/dist/form/widget/providers/textBox.js +34 -34
  127. package/dist/form/widget/widget.js +154 -154
  128. package/dist/form.js +10 -10
  129. package/dist/formValidator/formValidator.js +61 -61
  130. package/dist/formValidator/highlighters/highlighter.js +41 -41
  131. package/dist/formValidator/highlighters/message.js +29 -29
  132. package/dist/formValidator/highlighters/tooltip.js +32 -32
  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 +86 -86
  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 +51 -51
  139. package/dist/generis/widget/checkBox/checkBox.js +52 -52
  140. package/dist/generis/widget/comboBox/comboBox.js +45 -45
  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 +53 -53
  144. package/dist/generis/widget/loader.js +20 -20
  145. package/dist/generis/widget/textBox/textBox.js +40 -40
  146. package/dist/generis/widget/widget.js +60 -60
  147. package/dist/groupedComboBox.js +49 -49
  148. package/dist/groupvalidator.js +19 -19
  149. package/dist/hider.js +41 -41
  150. package/dist/highlighter.js +262 -262
  151. package/dist/image/ImgStateActive/extractLabel.js +20 -20
  152. package/dist/image/ImgStateActive/helper.js +16 -16
  153. package/dist/image/ImgStateActive/initHelper.js +85 -85
  154. package/dist/image/ImgStateActive/initMediaEditor.js +48 -48
  155. package/dist/image/ImgStateActive/mediaSizer.js +32 -32
  156. package/dist/image/ImgStateActive.js +104 -104
  157. package/dist/incrementer.js +58 -58
  158. package/dist/inplacer.js +87 -87
  159. package/dist/interactUtils.js +42 -42
  160. package/dist/itemButtonList/css/item-button-list.css +23 -23
  161. package/dist/itemButtonList/css/item-button-list.css.map +1 -1
  162. package/dist/itemButtonList.js +115 -115
  163. package/dist/keyNavigation/navigableDomElement.js +76 -76
  164. package/dist/keyNavigation/navigator.js +158 -158
  165. package/dist/listbox/css/listbox.css +7 -7
  166. package/dist/listbox/css/listbox.css.map +1 -1
  167. package/dist/listbox.js +97 -97
  168. package/dist/liststyler.js +57 -57
  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 +48 -48
  172. package/dist/lock.js +125 -125
  173. package/dist/login/login.js +100 -100
  174. package/dist/maths/calculator/basicCalculator.js +63 -63
  175. package/dist/maths/calculator/calculatorComponent.js +29 -29
  176. package/dist/maths/calculator/core/areaBroker.js +25 -25
  177. package/dist/maths/calculator/core/board.js +313 -313
  178. package/dist/maths/calculator/core/expression.js +111 -111
  179. package/dist/maths/calculator/core/labels.js +29 -29
  180. package/dist/maths/calculator/core/plugin.js +19 -19
  181. package/dist/maths/calculator/core/terms.js +26 -26
  182. package/dist/maths/calculator/core/tokenizer.js +98 -98
  183. package/dist/maths/calculator/core/tokens.js +59 -59
  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 +23 -23
  187. package/dist/maths/calculator/plugins/core/degrad.js +22 -22
  188. package/dist/maths/calculator/plugins/core/history.js +45 -45
  189. package/dist/maths/calculator/plugins/core/remind.js +22 -22
  190. package/dist/maths/calculator/plugins/core/stepNavigation.js +31 -31
  191. package/dist/maths/calculator/plugins/keyboard/templateKeyboard/templateKeyboard.js +61 -61
  192. package/dist/maths/calculator/plugins/modifiers/pow10.js +35 -35
  193. package/dist/maths/calculator/plugins/modifiers/sign.js +90 -90
  194. package/dist/maths/calculator/plugins/screen/simpleScreen/simpleScreen.js +60 -60
  195. package/dist/maths/calculator/pluginsLoader.js +21 -21
  196. package/dist/maths/calculator/scientificCalculator.js +91 -91
  197. package/dist/mediaEditor/mediaEditorComponent.js +64 -64
  198. package/dist/mediaEditor/plugins/mediaAlignment/helper.js +16 -16
  199. package/dist/mediaEditor/plugins/mediaAlignment/mediaAlignmentComponent.js +60 -60
  200. package/dist/mediaEditor/plugins/mediaAlignment/style.css +7 -7
  201. package/dist/mediaEditor/plugins/mediaDimension/helper.js +70 -70
  202. package/dist/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +153 -153
  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 +65 -65
  207. package/dist/mediaplayer/players/youtube.js +52 -52
  208. package/dist/mediaplayer/players.js +18 -18
  209. package/dist/mediaplayer/support.js +55 -55
  210. package/dist/mediaplayer/utils/reminder.js +100 -100
  211. package/dist/mediaplayer/utils/timeObserver.js +92 -92
  212. package/dist/mediaplayer/youtubeManager.js +35 -35
  213. package/dist/mediaplayer.js +460 -460
  214. package/dist/mediasizer.js +135 -135
  215. package/dist/modal.js +87 -87
  216. package/dist/movableComponent.js +35 -35
  217. package/dist/pageSizeSelector.js +44 -44
  218. package/dist/pageStatus.js +33 -33
  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 +23 -23
  222. package/dist/pagination/providers/pages.js +37 -37
  223. package/dist/pagination/providers/simple.js +35 -35
  224. package/dist/pagination.js +45 -45
  225. package/dist/previewer.js +67 -67
  226. package/dist/progressbar.js +58 -58
  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 +533 -0
  230. package/dist/report.js +86 -86
  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 +73 -73
  234. package/dist/resource/list.js +66 -66
  235. package/dist/resource/selectable.js +92 -92
  236. package/dist/resource/selector.js +195 -195
  237. package/dist/resource/tree.js +104 -104
  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 +88 -88
  241. package/dist/resourcemgr/fileSelector.js +58 -58
  242. package/dist/resourcemgr/util/updatePermissions.js +4 -4
  243. package/dist/resourcemgr.js +62 -62
  244. package/dist/scroller.js +26 -26
  245. package/dist/searchModal/advancedSearch.js +355 -161
  246. package/dist/searchModal/css/advancedSearch.css +7 -14
  247. package/dist/searchModal/css/advancedSearch.css.map +1 -1
  248. package/dist/searchModal/css/searchModal.css +29 -8
  249. package/dist/searchModal/css/searchModal.css.map +1 -1
  250. package/dist/searchModal.js +574 -190
  251. package/dist/selecter.js +6 -6
  252. package/dist/stacker.js +43 -43
  253. package/dist/switch/css/switch.css +7 -7
  254. package/dist/switch/css/switch.css.map +1 -1
  255. package/dist/switch/switch.js +75 -75
  256. package/dist/tableModel.js +33 -33
  257. package/dist/tabs/css/tabs.css +12 -12
  258. package/dist/tabs/css/tabs.css.map +1 -1
  259. package/dist/tabs.js +217 -217
  260. package/dist/taskQueue/css/taskQueue.css +7 -7
  261. package/dist/taskQueue/css/taskQueue.css.map +1 -1
  262. package/dist/taskQueue/status.js +72 -72
  263. package/dist/taskQueue/table.js +67 -67
  264. package/dist/taskQueue/taskQueue.js +18 -18
  265. package/dist/taskQueue/taskQueueModel.js +86 -86
  266. package/dist/taskQueue.js +47 -47
  267. package/dist/taskQueueButton/css/taskable.css +7 -7
  268. package/dist/taskQueueButton/css/taskable.css.map +1 -1
  269. package/dist/taskQueueButton/css/treeButton.css +7 -7
  270. package/dist/taskQueueButton/css/treeButton.css.map +1 -1
  271. package/dist/taskQueueButton/standardButton.js +39 -39
  272. package/dist/taskQueueButton/taskable.js +54 -54
  273. package/dist/taskQueueButton/treeButton.js +56 -56
  274. package/dist/themeLoader.js +75 -75
  275. package/dist/themes.js +84 -84
  276. package/dist/toggler.js +57 -57
  277. package/dist/tooltip.js +54 -54
  278. package/dist/tooltipster.js +17 -17
  279. package/dist/transformer.js +117 -117
  280. package/dist/tristateCheckboxGroup/css/tristateCheckboxGroup.css +7 -7
  281. package/dist/tristateCheckboxGroup/css/tristateCheckboxGroup.css.map +1 -1
  282. package/dist/tristateCheckboxGroup.js +75 -75
  283. package/dist/uploader.js +158 -158
  284. package/dist/validator/validators.js +48 -48
  285. package/dist/validator.js +24 -24
  286. package/dist/waitForMedia.js +33 -33
  287. package/dist/waitingDialog/css/waitingDialog.css +7 -7
  288. package/dist/waitingDialog/css/waitingDialog.css.map +1 -1
  289. package/dist/waitingDialog/waitingDialog.js +54 -54
  290. package/package.json +108 -107
  291. package/scss/basic.scss +16 -16
  292. package/scss/ckeditor/skins/tao/scss/inc/_ck-icons.scss +59 -59
  293. package/scss/ckeditor/skins/tao/scss/inc/_tao.scss +59 -59
  294. package/scss/font/tao/tao.svg +234 -234
  295. package/scss/inc/_base.scss +495 -495
  296. package/scss/inc/_bootstrap.scss +6 -6
  297. package/scss/inc/_buttons.scss +114 -114
  298. package/scss/inc/_colors.scss +93 -88
  299. package/scss/inc/_feedback.scss +150 -150
  300. package/scss/inc/_flex-grid.scss +15 -15
  301. package/scss/inc/_fonts.scss +4 -4
  302. package/scss/inc/_forms.scss +832 -832
  303. package/scss/inc/_functions.scss +283 -283
  304. package/scss/inc/_jquery.nouislider.scss +254 -254
  305. package/scss/inc/_normalize.scss +528 -528
  306. package/scss/inc/_report.scss +67 -67
  307. package/scss/inc/_secondary-properties.scss +89 -89
  308. package/scss/inc/_select2.scss +634 -634
  309. package/scss/inc/_toolbars.scss +155 -155
  310. package/scss/inc/_tooltip.scss +312 -312
  311. package/scss/inc/_variables.scss +21 -21
  312. package/scss/inc/base/_highlight.scss +5 -5
  313. package/scss/inc/base/_list-style.scss +58 -58
  314. package/scss/inc/base/_svg.scss +3 -3
  315. package/scss/inc/base/_table.scss +62 -62
  316. package/scss/inc/fonts/_source-sans-pro.scss +29 -29
  317. package/scss/inc/fonts/_tao-icon-classes.scss +226 -226
  318. package/scss/inc/fonts/_tao-icon-def.scss +12 -12
  319. package/scss/inc/fonts/_tao-icon-vars.scss +240 -240
  320. package/src/actionbar/tpl/main.tpl +8 -8
  321. package/src/actionbar.js +251 -251
  322. package/src/adder.js +250 -250
  323. package/src/animable/absorbable/absorbable.js +134 -134
  324. package/src/animable/absorbable/css/absorb.css +7 -7
  325. package/src/animable/absorbable/css/absorb.css.map +1 -1
  326. package/src/animable/absorbable/scss/absorb.scss +37 -37
  327. package/src/animable/pulsable/css/pulse.css +7 -7
  328. package/src/animable/pulsable/css/pulse.css.map +1 -1
  329. package/src/animable/pulsable/pulsable.js +90 -90
  330. package/src/animable/pulsable/scss/pulse.scss +22 -22
  331. package/src/areaBroker.js +160 -160
  332. package/src/autocomplete/css/autocomplete.css +7 -7
  333. package/src/autocomplete/css/autocomplete.css.map +1 -1
  334. package/src/autocomplete/scss/autocomplete.scss +37 -37
  335. package/src/autocomplete.js +1029 -1029
  336. package/src/autoscroll.js +57 -57
  337. package/src/badge/badge.js +119 -119
  338. package/src/badge/css/badge.css +7 -7
  339. package/src/badge/css/badge.css.map +1 -1
  340. package/src/badge/scss/badge.scss +92 -92
  341. package/src/badge/tpl/badge.tpl +4 -4
  342. package/src/breadcrumbs/css/breadcrumbs.css +7 -7
  343. package/src/breadcrumbs/css/breadcrumbs.css.map +1 -1
  344. package/src/breadcrumbs/scss/breadcrumbs.scss +52 -52
  345. package/src/breadcrumbs/tpl/breadcrumbs.tpl +20 -20
  346. package/src/breadcrumbs.js +99 -99
  347. package/src/btngrouper.js +213 -213
  348. package/src/bulkActionPopup/css/bulkActionPopup.css +7 -7
  349. package/src/bulkActionPopup/css/bulkActionPopup.css.map +1 -1
  350. package/src/bulkActionPopup/scss/bulkActionPopup.scss +63 -63
  351. package/src/bulkActionPopup/tpl/layout.tpl +76 -76
  352. package/src/bulkActionPopup/tpl/select.tpl +8 -8
  353. package/src/bulkActionPopup.js +274 -274
  354. package/src/button/tpl/button.tpl +4 -4
  355. package/src/button.js +135 -135
  356. package/src/calculator/css/calculator.css +10 -10
  357. package/src/calculator/css/calculator.css.map +1 -1
  358. package/src/calculator/scss/calculator.scss +139 -139
  359. package/src/calculator.js +188 -188
  360. package/src/cascadingComboBox.js +126 -126
  361. package/src/ckeditor/ckConfigurator.js +736 -736
  362. package/src/ckeditor/dtdHandler.js +1030 -1030
  363. package/src/class/css/selector.css +7 -7
  364. package/src/class/css/selector.css.map +1 -1
  365. package/src/class/scss/selector.scss +101 -101
  366. package/src/class/selector.js +329 -329
  367. package/src/class/tpl/listItem.tpl +9 -9
  368. package/src/class/tpl/selector.tpl +10 -10
  369. package/src/component/alignable.js +274 -274
  370. package/src/component/containable.js +122 -122
  371. package/src/component/css/components.css +7 -7
  372. package/src/component/css/components.css.map +1 -1
  373. package/src/component/css/windowComponent.css +7 -7
  374. package/src/component/css/windowComponent.css.map +1 -1
  375. package/src/component/draggable.js +104 -104
  376. package/src/component/placeable.js +233 -233
  377. package/src/component/resizable.js +195 -195
  378. package/src/component/scss/components.scss +507 -507
  379. package/src/component/scss/windowComponent.scss +62 -62
  380. package/src/component/stackable.js +67 -67
  381. package/src/component/tpl/window.tpl +7 -7
  382. package/src/component/windowed.js +206 -206
  383. package/src/component.js +401 -401
  384. package/src/container.js +200 -200
  385. package/src/contextualPopup/css/contextualPopup.css +7 -7
  386. package/src/contextualPopup/css/contextualPopup.css.map +1 -1
  387. package/src/contextualPopup/scss/contextualPopup.scss +78 -78
  388. package/src/contextualPopup/tpl/popup.tpl +10 -10
  389. package/src/contextualPopup.js +297 -297
  390. package/src/css/basic.css +103 -103
  391. package/src/css/basic.css.map +1 -1
  392. package/src/dashboard/css/dashboard.css +7 -7
  393. package/src/dashboard/css/dashboard.css.map +1 -1
  394. package/src/dashboard/scss/dashboard.scss +93 -93
  395. package/src/dashboard/tpl/dashboard.tpl +16 -16
  396. package/src/dashboard/tpl/dashboardMetricsList.tpl +15 -15
  397. package/src/dashboard.js +184 -184
  398. package/src/datalist/css/datalist.css +7 -7
  399. package/src/datalist/css/datalist.css.map +1 -1
  400. package/src/datalist/scss/datalist.scss +116 -116
  401. package/src/datalist/tpl/list.tpl +24 -24
  402. package/src/datalist/tpl/main.tpl +44 -44
  403. package/src/datalist.js +500 -500
  404. package/src/datatable/css/datatable.css +11 -7
  405. package/src/datatable/css/datatable.css.map +1 -1
  406. package/src/datatable/filterStrategy/filterStrategy.js +70 -70
  407. package/src/datatable/filterStrategy/multiple.js +126 -126
  408. package/src/datatable/filterStrategy/single.js +108 -108
  409. package/src/datatable/scss/datatable.scss +149 -146
  410. package/src/datatable/tpl/button.tpl +6 -6
  411. package/src/datatable/tpl/layout.tpl +158 -158
  412. package/src/datatable.js +1056 -1056
  413. package/src/dateRange/css/dateRange.css +7 -7
  414. package/src/dateRange/css/dateRange.css.map +1 -1
  415. package/src/dateRange/dateRange.js +341 -341
  416. package/src/dateRange/scss/dateRange.scss +7 -7
  417. package/src/dateRange/tpl/select.tpl +18 -18
  418. package/src/datetime/css/picker.css +7 -7
  419. package/src/datetime/css/picker.css.map +1 -1
  420. package/src/datetime/picker.js +576 -576
  421. package/src/datetime/scss/picker.scss +192 -192
  422. package/src/datetime/tpl/picker.tpl +18 -18
  423. package/src/deleter/undo.tpl +6 -6
  424. package/src/deleter.js +296 -296
  425. package/src/destination/css/selector.css +7 -7
  426. package/src/destination/css/selector.css.map +1 -1
  427. package/src/destination/scss/selector.scss +36 -36
  428. package/src/destination/selector.js +195 -195
  429. package/src/destination/tpl/selector.tpl +13 -13
  430. package/src/dialog/alert.js +70 -70
  431. package/src/dialog/confirm.js +85 -85
  432. package/src/dialog/confirmDelete.js +95 -95
  433. package/src/dialog/tpl/body.tpl +24 -24
  434. package/src/dialog/tpl/buttons.tpl +6 -6
  435. package/src/dialog/tpl/checkbox.tpl +5 -5
  436. package/src/dialog.js +517 -517
  437. package/src/disabler.js +230 -230
  438. package/src/documentViewer/css/documentViewer.css +7 -7
  439. package/src/documentViewer/css/documentViewer.css.map +1 -1
  440. package/src/documentViewer/providers/pdfViewer/fallback/viewer.js +69 -69
  441. package/src/documentViewer/providers/pdfViewer/pdfjs/areaBroker.js +41 -41
  442. package/src/documentViewer/providers/pdfViewer/pdfjs/findBar.js +475 -475
  443. package/src/documentViewer/providers/pdfViewer/pdfjs/findBar.tpl +20 -20
  444. package/src/documentViewer/providers/pdfViewer/pdfjs/match.tpl +1 -1
  445. package/src/documentViewer/providers/pdfViewer/pdfjs/page.tpl +4 -4
  446. package/src/documentViewer/providers/pdfViewer/pdfjs/pageView.js +318 -318
  447. package/src/documentViewer/providers/pdfViewer/pdfjs/pagesManager.js +167 -167
  448. package/src/documentViewer/providers/pdfViewer/pdfjs/searchEngine.js +451 -451
  449. package/src/documentViewer/providers/pdfViewer/pdfjs/textManager.js +252 -252
  450. package/src/documentViewer/providers/pdfViewer/pdfjs/viewer.js +299 -299
  451. package/src/documentViewer/providers/pdfViewer/pdfjs/viewer.tpl +16 -16
  452. package/src/documentViewer/providers/pdfViewer/pdfjs/wrapper.js +351 -351
  453. package/src/documentViewer/providers/pdfViewer.js +93 -93
  454. package/src/documentViewer/scss/documentViewer.scss +184 -184
  455. package/src/documentViewer/viewerFactory.js +191 -191
  456. package/src/documentViewer.js +238 -238
  457. package/src/dropdown/css/dropdown.css +7 -7
  458. package/src/dropdown/css/dropdown.css.map +1 -1
  459. package/src/dropdown/scss/dropdown.scss +99 -99
  460. package/src/dropdown/tpl/dropdown.tpl +8 -8
  461. package/src/dropdown/tpl/list-item.tpl +4 -4
  462. package/src/dropdown.js +255 -255
  463. package/src/durationer.js +222 -222
  464. package/src/dynamicComponent/css/dynamicComponent.css +7 -7
  465. package/src/dynamicComponent/css/dynamicComponent.css.map +1 -1
  466. package/src/dynamicComponent/scss/dynamicComponent.scss +98 -98
  467. package/src/dynamicComponent/tpl/layout.tpl +17 -17
  468. package/src/dynamicComponent.js +554 -554
  469. package/src/feedback/feedback.tpl +7 -7
  470. package/src/feedback.js +295 -295
  471. package/src/figure/FigureStateActive.js +174 -174
  472. package/src/filesender.js +114 -114
  473. package/src/filter/template.tpl +5 -5
  474. package/src/filter.js +135 -135
  475. package/src/form/css/dropdownForm.css +7 -7
  476. package/src/form/css/dropdownForm.css.map +1 -1
  477. package/src/form/css/form.css +7 -7
  478. package/src/form/css/form.css.map +1 -1
  479. package/src/form/dropdownForm.js +281 -281
  480. package/src/form/form.js +688 -688
  481. package/src/form/scss/dropdownForm.scss +60 -60
  482. package/src/form/scss/form.scss +25 -25
  483. package/src/form/simpleForm.js +125 -125
  484. package/src/form/tpl/dropdownForm.tpl +4 -4
  485. package/src/form/tpl/form.tpl +7 -7
  486. package/src/form/validator/css/validator.css +7 -7
  487. package/src/form/validator/css/validator.css.map +1 -1
  488. package/src/form/validator/renderer.js +118 -118
  489. package/src/form/validator/scss/validator.scss +14 -14
  490. package/src/form/validator/tpl/message.tpl +1 -1
  491. package/src/form/validator/tpl/validator.tpl +1 -1
  492. package/src/form/validator/validator.js +220 -220
  493. package/src/form/widget/css/widget.css +7 -7
  494. package/src/form/widget/css/widget.css.map +1 -1
  495. package/src/form/widget/definitions.js +51 -51
  496. package/src/form/widget/loader.js +40 -40
  497. package/src/form/widget/providers/checkBox.js +138 -138
  498. package/src/form/widget/providers/comboBox.js +63 -63
  499. package/src/form/widget/providers/default.js +90 -90
  500. package/src/form/widget/providers/hidden.js +62 -62
  501. package/src/form/widget/providers/hiddenBox.js +152 -152
  502. package/src/form/widget/providers/radioBox.js +99 -99
  503. package/src/form/widget/providers/textArea.js +52 -52
  504. package/src/form/widget/providers/textBox.js +48 -48
  505. package/src/form/widget/scss/widget.scss +55 -55
  506. package/src/form/widget/tpl/checkBox.tpl +25 -25
  507. package/src/form/widget/tpl/comboBox.tpl +13 -13
  508. package/src/form/widget/tpl/hidden.tpl +1 -1
  509. package/src/form/widget/tpl/hiddenBox.tpl +17 -17
  510. package/src/form/widget/tpl/label.tpl +6 -6
  511. package/src/form/widget/tpl/radioBox.tpl +25 -25
  512. package/src/form/widget/tpl/textArea.tpl +8 -8
  513. package/src/form/widget/tpl/widget.tpl +8 -8
  514. package/src/form/widget/widget.js +372 -372
  515. package/src/form.js +53 -53
  516. package/src/formValidator/formValidator.js +253 -253
  517. package/src/formValidator/highlighters/highlighter.js +102 -102
  518. package/src/formValidator/highlighters/message.js +70 -70
  519. package/src/formValidator/highlighters/tooltip.js +78 -78
  520. package/src/generis/form/css/form.css +7 -7
  521. package/src/generis/form/css/form.css.map +1 -1
  522. package/src/generis/form/form.js +239 -239
  523. package/src/generis/form/readme.md +70 -70
  524. package/src/generis/form/scss/form.scss +23 -23
  525. package/src/generis/form/tpl/form.tpl +16 -16
  526. package/src/generis/validator/css/validator.css +7 -7
  527. package/src/generis/validator/css/validator.css.map +1 -1
  528. package/src/generis/validator/readme.md +46 -46
  529. package/src/generis/validator/scss/validator.scss +13 -13
  530. package/src/generis/validator/validator.js +128 -128
  531. package/src/generis/widget/checkBox/checkBox.js +112 -112
  532. package/src/generis/widget/checkBox/checkBox.tpl +18 -18
  533. package/src/generis/widget/comboBox/comboBox.js +67 -67
  534. package/src/generis/widget/comboBox/comboBox.tpl +12 -12
  535. package/src/generis/widget/css/widget.css +7 -7
  536. package/src/generis/widget/css/widget.css.map +1 -1
  537. package/src/generis/widget/hiddenBox/hiddenBox.js +132 -132
  538. package/src/generis/widget/hiddenBox/hiddenBox.tpl +16 -16
  539. package/src/generis/widget/loader.js +49 -49
  540. package/src/generis/widget/readme.md +59 -59
  541. package/src/generis/widget/scss/widget.scss +61 -61
  542. package/src/generis/widget/textBox/textBox.js +65 -65
  543. package/src/generis/widget/textBox/textBox.tpl +7 -7
  544. package/src/generis/widget/widget.js +164 -164
  545. package/src/generis/widget/widget.tpl +5 -5
  546. package/src/groupedComboBox.js +99 -99
  547. package/src/groupvalidator.js +84 -84
  548. package/src/hider.js +88 -88
  549. package/src/highlighter.js +1166 -1166
  550. package/src/image/ImgStateActive/extractLabel.js +29 -29
  551. package/src/image/ImgStateActive/helper.js +36 -36
  552. package/src/image/ImgStateActive/initHelper.js +137 -137
  553. package/src/image/ImgStateActive/initMediaEditor.js +92 -92
  554. package/src/image/ImgStateActive/mediaSizer.js +63 -63
  555. package/src/image/ImgStateActive.js +115 -115
  556. package/src/incrementer.js +319 -319
  557. package/src/inplacer.js +316 -316
  558. package/src/interactUtils.js +140 -140
  559. package/src/itemButtonList/css/item-button-list.css +23 -23
  560. package/src/itemButtonList/css/item-button-list.css.map +1 -1
  561. package/src/itemButtonList/scss/item-button-list.scss +236 -236
  562. package/src/itemButtonList/tpl/itemButtonList.tpl +21 -21
  563. package/src/itemButtonList.js +274 -274
  564. package/src/keyNavigation/navigableDomElement.js +282 -282
  565. package/src/keyNavigation/navigator.js +543 -543
  566. package/src/listbox/css/listbox.css +7 -7
  567. package/src/listbox/css/listbox.css.map +1 -1
  568. package/src/listbox/scss/listbox.scss +116 -116
  569. package/src/listbox/tpl/list.tpl +14 -14
  570. package/src/listbox/tpl/main.tpl +9 -9
  571. package/src/listbox.js +251 -251
  572. package/src/liststyler.js +155 -155
  573. package/src/loadingButton/css/button.css +7 -7
  574. package/src/loadingButton/css/button.css.map +1 -1
  575. package/src/loadingButton/loadingButton.js +110 -110
  576. package/src/loadingButton/scss/button.scss +41 -41
  577. package/src/loadingButton/tpl/button.tpl +5 -5
  578. package/src/lock/lock.tpl +16 -16
  579. package/src/lock.js +395 -395
  580. package/src/login/login.js +322 -322
  581. package/src/login/tpl/login.tpl +29 -29
  582. package/src/login/tpl/passwordReveal.tpl +7 -7
  583. package/src/maths/calculator/basicCalculator.js +55 -55
  584. package/src/maths/calculator/calculatorComponent.js +128 -128
  585. package/src/maths/calculator/core/areaBroker.js +38 -38
  586. package/src/maths/calculator/core/board.js +841 -841
  587. package/src/maths/calculator/core/expression.js +430 -430
  588. package/src/maths/calculator/core/labels.js +116 -116
  589. package/src/maths/calculator/core/plugin.js +40 -40
  590. package/src/maths/calculator/core/terms.js +459 -459
  591. package/src/maths/calculator/core/tokenizer.js +245 -245
  592. package/src/maths/calculator/core/tokens.js +178 -178
  593. package/src/maths/calculator/core/tpl/board.tpl +4 -4
  594. package/src/maths/calculator/css/calculator.css +7 -7
  595. package/src/maths/calculator/css/calculator.css.map +1 -1
  596. package/src/maths/calculator/defaultCalculator.js +66 -66
  597. package/src/maths/calculator/plugins/core/degrad.js +90 -90
  598. package/src/maths/calculator/plugins/core/history.js +166 -166
  599. package/src/maths/calculator/plugins/core/remind.js +96 -96
  600. package/src/maths/calculator/plugins/core/stepNavigation.js +175 -175
  601. package/src/maths/calculator/plugins/keyboard/templateKeyboard/defaultTemplate.tpl +36 -36
  602. package/src/maths/calculator/plugins/keyboard/templateKeyboard/templateKeyboard.js +91 -91
  603. package/src/maths/calculator/plugins/modifiers/pow10.js +143 -143
  604. package/src/maths/calculator/plugins/modifiers/sign.js +339 -339
  605. package/src/maths/calculator/plugins/screen/simpleScreen/defaultTemplate.tpl +3 -3
  606. package/src/maths/calculator/plugins/screen/simpleScreen/history.tpl +3 -3
  607. package/src/maths/calculator/plugins/screen/simpleScreen/simpleScreen.js +191 -191
  608. package/src/maths/calculator/pluginsLoader.js +46 -46
  609. package/src/maths/calculator/scientificCalculator.js +74 -74
  610. package/src/maths/calculator/scss/calculator.scss +396 -396
  611. package/src/maths/calculator/tpl/basicKeyboard.tpl +37 -37
  612. package/src/maths/calculator/tpl/basicScreen.tpl +2 -2
  613. package/src/maths/calculator/tpl/scientificKeyboard.tpl +61 -61
  614. package/src/maths/calculator/tpl/scientificScreen.tpl +3 -3
  615. package/src/mediaEditor/mediaEditorComponent.js +141 -141
  616. package/src/mediaEditor/plugins/mediaAlignment/helper.js +62 -62
  617. package/src/mediaEditor/plugins/mediaAlignment/mediaAlignmentComponent.js +99 -99
  618. package/src/mediaEditor/plugins/mediaAlignment/style.css +7 -7
  619. package/src/mediaEditor/plugins/mediaAlignment/tpl/mediaAlignment.tpl +25 -25
  620. package/src/mediaEditor/plugins/mediaDimension/helper.js +189 -189
  621. package/src/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +561 -561
  622. package/src/mediaEditor/plugins/mediaDimension/style.css +141 -141
  623. package/src/mediaEditor/plugins/mediaDimension/tpl/mediaDimension.tpl +55 -55
  624. package/src/mediaEditor/tpl/editor.tpl +4 -4
  625. package/src/mediaplayer/css/player.css +7 -7
  626. package/src/mediaplayer/css/player.css.map +1 -1
  627. package/src/mediaplayer/players/html5.js +564 -564
  628. package/src/mediaplayer/players/youtube.js +323 -323
  629. package/src/mediaplayer/players.js +29 -29
  630. package/src/mediaplayer/readme.md +305 -305
  631. package/src/mediaplayer/scss/player.scss +569 -569
  632. package/src/mediaplayer/support.js +126 -126
  633. package/src/mediaplayer/tpl/audio.tpl +6 -6
  634. package/src/mediaplayer/tpl/player.tpl +37 -37
  635. package/src/mediaplayer/tpl/source.tpl +1 -1
  636. package/src/mediaplayer/tpl/video.tpl +6 -6
  637. package/src/mediaplayer/tpl/youtube.tpl +1 -1
  638. package/src/mediaplayer/utils/reminder.js +184 -184
  639. package/src/mediaplayer/utils/timeObserver.js +143 -143
  640. package/src/mediaplayer/youtubeManager.js +161 -161
  641. package/src/mediaplayer.js +1606 -1606
  642. package/src/mediasizer/mediasizer.tpl +55 -55
  643. package/src/mediasizer.js +635 -635
  644. package/src/modal.js +365 -365
  645. package/src/movableComponent.js +78 -78
  646. package/src/pageSizeSelector/tpl/pageSizeSelector.tpl +9 -9
  647. package/src/pageSizeSelector.js +107 -107
  648. package/src/pageStatus.js +147 -147
  649. package/src/pagination/css/pagination.css +7 -7
  650. package/src/pagination/css/pagination.css.map +1 -1
  651. package/src/pagination/paginationStrategy.js +53 -53
  652. package/src/pagination/providers/pages.js +161 -161
  653. package/src/pagination/providers/simple.js +74 -74
  654. package/src/pagination/providers/tpl/pages/page.tpl +1 -1
  655. package/src/pagination/providers/tpl/pages.tpl +8 -8
  656. package/src/pagination/providers/tpl/simple.tpl +7 -7
  657. package/src/pagination/scss/pagination.scss +111 -111
  658. package/src/pagination.js +237 -237
  659. package/src/previewer.js +300 -300
  660. package/src/progressbar.js +165 -165
  661. package/src/propertySelector/css/propertySelector.css +74 -0
  662. package/src/propertySelector/css/propertySelector.css.map +1 -0
  663. package/src/propertySelector/propertySelector.js +286 -0
  664. package/src/propertySelector/scss/propertySelector.scss +66 -0
  665. package/src/propertySelector/tpl/highlighted-text.tpl +1 -0
  666. package/src/propertySelector/tpl/property-description.tpl +13 -0
  667. package/src/propertySelector/tpl/property-selector.tpl +7 -0
  668. package/src/report/feedback.tpl +11 -11
  669. package/src/report/layout.tpl +10 -10
  670. package/src/report.js +184 -184
  671. package/src/resource/css/selector.css +7 -7
  672. package/src/resource/css/selector.css.map +1 -1
  673. package/src/resource/filters.js +208 -208
  674. package/src/resource/list.js +200 -200
  675. package/src/resource/scss/_filters.scss +26 -26
  676. package/src/resource/scss/_resource-list.scss +107 -107
  677. package/src/resource/scss/_resource-tree.scss +205 -205
  678. package/src/resource/scss/selector.scss +187 -187
  679. package/src/resource/selectable.js +322 -322
  680. package/src/resource/selector.js +871 -871
  681. package/src/resource/tpl/filters.tpl +2 -2
  682. package/src/resource/tpl/list.tpl +7 -7
  683. package/src/resource/tpl/listNode.tpl +4 -4
  684. package/src/resource/tpl/selector.tpl +46 -46
  685. package/src/resource/tpl/tree.tpl +4 -4
  686. package/src/resource/tpl/treeNode.tpl +30 -30
  687. package/src/resource/tree.js +400 -400
  688. package/src/resourcemgr/css/resourcemgr.css +7 -7
  689. package/src/resourcemgr/css/resourcemgr.css.map +1 -1
  690. package/src/resourcemgr/fileBrowser.js +381 -381
  691. package/src/resourcemgr/filePreview.js +73 -73
  692. package/src/resourcemgr/fileSelector.js +348 -348
  693. package/src/resourcemgr/scss/resourcemgr.scss +254 -254
  694. package/src/resourcemgr/tpl/fileSelect.tpl +39 -39
  695. package/src/resourcemgr/tpl/folder.tpl +11 -11
  696. package/src/resourcemgr/tpl/layout.tpl +84 -84
  697. package/src/resourcemgr/tpl/rootFolder.tpl +13 -13
  698. package/src/resourcemgr/util/updatePermissions.js +53 -53
  699. package/src/resourcemgr.js +216 -216
  700. package/src/scroller.js +94 -94
  701. package/src/scss/basic.scss +16 -16
  702. package/src/searchModal/advancedSearch.js +638 -601
  703. package/src/searchModal/css/advancedSearch.css +7 -14
  704. package/src/searchModal/css/advancedSearch.css.map +1 -1
  705. package/src/searchModal/css/searchModal.css +29 -8
  706. package/src/searchModal/css/searchModal.css.map +1 -1
  707. package/src/searchModal/scss/advancedSearch.scss +171 -177
  708. package/src/searchModal/scss/searchModal.scss +393 -375
  709. package/src/searchModal/tpl/advanced-search.tpl +9 -9
  710. package/src/searchModal/tpl/criteria-alias.tpl +1 -0
  711. package/src/searchModal/tpl/criteria-class-label.tpl +1 -0
  712. package/src/searchModal/tpl/criteria-label.tpl +1 -0
  713. package/src/searchModal/tpl/highlighted-text.tpl +1 -0
  714. package/src/searchModal/tpl/info-message.tpl +3 -3
  715. package/src/searchModal/tpl/invalid-criteria-warning.tpl +10 -10
  716. package/src/searchModal/tpl/layout.tpl +27 -25
  717. package/src/searchModal/tpl/list-checkbox-criterion.tpl +17 -12
  718. package/src/searchModal/tpl/list-select-criterion.tpl +12 -6
  719. package/src/searchModal/tpl/property-select-button.tpl +1 -0
  720. package/src/searchModal/tpl/results-container.tpl +1 -0
  721. package/src/searchModal/tpl/text-criterion.tpl +11 -6
  722. package/src/searchModal.js +761 -496
  723. package/src/selecter.js +43 -43
  724. package/src/stacker.js +133 -133
  725. package/src/switch/css/switch.css +7 -7
  726. package/src/switch/css/switch.css.map +1 -1
  727. package/src/switch/scss/switch.scss +83 -83
  728. package/src/switch/switch.js +195 -195
  729. package/src/switch/tpl/switch.tpl +7 -7
  730. package/src/tableModel.js +112 -112
  731. package/src/tabs/css/tabs.css +12 -12
  732. package/src/tabs/css/tabs.css.map +1 -1
  733. package/src/tabs/scss/tabs.scss +50 -50
  734. package/src/tabs/tpl/panel.tpl +3 -3
  735. package/src/tabs/tpl/tabs.tpl +10 -10
  736. package/src/tabs.js +528 -528
  737. package/src/taskQueue/css/taskQueue.css +7 -7
  738. package/src/taskQueue/css/taskQueue.css.map +1 -1
  739. package/src/taskQueue/scss/taskQueue.scss +47 -47
  740. package/src/taskQueue/status.js +228 -228
  741. package/src/taskQueue/table.js +350 -350
  742. package/src/taskQueue/taskQueue.js +33 -33
  743. package/src/taskQueue/taskQueueModel.js +548 -548
  744. package/src/taskQueue/tpl/statusMessage.tpl +7 -7
  745. package/src/taskQueue.js +218 -218
  746. package/src/taskQueueButton/css/taskable.css +7 -7
  747. package/src/taskQueueButton/css/taskable.css.map +1 -1
  748. package/src/taskQueueButton/css/treeButton.css +7 -7
  749. package/src/taskQueueButton/css/treeButton.css.map +1 -1
  750. package/src/taskQueueButton/scss/taskable.scss +4 -4
  751. package/src/taskQueueButton/scss/treeButton.scss +34 -34
  752. package/src/taskQueueButton/standardButton.js +108 -108
  753. package/src/taskQueueButton/taskable.js +202 -202
  754. package/src/taskQueueButton/tpl/report.tpl +5 -5
  755. package/src/taskQueueButton/tpl/treeButton.tpl +6 -6
  756. package/src/taskQueueButton/treeButton.js +109 -109
  757. package/src/themeLoader.js +252 -252
  758. package/src/themes.js +162 -162
  759. package/src/toggler.js +200 -200
  760. package/src/tooltip/default.tpl +3 -3
  761. package/src/tooltip.js +160 -160
  762. package/src/tooltipster.js +25 -25
  763. package/src/transformer.js +327 -327
  764. package/src/tristateCheckboxGroup/css/tristateCheckboxGroup.css +7 -7
  765. package/src/tristateCheckboxGroup/css/tristateCheckboxGroup.css.map +1 -1
  766. package/src/tristateCheckboxGroup/scss/tristateCheckboxGroup.scss +15 -15
  767. package/src/tristateCheckboxGroup/tpl/li.tpl +6 -6
  768. package/src/tristateCheckboxGroup.js +207 -207
  769. package/src/uploader/fileEntry.tpl +6 -6
  770. package/src/uploader/uploader.tpl +32 -32
  771. package/src/uploader.js +594 -594
  772. package/src/validator/Report.js +10 -10
  773. package/src/validator/Validator.js +108 -108
  774. package/src/validator/validators.js +220 -220
  775. package/src/validator.js +264 -264
  776. package/src/waitForMedia.js +82 -82
  777. package/src/waitingDialog/css/waitingDialog.css +7 -7
  778. package/src/waitingDialog/css/waitingDialog.css.map +1 -1
  779. package/src/waitingDialog/scss/waitingDialog.scss +34 -34
  780. 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
+ }