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,593 @@
|
|
|
1
|
+
const treeview_documents = $('#treeview-documents');
|
|
2
|
+
|
|
3
|
+
function flattenCollections(collectionsTree) {
|
|
4
|
+
const flat = {};
|
|
5
|
+
|
|
6
|
+
function addToFlat(nodes) {
|
|
7
|
+
for (let node of nodes) {
|
|
8
|
+
flat[node.id] = node;
|
|
9
|
+
if (node.children && node.children.length > 0) {
|
|
10
|
+
addToFlat(node.children);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
addToFlat(collectionsTree);
|
|
16
|
+
return flat;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function convertCollectionsToTreeNode(data) {
|
|
20
|
+
return data.map(collection => {
|
|
21
|
+
const collectionNode = {
|
|
22
|
+
id: 'collection_' + collection.id,
|
|
23
|
+
text: collection.name,
|
|
24
|
+
selectable: false,
|
|
25
|
+
nodes: []
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (collection.document_ids && collection.document_ids.length > 0 && typeof documents !== 'undefined') {
|
|
29
|
+
for (let docId of collection.document_ids) {
|
|
30
|
+
const document = documents[docId.toString()];
|
|
31
|
+
if (document) {
|
|
32
|
+
collectionNode.nodes.push({
|
|
33
|
+
id: 'document_' + docId,
|
|
34
|
+
text: document.name,
|
|
35
|
+
selectable: true,
|
|
36
|
+
document_id: parseInt(docId),
|
|
37
|
+
tags: [(document.highlights_count || 0).toString()]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (collection.children && collection.children.length > 0) {
|
|
44
|
+
const childCollections = convertCollectionsToTreeNode(collection.children);
|
|
45
|
+
collectionNode.nodes.push(...childCollections);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const documentNodes = collectionNode.nodes.filter(node => node.document_id !== undefined);
|
|
49
|
+
const subCollections = collectionNode.nodes.filter(node => node.document_id === undefined);
|
|
50
|
+
|
|
51
|
+
documentNodes.sort((a, b) => a.text.localeCompare(b.text));
|
|
52
|
+
subCollections.sort((a, b) => a.text.localeCompare(b.text));
|
|
53
|
+
|
|
54
|
+
collectionNode.nodes = [...documentNodes, ...subCollections];
|
|
55
|
+
|
|
56
|
+
// fake node to force chevron display (for icon alignment)
|
|
57
|
+
if (collectionNode.nodes.length === 0) {
|
|
58
|
+
collectionNode.nodes.push({
|
|
59
|
+
id: 'dummy_' + collection.id,
|
|
60
|
+
text: '',
|
|
61
|
+
icon: '',
|
|
62
|
+
selectable: false,
|
|
63
|
+
hideCheckbox: true,
|
|
64
|
+
isDummy: true,
|
|
65
|
+
state: {
|
|
66
|
+
disabled: true
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const documentCount = collection.document_ids ? collection.document_ids.length : 0;
|
|
72
|
+
collectionNode.tags = [documentCount.toString()];
|
|
73
|
+
|
|
74
|
+
return collectionNode;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function convertDocumentsToTreeNode(filteredCollections = null) {
|
|
79
|
+
const treeData = [];
|
|
80
|
+
|
|
81
|
+
const collectionsToUse = filteredCollections || collections;
|
|
82
|
+
|
|
83
|
+
if (collectionsToUse && collectionsToUse.length > 0) {
|
|
84
|
+
const collectionNodes = convertCollectionsToTreeNode(collectionsToUse);
|
|
85
|
+
treeData.push(...collectionNodes);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const shouldShowUncategorized = true;
|
|
89
|
+
|
|
90
|
+
if (shouldShowUncategorized) {
|
|
91
|
+
const uncategorizedDocs = [];
|
|
92
|
+
for (let [docId, document] of Object.entries(documents)) {
|
|
93
|
+
const docCollections = document.collections || [];
|
|
94
|
+
if (docCollections.length === 0) {
|
|
95
|
+
// If filtering, only include documents that match the search
|
|
96
|
+
if (filteredCollections) {
|
|
97
|
+
const searchText = window.document.getElementById("input-search-titles")?.value.toLowerCase() || '';
|
|
98
|
+
if (searchText && !document.name.toLowerCase().includes(searchText)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
uncategorizedDocs.push({
|
|
104
|
+
id: 'document_' + docId,
|
|
105
|
+
text: document.name,
|
|
106
|
+
selectable: true,
|
|
107
|
+
document_id: parseInt(docId),
|
|
108
|
+
tags: [(document.highlights_count || 0).toString()]
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (uncategorizedDocs.length > 0) {
|
|
114
|
+
uncategorizedDocs.sort((a, b) => a.text.localeCompare(b.text));
|
|
115
|
+
treeData.push({
|
|
116
|
+
id: 'uncategorized',
|
|
117
|
+
text: gettext('Without collection'),
|
|
118
|
+
icon: 'fas fa-folder',
|
|
119
|
+
selectable: false,
|
|
120
|
+
nodes: uncategorizedDocs,
|
|
121
|
+
tags: [uncategorizedDocs.length.toString()]
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return treeData;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function customDocumentNodeRow() {
|
|
130
|
+
return {
|
|
131
|
+
customContent: true,
|
|
132
|
+
nodeContentSelector: 'a',
|
|
133
|
+
nodeContentAttributes: {
|
|
134
|
+
href: '#',
|
|
135
|
+
class: 'document-link-a',
|
|
136
|
+
style: 'text-decoration: none;'
|
|
137
|
+
},
|
|
138
|
+
customizeNode: function(element, node, tree) {
|
|
139
|
+
element.attr('id', `treeview-node-${node.id}`);
|
|
140
|
+
|
|
141
|
+
if (node.document_id) {
|
|
142
|
+
element.attr('data-document-id', node.document_id);
|
|
143
|
+
element.attr('id', `document-link-${node.document_id}`);
|
|
144
|
+
element.addClass('document-link');
|
|
145
|
+
element.css('position', 'relative');
|
|
146
|
+
|
|
147
|
+
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
|
148
|
+
const cmdKey = isMac ? 'Cmd' : 'Ctrl';
|
|
149
|
+
const tooltipText = gettext('Click to open • %(key)s+Click to select • Right-click for options', {key: cmdKey});
|
|
150
|
+
const tooltip = `${node.text}\n\n${tooltipText}`;
|
|
151
|
+
element.attr('title', tooltip);
|
|
152
|
+
|
|
153
|
+
if (node.document_id === current_document) {
|
|
154
|
+
element.addClass('document-link-current');
|
|
155
|
+
}
|
|
156
|
+
element.addClass('document-context-menu');
|
|
157
|
+
|
|
158
|
+
linkDocument(element[0], node.document_id);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setTimeout(function() {
|
|
162
|
+
if (node.id && (node.id.startsWith('collection_') || node.id === 'uncategorized')) {
|
|
163
|
+
element.addClass('collection-context-menu');
|
|
164
|
+
}
|
|
165
|
+
}, 10);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function initDocumentsTreeview(filteredCollections = null, callback = null) {
|
|
171
|
+
const expandedIds = saveExpandedState();
|
|
172
|
+
|
|
173
|
+
const treeData = convertDocumentsToTreeNode(filteredCollections);
|
|
174
|
+
const customNodeOptions = customDocumentNodeRow();
|
|
175
|
+
|
|
176
|
+
const options = {
|
|
177
|
+
data: treeData,
|
|
178
|
+
...customNodeOptions,
|
|
179
|
+
showIcon: false,
|
|
180
|
+
showBorder: true,
|
|
181
|
+
showTags: true,
|
|
182
|
+
levels: 1,
|
|
183
|
+
collapseIcon: 'fas fa-chevron-down',
|
|
184
|
+
expandIcon: 'fas fa-chevron-right',
|
|
185
|
+
customizeBadges: function(container, node) {
|
|
186
|
+
container.addClass('document-badges');
|
|
187
|
+
if (node.document_id) {
|
|
188
|
+
container.addClass('document-badges-left');
|
|
189
|
+
}
|
|
190
|
+
if (!hideCodeCounts && node.tags && node.tags.length > 0) {
|
|
191
|
+
container.append($('<span>', {
|
|
192
|
+
class: 'badge badge-secondary badge-fixed-width',
|
|
193
|
+
title: node.document_id ? gettext('Highlights count') : gettext('Documents count'),
|
|
194
|
+
text: node.tags[0]
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
options.multiSelect = true;
|
|
201
|
+
|
|
202
|
+
treeview_documents.treeview(options);
|
|
203
|
+
|
|
204
|
+
if (callback) {
|
|
205
|
+
setTimeout(() => {
|
|
206
|
+
if (expandedIds.length > 0) {
|
|
207
|
+
restoreExpandedState(expandedIds, callback);
|
|
208
|
+
} else {
|
|
209
|
+
callback();
|
|
210
|
+
}
|
|
211
|
+
}, 50);
|
|
212
|
+
} else if (expandedIds.length > 0) {
|
|
213
|
+
restoreExpandedState(expandedIds, null);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (filteredCollections) {
|
|
217
|
+
treeview_documents.treeview('expandAll');
|
|
218
|
+
} else if (expandedIds.length === 0) {
|
|
219
|
+
treeview_documents.treeview('collapseAll');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
treeview_documents.on('nodeExpanded', function(event, node) {
|
|
223
|
+
if (node.nodes && node.nodes.length === 1 && node.nodes[0].isDummy) {
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
event.stopImmediatePropagation();
|
|
226
|
+
treeview_documents.treeview('collapseNode', [node.nodeId, {silent: true}]);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
treeview_documents.on('nodeSelected nodeUnselected nodeCollapsed nodeExpanded', function (event, node) {
|
|
232
|
+
if (event.type === 'nodeCollapsed' || event.type === 'nodeExpanded') {
|
|
233
|
+
deselectAllDocumentNodes();
|
|
234
|
+
hideDragHint();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (event.type === 'nodeSelected' || event.type === 'nodeUnselected') {
|
|
238
|
+
const selectedNodes = treeview_documents.treeview('getSelected');
|
|
239
|
+
const selectedDocuments = selectedNodes.filter(node =>
|
|
240
|
+
node.id && node.id.startsWith('document_')
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
if (event.type === 'nodeSelected' && node.id && node.id.startsWith('document_')) {
|
|
244
|
+
showDragHint(selectedDocuments.length, node.nodeId);
|
|
245
|
+
} else if (selectedDocuments.length <= 1) {
|
|
246
|
+
hideDragHint();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
setTimeout(function () {
|
|
251
|
+
addDocumentDragDrop();
|
|
252
|
+
|
|
253
|
+
}, 10);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
setTimeout(function() {
|
|
257
|
+
addDocumentDragDrop();
|
|
258
|
+
}, 10);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function updateDocumentTreeviewHighlight(current_document) {
|
|
262
|
+
if (current_document) {
|
|
263
|
+
const treeviewDocumentLink = document.getElementById('document-link-' + current_document);
|
|
264
|
+
if (treeviewDocumentLink) {
|
|
265
|
+
treeviewDocumentLink.classList.add('document-link-current');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function findDocumentParentCollection(documentId) {
|
|
271
|
+
if (!treeview_documents.length) return null;
|
|
272
|
+
|
|
273
|
+
const allNodes = treeview_documents.treeview('getEnabled');
|
|
274
|
+
const document = documents[documentId];
|
|
275
|
+
|
|
276
|
+
if (!document) return null;
|
|
277
|
+
|
|
278
|
+
const docCollections = document.collections || [];
|
|
279
|
+
|
|
280
|
+
if (docCollections.length > 0) {
|
|
281
|
+
const collectionId = docCollections[0];
|
|
282
|
+
const collectionNode = allNodes.find(node => node.id === `collection_${collectionId}`);
|
|
283
|
+
return collectionNode ? collectionNode.nodeId : null;
|
|
284
|
+
} else {
|
|
285
|
+
const uncategorizedNode = allNodes.find(node => node.id === 'uncategorized');
|
|
286
|
+
return uncategorizedNode ? uncategorizedNode.nodeId : null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function findDocumentRootParent(collectionId) {
|
|
291
|
+
const collection = findCollection(collectionId);
|
|
292
|
+
if (collection && collection.parent) {
|
|
293
|
+
return findDocumentRootParent(collection.parent);
|
|
294
|
+
}
|
|
295
|
+
return collectionId;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function expandDocumentNodeParent(collectionParent) {
|
|
299
|
+
const rootParentId = findDocumentRootParent(collectionParent);
|
|
300
|
+
const nodeId = getNodeIdFromTreeviewByCollectionId(treeview_documents, rootParentId);
|
|
301
|
+
expandDocumentNode(treeview_documents, nodeId);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function expandDocumentNode(treeview, nodeId) {
|
|
305
|
+
const node = getNode(treeview, nodeId);
|
|
306
|
+
if (!node.state.expanded) {
|
|
307
|
+
treeview.treeview('expandNode', [parseInt(nodeId, 10),
|
|
308
|
+
{levels: 5, silent: false}]);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function highlightCollection(collectionId) {
|
|
313
|
+
const nodeId = getNodeIdFromTreeviewByCollectionId(treeview_documents, collectionId);
|
|
314
|
+
const nodeElement = treeview_documents.find(`li[data-nodeid="${nodeId}"]`);
|
|
315
|
+
|
|
316
|
+
if (nodeElement.length > 0) {
|
|
317
|
+
nodeElement[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
nodeElement.addClass('highlight-collection');
|
|
321
|
+
setTimeout(() => {
|
|
322
|
+
nodeElement.addClass('fade-out');
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
nodeElement.removeClass('highlight-collection fade-out');
|
|
325
|
+
}, 1000);
|
|
326
|
+
}, 3000);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function highlightDocument(documentId) {
|
|
330
|
+
const nodeId = getNodeIdFromTreeviewByDocumentId(treeview_documents, documentId);
|
|
331
|
+
const nodeElement = treeview_documents.find(`li[data-nodeid="${nodeId}"]`);
|
|
332
|
+
|
|
333
|
+
if (nodeElement.length > 0) {
|
|
334
|
+
nodeElement[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
nodeElement.addClass('highlight-document');
|
|
338
|
+
setTimeout(() => {
|
|
339
|
+
nodeElement.addClass('fade-out');
|
|
340
|
+
}, 3000);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function getNodeIdFromTreeviewByCollectionId(treeview, collectionId) {
|
|
344
|
+
const allNodes = treeview.treeview('getEnabled');
|
|
345
|
+
const node = allNodes.find(node => node.id === `collection_${collectionId}`);
|
|
346
|
+
return node ? node.nodeId : null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function getNodeIdFromTreeviewByDocumentId(treeview, documentId) {
|
|
350
|
+
const allNodes = treeview.treeview('getEnabled');
|
|
351
|
+
const node = allNodes.find(node => node.id === `document_${documentId}`);
|
|
352
|
+
return node ? node.nodeId : null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function getDocumentIdFromTreeviewNode(treeview, nodeId) {
|
|
356
|
+
const allNodes = treeview.treeview('getEnabled');
|
|
357
|
+
const node = allNodes.find(item => item.nodeId === parseInt(nodeId));
|
|
358
|
+
|
|
359
|
+
if (node && node.id && node.id.startsWith('document_')) {
|
|
360
|
+
return parseInt(node.id.replace('document_', ''));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
log('getDocumentIdFromTreeviewNode - node not a document:', 'warn', node);
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function deselectAllDocumentNodes() {
|
|
368
|
+
const allNodes = treeview_documents.treeview('getEnabled');
|
|
369
|
+
allNodes.forEach(node => {
|
|
370
|
+
if (node.state.selected) {
|
|
371
|
+
treeview_documents.treeview('unselectNode', [node.nodeId, {silent: false}]);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function showDragHint(selectedCount, lastSelectedNodeId) {
|
|
377
|
+
|
|
378
|
+
$('#drag-hint').remove();
|
|
379
|
+
|
|
380
|
+
if (selectedCount > 1) {
|
|
381
|
+
const message = gettext("You can drag and drop %s documents to a collection").replace('%s', selectedCount);
|
|
382
|
+
const popup = $('<div id="drag-hint" class="drag-hint-popup">' +
|
|
383
|
+
'<i class="fas fa-grip-vertical"></i> ' + message +
|
|
384
|
+
'</div>');
|
|
385
|
+
|
|
386
|
+
$('#treeview-documents').before(popup);
|
|
387
|
+
|
|
388
|
+
setTimeout(() => {
|
|
389
|
+
$('#drag-hint').fadeOut(300, function() {
|
|
390
|
+
$(this).remove();
|
|
391
|
+
});
|
|
392
|
+
}, 3000);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function hideDragHint() {
|
|
397
|
+
$('#drag-hint').fadeOut(200, function() {
|
|
398
|
+
$(this).remove();
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function addDocumentDragDrop() {
|
|
403
|
+
|
|
404
|
+
$('.node-treeview-documents').on('dragstart', function (event) {
|
|
405
|
+
const nodeId = $(this).data('nodeid');
|
|
406
|
+
const documentId = getDocumentIdFromTreeviewNode(treeview_documents, nodeId);
|
|
407
|
+
|
|
408
|
+
// Only allow dragging documents, not collections
|
|
409
|
+
if (!documentId) {
|
|
410
|
+
event.preventDefault();
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Get all selected documents
|
|
415
|
+
const selectedNodes = treeview_documents.treeview('getSelected');
|
|
416
|
+
const selectedDocumentIds = selectedNodes
|
|
417
|
+
.map(node => getDocumentIdFromTreeviewNode(treeview_documents, node.nodeId))
|
|
418
|
+
.filter(id => id !== null);
|
|
419
|
+
|
|
420
|
+
// If no documents are selected, use only the dragged document
|
|
421
|
+
const documentsToMove = selectedDocumentIds.length > 0 ? selectedDocumentIds : [documentId];
|
|
422
|
+
|
|
423
|
+
// Store document IDs in dataTransfer
|
|
424
|
+
event.originalEvent.dataTransfer.setData('text', JSON.stringify(documentsToMove));
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
$(".node-treeview-documents").each(function (index, element) {
|
|
428
|
+
|
|
429
|
+
$(element).off('drop');
|
|
430
|
+
$(element).off('dragover');
|
|
431
|
+
|
|
432
|
+
$(element).on('dragover', function (event) {
|
|
433
|
+
event.preventDefault();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
$(element).on('drop', function (event) {
|
|
437
|
+
event.preventDefault();
|
|
438
|
+
|
|
439
|
+
const collectionId = getCollectionIdFromTreeviewNode(treeview_documents, $(element).data("nodeid"));
|
|
440
|
+
const draggedData = event.originalEvent.dataTransfer.getData('text');
|
|
441
|
+
|
|
442
|
+
// Hide drag hint after drop
|
|
443
|
+
hideDragHint();
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
// Try to parse as JSON (multiple documents)
|
|
447
|
+
const selectedDocumentIds = JSON.parse(draggedData);
|
|
448
|
+
if (Array.isArray(selectedDocumentIds)) {
|
|
449
|
+
// Move all selected documents with sequential delay
|
|
450
|
+
selectedDocumentIds.forEach((documentId, index) => {
|
|
451
|
+
// Check if document is already in this collection
|
|
452
|
+
const document = documents[documentId.toString()];
|
|
453
|
+
const currentCollections = document ? document.collections || [] : [];
|
|
454
|
+
const isAlreadyInCollection = currentCollections.includes(collectionId);
|
|
455
|
+
|
|
456
|
+
if (!isAlreadyInCollection) {
|
|
457
|
+
setTimeout(() => {
|
|
458
|
+
moveDocumentToCollection(documentId, collectionId);
|
|
459
|
+
}, index * 200); // 200ms delay between each document
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
} catch (e) {
|
|
464
|
+
// Fallback to single document (old format)
|
|
465
|
+
const documentId = parseInt(draggedData);
|
|
466
|
+
if (!isNaN(documentId)) {
|
|
467
|
+
// Check if document is already in this collection
|
|
468
|
+
const document = documents[documentId.toString()];
|
|
469
|
+
const currentCollections = document ? document.collections || [] : [];
|
|
470
|
+
const isAlreadyInCollection = currentCollections.includes(collectionId);
|
|
471
|
+
|
|
472
|
+
if (!isAlreadyInCollection) {
|
|
473
|
+
moveDocumentToCollection(documentId, collectionId);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function getCollectionIdFromTreeviewNode(treeview, nodeId) {
|
|
482
|
+
const allNodes = treeview.treeview('getEnabled');
|
|
483
|
+
const node = allNodes.find(item => item.nodeId === parseInt(nodeId));
|
|
484
|
+
|
|
485
|
+
if (node && node.id && node.id.startsWith('collection_')) {
|
|
486
|
+
return parseInt(node.id.replace('collection_', ''));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function isUncategorizedNode(nodeId) {
|
|
493
|
+
const allNodes = treeview_documents.treeview('getEnabled');
|
|
494
|
+
const node = allNodes.find(item => item.nodeId === parseInt(nodeId));
|
|
495
|
+
return node && node.id === 'uncategorized';
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function moveDocumentToCollection(documentId, targetCollectionId) {
|
|
499
|
+
const document = documents[documentId.toString()];
|
|
500
|
+
if (!document) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (targetCollectionId === undefined || targetCollectionId === null) {
|
|
504
|
+
log('Target collection ID not provided');
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const updatedDocument = {
|
|
509
|
+
id: documentId,
|
|
510
|
+
collections: targetCollectionId !== null ? [targetCollectionId] : []
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
submitDocumentUpdate(updatedDocument,
|
|
514
|
+
(response) => {
|
|
515
|
+
log('Document moved successfully:', 'success', {response});
|
|
516
|
+
},
|
|
517
|
+
(error) => {
|
|
518
|
+
log('Error moving document:', 'error', {error});
|
|
519
|
+
}
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
$(document).ready(function() {
|
|
524
|
+
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
|
525
|
+
const cmdKey = isMac ? 'Cmd' : 'Ctrl';
|
|
526
|
+
|
|
527
|
+
const sections = [
|
|
528
|
+
{
|
|
529
|
+
title: gettext('Right-click on a collection:'),
|
|
530
|
+
items: [
|
|
531
|
+
gettext('Add sub-collection'),
|
|
532
|
+
gettext('Edit collection'),
|
|
533
|
+
gettext('Remove parent'),
|
|
534
|
+
gettext('Delete collection')
|
|
535
|
+
]
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
title: gettext('Right-click on a document:'),
|
|
539
|
+
items: [
|
|
540
|
+
gettext('Edit document'),
|
|
541
|
+
gettext('Toggle bookmark'),
|
|
542
|
+
gettext('Delete document')
|
|
543
|
+
]
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
title: gettext('Select multiple documents:'),
|
|
547
|
+
items: [
|
|
548
|
+
gettext('Hold %(key)s and click documents', { key: cmdKey }),
|
|
549
|
+
gettext('Drag selected documents to a collection')
|
|
550
|
+
]
|
|
551
|
+
}
|
|
552
|
+
];
|
|
553
|
+
|
|
554
|
+
initHelpPopover('treeview-help-icon', sections);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
function saveExpandedState() {
|
|
559
|
+
if (!treeview_documents.length) return [];
|
|
560
|
+
if (!treeview_documents.data('treeview')) return [];
|
|
561
|
+
|
|
562
|
+
try {
|
|
563
|
+
const expandedNodes = treeview_documents.treeview('getExpanded');
|
|
564
|
+
return expandedNodes.map(node => node.id).filter(id => id);
|
|
565
|
+
} catch (e) {
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function restoreExpandedState(expandedIds, callback) {
|
|
571
|
+
if (!expandedIds || expandedIds.length === 0) {
|
|
572
|
+
if (callback) setTimeout(callback, 50);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
setTimeout(() => {
|
|
577
|
+
const allNodes = treeview_documents.treeview('getEnabled');
|
|
578
|
+
expandedIds.forEach(id => {
|
|
579
|
+
const node = allNodes.find(n => n.id === id);
|
|
580
|
+
if (node && !node.state.expanded) {
|
|
581
|
+
treeview_documents.treeview('expandNode', [node.nodeId, {silent: true}]);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
addDocumentDragDrop();
|
|
585
|
+
|
|
586
|
+
if (callback) {
|
|
587
|
+
setTimeout(() => {
|
|
588
|
+
callback();
|
|
589
|
+
}, 150);
|
|
590
|
+
}
|
|
591
|
+
}, 50);
|
|
592
|
+
}
|
|
593
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Highlight System
|
|
2
|
+
|
|
3
|
+
SVG overlay approach — the HTML document stays intact, highlights are drawn as lines on top.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
1. **loader.js** fetches the document and builds two layers: a content div (text) and an SVG element (highlights)
|
|
8
|
+
2. **renderer.js** converts UTF-8 byte offsets to DOM positions using a char map, then uses `Range.getClientRects()` to get pixel coordinates for each highlight
|
|
9
|
+
3. Only block elements (`<p>`, `<h1>`-`<h6>`, etc.) that contain highlights get extra line-height — the rest stays normal
|
|
10
|
+
4. Overlapping highlights are stacked in lanes so they don't collide
|
|
11
|
+
5. Multi-tag highlights use SVG gradients
|
|
12
|
+
6. **crud.js** handles create/edit/delete via the API
|
|
13
|
+
|
|
14
|
+
## Files
|
|
15
|
+
|
|
16
|
+
- `renderer.js` — rendering pipeline, smart lanes, tooltips
|
|
17
|
+
- `loader.js` — document loading, ResizeObserver
|
|
18
|
+
- `crud.js` — CRUD operations
|