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,69 @@
1
+ const tag_hierarchy_select = $('#tag-hierarchy-select');
2
+ const tag_hierarchy_merge_select = $('#tag-hierarchy-merge-select');
3
+ const hs_menu_inner = $('.hs-menu-inner');
4
+ const hs_menu_inner_merge = $('.hs-menu-inner');
5
+
6
+ const DEFAULT_LEVEL = 1;
7
+
8
+ // Creates a default 'None' option
9
+ function addDefaultValue(parentContainer) {
10
+ const li = $("<li>");
11
+ const a = $("<a>").addClass("dropdown-item")
12
+ .attr("data-value", '')
13
+ .attr("data-default-selected", '')
14
+ .attr("data-level", DEFAULT_LEVEL)
15
+ .text(gettext('None'));
16
+ li.append(a);
17
+
18
+ parentContainer.append(li);
19
+ }
20
+
21
+
22
+ // Recursively generates items from tag hierarchy data
23
+ function generateTagHierarchyItem(data, parentContainer, level) {
24
+
25
+ $.each(data, function (index, item) {
26
+ const li = $("<li>");
27
+ const a = $("<a>").addClass("dropdown-item")
28
+ .attr("data-value", item.id)
29
+ .attr("data-level", level)
30
+ .attr("href", "#")
31
+ .text(item.path);
32
+
33
+ li.append(a);
34
+
35
+ parentContainer.append(li);
36
+
37
+ if (item.children && item.children.length > 0) {
38
+ generateTagHierarchyItem(item.children, parentContainer, level + DEFAULT_LEVEL);
39
+ }
40
+ });
41
+ }
42
+
43
+ // Init tag hierarchy select dropdown for create tag
44
+ function initTagHierarchySelect() {
45
+
46
+ initHierarchySelect(hs_menu_inner, tag_hierarchy_select, true);
47
+ }
48
+
49
+ // Init tag hierarchy select dropdown for the merge form
50
+ function initMergeTagHierarchySelect() {
51
+
52
+ initHierarchySelect(hs_menu_inner_merge, tag_hierarchy_merge_select, true);
53
+ }
54
+
55
+ // Sets up a hierarchy select dropdown
56
+ function initHierarchySelect(menu, select, withDefault = true) {
57
+
58
+ menu.html('');
59
+
60
+ if(withDefault) {
61
+ addDefaultValue(menu)
62
+ }
63
+
64
+ generateTagHierarchyItem(tags, menu, DEFAULT_LEVEL);
65
+
66
+ select.hierarchySelect({
67
+ width: 'auto'
68
+ });
69
+ }
@@ -0,0 +1,545 @@
1
+ const SPLICE_DEFAULT_INDEX = 1;
2
+ const DEFAULT_COUNT = 0;
3
+
4
+ // Find a tag by its ID or path in the tag tree
5
+ const findTag = (id) => {
6
+ // Recursive function to search for a tag
7
+ const recursiveSearch = (tag) => {
8
+ // Check if current tag matches the search criteria
9
+ if (tag.id === parseInt(id) || tag.path === id) {
10
+ return tag;
11
+ }
12
+
13
+ // Search in children if present
14
+ if (tag.children) {
15
+ for (const child of tag.children) {
16
+ const found = recursiveSearch(child);
17
+ if (found) {
18
+ return found;
19
+ }
20
+ }
21
+ }
22
+
23
+ return null;
24
+ };
25
+
26
+ // Iterate through all top-level tags
27
+ for (const tag of tags) {
28
+ const found = recursiveSearch(tag);
29
+ if (found) {
30
+ return found;
31
+ }
32
+ }
33
+
34
+ return null;
35
+ }
36
+
37
+ // Find the root parent of tag
38
+ function findRootParent(tagId) {
39
+ const tag = findTag(tagId);
40
+ if (tag && tag.parent) {
41
+ return findRootParent(tag.parent);
42
+ }
43
+ return tagId;
44
+ }
45
+
46
+ function highlightTag(tagId) {
47
+ const nodeId = getNodeIdFromTreeviewByTagId(treeview_tags, tagId);
48
+ const nodeElement = treeview_tags.find(`li[data-nodeid="${nodeId}"]`);
49
+
50
+ nodeElement.addClass('highlight-tag');
51
+ setTimeout(() => {
52
+ nodeElement.addClass('fade-out');
53
+ }, 3000);
54
+ }
55
+
56
+
57
+ const findTagAndDelete = (id) => {
58
+
59
+ const tag = findTag(parseInt(id));
60
+
61
+ if (tag.parent) {
62
+ const parent = findTag(tag.parent);
63
+ const indexToDelete = parent.children.findIndex(item => item.id === tag.id);
64
+ parent.children.splice(indexToDelete, SPLICE_DEFAULT_INDEX);
65
+ } else {
66
+ const indexToDelete = tags.findIndex(item => item.id === tag.id);
67
+ tags.splice(indexToDelete, SPLICE_DEFAULT_INDEX);
68
+ }
69
+ }
70
+
71
+ // Main function to add or update a tag
72
+ function addTag(tag) {
73
+ // Check if the tag with the same id already exists
74
+ let existingTag = findTag(tag.id);
75
+ const existingTagColor = existingTag?.color ?? '';
76
+
77
+ if (existingTag) {
78
+ updateExistingTag(existingTag, tag);
79
+ } else {
80
+ addNewTag(tag);
81
+ }
82
+
83
+ checkColorChangeAndUpdate(existingTagColor, tag.color);
84
+
85
+ if(!createTagFromHighlight) {
86
+ updateTagsListWithCallback(function() {
87
+ highlightTag(tag.id);
88
+ });
89
+ } else {
90
+ updateTagsList();
91
+ }
92
+
93
+ }
94
+
95
+ // Function to add a new tag to the tag tree
96
+ function addNewTag(tag) {
97
+ const newTag = {
98
+ ...tag,
99
+ count: DEFAULT_COUNT,
100
+ count_document: DEFAULT_COUNT,
101
+ children: []
102
+ };
103
+
104
+ if (tag.parent) {
105
+ const parentTag = findTag(tag.parent);
106
+ if (!parentTag) {
107
+ return;
108
+ }
109
+ parentTag.children = parentTag.children || [];
110
+ parentTag.children.push(newTag);
111
+ } else {
112
+ tags.push(newTag);
113
+ }
114
+ }
115
+
116
+ // Function to update an existing tag
117
+ function updateExistingTag(existingTag, newTag) {
118
+ if (newTag.parent) {
119
+ handleExistingTagWithParent(existingTag, newTag);
120
+ } else {
121
+ handleExistingTagWithoutParent(existingTag, newTag);
122
+ }
123
+ }
124
+
125
+ // Handle updating an existing a tag that has a parent
126
+ function handleExistingTagWithParent(existingTag, newTag) {
127
+ const newParent = findTag(newTag.parent);
128
+ if (!newParent) {
129
+ return;
130
+ }
131
+
132
+ if (!existingTag.parent) {
133
+ // If the existing tag had no parent, move it to the new parent
134
+ findTagAndDelete(newTag.id);
135
+ moveTagToNewParent(existingTag, newParent);
136
+ } else if (newTag.parent !== existingTag.parent) {
137
+ // If the tag is moving to a different parent
138
+ const oldParent = findTag(existingTag.parent);
139
+ removeTagFromParent(existingTag, oldParent);
140
+ moveTagToNewParent(existingTag, newParent);
141
+ }
142
+
143
+ // Update the existing tag with new properties
144
+ Object.assign(existingTag, newTag);
145
+ }
146
+
147
+ // Handle updating an existing tag that has no parent
148
+ function handleExistingTagWithoutParent(existingTag, newTag) {
149
+ if (existingTag.parent) {
150
+ // If the existing tag had a parent, remove it from the parent and add to top level
151
+ const oldParent = findTag(existingTag.parent);
152
+ removeTagFromParent(existingTag, oldParent);
153
+ tags.push(existingTag);
154
+ }
155
+ // Update the existing tag with new properties
156
+ Object.assign(existingTag, newTag);
157
+ }
158
+
159
+ // Move a tag to a new parent
160
+ function moveTagToNewParent(tag, newParent) {
161
+ newParent.children = newParent.children || [];
162
+ newParent.children.push(tag);
163
+ }
164
+
165
+ // Remove a tag from its parent
166
+ function removeTagFromParent(tag, parent) {
167
+ if (!parent || !parent.children) return;
168
+ const index = parent.children.findIndex(child => child.id === tag.id);
169
+ if (index !== -1) {
170
+ parent.children.splice(index, 1);
171
+ }
172
+ }
173
+
174
+ function checkColorChangeAndUpdate(oldColor, newColor) {
175
+ if (oldColor !== '' && oldColor !== newColor) {
176
+ updateAndLoadDocument();
177
+ }
178
+ }
179
+
180
+ function updateAndLoadDocument() {
181
+ const url = window.location.href;
182
+ const documentPattern = /\/document\/(\d+)/;
183
+ const match = url.match(documentPattern);
184
+
185
+ if (match) {
186
+ const doc_id = match[1];
187
+ window.history.pushState({document_id: doc_id}, "Document " + doc_id, url);
188
+ loadDocument(doc_id);
189
+ }
190
+ }
191
+
192
+ function removeTag(id) {
193
+ const tagToDelete = findTag(id);
194
+ findTagAndDelete(id);
195
+ updateTagsList();
196
+ }
197
+
198
+ /*
199
+ mergeTags
200
+ */
201
+ function mergeTags(tag_src, tag_dest) {
202
+ const tagToRemove = findTag(tag_src);
203
+ const tagToUpdate = findTag(tag_dest);
204
+
205
+ if (!tagToRemove || !tagToUpdate) return;
206
+
207
+ tagToUpdate.count = parseInt(tagToUpdate.count) + parseInt(tagToRemove.count);
208
+
209
+ if (tagToRemove.children && tagToRemove.children.length > 0) {
210
+ for (const child of tagToRemove.children) {
211
+ child.parent = tag_dest;
212
+ }
213
+ tagToUpdate.children = tagToUpdate.children || [];
214
+ tagToUpdate.children.push(...tagToRemove.children);
215
+ tagToRemove.children = [];
216
+ }
217
+
218
+ findTagAndDelete(tag_src);
219
+ updateTagsList();
220
+ }
221
+
222
+ function selectAllTag() {
223
+ expandAllNodes(treeview_tags);
224
+ const allNodes = treeview_tags.treeview('getEnabled');
225
+ for(const node of allNodes) {
226
+ const nodeId = node.nodeId;
227
+ const nodeSelector = $('li[data-nodeid="' + nodeId + '"]');
228
+ nodeSelector.addClass("highlight-tag")
229
+ }
230
+ }
231
+
232
+ /*
233
+ updateTagCount
234
+ */
235
+ function updateTagCount(tagsId) {
236
+
237
+ let updateTagList = [];
238
+ for (const key in tagsId) {
239
+ const tagId = parseInt(key);
240
+ const tagData = tagsId[key];
241
+ const tagToUpdate = findTag(tagId);
242
+ updateTagList.push(tagToUpdate);
243
+
244
+ if (tagToUpdate) {
245
+ tagToUpdate.count = tagData.highlights_count;
246
+ tagToUpdate.count_document = tagData.documents_count;
247
+ }
248
+ }
249
+
250
+ initTagsTreeview();
251
+
252
+ for (const tag of updateTagList) {
253
+ if(!tag.parent) {
254
+ highlightTag(tag.id);
255
+ continue;
256
+ }
257
+ const parentRoot = findRootParent(tag.id);
258
+ expandNodeParent(parentRoot);
259
+ highlightTag(tag.id);
260
+ }
261
+ }
262
+
263
+ function updateTagsList() {
264
+ const isReverse = sortTags[1] === 'desc';
265
+ sortTagsByKey(tags, sortTags[0], isReverse);
266
+ let selectedHighLightTags = [];
267
+
268
+ if(createTagFromHighlight) {
269
+ selectedHighLightTags = getAllSelectedNodes(treeview_tags_highlight).map(node => node.id)
270
+ }
271
+
272
+ initTagsTreeview();
273
+ initTagsHighlightTreeview();
274
+ initTagsExploreTreeview();
275
+ initTagHierarchySelect();
276
+ initMergeTagHierarchySelect();
277
+ hideSearchErrorMessage();
278
+
279
+ if(createTagFromHighlight) {
280
+ selectedHighLightTags.forEach(id => {
281
+ const nodeId = getNodeIdFromTreeviewByTagId(treeview_tags_highlight, id)
282
+ selectNode(treeview_tags_highlight, nodeId);
283
+ });
284
+ const nodeId = getNodeIdFromTreeviewByTagId(treeview_tags_highlight, last_added_tag)
285
+ selectNode(treeview_tags_highlight, nodeId);
286
+ }
287
+ }
288
+
289
+ function updateTagsListWithCallback(callback) {
290
+ const isReverse = sortTags[1] === 'desc';
291
+ sortTagsByKey(tags, sortTags[0], isReverse);
292
+
293
+ initTagsTreeview(null, callback);
294
+ initTagsHighlightTreeview();
295
+ initTagsExploreTreeview();
296
+ initTagHierarchySelect();
297
+ initMergeTagHierarchySelect();
298
+ hideSearchErrorMessage();
299
+ }
300
+
301
+ function sortTagsByKey(array, key, reverse) {
302
+ const orderByX = reverse ? -1 : 1;
303
+
304
+ array.sort((a, b) => {
305
+ const keyA = typeof key === 'function' ? key(a) : a[key];
306
+ const keyB = typeof key === 'function' ? key(b) : b[key];
307
+
308
+ if (keyA < keyB) {
309
+ return -1 * orderByX;
310
+ } else if (keyA > keyB) {
311
+ return 1 * orderByX;
312
+ } else {
313
+ return 0;
314
+ }
315
+ });
316
+
317
+ // sort each level of "children"
318
+ array.forEach(item => {
319
+ if (item.children && item.children.length > 0) {
320
+ sortTagsByKey(item.children, key, reverse);
321
+ }
322
+ });
323
+ }
324
+
325
+ function handleSearch(filterText) {
326
+ function searchInChildren(item) {
327
+ const filteredChildren = [];
328
+
329
+ item.children.forEach(child => {
330
+ const childMatches = child.path.toLowerCase().includes(filterText);
331
+ const matchingChildren = searchInChildren(child);
332
+
333
+ if (childMatches || matchingChildren.length > 0) {
334
+ filteredChildren.push({
335
+ ...child,
336
+ children: matchingChildren
337
+ });
338
+ }
339
+ });
340
+
341
+ return filteredChildren;
342
+ }
343
+
344
+ return tags
345
+ .map(item => {
346
+ const matches = item.path.toLowerCase().includes(filterText);
347
+ const filteredChildren = searchInChildren(item);
348
+
349
+ if (matches || filteredChildren.length > 0) {
350
+ return {
351
+ ...item,
352
+ children: filteredChildren
353
+ };
354
+ }
355
+
356
+ return null;
357
+ })
358
+ .filter(item => item !== null);
359
+ }
360
+
361
+ function submitTagCreation(tag, callbackSuccess, callbackFail) {
362
+
363
+ const { id } = tag;
364
+ let newTag = { ...tag };
365
+ delete newTag.id;
366
+
367
+ let request;
368
+ if (id) {
369
+ request = postJSON(`/api/project/${project_id}/tag/${id}`, newTag);
370
+ } else {
371
+ request = postJSON(`/api/project/${project_id}/tag/new`, newTag);
372
+ }
373
+
374
+ showSpinner();
375
+ request.then(function (reply) {
376
+ callbackSuccess(reply);
377
+ }).catch(function (error) {
378
+ callbackFail(error);
379
+ }).finally(hideSpinner);
380
+ }
381
+
382
+ function submitTagDelete(tag_id, callbackSuccess, callbackFail) {
383
+
384
+ const request = deleteURL(`/api/project/${project_id}/tag/${tag_id}`)
385
+
386
+ request.then(function (result) {
387
+ callbackSuccess(result);
388
+ }).catch(function (error) {
389
+ callbackFail(error);
390
+ }).finally(hideSpinner);
391
+ }
392
+
393
+ function submitTagMerge(tag_src, tag_dest, callbackSuccess, callbackFail) {
394
+
395
+ const request = postJSON(`/api/project/${project_id}/tag/merge`,
396
+ {src: tag_src, dest: tag_dest})
397
+
398
+ showSpinner();
399
+ request.then(function (result) {
400
+ callbackSuccess(result);
401
+ }).catch(function (error) {
402
+ callbackFail(error);
403
+ }).finally(hideSpinner);
404
+ }
405
+
406
+ // update tag parent
407
+ function updateTagParent(child_tag_1, new_parent_tag_id) {
408
+ const tag = findTag(child_tag_1);
409
+ if(tag.parent === new_parent_tag_id) return;
410
+ const new_tag = {
411
+ id: tag.id,
412
+ path: tag.path,
413
+ description: tag.description,
414
+ color: tag.color,
415
+ parent_id: new_parent_tag_id
416
+ };
417
+ submitTagCreation(new_tag, () => {}, () => {});
418
+ }
419
+
420
+ // update tag parent
421
+ function removeTagParent(tagId) {
422
+ const tag = findTag(tagId);
423
+ tag.parent_id = null;
424
+ submitTagCreation(tag,
425
+ () => {},
426
+ () => {}
427
+ );
428
+ }
429
+
430
+ function generateTagBadgeHtml(tag, index, condition) {
431
+ const abbreviation = abbreviateTag(tag.path);
432
+ let conditionTextHtml = "";
433
+ if (index > 0) {
434
+ conditionTextHtml = getConditionText(condition);
435
+ }
436
+ return `${conditionTextHtml}<span title="${tag.path}" class="tag-badge" style="background-color: ${tag.color};">
437
+ ${abbreviation}
438
+ <button onclick="removeTagBadge(${tag.id})" class="tag-badge-button">x</button>
439
+ </span>`;
440
+ }
441
+
442
+ function generateTagBadgeForHighlight(tag) {
443
+ const abbreviation = abbreviateTag(tag.path);
444
+ return `<span class="tag-badge me-2 my-2" title="${tag.path}" style="background-color: ${tag.color};">
445
+ ${abbreviation}
446
+ <button onclick="removeHighLightSelectedTagBadge(${tag.id})" class="tag-badge-button">x</button>
447
+ </span>`;
448
+ }
449
+
450
+ function getConditionText(condition) {
451
+ switch (condition) {
452
+ case 'and':
453
+ return `<span style="margin: 5px;">et</span>`;
454
+ case 'or':
455
+ return `<span style="margin: 5px;">ou</span>`;
456
+ case 'not':
457
+ return `<span style="margin: 5px;">sans</span>`;
458
+ }
459
+ }
460
+
461
+ function showTagSelection() {
462
+ const tagsCriteria = $('#tag-selection-criteria-area');
463
+
464
+ let content = '';
465
+ if (selectedTags.length > 1) {
466
+ const yourSelectionLabel = gettext("Your selection");
467
+ const clearSelectionLabel = gettext("Clear selection");
468
+ content += `<div class="d-flex justify-content-between">`;
469
+ content += `<span style='font-size: 0.9rem'>${yourSelectionLabel} :</span>`;
470
+ content += `<span id="clear-selection" onclick="emptyTagSelection(true)" style="cursor:pointer;">` +
471
+ `<i class="fa fa-times" aria-hidden="true" title="${clearSelectionLabel}"></i></span>`;
472
+ content += `</div>`;
473
+
474
+ tagsCriteria.show();
475
+ selectedTags.forEach((item, index) => {
476
+ const tag = findTag(item.id);
477
+ content += generateTagBadgeHtml(tag, index, item.condition);
478
+ });
479
+ } else if (typeof appliedExploreTags !== 'undefined' && appliedExploreTags.length) {
480
+ showExploreQuerySummary(appliedExploreTags);
481
+ return;
482
+ } else {
483
+ tagsCriteria.hide();
484
+ }
485
+ tagsCriteria.html(content);
486
+ }
487
+
488
+ function showHighlightTagSelection() {
489
+
490
+ const HighlightSelectedTagsArea = $('#highlight-selected-tag-area');
491
+
492
+ let content = '';
493
+
494
+ if (highlightSelectedTags.length > 0) {
495
+ const yourSelectionLabel = gettext("Your selection");
496
+ content += `<div class="d-flex justify-content-between">`;
497
+ content += `<span style='font-size: 0.9rem'>${yourSelectionLabel} :</span>`;
498
+ content += `</div>`;
499
+
500
+ HighlightSelectedTagsArea.show();
501
+ highlightSelectedTags.forEach((item, index) => {
502
+ const tag = findTag(item);
503
+ content += generateTagBadgeForHighlight(tag, index, item.condition);
504
+ });
505
+ } else {
506
+ HighlightSelectedTagsArea.hide();
507
+ }
508
+ HighlightSelectedTagsArea.html(content);
509
+
510
+ }
511
+
512
+ function emptyTagSelection(unSelectNode = false, collapse = true) {
513
+ selectedTags = [];
514
+ showTagSelection();
515
+ hideTagReturnLink();
516
+ if (unSelectNode) {
517
+ deselectAllNodes(treeview_tags);
518
+ isExplorationMode = false;
519
+ initTagsTreeview();
520
+ }
521
+ }
522
+
523
+ function hideTagReturnLink() {
524
+ const tags_return = $('#tag-selection-return'),
525
+ tags_return_link = $('a', tags_return);
526
+ tags_return_link.hide();
527
+ }
528
+
529
+ function removeTagBadge(tagId) {
530
+ selectedTags = selectedTags.filter(tag => tag.id !== tagId);
531
+ showTagSelection();
532
+ const nodeId = getNodeIdFromTreeviewByTagId(treeview_tags, tagId);
533
+ unselectNode(treeview_tags, nodeId);
534
+ }
535
+
536
+ function removeHighLightSelectedTagBadge(tagId) {
537
+ highlightSelectedTags = highlightSelectedTags.filter(item => item !== tagId);
538
+ showHighlightTagSelection();
539
+ const nodeId = getNodeIdFromTreeviewByTagId(treeview_tags_highlight, tagId);
540
+ unselectNode(treeview_tags_highlight, nodeId);
541
+ }
542
+
543
+ function abbreviateTag(tagPath) {
544
+ return tagPath;
545
+ }