libreqda 1.2.0__py3-none-any.whl

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 (255) hide show
  1. CHANGELOG.md +481 -0
  2. libreqda-1.2.0.dist-info/LICENSE.txt +28 -0
  3. libreqda-1.2.0.dist-info/METADATA +131 -0
  4. libreqda-1.2.0.dist-info/RECORD +255 -0
  5. libreqda-1.2.0.dist-info/WHEEL +4 -0
  6. libreqda-1.2.0.dist-info/entry_points.txt +4 -0
  7. scripts/babelrc +8 -0
  8. scripts/dist/LICENSE.txt +33 -0
  9. scripts/dist/RELEASE.txt +23 -0
  10. scripts/dist/macos/libreqda.icns +0 -0
  11. scripts/dist/macos/macos.spec +43 -0
  12. scripts/dist/macos/plist.patch +10 -0
  13. scripts/dist/macos/taguette.icns +0 -0
  14. scripts/dist/macos/taguette_console_wrapper +4 -0
  15. scripts/dist/macos.sh +32 -0
  16. scripts/dist/pyinstaller_entrypoint.py +19 -0
  17. scripts/dist/windows/windows-installer.iss +51 -0
  18. scripts/dist/windows/windows.spec +39 -0
  19. scripts/edugain/worker_update_edugain_idps.py +89 -0
  20. scripts/edugain/worker_update_edugain_idps.sh +9 -0
  21. scripts/make_password_reset_link.py +56 -0
  22. scripts/make_theme.py +99 -0
  23. scripts/new_db_revision.sh +8 -0
  24. scripts/register_user.py +76 -0
  25. scripts/update_pot.sh +33 -0
  26. scripts/update_translations.sh +17 -0
  27. scripts/validate.py +140 -0
  28. scripts/wipe_and_disable_user.py +90 -0
  29. taguette/__init__.py +13 -0
  30. taguette/access_control.py +145 -0
  31. taguette/convert.py +623 -0
  32. taguette/database/__init__.py +177 -0
  33. taguette/database/base.py +9 -0
  34. taguette/database/copy.py +433 -0
  35. taguette/database/models.py +920 -0
  36. taguette/demodata.py +688 -0
  37. taguette/export.py +500 -0
  38. taguette/extract.py +287 -0
  39. taguette/import_codebook.py +110 -0
  40. taguette/l10n/de_DE/LC_MESSAGES/taguette_javascript.mo +0 -0
  41. taguette/l10n/de_DE/LC_MESSAGES/taguette_main.mo +0 -0
  42. taguette/l10n/el_EL/LC_MESSAGES/taguette_javascript.mo +0 -0
  43. taguette/l10n/el_GR/LC_MESSAGES/taguette_javascript.mo +0 -0
  44. taguette/l10n/el_GR/LC_MESSAGES/taguette_main.mo +0 -0
  45. taguette/l10n/en_US/LC_MESSAGES/taguette_javascript.mo +0 -0
  46. taguette/l10n/en_US/LC_MESSAGES/taguette_main.mo +0 -0
  47. taguette/l10n/es_ES/LC_MESSAGES/taguette_javascript.mo +0 -0
  48. taguette/l10n/es_ES/LC_MESSAGES/taguette_main.mo +0 -0
  49. taguette/l10n/fr_FR/LC_MESSAGES/taguette_javascript.mo +0 -0
  50. taguette/l10n/fr_FR/LC_MESSAGES/taguette_main.mo +0 -0
  51. taguette/l10n/id_ID/LC_MESSAGES/taguette_javascript.mo +0 -0
  52. taguette/l10n/id_ID/LC_MESSAGES/taguette_main.mo +0 -0
  53. taguette/l10n/it_IT/LC_MESSAGES/taguette_javascript.mo +0 -0
  54. taguette/l10n/it_IT/LC_MESSAGES/taguette_main.mo +0 -0
  55. taguette/l10n/nl_NL/LC_MESSAGES/taguette_javascript.mo +0 -0
  56. taguette/l10n/nl_NL/LC_MESSAGES/taguette_main.mo +0 -0
  57. taguette/l10n/pt_PT/LC_MESSAGES/taguette_javascript.mo +0 -0
  58. taguette/l10n/pt_PT/LC_MESSAGES/taguette_main.mo +0 -0
  59. taguette/l10n/sv_SE/LC_MESSAGES/taguette_javascript.mo +0 -0
  60. taguette/l10n/sv_SE/LC_MESSAGES/taguette_main.mo +0 -0
  61. taguette/loki_handler.py +107 -0
  62. taguette/main.py +649 -0
  63. taguette/migrations/env.py +72 -0
  64. taguette/migrations/script.py.mako +24 -0
  65. taguette/migrations/versions/09c662cd9483_make_documents_filename_not_null.py +39 -0
  66. taguette/migrations/versions/1b29e68900c1_add_user_roles.py +55 -0
  67. taguette/migrations/versions/1b96a606551d_update_user_to_have_a_display_name.py +34 -0
  68. taguette/migrations/versions/2467bab602b7_add_color_column_to_tags_table.py +33 -0
  69. taguette/migrations/versions/382db36f20f4_add_user_disabled.py +66 -0
  70. taguette/migrations/versions/43d6c240309d_remove_document_groups.py +81 -0
  71. taguette/migrations/versions/447d636f72c5_cascades.py +40 -0
  72. taguette/migrations/versions/491de2dc7cd7_add_indexes.py +51 -0
  73. taguette/migrations/versions/4c55b3b3ff30_user_login_length.py +58 -0
  74. taguette/migrations/versions/61a4abcc8769_user_with_parent_for_permissions.py +40 -0
  75. taguette/migrations/versions/6489b5f9cfb5_increase_password_hash_size.py +34 -0
  76. taguette/migrations/versions/679f625e6e6a_fix_unique_tags.py +59 -0
  77. taguette/migrations/versions/77c4543a5b9b_add_parent_id_column_to_collection_table.py +41 -0
  78. taguette/migrations/versions/7c94b7a31e69_add_is_archived_column_to_projets.py +39 -0
  79. taguette/migrations/versions/7f56de97e3c8_change_edugain_displayname_length.py +42 -0
  80. taguette/migrations/versions/807c0cc0ecf6_remove_invalid_hltags.py +43 -0
  81. taguette/migrations/versions/80b1cc9d4c22_add_user_email.py +35 -0
  82. taguette/migrations/versions/91ade71ccf4d_add_user_language.py +28 -0
  83. taguette/migrations/versions/955db2e523d1_add_collections_tables_and_associations_.py +60 -0
  84. taguette/migrations/versions/a1b2c3d4e5f6_add_user_hide_code_counts.py +28 -0
  85. taguette/migrations/versions/b23f3b7a638e_add_user_password_change_date.py +36 -0
  86. taguette/migrations/versions/b7c1e2d3f4a5_add_project_compact_layout.py +28 -0
  87. taguette/migrations/versions/bc8e0e0677e9_rename_command_fields.py +126 -0
  88. taguette/migrations/versions/bce44849c2f2_lowercase_logins.py +96 -0
  89. taguette/migrations/versions/ca076140922f_update_user_for_external_logins.py +32 -0
  90. taguette/migrations/versions/d86c45d7aa8a_add_document_direction.py +127 -0
  91. taguette/migrations/versions/db5e31a0233d_remove_blank_highlights.py +88 -0
  92. taguette/migrations/versions/dd37d9027a24_unique_tags.py +68 -0
  93. taguette/migrations/versions/de678cef2a94_add_project_id_to_collections_and_.py +41 -0
  94. taguette/migrations/versions/e459991d960e_add_table_edugainidps.py +39 -0
  95. taguette/migrations/versions/e4cf92942271_add_index.py +29 -0
  96. taguette/migrations/versions/e4e090a5b511_remove_commands_document_id_fk.py +38 -0
  97. taguette/migrations/versions/e9a9f1c77971_add_favorite_column_to_document.py +32 -0
  98. taguette/migrations/versions/ecb4065de575_don_t_use_server_default_now_timezone_.py +44 -0
  99. taguette/migrations/versions/fbdc29566f62_edugain_fix_field_type.py +42 -0
  100. taguette/migrations/versions/fdc6eb54b034_add_parent_id_column_to_tags_table.py +31 -0
  101. taguette/static/README.md +34 -0
  102. taguette/static/alt_templates/.gitkeep +0 -0
  103. taguette/static/assets/test/logos/monsters-university-logo.png +0 -0
  104. taguette/static/css/custom.css +1696 -0
  105. taguette/static/css/fa-all.min.css +5 -0
  106. taguette/static/css/themed.css +3 -0
  107. taguette/static/css/themed.css.map +25 -0
  108. taguette/static/css/vendor/bootstrap-treeview.min.css +1 -0
  109. taguette/static/css/vendor/bootstrap.min.css +6 -0
  110. taguette/static/css/vendor/font/context-menu-icons.eot +0 -0
  111. taguette/static/css/vendor/font/context-menu-icons.ttf +0 -0
  112. taguette/static/css/vendor/font/context-menu-icons.woff +0 -0
  113. taguette/static/css/vendor/font/context-menu-icons.woff2 +0 -0
  114. taguette/static/css/vendor/hierarchy-select.min.css +1 -0
  115. taguette/static/css/vendor/introjs.min.css +2 -0
  116. taguette/static/css/vendor/jquery.contextMenu.min.css +16 -0
  117. taguette/static/favicon.ico +0 -0
  118. taguette/static/js/codebook-viz.js +428 -0
  119. taguette/static/js/collection/context-menu.js +180 -0
  120. taguette/static/js/collection/hierarchy-select.js +71 -0
  121. taguette/static/js/collection/manager.js +217 -0
  122. taguette/static/js/document/context-menu.js +92 -0
  123. taguette/static/js/document/custom-select.js +82 -0
  124. taguette/static/js/document/manager.js +943 -0
  125. taguette/static/js/document/treeview.js +593 -0
  126. taguette/static/js/highlight/README.md +18 -0
  127. taguette/static/js/highlight/crud.js +119 -0
  128. taguette/static/js/highlight/extracts.js +497 -0
  129. taguette/static/js/highlight/loader.js +109 -0
  130. taguette/static/js/highlight/renderer.js +405 -0
  131. taguette/static/js/login.js +15 -0
  132. taguette/static/js/permissions.js +153 -0
  133. taguette/static/js/polling-event.js +145 -0
  134. taguette/static/js/project/intro.js +524 -0
  135. taguette/static/js/project/manager.js +0 -0
  136. taguette/static/js/project/search.js +124 -0
  137. taguette/static/js/tag/context-menu.js +133 -0
  138. taguette/static/js/tag/explore-codes.js +400 -0
  139. taguette/static/js/tag/hierarchy-select.js +69 -0
  140. taguette/static/js/tag/manager.js +545 -0
  141. taguette/static/js/tag/treeview.js +613 -0
  142. taguette/static/js/taguette.js +1790 -0
  143. taguette/static/js/utilities.js +714 -0
  144. taguette/static/js/vendor/bootstrap-treeview-custum.js +421 -0
  145. taguette/static/js/vendor/bootstrap-treeview-custum.min.js +1 -0
  146. taguette/static/js/vendor/bootstrap-treeview-default.min.js +1 -0
  147. taguette/static/js/vendor/bootstrap.bundle.min.js +7 -0
  148. taguette/static/js/vendor/bootstrap.bundle.min.js.map +1 -0
  149. taguette/static/js/vendor/hierarchy-select.min.js +1 -0
  150. taguette/static/js/vendor/introjs.min.js +17 -0
  151. taguette/static/js/vendor/jquery-3.7.1.js +2 -0
  152. taguette/static/js/vendor/jquery.contextMenu.min.js +2 -0
  153. taguette/static/libreqda.ico +0 -0
  154. taguette/static/logo-h.png +0 -0
  155. taguette/static/logo.png +0 -0
  156. taguette/static/logo_blanc.png +0 -0
  157. taguette/static/missing.png +0 -0
  158. taguette/static/robots.txt +2 -0
  159. taguette/static/scss/components/_badge.scss +42 -0
  160. taguette/static/scss/components/_button.scss +169 -0
  161. taguette/static/scss/components/_checkbox.scss +40 -0
  162. taguette/static/scss/components/_context_menu.scss +28 -0
  163. taguette/static/scss/components/_custom_select.scss +150 -0
  164. taguette/static/scss/components/_dropdown.scss +43 -0
  165. taguette/static/scss/components/_form.scss +54 -0
  166. taguette/static/scss/components/_modal.scss +57 -0
  167. taguette/static/scss/components/_project_card.scss +163 -0
  168. taguette/static/scss/components/_tag.scss +308 -0
  169. taguette/static/scss/components/_treeview.scss +76 -0
  170. taguette/static/scss/custom.scss +809 -0
  171. taguette/static/scss/layout/_credits.scss +62 -0
  172. taguette/static/scss/layout/_footer.scss +6 -0
  173. taguette/static/scss/layout/_navigation.scss +165 -0
  174. taguette/static/scss/layout/_sidebar.scss +37 -0
  175. taguette/static/scss/themes/default/_variables.scss +103 -0
  176. taguette/static/scss/themes/default/theme.css +3 -0
  177. taguette/static/scss/themes/default/theme.css.map +26 -0
  178. taguette/static/scss/themes/default/theme.scss +4 -0
  179. taguette/static/webfonts/BadScript-OFL.txt +94 -0
  180. taguette/static/webfonts/BadScript.ttf +0 -0
  181. taguette/static/webfonts/Montserrat-Light.ttf +0 -0
  182. taguette/static/webfonts/Montserrat-OFL.txt +93 -0
  183. taguette/static/webfonts/fa-brands-400.eot +0 -0
  184. taguette/static/webfonts/fa-brands-400.svg +1184 -0
  185. taguette/static/webfonts/fa-brands-400.ttf +0 -0
  186. taguette/static/webfonts/fa-brands-400.woff +0 -0
  187. taguette/static/webfonts/fa-brands-400.woff2 +0 -0
  188. taguette/static/webfonts/fa-regular-400.eot +0 -0
  189. taguette/static/webfonts/fa-regular-400.svg +467 -0
  190. taguette/static/webfonts/fa-regular-400.ttf +0 -0
  191. taguette/static/webfonts/fa-regular-400.woff +0 -0
  192. taguette/static/webfonts/fa-regular-400.woff2 +0 -0
  193. taguette/static/webfonts/fa-solid-900.eot +0 -0
  194. taguette/static/webfonts/fa-solid-900.svg +2618 -0
  195. taguette/static/webfonts/fa-solid-900.ttf +0 -0
  196. taguette/static/webfonts/fa-solid-900.woff +0 -0
  197. taguette/static/webfonts/fa-solid-900.woff2 +0 -0
  198. taguette/templates/alt_templates/.gitkeep +0 -0
  199. taguette/templates/archived_projects.html +50 -0
  200. taguette/templates/auth/account.html +74 -0
  201. taguette/templates/auth/cilogon.login.html +89 -0
  202. taguette/templates/auth/email_reset_password.html +5 -0
  203. taguette/templates/auth/external_account.html +60 -0
  204. taguette/templates/auth/login.html +144 -0
  205. taguette/templates/auth/reset_password.html +27 -0
  206. taguette/templates/auth/token_needed.html +7 -0
  207. taguette/templates/base.html +209 -0
  208. taguette/templates/bug_report.html +158 -0
  209. taguette/templates/cookies_prompt.html +15 -0
  210. taguette/templates/downloads.html +127 -0
  211. taguette/templates/email_bug_report.txt +9 -0
  212. taguette/templates/email_reset_password.txt +5 -0
  213. taguette/templates/error.html +8 -0
  214. taguette/templates/export_codebook.html +20 -0
  215. taguette/templates/export_document.html +20 -0
  216. taguette/templates/export_highlights.html +47 -0
  217. taguette/templates/index.html +347 -0
  218. taguette/templates/maintenance.html +8 -0
  219. taguette/templates/messages.js +74 -0
  220. taguette/templates/modal/change_document.html +68 -0
  221. taguette/templates/modal/create_collection.html +100 -0
  222. taguette/templates/modal/create_document.html +84 -0
  223. taguette/templates/modal/create_highlight.html +38 -0
  224. taguette/templates/modal/create_tag.html +73 -0
  225. taguette/templates/modal/explore_codes.html +58 -0
  226. taguette/templates/modal/manage_collaborators.html +44 -0
  227. taguette/templates/modal/merge_tag.html +49 -0
  228. taguette/templates/new_password.html +32 -0
  229. taguette/templates/partials/alert.html +16 -0
  230. taguette/templates/partials/codebook_viz_modal.html +34 -0
  231. taguette/templates/partials/confirm.html +17 -0
  232. taguette/templates/partials/document_toolbar.html +37 -0
  233. taguette/templates/partials/hlinfo.html +13 -0
  234. taguette/templates/partials/project_card.html +44 -0
  235. taguette/templates/partials/project_card_archived.html +41 -0
  236. taguette/templates/partials/project_sidebar.html +99 -0
  237. taguette/templates/partials/spinner.html +11 -0
  238. taguette/templates/permissions.html +54 -0
  239. taguette/templates/project.html +288 -0
  240. taguette/templates/project_delete.html +17 -0
  241. taguette/templates/project_import.html +122 -0
  242. taguette/templates/project_import_codebook.html +34 -0
  243. taguette/templates/project_import_codebook_confirm.html +49 -0
  244. taguette/templates/project_new.html +31 -0
  245. taguette/templates/tos.html +8 -0
  246. taguette/templates/trans.js +7 -0
  247. taguette/templates/welcome.html +15 -0
  248. taguette/utils.py +177 -0
  249. taguette/validate.py +141 -0
  250. taguette/version_check.py +102 -0
  251. taguette/web/__init__.py +252 -0
  252. taguette/web/api.py +1835 -0
  253. taguette/web/base.py +864 -0
  254. taguette/web/export.py +338 -0
  255. taguette/web/views.py +1618 -0
