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.
- CHANGELOG.md +481 -0
- libreqda-1.2.0.dist-info/LICENSE.txt +28 -0
- libreqda-1.2.0.dist-info/METADATA +131 -0
- libreqda-1.2.0.dist-info/RECORD +255 -0
- libreqda-1.2.0.dist-info/WHEEL +4 -0
- libreqda-1.2.0.dist-info/entry_points.txt +4 -0
- scripts/babelrc +8 -0
- scripts/dist/LICENSE.txt +33 -0
- scripts/dist/RELEASE.txt +23 -0
- scripts/dist/macos/libreqda.icns +0 -0
- scripts/dist/macos/macos.spec +43 -0
- scripts/dist/macos/plist.patch +10 -0
- scripts/dist/macos/taguette.icns +0 -0
- scripts/dist/macos/taguette_console_wrapper +4 -0
- scripts/dist/macos.sh +32 -0
- scripts/dist/pyinstaller_entrypoint.py +19 -0
- scripts/dist/windows/windows-installer.iss +51 -0
- scripts/dist/windows/windows.spec +39 -0
- scripts/edugain/worker_update_edugain_idps.py +89 -0
- scripts/edugain/worker_update_edugain_idps.sh +9 -0
- scripts/make_password_reset_link.py +56 -0
- scripts/make_theme.py +99 -0
- scripts/new_db_revision.sh +8 -0
- scripts/register_user.py +76 -0
- scripts/update_pot.sh +33 -0
- scripts/update_translations.sh +17 -0
- scripts/validate.py +140 -0
- scripts/wipe_and_disable_user.py +90 -0
- taguette/__init__.py +13 -0
- taguette/access_control.py +145 -0
- taguette/convert.py +623 -0
- taguette/database/__init__.py +177 -0
- taguette/database/base.py +9 -0
- taguette/database/copy.py +433 -0
- taguette/database/models.py +920 -0
- taguette/demodata.py +688 -0
- taguette/export.py +500 -0
- taguette/extract.py +287 -0
- taguette/import_codebook.py +110 -0
- taguette/l10n/de_DE/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/de_DE/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/el_EL/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/el_GR/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/el_GR/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/en_US/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/en_US/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/es_ES/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/es_ES/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/fr_FR/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/fr_FR/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/id_ID/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/id_ID/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/it_IT/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/it_IT/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/nl_NL/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/nl_NL/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/pt_PT/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/pt_PT/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/sv_SE/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/sv_SE/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/loki_handler.py +107 -0
- taguette/main.py +649 -0
- taguette/migrations/env.py +72 -0
- taguette/migrations/script.py.mako +24 -0
- taguette/migrations/versions/09c662cd9483_make_documents_filename_not_null.py +39 -0
- taguette/migrations/versions/1b29e68900c1_add_user_roles.py +55 -0
- taguette/migrations/versions/1b96a606551d_update_user_to_have_a_display_name.py +34 -0
- taguette/migrations/versions/2467bab602b7_add_color_column_to_tags_table.py +33 -0
- taguette/migrations/versions/382db36f20f4_add_user_disabled.py +66 -0
- taguette/migrations/versions/43d6c240309d_remove_document_groups.py +81 -0
- taguette/migrations/versions/447d636f72c5_cascades.py +40 -0
- taguette/migrations/versions/491de2dc7cd7_add_indexes.py +51 -0
- taguette/migrations/versions/4c55b3b3ff30_user_login_length.py +58 -0
- taguette/migrations/versions/61a4abcc8769_user_with_parent_for_permissions.py +40 -0
- taguette/migrations/versions/6489b5f9cfb5_increase_password_hash_size.py +34 -0
- taguette/migrations/versions/679f625e6e6a_fix_unique_tags.py +59 -0
- taguette/migrations/versions/77c4543a5b9b_add_parent_id_column_to_collection_table.py +41 -0
- taguette/migrations/versions/7c94b7a31e69_add_is_archived_column_to_projets.py +39 -0
- taguette/migrations/versions/7f56de97e3c8_change_edugain_displayname_length.py +42 -0
- taguette/migrations/versions/807c0cc0ecf6_remove_invalid_hltags.py +43 -0
- taguette/migrations/versions/80b1cc9d4c22_add_user_email.py +35 -0
- taguette/migrations/versions/91ade71ccf4d_add_user_language.py +28 -0
- taguette/migrations/versions/955db2e523d1_add_collections_tables_and_associations_.py +60 -0
- taguette/migrations/versions/a1b2c3d4e5f6_add_user_hide_code_counts.py +28 -0
- taguette/migrations/versions/b23f3b7a638e_add_user_password_change_date.py +36 -0
- taguette/migrations/versions/b7c1e2d3f4a5_add_project_compact_layout.py +28 -0
- taguette/migrations/versions/bc8e0e0677e9_rename_command_fields.py +126 -0
- taguette/migrations/versions/bce44849c2f2_lowercase_logins.py +96 -0
- taguette/migrations/versions/ca076140922f_update_user_for_external_logins.py +32 -0
- taguette/migrations/versions/d86c45d7aa8a_add_document_direction.py +127 -0
- taguette/migrations/versions/db5e31a0233d_remove_blank_highlights.py +88 -0
- taguette/migrations/versions/dd37d9027a24_unique_tags.py +68 -0
- taguette/migrations/versions/de678cef2a94_add_project_id_to_collections_and_.py +41 -0
- taguette/migrations/versions/e459991d960e_add_table_edugainidps.py +39 -0
- taguette/migrations/versions/e4cf92942271_add_index.py +29 -0
- taguette/migrations/versions/e4e090a5b511_remove_commands_document_id_fk.py +38 -0
- taguette/migrations/versions/e9a9f1c77971_add_favorite_column_to_document.py +32 -0
- taguette/migrations/versions/ecb4065de575_don_t_use_server_default_now_timezone_.py +44 -0
- taguette/migrations/versions/fbdc29566f62_edugain_fix_field_type.py +42 -0
- taguette/migrations/versions/fdc6eb54b034_add_parent_id_column_to_tags_table.py +31 -0
- taguette/static/README.md +34 -0
- taguette/static/alt_templates/.gitkeep +0 -0
- taguette/static/assets/test/logos/monsters-university-logo.png +0 -0
- taguette/static/css/custom.css +1696 -0
- taguette/static/css/fa-all.min.css +5 -0
- taguette/static/css/themed.css +3 -0
- taguette/static/css/themed.css.map +25 -0
- taguette/static/css/vendor/bootstrap-treeview.min.css +1 -0
- taguette/static/css/vendor/bootstrap.min.css +6 -0
- taguette/static/css/vendor/font/context-menu-icons.eot +0 -0
- taguette/static/css/vendor/font/context-menu-icons.ttf +0 -0
- taguette/static/css/vendor/font/context-menu-icons.woff +0 -0
- taguette/static/css/vendor/font/context-menu-icons.woff2 +0 -0
- taguette/static/css/vendor/hierarchy-select.min.css +1 -0
- taguette/static/css/vendor/introjs.min.css +2 -0
- taguette/static/css/vendor/jquery.contextMenu.min.css +16 -0
- taguette/static/favicon.ico +0 -0
- taguette/static/js/codebook-viz.js +428 -0
- taguette/static/js/collection/context-menu.js +180 -0
- taguette/static/js/collection/hierarchy-select.js +71 -0
- taguette/static/js/collection/manager.js +217 -0
- taguette/static/js/document/context-menu.js +92 -0
- taguette/static/js/document/custom-select.js +82 -0
- taguette/static/js/document/manager.js +943 -0
- taguette/static/js/document/treeview.js +593 -0
- taguette/static/js/highlight/README.md +18 -0
- taguette/static/js/highlight/crud.js +119 -0
- taguette/static/js/highlight/extracts.js +497 -0
- taguette/static/js/highlight/loader.js +109 -0
- taguette/static/js/highlight/renderer.js +405 -0
- taguette/static/js/login.js +15 -0
- taguette/static/js/permissions.js +153 -0
- taguette/static/js/polling-event.js +145 -0
- taguette/static/js/project/intro.js +524 -0
- taguette/static/js/project/manager.js +0 -0
- taguette/static/js/project/search.js +124 -0
- taguette/static/js/tag/context-menu.js +133 -0
- taguette/static/js/tag/explore-codes.js +400 -0
- taguette/static/js/tag/hierarchy-select.js +69 -0
- taguette/static/js/tag/manager.js +545 -0
- taguette/static/js/tag/treeview.js +613 -0
- taguette/static/js/taguette.js +1790 -0
- taguette/static/js/utilities.js +714 -0
- taguette/static/js/vendor/bootstrap-treeview-custum.js +421 -0
- taguette/static/js/vendor/bootstrap-treeview-custum.min.js +1 -0
- taguette/static/js/vendor/bootstrap-treeview-default.min.js +1 -0
- taguette/static/js/vendor/bootstrap.bundle.min.js +7 -0
- taguette/static/js/vendor/bootstrap.bundle.min.js.map +1 -0
- taguette/static/js/vendor/hierarchy-select.min.js +1 -0
- taguette/static/js/vendor/introjs.min.js +17 -0
- taguette/static/js/vendor/jquery-3.7.1.js +2 -0
- taguette/static/js/vendor/jquery.contextMenu.min.js +2 -0
- taguette/static/libreqda.ico +0 -0
- taguette/static/logo-h.png +0 -0
- taguette/static/logo.png +0 -0
- taguette/static/logo_blanc.png +0 -0
- taguette/static/missing.png +0 -0
- taguette/static/robots.txt +2 -0
- taguette/static/scss/components/_badge.scss +42 -0
- taguette/static/scss/components/_button.scss +169 -0
- taguette/static/scss/components/_checkbox.scss +40 -0
- taguette/static/scss/components/_context_menu.scss +28 -0
- taguette/static/scss/components/_custom_select.scss +150 -0
- taguette/static/scss/components/_dropdown.scss +43 -0
- taguette/static/scss/components/_form.scss +54 -0
- taguette/static/scss/components/_modal.scss +57 -0
- taguette/static/scss/components/_project_card.scss +163 -0
- taguette/static/scss/components/_tag.scss +308 -0
- taguette/static/scss/components/_treeview.scss +76 -0
- taguette/static/scss/custom.scss +809 -0
- taguette/static/scss/layout/_credits.scss +62 -0
- taguette/static/scss/layout/_footer.scss +6 -0
- taguette/static/scss/layout/_navigation.scss +165 -0
- taguette/static/scss/layout/_sidebar.scss +37 -0
- taguette/static/scss/themes/default/_variables.scss +103 -0
- taguette/static/scss/themes/default/theme.css +3 -0
- taguette/static/scss/themes/default/theme.css.map +26 -0
- taguette/static/scss/themes/default/theme.scss +4 -0
- taguette/static/webfonts/BadScript-OFL.txt +94 -0
- taguette/static/webfonts/BadScript.ttf +0 -0
- taguette/static/webfonts/Montserrat-Light.ttf +0 -0
- taguette/static/webfonts/Montserrat-OFL.txt +93 -0
- taguette/static/webfonts/fa-brands-400.eot +0 -0
- taguette/static/webfonts/fa-brands-400.svg +1184 -0
- taguette/static/webfonts/fa-brands-400.ttf +0 -0
- taguette/static/webfonts/fa-brands-400.woff +0 -0
- taguette/static/webfonts/fa-brands-400.woff2 +0 -0
- taguette/static/webfonts/fa-regular-400.eot +0 -0
- taguette/static/webfonts/fa-regular-400.svg +467 -0
- taguette/static/webfonts/fa-regular-400.ttf +0 -0
- taguette/static/webfonts/fa-regular-400.woff +0 -0
- taguette/static/webfonts/fa-regular-400.woff2 +0 -0
- taguette/static/webfonts/fa-solid-900.eot +0 -0
- taguette/static/webfonts/fa-solid-900.svg +2618 -0
- taguette/static/webfonts/fa-solid-900.ttf +0 -0
- taguette/static/webfonts/fa-solid-900.woff +0 -0
- taguette/static/webfonts/fa-solid-900.woff2 +0 -0
- taguette/templates/alt_templates/.gitkeep +0 -0
- taguette/templates/archived_projects.html +50 -0
- taguette/templates/auth/account.html +74 -0
- taguette/templates/auth/cilogon.login.html +89 -0
- taguette/templates/auth/email_reset_password.html +5 -0
- taguette/templates/auth/external_account.html +60 -0
- taguette/templates/auth/login.html +144 -0
- taguette/templates/auth/reset_password.html +27 -0
- taguette/templates/auth/token_needed.html +7 -0
- taguette/templates/base.html +209 -0
- taguette/templates/bug_report.html +158 -0
- taguette/templates/cookies_prompt.html +15 -0
- taguette/templates/downloads.html +127 -0
- taguette/templates/email_bug_report.txt +9 -0
- taguette/templates/email_reset_password.txt +5 -0
- taguette/templates/error.html +8 -0
- taguette/templates/export_codebook.html +20 -0
- taguette/templates/export_document.html +20 -0
- taguette/templates/export_highlights.html +47 -0
- taguette/templates/index.html +347 -0
- taguette/templates/maintenance.html +8 -0
- taguette/templates/messages.js +74 -0
- taguette/templates/modal/change_document.html +68 -0
- taguette/templates/modal/create_collection.html +100 -0
- taguette/templates/modal/create_document.html +84 -0
- taguette/templates/modal/create_highlight.html +38 -0
- taguette/templates/modal/create_tag.html +73 -0
- taguette/templates/modal/explore_codes.html +58 -0
- taguette/templates/modal/manage_collaborators.html +44 -0
- taguette/templates/modal/merge_tag.html +49 -0
- taguette/templates/new_password.html +32 -0
- taguette/templates/partials/alert.html +16 -0
- taguette/templates/partials/codebook_viz_modal.html +34 -0
- taguette/templates/partials/confirm.html +17 -0
- taguette/templates/partials/document_toolbar.html +37 -0
- taguette/templates/partials/hlinfo.html +13 -0
- taguette/templates/partials/project_card.html +44 -0
- taguette/templates/partials/project_card_archived.html +41 -0
- taguette/templates/partials/project_sidebar.html +99 -0
- taguette/templates/partials/spinner.html +11 -0
- taguette/templates/permissions.html +54 -0
- taguette/templates/project.html +288 -0
- taguette/templates/project_delete.html +17 -0
- taguette/templates/project_import.html +122 -0
- taguette/templates/project_import_codebook.html +34 -0
- taguette/templates/project_import_codebook_confirm.html +49 -0
- taguette/templates/project_new.html +31 -0
- taguette/templates/tos.html +8 -0
- taguette/templates/trans.js +7 -0
- taguette/templates/welcome.html +15 -0
- taguette/utils.py +177 -0
- taguette/validate.py +141 -0
- taguette/version_check.py +102 -0
- taguette/web/__init__.py +252 -0
- taguette/web/api.py +1835 -0
- taguette/web/base.py +864 -0
- taguette/web/export.py +338 -0
- taguette/web/views.py +1618 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const collection_hierarchy_select = $('#collection-hierarchy-select');
|
|
2
|
+
const document_collection_hierarchy_select = $('#document-collection-hierarchy-select');
|
|
3
|
+
const hs_menu_inner_collection = $('.hs-menu-inner');
|
|
4
|
+
const hs_menu_inner_document = $('.hs-menu-inner');
|
|
5
|
+
|
|
6
|
+
const COLLECTION_DEFAULT_LEVEL = 1;
|
|
7
|
+
|
|
8
|
+
// Creates a default 'None' option for collections
|
|
9
|
+
function addDefaultValueCollection(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", COLLECTION_DEFAULT_LEVEL)
|
|
15
|
+
.text(gettext('None'));
|
|
16
|
+
li.append(a);
|
|
17
|
+
|
|
18
|
+
parentContainer.append(li);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Recursively generates items from collection hierarchy data
|
|
22
|
+
function generateCollectionHierarchyItem(data, parentContainer, level) {
|
|
23
|
+
$.each(data, function (index, item) {
|
|
24
|
+
if (!item) return;
|
|
25
|
+
|
|
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.name);
|
|
32
|
+
|
|
33
|
+
li.append(a);
|
|
34
|
+
parentContainer.append(li);
|
|
35
|
+
|
|
36
|
+
if (item.children && item.children.length > 0) {
|
|
37
|
+
generateCollectionHierarchyItem(item.children, parentContainer, level + COLLECTION_DEFAULT_LEVEL);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Init collection hierarchy select dropdown for create collection
|
|
43
|
+
function initCollectionHierarchySelect() {
|
|
44
|
+
initHierarchySelectCollection(hs_menu_inner_collection, collection_hierarchy_select, true);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Sets up a hierarchy select dropdown for collections
|
|
48
|
+
function initHierarchySelectCollection(menu, select, withDefault = true) {
|
|
49
|
+
menu.html('');
|
|
50
|
+
|
|
51
|
+
if(withDefault) {
|
|
52
|
+
addDefaultValueCollection(menu);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
generateCollectionHierarchyItem(collections, menu, COLLECTION_DEFAULT_LEVEL);
|
|
56
|
+
|
|
57
|
+
select.hierarchySelect({
|
|
58
|
+
width: 'auto'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
function initDocumentCollectionHierarchySelect(initialValueSet = false) {
|
|
64
|
+
initHierarchySelectCollection(hs_menu_inner_document, document_collection_hierarchy_select, true, initialValueSet);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Init document change collection hierarchy select dropdown
|
|
68
|
+
function initDocumentChangeCollectionHierarchySelect(initialValueSet = false) {
|
|
69
|
+
const document_change_collection_hierarchy_select = $('#document-change-collection-hierarchy-select');
|
|
70
|
+
initHierarchySelectCollection(hs_menu_inner_document, document_change_collection_hierarchy_select, true, initialValueSet);
|
|
71
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Collection management: add, update, remove
|
|
2
|
+
|
|
3
|
+
function addCollection(collectionData) {
|
|
4
|
+
// Check if collection already exists to prevent duplicates
|
|
5
|
+
const existingCollection = findCollection(parseInt(collectionData.id));
|
|
6
|
+
if (existingCollection) {
|
|
7
|
+
// Update existing collection instead of creating duplicate
|
|
8
|
+
existingCollection.name = collectionData.name;
|
|
9
|
+
existingCollection.description = collectionData.description;
|
|
10
|
+
existingCollection.document_ids = collectionData.document_ids;
|
|
11
|
+
initDocumentsTreeview();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const newCollection = {
|
|
16
|
+
id: collectionData.id,
|
|
17
|
+
name: collectionData.name,
|
|
18
|
+
description: collectionData.description,
|
|
19
|
+
parent: collectionData.parent_id,
|
|
20
|
+
document_ids: collectionData.document_ids,
|
|
21
|
+
children: []
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (!collections) {
|
|
25
|
+
collections = [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check if collection has parent
|
|
29
|
+
if (collectionData.parent_id) {
|
|
30
|
+
const parentCollection = findCollection(parseInt(collectionData.parent_id));
|
|
31
|
+
if (parentCollection) {
|
|
32
|
+
if (!parentCollection.children) {
|
|
33
|
+
parentCollection.children = [];
|
|
34
|
+
}
|
|
35
|
+
parentCollection.children.push(newCollection);
|
|
36
|
+
} else {
|
|
37
|
+
// Parent not found, add to root level
|
|
38
|
+
collections.push(newCollection);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// Root level collection
|
|
42
|
+
collections.push(newCollection);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Update document collections
|
|
46
|
+
if (collectionData.document_ids && collectionData.document_ids.length > 0) {
|
|
47
|
+
for (let docId of collectionData.document_ids) {
|
|
48
|
+
const document = documents[docId.toString()];
|
|
49
|
+
if (document) {
|
|
50
|
+
// Remove document from all existing collections first
|
|
51
|
+
if (document.collections && document.collections.length > 0) {
|
|
52
|
+
for (let existingCollectionId of document.collections) {
|
|
53
|
+
const existingCollection = findCollection(existingCollectionId);
|
|
54
|
+
if (existingCollection && existingCollection.document_ids) {
|
|
55
|
+
const docIndex = existingCollection.document_ids.indexOf(parseInt(docId));
|
|
56
|
+
if (docIndex !== -1) {
|
|
57
|
+
existingCollection.document_ids.splice(docIndex, 1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add document to new collection
|
|
64
|
+
if (!document.collections) {
|
|
65
|
+
document.collections = [];
|
|
66
|
+
}
|
|
67
|
+
if (!document.collections.includes(collectionData.id)) {
|
|
68
|
+
document.collections.push(collectionData.id);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
initDocumentsTreeview(null, function() {
|
|
75
|
+
highlightCollection(collectionData.id);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function updateCollection(collectionData) {
|
|
80
|
+
// Find existing collection in hierarchy
|
|
81
|
+
let existingCollection = findCollection(parseInt(collectionData.id));
|
|
82
|
+
if (!existingCollection) {
|
|
83
|
+
addCollection(collectionData);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const oldDocumentIds = existingCollection.document_ids || [];
|
|
88
|
+
const oldParentId = existingCollection.parent ? parseInt(existingCollection.parent) : null;
|
|
89
|
+
|
|
90
|
+
// Update basic properties
|
|
91
|
+
existingCollection.name = collectionData.name;
|
|
92
|
+
existingCollection.description = collectionData.description;
|
|
93
|
+
existingCollection.document_ids = collectionData.document_ids;
|
|
94
|
+
|
|
95
|
+
// Handle parent change if necessary
|
|
96
|
+
const newParentId = collectionData.parent_id ? parseInt(collectionData.parent_id) : null;
|
|
97
|
+
const currentParentId = existingCollection.parent ? parseInt(existingCollection.parent) : null;
|
|
98
|
+
if (currentParentId !== newParentId) {
|
|
99
|
+
existingCollection.parent = newParentId;
|
|
100
|
+
|
|
101
|
+
// Remove from old parent
|
|
102
|
+
if (oldParentId) {
|
|
103
|
+
const oldParent = findCollection(oldParentId);
|
|
104
|
+
if (oldParent && oldParent.children) {
|
|
105
|
+
const index = oldParent.children.findIndex(c => c.id === parseInt(collectionData.id));
|
|
106
|
+
if (index !== -1) {
|
|
107
|
+
oldParent.children.splice(index, 1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
// Remove from root list
|
|
112
|
+
const index = collections.findIndex(c => c.id === parseInt(collectionData.id));
|
|
113
|
+
if (index !== -1) {
|
|
114
|
+
collections.splice(index, 1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Add to new parent
|
|
119
|
+
if (collectionData.parent_id) {
|
|
120
|
+
const newParent = findCollection(parseInt(collectionData.parent_id));
|
|
121
|
+
if (newParent) {
|
|
122
|
+
if (!newParent.children) {
|
|
123
|
+
newParent.children = [];
|
|
124
|
+
}
|
|
125
|
+
newParent.children.push(existingCollection);
|
|
126
|
+
} else {
|
|
127
|
+
// New parent not found, add to root level
|
|
128
|
+
const isAlreadyInRoot = collections.some(col => col.id === existingCollection.id);
|
|
129
|
+
if (!isAlreadyInRoot) {
|
|
130
|
+
collections.push(existingCollection);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
// Add to root level
|
|
135
|
+
const isAlreadyInRoot = collections.some(col => col.id === existingCollection.id);
|
|
136
|
+
if (!isAlreadyInRoot) {
|
|
137
|
+
collections.push(existingCollection);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Update document collections
|
|
143
|
+
// Remove from old documents
|
|
144
|
+
for (let docId of oldDocumentIds) {
|
|
145
|
+
const document = documents[docId.toString()];
|
|
146
|
+
if (document && document.collections) {
|
|
147
|
+
const index = document.collections.indexOf(parseInt(collectionData.id));
|
|
148
|
+
if (index !== -1) {
|
|
149
|
+
document.collections.splice(index, 1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Add to new documents
|
|
155
|
+
if (collectionData.document_ids && collectionData.document_ids.length > 0) {
|
|
156
|
+
for (let docId of collectionData.document_ids) {
|
|
157
|
+
const document = documents[docId.toString()];
|
|
158
|
+
if (document) {
|
|
159
|
+
if (!document.collections) {
|
|
160
|
+
document.collections = [];
|
|
161
|
+
}
|
|
162
|
+
if (!document.collections.includes(parseInt(collectionData.id))) {
|
|
163
|
+
document.collections.push(parseInt(collectionData.id));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
initDocumentsTreeview(null, function() {
|
|
170
|
+
highlightCollection(collectionData.id);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function removeCollection(collectionId) {
|
|
175
|
+
// Find collection in hierarchysss
|
|
176
|
+
const collectionToRemove = findCollection(parseInt(collectionId));
|
|
177
|
+
if (!collectionToRemove) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Remove from parent or root list
|
|
182
|
+
if (collectionToRemove.parent) {
|
|
183
|
+
const parent = findCollection(parseInt(collectionToRemove.parent));
|
|
184
|
+
if (parent && parent.children) {
|
|
185
|
+
const index = parent.children.findIndex(c => c.id === parseInt(collectionId));
|
|
186
|
+
if (index !== -1) {
|
|
187
|
+
parent.children.splice(index, 1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
// Remove from root list
|
|
192
|
+
const index = collections.findIndex(c => c.id === parseInt(collectionId));
|
|
193
|
+
if (index !== -1) {
|
|
194
|
+
collections.splice(index, 1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Remove from documents that belong to it
|
|
199
|
+
if (collectionToRemove.document_ids && collectionToRemove.document_ids.length > 0) {
|
|
200
|
+
for (let docId of collectionToRemove.document_ids) {
|
|
201
|
+
const document = documents[docId.toString()];
|
|
202
|
+
if (document && document.collections) {
|
|
203
|
+
const index = document.collections.indexOf(parseInt(collectionId));
|
|
204
|
+
if (index !== -1) {
|
|
205
|
+
document.collections.splice(index, 1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
initDocumentsTreeview(null, function() {
|
|
212
|
+
if (collectionToRemove.parent) {
|
|
213
|
+
highlightCollection(parseInt(collectionToRemove.parent));
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
function addDocumentContextMenu() {
|
|
2
|
+
$.contextMenu({
|
|
3
|
+
selector: '.document-context-menu',
|
|
4
|
+
build: function ($trigger) {
|
|
5
|
+
const nodeId = $trigger.attr('id');
|
|
6
|
+
const documentId = extractDocumentId(nodeId);
|
|
7
|
+
|
|
8
|
+
if (!documentId) return false;
|
|
9
|
+
|
|
10
|
+
const document = documents[documentId.toString()];
|
|
11
|
+
if (!document) return false;
|
|
12
|
+
|
|
13
|
+
let items = generateDocumentContextMenuItems(document);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
callback: (key, options) => documentContextMenuAction(key, options.$trigger.attr("id")),
|
|
17
|
+
items
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function generateDocumentContextMenuItems(document) {
|
|
24
|
+
const isFavorite = document.favorite === true;
|
|
25
|
+
const favoriteLabel = isFavorite ? gettext('Remove from Bookmarks') : gettext('Add to Bookmarks');
|
|
26
|
+
const favoriteIcon = isFavorite ? 'fas fa-star' : 'far fa-star';
|
|
27
|
+
|
|
28
|
+
const items = {
|
|
29
|
+
"edit": {
|
|
30
|
+
name: gettext('Edit document'),
|
|
31
|
+
icon: "fas fa-edit",
|
|
32
|
+
callback: (key, options) => documentContextMenuAction('edit', document.id)
|
|
33
|
+
},
|
|
34
|
+
"favorite": {
|
|
35
|
+
name: favoriteLabel,
|
|
36
|
+
icon: favoriteIcon,
|
|
37
|
+
callback: (key, options) => documentContextMenuAction('favorite', document.id)
|
|
38
|
+
},
|
|
39
|
+
"separator1": "---------",
|
|
40
|
+
"delete": {
|
|
41
|
+
name: gettext('Delete document'),
|
|
42
|
+
icon: "fas fa-trash",
|
|
43
|
+
callback: (key, options) => documentContextMenuAction('delete', document.id)
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return items;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function documentContextMenuAction(action, param) {
|
|
51
|
+
const actionFunction = documentActions[action];
|
|
52
|
+
|
|
53
|
+
if (actionFunction) {
|
|
54
|
+
actionFunction(param);
|
|
55
|
+
} else {
|
|
56
|
+
log('No action found for:', action);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toggleDocumentFavorite(documentId) {
|
|
61
|
+
const document = documents[documentId.toString()];
|
|
62
|
+
if (!document) return;
|
|
63
|
+
|
|
64
|
+
const updatedDocument = {
|
|
65
|
+
id: parseInt(documentId),
|
|
66
|
+
favorite: !document.favorite
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
submitDocumentUpdate(updatedDocument, function(response) {
|
|
70
|
+
updateDocumentsList();
|
|
71
|
+
initDocumentsTreeview();
|
|
72
|
+
}, function(error) {
|
|
73
|
+
log('Error updating document favorite:', 'error', error);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const documentActions = {
|
|
78
|
+
edit: documentId => editDocument(documentId),
|
|
79
|
+
favorite: documentId => toggleDocumentFavorite(documentId),
|
|
80
|
+
delete: documentId => deleteDocument(documentId)
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function extractDocumentId(nodeId) {
|
|
84
|
+
if (!nodeId) return null;
|
|
85
|
+
|
|
86
|
+
const match = nodeId.match(/document-link-(\d+)/);
|
|
87
|
+
return match ? parseInt(match[1]) : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
$(document).ready(function() {
|
|
91
|
+
addDocumentContextMenu();
|
|
92
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Custom Select Dropdown with Toolbar Actions
|
|
2
|
+
// Based on https://blog.logrocket.com/creating-custom-select-dropdown-css/
|
|
3
|
+
|
|
4
|
+
// DOM Elements
|
|
5
|
+
const customSelect = document.querySelector(".custom-select");
|
|
6
|
+
const selectBtn = document.querySelector(".select-button");
|
|
7
|
+
const selectedValue = document.querySelector(".selected-value");
|
|
8
|
+
const toolbarActionButtons = document.querySelector(".toolbar-action-buttons");
|
|
9
|
+
|
|
10
|
+
// Toggle dropdown on button click
|
|
11
|
+
selectBtn.addEventListener("click", () => {
|
|
12
|
+
customSelect.classList.toggle("active");
|
|
13
|
+
selectBtn.setAttribute(
|
|
14
|
+
"aria-expanded",
|
|
15
|
+
selectBtn.getAttribute("aria-expanded") === "true" ? "false" : "true"
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Add event listeners to options
|
|
20
|
+
function addEventListenerToOption() {
|
|
21
|
+
const optionsList = document.querySelectorAll(".select-dropdown li");
|
|
22
|
+
|
|
23
|
+
optionsList.forEach((option) => {
|
|
24
|
+
option.addEventListener("keyup", createHandler(option));
|
|
25
|
+
option.addEventListener("click", createHandler(option));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create event handler
|
|
30
|
+
function createHandler(option) {
|
|
31
|
+
return function(e) {
|
|
32
|
+
// Handle click events
|
|
33
|
+
if (e.type === "click" && e.clientX !== 0 && e.clientY !== 0) {
|
|
34
|
+
const text = this.children[1].textContent;
|
|
35
|
+
updateSelectedValue(text);
|
|
36
|
+
|
|
37
|
+
const input = option.querySelector('input[type="radio"]');
|
|
38
|
+
const docId = input.id.split('-')[1];
|
|
39
|
+
|
|
40
|
+
linkDocument(input, docId);
|
|
41
|
+
toggleToolbarActions(true);
|
|
42
|
+
emptyTagSelection(true);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Handle keyboard events
|
|
46
|
+
if (e.key === "Enter") {
|
|
47
|
+
const text = this.textContent;
|
|
48
|
+
updateSelectedValue(text);
|
|
49
|
+
customSelect.classList.remove("active");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Update selected value
|
|
55
|
+
function updateSelectedValue(text) {
|
|
56
|
+
selectedValue.textContent = text;
|
|
57
|
+
selectedValue.title = text;
|
|
58
|
+
customSelect.classList.toggle('empty', !text);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Toggle toolbar actions
|
|
62
|
+
function toggleToolbarActions(show = false) {
|
|
63
|
+
if(show) {
|
|
64
|
+
customSelect.classList.remove("active");
|
|
65
|
+
toolbarActionButtons.style.display = '';
|
|
66
|
+
|
|
67
|
+
if(current_document) {
|
|
68
|
+
const doc = documents[current_document];
|
|
69
|
+
updateSelectedValue(doc ? doc.name : '');
|
|
70
|
+
} else {
|
|
71
|
+
updateSelectedValue('');
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
toolbarActionButtons.style.setProperty('display', 'none', 'important');
|
|
75
|
+
updateSelectedValue('');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Initialize event listeners
|
|
80
|
+
addEventListenerToOption();
|
|
81
|
+
// Toggle Toolbars actions
|
|
82
|
+
toggleToolbarActions(false);
|