@@ -0,0 +1,613 @@
1
+ const treeview_tags = $('#treeview-tags');
2
+ const treeview_tags_highlight = $('#treeview-tags-highlight');
3
+
4
+ const DEFAULT_LEVEL_OPTION = 5;
5
+ const CUSTOM_LEVEL_OPTION = 3;
6
+
7
+ let selectedTags = [];
8
+ let highlightSelectedTags = [];
9
+
10
+ let isExplorationMode = false;
11
+ let isProgrammaticSelection = false;
12
+
13
+ function getNode(treeview, nodeId) {
14
+ return treeview.treeview('getNode', parseInt(nodeId));
15
+ }
16
+
17
+ function getAllNodes(treeview) {
18
+ return treeview.treeview('getEnabled');
19
+ }
20
+
21
+ function getAllSelectedNodes(treeview) {
22
+ return treeview.treeview('getSelected');
23
+ }
24
+
25
+ function treeviewHasChildren(treeview) {
26
+
27
+ const data = getAllNodes(treeview);
28
+
29
+ // loop to check if nodes has children
30
+ for (let i = 0; i < data.length; i++) {
31
+ if (data[i].nodes && data[i].nodes.length > 0) {
32
+ return true;
33
+ }
34
+ }
35
+
36
+ return false;
37
+ }
38
+
39
+ function selectNode(treeview, nodeId) {
40
+ const node = getNode(treeview, nodeId);
41
+ if (node && node.state && !node.state.selected) {
42
+ isProgrammaticSelection = true;
43
+ treeview.treeview('selectNode', [node.nodeId, {silent: false}]);
44
+ isProgrammaticSelection = false;
45
+ } else {
46
+ log(`Node with nodeId ${nodeId} not found or already selected.`);
47
+ }
48
+ }
49
+
50
+ function unselectNode(treeview, nodeId) {
51
+ const node = getNode(treeview, nodeId);
52
+ if (node.state.selected) {
53
+ treeview.treeview('unselectNode', [node.nodeId, {silent: false}]);
54
+ }
55
+ }
56
+
57
+ function deselectAllNodes(treeview) {
58
+ const allNodes = getAllNodes(treeview);
59
+ allNodes.forEach(node => {
60
+ if (node.state.selected) {
61
+ treeview.treeview('unselectNode', [node.nodeId, {silent: false}]);
62
+ }
63
+ });
64
+ }
65
+
66
+ function toggleNodeSelection(treeview, nodeId, select = true) {
67
+ const node = getNode(treeview, nodeId);
68
+ //check if change is needed
69
+ if (node.state.selected !== select) {
70
+ const action = select ? 'selectNode' : 'unselectNode';
71
+ treeview.treeview(action, [node.nodeId, {silent: false}]);
72
+ }
73
+ }
74
+
75
+ function selectNodeAndChildren(treeview, nodeId) {
76
+ isExplorationMode = true;
77
+ isProgrammaticSelection = true;
78
+ initTagsTreeview();
79
+ setTimeout(() => {
80
+ expandNode(treeview, nodeId);
81
+ const node = getNode(treeview, nodeId);
82
+ selectSingleNodeAndChildren(treeview, node);
83
+ }, 10);
84
+
85
+ }
86
+
87
+ function unselectNodeAndChildren(treeview, nodeId) {
88
+ setTimeout(() => {
89
+ const node = getNode(treeview, nodeId);
90
+ unselectSingleNodeAndChildren(treeview, node);
91
+ }, 10);
92
+ }
93
+
94
+ function checkNodeHasSelectedChildren(node) {
95
+ if (node.nodes && node.nodes.length > 0) {
96
+ return node.nodes.some(function (childNode) {
97
+ if (childNode.state.selected) {
98
+ return true;
99
+ }
100
+ return checkNodeHasSelectedChildren(childNode);
101
+ });
102
+ }
103
+ return false;
104
+ }
105
+
106
+ function checkNodeHasUnselectedChildren(node) {
107
+ if (node.nodes && node.nodes.length > 0) {
108
+ return node.nodes.some(function (childNode) {
109
+ if (!childNode.state.selected) {
110
+ return true;
111
+ }
112
+ return checkNodeHasUnselectedChildren(childNode);
113
+ });
114
+ }
115
+ return false;
116
+ }
117
+
118
+ function selectSingleNodeAndChildren(treeview, node) {
119
+ if (!node.state.selected) {
120
+ treeview.treeview('selectNode', [node.nodeId, {silent: false}]);
121
+ if (node.nodes && node.nodes.length > 0) {
122
+ node.nodes.forEach(function (childNode) {
123
+ selectSingleNodeAndChildren(treeview, childNode);
124
+ });
125
+ }
126
+ }
127
+ }
128
+ function unselectSingleNodeAndChildren(treeview, node) {
129
+ if (node.state.selected) {
130
+ treeview.treeview('unselectNode', [node.nodeId, {silent: false}]);
131
+ if (node.nodes && node.nodes.length > 0) {
132
+ node.nodes.forEach(function (childNode) {
133
+ unselectSingleNodeAndChildren(treeview, childNode);
134
+ });
135
+ }
136
+ }
137
+ }
138
+
139
+
140
+ function expandNodeParent(tagParent) {
141
+ const rootParentId = findRootParent(tagParent);
142
+ const nodeId = getNodeIdFromTreeviewByTagId(treeview_tags, rootParentId);
143
+ expandNode(treeview_tags, nodeId);
144
+ }
145
+
146
+ function expandNode(treeview, nodeId) {
147
+ const node = getNode(treeview, nodeId);
148
+ if (!node.state.expanded) {
149
+ treeview.treeview('expandNode', [parseInt(nodeId, 10),
150
+ {levels: DEFAULT_LEVEL_OPTION, silent: false}]);
151
+ treeview.data('expanded', true);
152
+ }
153
+ }
154
+
155
+ function collapseNode(treeview, nodeId) {
156
+ const node = getNode(treeview, nodeId);
157
+ if (node.state.expanded) {
158
+ treeview.treeview('collapseNode', [parseInt(nodeId, 10),
159
+ {silent: false}]);
160
+ }
161
+ }
162
+
163
+ function toggleNodeExpansion(treeview, nodeId, expand = true) {
164
+ const node = getNode(treeview, nodeId);
165
+ // check action is expand or collapse
166
+ const actionNeeded = expand ? !node.state.expanded : node.state.expanded;
167
+ if (actionNeeded) {
168
+ const action = expand ? 'expandNode' : 'collapseNode';
169
+ treeview.treeview(action, [node.nodeId, {levels: DEFAULT_LEVEL_OPTION, silent: false}]);
170
+ }
171
+ }
172
+
173
+ function expandAllNodes(treeview) {
174
+ if (treeviewHasChildren(treeview) && !treeview.data('expanded')) {
175
+ treeview.treeview('expandAll', {levels: DEFAULT_LEVEL_OPTION, silent: false});
176
+ treeview.data('expanded', true);
177
+ }
178
+ }
179
+
180
+ function collapseAllNodes(treeview) {
181
+ if (treeview.length && treeview.data('expanded')) {
182
+ treeview.treeview('collapseAll', {silent: false});
183
+ treeview.data('expanded', false);
184
+ }
185
+ }
186
+
187
+ // custom treeview function
188
+ function convertToTreeNode(data, includeTags) {
189
+
190
+ function getNodeCount(item) {
191
+ if (item.children.length > 0 && item.count === 0 && item.count_document === 0) {
192
+ return { totalCount: '-', totalDocumentCount: '-' };
193
+ } else {
194
+ return {
195
+ totalCount: item.count > 100 ? '+100' : item.count.toString(),
196
+ totalDocumentCount: item.count_document > 100 ? '+100' : item.count_document.toString(),
197
+ rawCount: item.count,
198
+ rawDocumentCount: item.count_document
199
+ };
200
+ }
201
+ }
202
+
203
+ return data.map(item => {
204
+ const node = {
205
+ id: item.id,
206
+ text: item.path,
207
+ selectable: true,
208
+ nodes: item.children.length > 0 ? convertToTreeNode(item.children, includeTags) : undefined
209
+ };
210
+
211
+ if (includeTags) {
212
+ let counts = getNodeCount(item);
213
+ node.tags = [`${counts.totalDocumentCount}`, `${counts.totalCount}`];
214
+ node.rawCounts = [counts.rawDocumentCount, counts.rawCount];
215
+ }
216
+
217
+ return node;
218
+ });
219
+ }
220
+ function initTagsTreeview(filterTags = null, callback = null) {
221
+ const expandedIds = saveTagExpandedState();
222
+
223
+ if (filterTags == null) {
224
+ filterTags = tags
225
+ }
226
+
227
+ const treeData = convertToTreeNode(filterTags, true);
228
+
229
+ const customNodeOptions = customNodeRow(true, false, compactLayout);
230
+
231
+ treeview_tags.toggleClass('compact-layout', compactLayout);
232
+
233
+ const options = {
234
+ data: treeData,
235
+ ...customNodeOptions,
236
+ customizeBadges: function(container, node, tree) {
237
+ container.addClass('tag-badges');
238
+ if (hideCodeCounts) {
239
+ return;
240
+ }
241
+ if (node.tags && node.tags.length > 0) {
242
+ const rawCounts = node.rawCounts || [];
243
+ const docTitle = gettext('Documents') + (rawCounts[0] > 100 ? ': ' + rawCounts[0] : '');
244
+ const hlTitle = gettext('Highlights') + (rawCounts[1] > 100 ? ': ' + rawCounts[1] : '');
245
+ if (compactLayout) {
246
+ const tag = findTag(node.id);
247
+ container.append($('<span>', {
248
+ class: 'badge badge-secondary badge-compact',
249
+ title: hlTitle,
250
+ text: tag ? tag.count : 0
251
+ }));
252
+ } else {
253
+ container.append($('<span>', {
254
+ class: 'badge badge-primary badge-fixed-width',
255
+ title: docTitle,
256
+ text: node.tags[0]
257
+ }));
258
+ if (node.tags.length > 1) {
259
+ container.append($('<span>', {
260
+ class: 'badge badge-secondary badge-fixed-width',
261
+ title: hlTitle,
262
+ text: node.tags[1]
263
+ }));
264
+ }
265
+ }
266
+ }
267
+ },
268
+ showBorder: true,
269
+ levels: 1,
270
+ showTags: true
271
+ };
272
+
273
+ if (isExplorationMode) {
274
+ options.multiSelect = true;
275
+ }
276
+
277
+ treeview_tags.treeview(options);
278
+
279
+ if (callback) {
280
+ setTimeout(() => {
281
+ if (expandedIds.length > 0) {
282
+ restoreTagExpandedState(expandedIds, callback);
283
+ } else {
284
+ callback();
285
+ }
286
+ }, 50);
287
+ } else if (expandedIds.length > 0) {
288
+ restoreTagExpandedState(expandedIds, null);
289
+ } else {
290
+ treeview_tags.treeview('collapseAll');
291
+ treeview_tags.data('expanded', false);
292
+ }
293
+
294
+ treeview_tags.on('nodeSelected', function (event, node) {
295
+ if (isExplorationMode && !isProgrammaticSelection){
296
+ const collapse = node.parentId;
297
+ emptyTagSelection(true, !collapse);
298
+ setTimeout(() => {
299
+ selectNode(treeview_tags, node.nodeId);
300
+ }, 10);
301
+ }
302
+ addTagsHighlightSelection(node);
303
+ loadTagsHighlights();
304
+ });
305
+
306
+ treeview_tags.on('nodeUnselected nodeCollapsed nodeExpanded', function (event, node) {
307
+
308
+ if (event.type === 'nodeUnselected') {
309
+ selectedTags = selectedTags.filter(function (tag) {
310
+ return tag.id !== node.id;
311
+ });
312
+ loadTagsHighlights();
313
+ }
314
+
315
+ // Add a delay to ensure the treeview recreation is finished
316
+ setTimeout(function () {
317
+ addTagDragDrop();
318
+ }, 10);
319
+ });
320
+
321
+ addTagDragDrop();
322
+ emptyTagSelection();
323
+ }
324
+
325
+ function initTagsHighlightTreeview(filterTags = null) {
326
+
327
+ if (filterTags == null) {
328
+ filterTags = tags
329
+ }
330
+
331
+ const treeData = convertToTreeNode(filterTags, true);
332
+
333
+ const customNodeOptions = customNodeRow(false, true);
334
+
335
+ const options = {
336
+ data: treeData,
337
+ ...customNodeOptions,
338
+ showBorder: true,
339
+ levels: CUSTOM_LEVEL_OPTION,
340
+ multiSelect: true,
341
+ };
342
+
343
+ treeview_tags_highlight.treeview(options);
344
+
345
+ treeview_tags_highlight.on('nodeSelected', function (event, node) {
346
+ if (!highlightSelectedTags.some(item => item === node.id)) {
347
+ highlightSelectedTags.push(node.id);
348
+ showHighlightTagSelection();
349
+ }
350
+ });
351
+
352
+ treeview_tags_highlight.on('nodeUnselected', function (event, node) {
353
+ savedSelections = savedSelections.filter(id => id !== node.id);
354
+ highlightSelectedTags = highlightSelectedTags.filter(function (item) {
355
+ return item !== node.id;
356
+ });
357
+ showHighlightTagSelection();
358
+ });
359
+ }
360
+
361
+ function addTagsHighlightSelection(data)
362
+ {
363
+ if (!selectedTags.some(tag => tag.id === data.id)) {
364
+ selectedTags.push({
365
+ id: data.id,
366
+ path: data.text,
367
+ condition: selectCondition
368
+ });
369
+ }
370
+ }
371
+ function loadTagsHighlights() {
372
+
373
+ let selectedTagPathsString = '';
374
+
375
+ selectedTags.forEach((tag, index) => {
376
+ let conditionSymbol = '';
377
+ if (index > 0) {
378
+ conditionSymbol = getConditionalSymbol(tag.condition);
379
+ }
380
+ selectedTagPathsString += `${conditionSymbol}${tag.path}`;
381
+ });
382
+
383
+ const url = `${base_path}/project/${project_id}/highlights/${encodeURIComponent(selectedTagPathsString)}`;
384
+ window.history.pushState({tag_path: selectedTagPathsString}, `Tag ${selectedTagPathsString}`, url);
385
+
386
+ const tags_return = $('#tag-selection-return'),
387
+ tags_return_link = $('a', tags_return);
388
+ tags_return_link.unbind('click').hide();
389
+ if (selectedTags.length > 0) {
390
+ loadTag(selectedTagPathsString);
391
+ tags_return_link.attr('href', url);
392
+ tags_return_link.bind('click', function(e){
393
+ e.preventDefault();
394
+ if(window.history.state.tag_path === "") {
395
+ loadTag('');
396
+ selectAllTag();
397
+ } else {
398
+ window.history.back();
399
+ }
400
+ tags_return_link.hide();
401
+ });
402
+ } else {
403
+ // reload the last document
404
+ loadDocument(current_document)
405
+ }
406
+ setTimeout(function () {
407
+ addTagDragDrop();
408
+ showTagSelection();
409
+ }, 10);
410
+ }
411
+
412
+ let selectCondition = 'or';
413
+
414
+ function selectWithCondition(treeview, nodeId, condition) {
415
+ selectCondition = condition;
416
+
417
+ if(isExplorationMode) {
418
+ selectNode(treeview, nodeId);
419
+ } else {
420
+ isExplorationMode = true;
421
+ const selectedNodes = getAllSelectedNodes(treeview);
422
+ const node = getNode(treeview, nodeId);
423
+ initTagsTreeview();
424
+ if (node.parentId) {
425
+ const tag = findTag(node.id);
426
+ expandNodeParent(tag.parent);
427
+ }
428
+ selectedNodes.forEach(node => {
429
+ selectNode(treeview, node.nodeId);
430
+ });
431
+ selectNode(treeview, nodeId);
432
+ }
433
+
434
+ selectCondition = 'or';
435
+ }
436
+
437
+ function getConditionalSymbol(condition) {
438
+ if (condition === 'and') {
439
+ return '∩';
440
+ } else if (condition === 'or') {
441
+ return '∪';
442
+ } else if (condition === 'not') {
443
+ return '¬';
444
+ }
445
+ return '';
446
+ }
447
+
448
+ function getTagIdFromTreeviewNode(treeview, nodeId) {
449
+ const allNodes = getAllNodes(treeview);
450
+ const node = allNodes.find(item => item.nodeId === parseInt(nodeId))
451
+ if (node) {
452
+ return node.id;
453
+ } else {
454
+ log(`Node with nodeId ${nodeId} not found in treeview.`);
455
+ return null;
456
+ }
457
+ }
458
+
459
+ function getNodeIdFromTreeviewByTagId(treeview, tagId) {
460
+ const allNodes = getAllNodes(treeview);
461
+ const node = allNodes.find((node) => node.id === parseInt(tagId));
462
+ if (node) {
463
+ return node.nodeId;
464
+ } else {
465
+ log(`Node with tagId ${tagId} not found in treeview.`);
466
+ return null;
467
+ }
468
+ }
469
+
470
+ function addTagDragDrop() {
471
+
472
+ $('.node-treeview-tags').on('dragstart', function (event) {
473
+ const nodeId = $(this).data('nodeid');
474
+ event.originalEvent.dataTransfer.setData('text', nodeId);
475
+ });
476
+
477
+ $(".node-treeview-tags").each(function (index, element) {
478
+
479
+ $(element).off('drop');
480
+ $(element).off('dragover');
481
+
482
+ $(element).on('dragover', function (event) {
483
+ event.preventDefault();
484
+ });
485
+
486
+ $(element).on('drop', function (event) {
487
+ event.preventDefault();
488
+
489
+ const tagId = getTagIdFromTreeviewNode(treeview_tags, $(element).data("nodeid"));
490
+ const draggedElementNodeId = event.originalEvent.dataTransfer.getData('text');
491
+
492
+ if (!isNaN(parseInt(draggedElementNodeId))) {
493
+ const tagChildId = getTagIdFromTreeviewNode(treeview_tags, draggedElementNodeId);
494
+ if (tagId === tagChildId) return;
495
+ updateTagParent(tagChildId, tagId);
496
+ } else {
497
+ saveHighlightDragDrop(tagId);
498
+ }
499
+ });
500
+ });
501
+ }
502
+
503
+ function customNodeRow(isTagContextMenu = false, isHighlightTag = false, showCountsInTooltip = false, testIdPrefix = null) {
504
+
505
+ return {
506
+ customContent: true,
507
+ nodeContentSelector: 'a',
508
+ nodeContentAttributes: {
509
+ href: '#',
510
+ class: 'tag-text',
511
+ style: 'text-decoration: none;'
512
+ },
513
+ customizeNode: function(element, node, tree) {
514
+ element.attr('id', `${node.nodeId}`);
515
+ element.attr('data-tag-id', `${node.id}`);
516
+ const tooltipText = gettext('Click to explore • Right-click for options');
517
+ let tooltip = node.text;
518
+ if (showCountsInTooltip && !hideCodeCounts) {
519
+ const tag = findTag(node.id);
520
+ if (tag) {
521
+ tooltip += `\n${gettext('Documents')}: ${tag.count_document} • ${gettext('Highlights')}: ${tag.count}`;
522
+ }
523
+ }
524
+ tooltip += `\n\n${tooltipText}`;
525
+ element.attr('title', tooltip);
526
+ element.attr('data-test-id', testIdPrefix ? `${testIdPrefix}${node.id}`
527
+ : (isHighlightTag ? `tag-node-highlight-${node.id}` : `tag-node-${node.id}`));
528
+ if (isTagContextMenu) {
529
+ element.addClass('tag-context-menu');
530
+ }
531
+ },
532
+ addVerticalLine: function(element, node, tree) {
533
+ const color = findTag(node.id).color;
534
+ $('<span>', {
535
+ class: `vertical-line-${node.id}`,
536
+ css: {
537
+ display: 'inline-block',
538
+ position: 'absolute',
539
+ left: '0',
540
+ top: '0',
541
+ bottom: '0',
542
+ width: '5px',
543
+ backgroundColor: color,
544
+ }
545
+ }).prependTo(element);
546
+ }
547
+ };
548
+ }
549
+
550
+ $(document).ready(function() {
551
+ const sections = [
552
+ {
553
+ title: gettext('Right-click on a code:'),
554
+ items: [
555
+ gettext('Add child code'),
556
+ gettext('Edit code'),
557
+ gettext('Merge codes'),
558
+ gettext('Delete code')
559
+ ]
560
+ },
561
+ {
562
+ title: gettext('Click on a code:'),
563
+ items: [
564
+ gettext('Filter highlights by this code')
565
+ ]
566
+ },
567
+ {
568
+ title: gettext('Explore codes button:'),
569
+ items: [
570
+ gettext('Build AND / OR / NOT queries')
571
+ ]
572
+ }
573
+ ];
574
+
575
+ initHelpPopover('tag-treeview-help-icon', sections);
576
+ initHelpPopover('dropdown-tips-btn', sections);
577
+ });
578
+
579
+ function saveTagExpandedState() {
580
+ if (!treeview_tags.length) return [];
581
+ if (!treeview_tags.data('treeview')) return [];
582
+
583
+ try {
584
+ const expandedNodes = treeview_tags.treeview('getExpanded');
585
+ return expandedNodes.map(node => node.id).filter(id => id);
586
+ } catch (e) {
587
+ return [];
588
+ }
589
+ }
590
+
591
+ function restoreTagExpandedState(expandedIds, callback) {
592
+ if (!expandedIds || expandedIds.length === 0) {
593
+ if (callback) setTimeout(callback, 50);
594
+ return;
595
+ }
596
+
597
+ setTimeout(() => {
598
+ const allNodes = treeview_tags.treeview('getEnabled');
599
+ expandedIds.forEach(id => {
600
+ const node = allNodes.find(n => n.id === id);
601
+ if (node && !node.state.expanded) {
602
+ treeview_tags.treeview('expandNode', [node.nodeId, {silent: true}]);
603
+ }
604
+ });
605
+ addTagDragDrop();
606
+
607
+ if (callback) {
608
+ setTimeout(() => {
609
+ callback();
610
+ }, 150);
611
+ }
612
+ }, 50);
613
+ }