tg-prepare 1.1.0__py3-none-any.whl → 2.1.0b1__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.

Potentially problematic release.


This version of tg-prepare might be problematic. Click here for more details.

Files changed (46) hide show
  1. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/METADATA +3 -3
  2. tg_prepare-2.1.0b1.dist-info/RECORD +54 -0
  3. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/WHEEL +1 -1
  4. tg_prepare-2.1.0b1.dist-info/projects/.secret_key +1 -0
  5. tgp_backend/__init__.py +3 -4
  6. tgp_backend/config.py +31 -0
  7. tgp_backend/directories.py +6 -8
  8. tgp_backend/nextcloud.py +40 -19
  9. tgp_backend/project.py +172 -45
  10. tgp_backend/session_manager.py +47 -0
  11. tgp_backend/util.py +73 -25
  12. tgp_ui/app.py +43 -335
  13. tgp_ui/routes/__init__.py +0 -0
  14. tgp_ui/routes/collection.py +272 -0
  15. tgp_ui/routes/data.py +228 -0
  16. tgp_ui/routes/project.py +102 -0
  17. tgp_ui/routes/publication.py +129 -0
  18. tgp_ui/routes/tabs.py +34 -0
  19. tgp_ui/routes/views.py +62 -0
  20. tgp_ui/static/css/navbar.css +92 -0
  21. tgp_ui/static/js/collectionManager.js +60 -0
  22. tgp_ui/static/js/fileManager.js +186 -0
  23. tgp_ui/static/js/main.js +32 -485
  24. tgp_ui/static/js/modalManager.js +105 -0
  25. tgp_ui/static/js/navbarManager.js +151 -0
  26. tgp_ui/static/js/projectManager.js +60 -0
  27. tgp_ui/static/js/require.js +5 -0
  28. tgp_ui/static/js/sidebarManager.js +32 -0
  29. tgp_ui/static/js/tabManager.js +79 -0
  30. tgp_ui/templates/layout.html +9 -48
  31. tgp_ui/templates/macros.html +79 -72
  32. tgp_ui/templates/project_main.html +36 -0
  33. tgp_ui/templates/project_navbar.html +81 -0
  34. tgp_ui/templates/{projects.html → projects_main.html} +13 -28
  35. tgp_ui/templates/tab_final_upload.html +29 -0
  36. tg_prepare-1.1.0.dist-info/RECORD +0 -39
  37. tgp_ui/templates/collection.html +0 -194
  38. tgp_ui/templates/file_upload.html +0 -24
  39. tgp_ui/templates/nxc_file_tree.html +0 -33
  40. tgp_ui/templates/project.html +0 -26
  41. tgp_ui/templates/storage.html +0 -49
  42. tgp_ui/templates/tei_explorer.html +0 -48
  43. tgp_ui/templates/xpath_parser_modal_content.html +0 -37
  44. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/entry_points.txt +0 -0
  45. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/licenses/LICENSE +0 -0
  46. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,151 @@
1
+ define([], function () {
2
+ const NavbarManager = {
3
+ resetButtons: function (buttons, additionalClasses = []) {
4
+ buttons.forEach(btn => {
5
+ btn.classList.remove('animate-to-long', 'border-primary', ...additionalClasses);
6
+ const icon = btn.querySelector('i');
7
+ const subButtons = btn.querySelector('.sub-buttons');
8
+
9
+ if (icon) icon.classList.remove('d-none');
10
+ if (subButtons) {
11
+ subButtons.classList.add('d-none');
12
+ subButtons.style.opacity = '0';
13
+ }
14
+
15
+ btn.style.width = ''; // Reset width
16
+
17
+ // Füge für kleine Buttons die Outline-Secondary-Klasse wieder hinzu
18
+ if (btn.classList.contains('btn-sm')) {
19
+ btn.classList.add('btn-outline-secondary');
20
+ }
21
+ });
22
+ },
23
+
24
+ activateButton: function (button, index, buttons) {
25
+ // Vorherige Buttons hervorheben / Progress anzeigen
26
+ for (let i = 0; i <= index; i++) {
27
+ buttons[i].classList.add('border-primary');
28
+ }
29
+
30
+ // Aktuellen Button erweitern
31
+ button.classList.add('animate-to-long');
32
+ const icon = button.querySelector('i');
33
+ const subButtons = button.querySelector('.sub-buttons');
34
+
35
+ if (icon) icon.classList.add('d-none');
36
+ if (subButtons) {
37
+ subButtons.classList.remove('d-none');
38
+ setTimeout(() => {
39
+ subButtons.style.opacity = '1';
40
+ }, 300);
41
+
42
+ // Ersten kleinen Button klicken, wenn kein last-opened vorhanden
43
+ const lastOpenedButton = subButtons.querySelector('.last-opened');
44
+ if (lastOpenedButton) {
45
+ lastOpenedButton.click();
46
+ } else {
47
+ const firstSmButton = subButtons.querySelector('.btn-sm');
48
+ if (firstSmButton) firstSmButton.click();
49
+ }
50
+ }
51
+
52
+ // Dynamische Breite basierend auf kleinen Buttons
53
+ const smallButtonsCount = subButtons?.querySelectorAll('.btn-sm').length || 0;
54
+ const sizeAdjustment = smallButtonsCount * 5.4;
55
+ button.style.width = `${sizeAdjustment}rem`;
56
+ },
57
+
58
+ adjustLine: function (button) {
59
+ const container = document.querySelector('.container.bg'); // Container der Buttons
60
+ const lineBlue = document.querySelector('.line.blue'); // Zweite Linie
61
+ const containerRect = container.getBoundingClientRect(); // Position des Containers
62
+ const buttonRect = button.getBoundingClientRect(); // Position des angeklickten Buttons
63
+
64
+ // Berechne die Breite der Linie basierend auf der Position des Buttons
65
+ const newWidth = buttonRect.left - containerRect.left + buttonRect.width / 2;
66
+ lineBlue.style.width = `${newWidth}px`; // Setze die neue Breite der Linie
67
+ },
68
+
69
+ triggerLastOpenedTab: function (projectname) {
70
+ fetch(`/get_last_tab/${projectname}`)
71
+ .then(response => response.json())
72
+ .then(data => {
73
+ if (data.initial_tab) {
74
+ const initialTab = document.querySelector(`.btn-sm[data-bs-target="#${data.initial_tab}"]`);
75
+ if (initialTab) {
76
+ initialTab.closest('.btn-xl').click();
77
+ initialTab.click();
78
+ }
79
+ }
80
+ })
81
+ .catch(error => console.error('Error fetching initial tab:', error));
82
+ },
83
+
84
+ setLastOpenedTab: function (projectname, tab) {
85
+ fetch(`/set_last_tab/${projectname}/${tab}`, {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/json'
89
+ },
90
+ body: JSON.stringify({ projectname: projectname, tab: tab })
91
+ })
92
+ .then(response => response.json())
93
+ .catch(error => console.error('Error writing last tab:', error));
94
+ },
95
+
96
+ init: function () {
97
+ // Event-Listener für große Buttons (btn-xl)
98
+ document.querySelectorAll('.btn-xl').forEach((button, index, buttons) => {
99
+ button.addEventListener('click', () => {
100
+ // Delete activate class from all small buttons
101
+ const smBtnContainer = button.closest('.container.bg');
102
+ smBtnContainer.querySelectorAll('.btn-sm').forEach(btn => {
103
+ btn.classList.remove('active');
104
+ });
105
+
106
+ this.resetButtons(document.querySelectorAll('.btn-xl'));
107
+ this.activateButton(button, index, buttons);
108
+ this.adjustLine(button);
109
+ });
110
+ });
111
+
112
+ // Event-Listener für kleine Buttons (btn-sm)
113
+ document.querySelectorAll('.btn-sm').forEach(button => {
114
+ button.addEventListener('click', event => {
115
+ event.stopPropagation(); // Verhindert, dass der große Button reagiert
116
+
117
+ // Alle kleinen Buttons in der gleichen Sektion zurücksetzen
118
+ const parentSection = button.closest('.sub-buttons');
119
+ this.resetButtons(parentSection.querySelectorAll('.btn-sm'), ['btn-outline-primary', 'last-opened']);
120
+
121
+ // Aktuellen Button aktivieren
122
+ button.classList.add('btn-outline-primary', 'last-opened');
123
+ button.classList.remove('btn-outline-secondary');
124
+
125
+ // Setze den zuletzt geöffneten Tab
126
+ const projectname = this.getActiveProjectName();
127
+ const tab = button.getAttribute('data-bs-target').replace('#', '');
128
+ this.setLastOpenedTab(projectname, tab);
129
+ });
130
+ });
131
+
132
+ // Initialisiere den zuletzt geöffneten Tab
133
+ const projectname = this.getActiveProjectName();
134
+ if (projectname) {
135
+ this.triggerLastOpenedTab(projectname);
136
+ }
137
+ },
138
+
139
+ getActiveProjectName: function () {
140
+ const activeBtn = Array.from(document.querySelectorAll('.project-btn'))
141
+ .find(btn => btn.querySelector('.bi.bi-circle-fill'));
142
+ if (activeBtn) {
143
+ const nameSpan = activeBtn.querySelector('.projectname');
144
+ return nameSpan ? nameSpan.textContent : null;
145
+ }
146
+ return null;
147
+ }
148
+ };
149
+
150
+ return NavbarManager;
151
+ });
@@ -0,0 +1,60 @@
1
+ define([], function () {
2
+ const ProjectManager = {
3
+ addMultiInputAttributes: function (event) {
4
+ event.preventDefault();
5
+
6
+ // Clone the current input group element
7
+ const currentInputGroup = event.target.closest('.input-group');
8
+ const newInputGroup = currentInputGroup.cloneNode(true);
9
+
10
+ // Clear the input field in the cloned element
11
+ const input = newInputGroup.querySelector('input');
12
+ if (input) {
13
+ input.value = '';
14
+ }
15
+
16
+ // Replace the plus symbol with a minus symbol and add the 'remove-multi-input' class
17
+ const button = newInputGroup.querySelector('.add-multi-input');
18
+ if (button) {
19
+ button.classList.remove('add-multi-input');
20
+ button.classList.add('remove-multi-input');
21
+ const logo = button.querySelector('i');
22
+ logo.classList.remove('bi-plus-circle-dotted');
23
+ logo.classList.add('bi-dash-circle-dotted');
24
+ }
25
+
26
+ // Append the cloned element at the end of the parent container
27
+ currentInputGroup.parentNode.appendChild(newInputGroup);
28
+ },
29
+
30
+ removeMultiInputAttributes: function (event) {
31
+ event.preventDefault();
32
+
33
+ // Remove the parent div of the minus button
34
+ const inputGroup = event.target.closest('.input-group');
35
+ if (inputGroup) {
36
+ inputGroup.remove();
37
+ }
38
+ },
39
+
40
+ focusInputProjectname: function () {
41
+ const projectInput = document.getElementById('projectNameInput');
42
+ if (projectInput) {
43
+ projectInput.focus();
44
+ }
45
+ },
46
+
47
+ init: function () {
48
+ // Event binding for adding a new multi-input field
49
+ $(document).on('click', '.add-multi-input', this.addMultiInputAttributes);
50
+
51
+ // Event binding for removing a multi-input field
52
+ $(document).on('click', '.remove-multi-input', this.removeMultiInputAttributes);
53
+
54
+ // Event binding for focusing on the project name input in the modal
55
+ $(document).on('shown.bs.modal', '#newProject', this.focusInputProjectname);
56
+ }
57
+ };
58
+
59
+ return ProjectManager;
60
+ });
@@ -0,0 +1,5 @@
1
+ /** vim: et:ts=4:sw=4:sts=4
2
+ * @license RequireJS 2.3.7 Copyright jQuery Foundation and other contributors.
3
+ * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE
4
+ */
5
+ var requirejs, require, define; !function (global, setTimeout) { var req, s, head, baseElement, dataMain, src, interactiveScript, currentlyAddingScript, mainScript, subPath, version = "2.3.7", commentRegExp = /\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, jsSuffixRegExp = /\.js$/, currDirRegExp = /^\.\//, op = Object.prototype, ostring = op.toString, hasOwn = op.hasOwnProperty, isBrowser = !("undefined" == typeof window || "undefined" == typeof navigator || !window.document), isWebWorker = !isBrowser && "undefined" != typeof importScripts, readyRegExp = isBrowser && "PLAYSTATION 3" === navigator.platform ? /^complete$/ : /^(complete|loaded)$/, defContextName = "_", isOpera = "undefined" != typeof opera && "[object Opera]" === opera.toString(), contexts = {}, cfg = {}, globalDefQueue = [], useInteractive = !1, disallowedProps = ["__proto__", "constructor"]; function commentReplace(e, t) { return t || "" } function isFunction(e) { return "[object Function]" === ostring.call(e) } function isArray(e) { return "[object Array]" === ostring.call(e) } function each(e, t) { if (e) for (var i = 0; i < e.length && (!e[i] || !t(e[i], i, e)); i += 1); } function eachReverse(e, t) { if (e) for (var i = e.length - 1; -1 < i && (!e[i] || !t(e[i], i, e)); --i); } function hasProp(e, t) { return hasOwn.call(e, t) } function getOwn(e, t) { return hasProp(e, t) && e[t] } function eachProp(e, t) { for (var i in e) if (hasProp(e, i) && -1 == disallowedProps.indexOf(i) && t(e[i], i)) break } function mixin(i, e, r, n) { e && eachProp(e, function (e, t) { !r && hasProp(i, t) || (!n || "object" != typeof e || !e || isArray(e) || isFunction(e) || e instanceof RegExp ? i[t] = e : (i[t] || (i[t] = {}), mixin(i[t], e, r, n))) }) } function bind(e, t) { return function () { return t.apply(e, arguments) } } function scripts() { return document.getElementsByTagName("script") } function defaultOnError(e) { throw e } function getGlobal(e) { var t; return e && (t = global, each(e.split("."), function (e) { t = t[e] }), t) } function makeError(e, t, i, r) { t = new Error(t + "\nhttps://requirejs.org/docs/errors.html#" + e); return t.requireType = e, t.requireModules = r, i && (t.originalError = i), t } if (void 0 === define) { if (void 0 !== requirejs) { if (isFunction(requirejs)) return; cfg = requirejs, requirejs = void 0 } void 0 === require || isFunction(require) || (cfg = require, require = void 0), req = requirejs = function (e, t, i, r) { var n, o = defContextName; return isArray(e) || "string" == typeof e || (n = e, isArray(t) ? (e = t, t = i, i = r) : e = []), n && n.context && (o = n.context), r = (r = getOwn(contexts, o)) || (contexts[o] = req.s.newContext(o)), n && r.configure(n), r.require(e, t, i) }, req.config = function (e) { return req(e) }, req.nextTick = void 0 !== setTimeout ? function (e) { setTimeout(e, 4) } : function (e) { e() }, require = require || req, req.version = version, req.jsExtRegExp = /^\/|:|\?|\.js$/, req.isBrowser = isBrowser, s = req.s = { contexts: contexts, newContext: newContext }, req({}), each(["toUrl", "undef", "defined", "specified"], function (t) { req[t] = function () { var e = contexts[defContextName]; return e.require[t].apply(e, arguments) } }), isBrowser && (head = s.head = document.getElementsByTagName("head")[0], baseElement = document.getElementsByTagName("base")[0], baseElement) && (head = s.head = baseElement.parentNode), req.onError = defaultOnError, req.createNode = function (e, t, i) { var r = e.xhtml ? document.createElementNS("http://www.w3.org/1999/xhtml", "html:script") : document.createElement("script"); return r.type = e.scriptType || "text/javascript", r.charset = "utf-8", r.async = !0, r }, req.load = function (t, i, r) { var e, n = t && t.config || {}; if (isBrowser) return (e = req.createNode(n, i, r)).setAttribute("data-requirecontext", t.contextName), e.setAttribute("data-requiremodule", i), !e.attachEvent || e.attachEvent.toString && e.attachEvent.toString().indexOf("[native code") < 0 || isOpera ? (e.addEventListener("load", t.onScriptLoad, !1), e.addEventListener("error", t.onScriptError, !1)) : (useInteractive = !0, e.attachEvent("onreadystatechange", t.onScriptLoad)), e.src = r, n.onNodeCreated && n.onNodeCreated(e, n, i, r), currentlyAddingScript = e, baseElement ? head.insertBefore(e, baseElement) : head.appendChild(e), currentlyAddingScript = null, e; if (isWebWorker) try { setTimeout(function () { }, 0), importScripts(r), t.completeLoad(i) } catch (e) { t.onError(makeError("importscripts", "importScripts failed for " + i + " at " + r, e, [i])) } }, isBrowser && !cfg.skipDataMain && eachReverse(scripts(), function (e) { if (head = head || e.parentNode, dataMain = e.getAttribute("data-main")) return mainScript = dataMain, cfg.baseUrl || -1 !== mainScript.indexOf("!") || (mainScript = (src = mainScript.split("/")).pop(), subPath = src.length ? src.join("/") + "/" : "./", cfg.baseUrl = subPath), mainScript = mainScript.replace(jsSuffixRegExp, ""), req.jsExtRegExp.test(mainScript) && (mainScript = dataMain), cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript], !0 }), define = function (e, i, t) { var r, n; "string" != typeof e && (t = i, i = e, e = null), isArray(i) || (t = i, i = null), !i && isFunction(t) && (i = [], t.length) && (t.toString().replace(commentRegExp, commentReplace).replace(cjsRequireRegExp, function (e, t) { i.push(t) }), i = (1 === t.length ? ["require"] : ["require", "exports", "module"]).concat(i)), useInteractive && (r = currentlyAddingScript || getInteractiveScript()) && (e = e || r.getAttribute("data-requiremodule"), n = contexts[r.getAttribute("data-requirecontext")]), n ? (n.defQueue.push([e, i, t]), n.defQueueMap[e] = !0) : globalDefQueue.push([e, i, t]) }, define.amd = { jQuery: !0 }, req.exec = function (text) { return eval(text) }, req(cfg) } function newContext(u) { var t, e, f, c, i, b = { waitSeconds: 7, baseUrl: "./", paths: {}, bundles: {}, pkgs: {}, shim: {}, config: {} }, d = {}, p = {}, r = {}, l = [], h = {}, n = {}, m = {}, g = 1, x = 1; function v(e, t, i) { var r, n, o, a, s, u, c, d, p, f = t && t.split("/"), l = b.map, h = l && l["*"]; if (e) { t = (e = e.split("/")).length - 1, b.nodeIdCompat && jsSuffixRegExp.test(e[t]) && (e[t] = e[t].replace(jsSuffixRegExp, "")); for (var m, g = e = "." === e[0].charAt(0) && f ? f.slice(0, f.length - 1).concat(e) : e, x = 0; x < g.length; x++)"." === (m = g[x]) ? (g.splice(x, 1), --x) : ".." !== m || 0 === x || 1 === x && ".." === g[2] || ".." === g[x - 1] || 0 < x && (g.splice(x - 1, 2), x -= 2); e = e.join("/") } if (i && l && (f || h)) { e: for (o = (n = e.split("/")).length; 0 < o; --o) { if (s = n.slice(0, o).join("/"), f) for (a = f.length; 0 < a; --a)if (r = (r = getOwn(l, f.slice(0, a).join("/"))) && getOwn(r, s)) { u = r, c = o; break e } !d && h && getOwn(h, s) && (d = getOwn(h, s), p = o) } !u && d && (u = d, c = p), u && (n.splice(0, c, u), e = n.join("/")) } return getOwn(b.pkgs, e) || e } function q(t) { isBrowser && each(scripts(), function (e) { if (e.getAttribute("data-requiremodule") === t && e.getAttribute("data-requirecontext") === f.contextName) return e.parentNode.removeChild(e), !0 }) } function E(e) { var t = getOwn(b.paths, e); return t && isArray(t) && 1 < t.length && (t.shift(), f.require.undef(e), f.makeRequire(null, { skipMap: !0 })([e]), 1) } function w(e) { var t, i = e ? e.indexOf("!") : -1; return -1 < i && (t = e.substring(0, i), e = e.substring(i + 1, e.length)), [t, e] } function y(e, t, i, r) { var n, o, a, s = null, u = t ? t.name : null, c = e, d = !0, p = ""; return e || (d = !1, e = "_@r" + (g += 1)), s = (a = w(e))[0], e = a[1], s && (s = v(s, u, r), o = getOwn(h, s)), e && (s ? p = i ? e : o && o.normalize ? o.normalize(e, function (e) { return v(e, u, r) }) : -1 === e.indexOf("!") ? v(e, u, r) : e : (s = (a = w(p = v(e, u, r)))[0], i = !0, n = f.nameToUrl(p = a[1]))), { prefix: s, name: p, parentMap: t, unnormalized: !!(e = !s || o || i ? "" : "_unnormalized" + (x += 1)), url: n, originalName: c, isDefine: d, id: (s ? s + "!" + p : p) + e } } function S(e) { var t = e.id; return getOwn(d, t) || (d[t] = new f.Module(e)) } function k(e, t, i) { var r = e.id, n = getOwn(d, r); !hasProp(h, r) || n && !n.defineEmitComplete ? (n = S(e)).error && "error" === t ? i(n.error) : n.on(t, i) : "defined" === t && i(h[r]) } function M(t, e) { var i = t.requireModules, r = !1; e ? e(t) : (each(i, function (e) { e = getOwn(d, e); e && (e.error = t, e.events.error) && (r = !0, e.emit("error", t)) }), r || req.onError(t)) } function O() { globalDefQueue.length && (each(globalDefQueue, function (e) { var t = e[0]; "string" == typeof t && (f.defQueueMap[t] = !0), l.push(e) }), globalDefQueue = []) } function j(e) { delete d[e], delete p[e] } function P() { var r, e = 1e3 * b.waitSeconds, n = e && f.startTime + e < (new Date).getTime(), o = [], a = [], s = !1, u = !0; if (!t) { if (t = !0, eachProp(p, function (e) { var t = e.map, i = t.id; if (e.enabled && (t.isDefine || a.push(e), !e.error)) if (!e.inited && n) E(i) ? s = r = !0 : (o.push(i), q(i)); else if (!e.inited && e.fetched && t.isDefine && (s = !0, !t.prefix)) return u = !1 }), n && o.length) return (e = makeError("timeout", "Load timeout for modules: " + o, null, o)).contextName = f.contextName, M(e); u && each(a, function (e) { !function r(n, o, a) { var e = n.map.id; n.error ? n.emit("error", n.error) : (o[e] = !0, each(n.depMaps, function (e, t) { var e = e.id, i = getOwn(d, e); !i || n.depMatched[t] || a[e] || (getOwn(o, e) ? (n.defineDep(t, h[e]), n.check()) : r(i, o, a)) }), a[e] = !0) }(e, {}, {}) }), n && !r || !s || (isBrowser || isWebWorker) && (i = i || setTimeout(function () { i = 0, P() }, 50)), t = !1 } } function a(e) { hasProp(h, e[0]) || S(y(e[0], null, !0)).init(e[1], e[2]) } function o(e, t, i, r) { e.detachEvent && !isOpera ? r && e.detachEvent(r, t) : e.removeEventListener(i, t, !1) } function s(e) { e = e.currentTarget || e.srcElement; return o(e, f.onScriptLoad, "load", "onreadystatechange"), o(e, f.onScriptError, "error"), { node: e, id: e && e.getAttribute("data-requiremodule") } } function R() { var e; for (O(); l.length;) { if (null === (e = l.shift())[0]) return M(makeError("mismatch", "Mismatched anonymous define() module: " + e[e.length - 1])); a(e) } f.defQueueMap = {} } return c = { require: function (e) { return e.require || (e.require = f.makeRequire(e.map)) }, exports: function (e) { if (e.usingExports = !0, e.map.isDefine) return e.exports ? h[e.map.id] = e.exports : e.exports = h[e.map.id] = {} }, module: function (e) { return e.module || (e.module = { id: e.map.id, uri: e.map.url, config: function () { return getOwn(b.config, e.map.id) || {} }, exports: e.exports || (e.exports = {}) }) } }, (e = function (e) { this.events = getOwn(r, e.id) || {}, this.map = e, this.shim = getOwn(b.shim, e.id), this.depExports = [], this.depMaps = [], this.depMatched = [], this.pluginMaps = {}, this.depCount = 0 }).prototype = { init: function (e, t, i, r) { r = r || {}, this.inited || (this.factory = t, i ? this.on("error", i) : this.events.error && (i = bind(this, function (e) { this.emit("error", e) })), this.depMaps = e && e.slice(0), this.errback = i, this.inited = !0, this.ignore = r.ignore, r.enabled || this.enabled ? this.enable() : this.check()) }, defineDep: function (e, t) { this.depMatched[e] || (this.depMatched[e] = !0, --this.depCount, this.depExports[e] = t) }, fetch: function () { if (!this.fetched) { this.fetched = !0, f.startTime = (new Date).getTime(); var e = this.map; if (!this.shim) return e.prefix ? this.callPlugin() : this.load(); f.makeRequire(this.map, { enableBuildCallback: !0 })(this.shim.deps || [], bind(this, function () { return e.prefix ? this.callPlugin() : this.load() })) } }, load: function () { var e = this.map.url; n[e] || (n[e] = !0, f.load(this.map.id, e)) }, check: function () { if (this.enabled && !this.enabling) { var t, i, e = this.map.id, r = this.depExports, n = this.exports, o = this.factory; if (this.inited) { if (this.error) this.emit("error", this.error); else if (!this.defining) { if (this.defining = !0, this.depCount < 1 && !this.defined) { if (isFunction(o)) { if (this.events.error && this.map.isDefine || req.onError !== defaultOnError) try { n = f.execCb(e, o, r, n) } catch (e) { t = e } else n = f.execCb(e, o, r, n); if (this.map.isDefine && void 0 === n && ((r = this.module) ? n = r.exports : this.usingExports && (n = this.exports)), t) return t.requireMap = this.map, t.requireModules = this.map.isDefine ? [this.map.id] : null, t.requireType = this.map.isDefine ? "define" : "require", M(this.error = t) } else n = o; this.exports = n, this.map.isDefine && !this.ignore && (h[e] = n, req.onResourceLoad) && (i = [], each(this.depMaps, function (e) { i.push(e.normalizedMap || e) }), req.onResourceLoad(f, this.map, i)), j(e), this.defined = !0 } this.defining = !1, this.defined && !this.defineEmitted && (this.defineEmitted = !0, this.emit("defined", this.exports), this.defineEmitComplete = !0) } } else hasProp(f.defQueueMap, e) || this.fetch() } }, callPlugin: function () { var s = this.map, u = s.id, e = y(s.prefix); this.depMaps.push(e), k(e, "defined", bind(this, function (e) { var o, t, i = getOwn(m, this.map.id), r = this.map.name, n = this.map.parentMap ? this.map.parentMap.name : null, a = f.makeRequire(s.parentMap, { enableBuildCallback: !0 }); this.map.unnormalized ? (e.normalize && (r = e.normalize(r, function (e) { return v(e, n, !0) }) || ""), k(t = y(s.prefix + "!" + r, this.map.parentMap, !0), "defined", bind(this, function (e) { this.map.normalizedMap = t, this.init([], function () { return e }, null, { enabled: !0, ignore: !0 }) })), (r = getOwn(d, t.id)) && (this.depMaps.push(t), this.events.error && r.on("error", bind(this, function (e) { this.emit("error", e) })), r.enable())) : i ? (this.map.url = f.nameToUrl(i), this.load()) : ((o = bind(this, function (e) { this.init([], function () { return e }, null, { enabled: !0 }) })).error = bind(this, function (e) { this.inited = !0, (this.error = e).requireModules = [u], eachProp(d, function (e) { 0 === e.map.id.indexOf(u + "_unnormalized") && j(e.map.id) }), M(e) }), o.fromText = bind(this, function (e, t) { var i = s.name, r = y(i), n = useInteractive; t && (e = t), n && (useInteractive = !1), S(r), hasProp(b.config, u) && (b.config[i] = b.config[u]); try { req.exec(e) } catch (e) { return M(makeError("fromtexteval", "fromText eval for " + u + " failed: " + e, e, [u])) } n && (useInteractive = !0), this.depMaps.push(r), f.completeLoad(i), a([i], o) }), e.load(s.name, a, o, b)) })), f.enable(e, this), this.pluginMaps[e.id] = e }, enable: function () { (p[this.map.id] = this).enabled = !0, this.enabling = !0, each(this.depMaps, bind(this, function (e, t) { var i, r; if ("string" == typeof e) { if (e = y(e, this.map.isDefine ? this.map : this.map.parentMap, !1, !this.skipMap), this.depMaps[t] = e, r = getOwn(c, e.id)) return void (this.depExports[t] = r(this)); this.depCount += 1, k(e, "defined", bind(this, function (e) { this.undefed || (this.defineDep(t, e), this.check()) })), this.errback ? k(e, "error", bind(this, this.errback)) : this.events.error && k(e, "error", bind(this, function (e) { this.emit("error", e) })) } r = e.id, i = d[r], hasProp(c, r) || !i || i.enabled || f.enable(e, this) })), eachProp(this.pluginMaps, bind(this, function (e) { var t = getOwn(d, e.id); t && !t.enabled && f.enable(e, this) })), this.enabling = !1, this.check() }, on: function (e, t) { (this.events[e] || (this.events[e] = [])).push(t) }, emit: function (e, t) { each(this.events[e], function (e) { e(t) }), "error" === e && delete this.events[e] } }, (f = { config: b, contextName: u, registry: d, defined: h, urlFetched: n, defQueue: l, defQueueMap: {}, Module: e, makeModuleMap: y, nextTick: req.nextTick, onError: M, configure: function (e) { e.baseUrl && "/" !== e.baseUrl.charAt(e.baseUrl.length - 1) && (e.baseUrl += "/"), "string" == typeof e.urlArgs && (i = e.urlArgs, e.urlArgs = function (e, t) { return (-1 === t.indexOf("?") ? "?" : "&") + i }); var i, r = b.shim, n = { paths: !0, bundles: !0, config: !0, map: !0 }; eachProp(e, function (e, t) { n[t] ? (b[t] || (b[t] = {}), mixin(b[t], e, !0, !0)) : b[t] = e }), e.bundles && eachProp(e.bundles, function (e, t) { each(e, function (e) { e !== t && (m[e] = t) }) }), e.shim && (eachProp(e.shim, function (e, t) { !(e = isArray(e) ? { deps: e } : e).exports && !e.init || e.exportsFn || (e.exportsFn = f.makeShimExports(e)), r[t] = e }), b.shim = r), e.packages && each(e.packages, function (e) { var t = (e = "string" == typeof e ? { name: e } : e).name; e.location && (b.paths[t] = e.location), b.pkgs[t] = e.name + "/" + (e.main || "main").replace(currDirRegExp, "").replace(jsSuffixRegExp, "") }), eachProp(d, function (e, t) { e.inited || e.map.unnormalized || (e.map = y(t, null, !0)) }), (e.deps || e.callback) && f.require(e.deps || [], e.callback) }, makeShimExports: function (t) { return function () { var e; return (e = t.init ? t.init.apply(global, arguments) : e) || t.exports && getGlobal(t.exports) } }, makeRequire: function (o, a) { function s(e, t, i) { var r, n; return a.enableBuildCallback && t && isFunction(t) && (t.__requireJsBuild = !0), "string" == typeof e ? isFunction(t) ? M(makeError("requireargs", "Invalid require call"), i) : o && hasProp(c, e) ? c[e](d[o.id]) : req.get ? req.get(f, e, o, s) : (r = y(e, o, !1, !0).id, hasProp(h, r) ? h[r] : M(makeError("notloaded", 'Module name "' + r + '" has not been loaded yet for context: ' + u + (o ? "" : ". Use require([])")))) : (R(), f.nextTick(function () { R(), (n = S(y(null, o))).skipMap = a.skipMap, n.init(e, t, i, { enabled: !0 }), P() }), s) } return a = a || {}, mixin(s, { isBrowser: isBrowser, toUrl: function (e) { var t, i = e.lastIndexOf("."), r = e.split("/")[0]; return -1 !== i && (!("." === r || ".." === r) || 1 < i) && (t = e.substring(i, e.length), e = e.substring(0, i)), f.nameToUrl(v(e, o && o.id, !0), t, !0) }, defined: function (e) { return hasProp(h, y(e, o, !1, !0).id) }, specified: function (e) { return e = y(e, o, !1, !0).id, hasProp(h, e) || hasProp(d, e) } }), o || (s.undef = function (i) { O(); var e = y(i, o, !0), t = getOwn(d, i); t.undefed = !0, q(i), delete h[i], delete n[e.url], delete r[i], eachReverse(l, function (e, t) { e[0] === i && l.splice(t, 1) }), delete f.defQueueMap[i], t && (t.events.defined && (r[i] = t.events), j(i)) }), s }, enable: function (e) { getOwn(d, e.id) && S(e).enable() }, completeLoad: function (e) { var t, i, r, n = getOwn(b.shim, e) || {}, o = n.exports; for (O(); l.length;) { if (null === (i = l.shift())[0]) { if (i[0] = e, t) break; t = !0 } else i[0] === e && (t = !0); a(i) } if (f.defQueueMap = {}, r = getOwn(d, e), !t && !hasProp(h, e) && r && !r.inited) { if (!(!b.enforceDefine || o && getGlobal(o))) return E(e) ? void 0 : M(makeError("nodefine", "No define call for " + e, null, [e])); a([e, n.deps || [], n.exportsFn]) } P() }, nameToUrl: function (e, t, i) { var r, n, o, a, s, u = getOwn(b.pkgs, e); if (u = getOwn(m, e = u ? u : e)) return f.nameToUrl(u, t, i); if (req.jsExtRegExp.test(e)) a = e + (t || ""); else { for (r = b.paths, o = (n = e.split("/")).length; 0 < o; --o)if (s = getOwn(r, n.slice(0, o).join("/"))) { isArray(s) && (s = s[0]), n.splice(0, o, s); break } a = n.join("/"), a = ("/" === (a += t || (/^data\:|^blob\:|\?/.test(a) || i ? "" : ".js")).charAt(0) || a.match(/^[\w\+\.\-]+:/) ? "" : b.baseUrl) + a } return b.urlArgs && !/^blob\:/.test(a) ? a + b.urlArgs(e, a) : a }, load: function (e, t) { req.load(f, e, t) }, execCb: function (e, t, i, r) { return t.apply(r, i) }, onScriptLoad: function (e) { "load" !== e.type && !readyRegExp.test((e.currentTarget || e.srcElement).readyState) || (interactiveScript = null, e = s(e), f.completeLoad(e.id)) }, onScriptError: function (e) { var i, r = s(e); if (!E(r.id)) return i = [], eachProp(d, function (e, t) { 0 !== t.indexOf("_@r") && each(e.depMaps, function (e) { if (e.id === r.id) return i.push(t), !0 }) }), M(makeError("scripterror", 'Script error for "' + r.id + (i.length ? '", needed by: ' + i.join(", ") : '"'), e, [r.id])) } }).require = f.makeRequire(), f } function getInteractiveScript() { return interactiveScript && "interactive" === interactiveScript.readyState || eachReverse(scripts(), function (e) { if ("interactive" === e.readyState) return interactiveScript = e }), interactiveScript } }(this, "undefined" == typeof setTimeout ? void 0 : setTimeout);
@@ -0,0 +1,32 @@
1
+ define([], function () {
2
+ const SidebarManager = {
3
+ sidebar: document.querySelector("#sidebar"),
4
+
5
+ toggleSidebar: function () {
6
+ this.sidebar.classList.toggle("expand");
7
+ },
8
+
9
+ checkSidebarVisibility: function () {
10
+ const isCurrentlyVisible = window.innerWidth >= 1080;
11
+ this.sidebar.classList.toggle("expand", isCurrentlyVisible);
12
+ },
13
+
14
+ init: function () {
15
+ // Toggle Sidebar on Button Click
16
+ document.querySelector(".toggle-btn").addEventListener("click", () => {
17
+ this.toggleSidebar();
18
+ });
19
+
20
+ // Check Sidebar Visibility on Resize
21
+ window.addEventListener("resize", () => {
22
+ this.checkSidebarVisibility();
23
+ });
24
+
25
+ // Initial Sidebar Visibility Check
26
+ this.checkSidebarVisibility();
27
+
28
+ }
29
+ };
30
+
31
+ return SidebarManager;
32
+ });
@@ -0,0 +1,79 @@
1
+ define([], function () {
2
+ const TabManager = {
3
+ loadTab: function () {
4
+ const self = $(this);
5
+ const tab = $(self.data('bs-target'));
6
+ if (!tab.hasClass('loaded')) {
7
+ tab.load('/tabs/container')
8
+ tab.load(self.data('url'), function () {
9
+ tab.addClass('loaded');
10
+ });
11
+ }
12
+ },
13
+
14
+ formSubmit: function (event) {
15
+ event.preventDefault();
16
+ const form = $(this);
17
+ const formData = new FormData(this);
18
+
19
+ const submitButton = form.find('button[type="submit"]');
20
+ submitButton.prop('disabled', true);
21
+ const originalButtonText = submitButton.html();
22
+ submitButton.html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>');
23
+ $.ajax({
24
+ url: form.attr('action'),
25
+ type: form.attr("method") || 'POST',
26
+ data: formData,
27
+ processData: false, // Important for file upload
28
+ contentType: false, // Important for file upload
29
+ success: function (response) {
30
+ const next_target_id = form.data('next-target');
31
+ const reload_subtab_id = form.data('reload-subtab');
32
+ if (next_target_id) {
33
+ $(form.closest('.tab-pane')).html(response);
34
+ const nextTarget = $(next_target_id);
35
+ const nextTargetButton = $('[data-bs-target="' + next_target_id + '"]')
36
+ nextTarget.removeClass('loaded');
37
+ nextTargetButton.trigger('click');
38
+
39
+ } else if (reload_subtab_id) {
40
+ const response_html = $(response);
41
+ const filteredContent = response_html.find(reload_subtab_id);
42
+ $(reload_subtab_id).html(filteredContent.html());
43
+ }
44
+ else {
45
+ $(form.closest('.tab-pane')).html(response);
46
+ }
47
+ },
48
+ complete: function () {
49
+ submitButton.prop('disabled', false);
50
+ submitButton.html(originalButtonText);
51
+ }
52
+ });
53
+ },
54
+
55
+ loadNextTab: function (e) {
56
+ e.preventDefault();
57
+ const self = $(this);
58
+ const nextTargetId = self.data('next-target');
59
+ const additionalButtonId = self.data('additional-button');
60
+ const nextTarget = $(nextTargetId);
61
+ const nextTargetButton = $(`[data-bs-target="${nextTargetId}"]`);
62
+
63
+ nextTarget.removeClass('loaded');
64
+ nextTargetButton.trigger('click');
65
+ if (additionalButtonId) {
66
+ const additionalButton = $(additionalButtonId);
67
+ additionalButton.trigger('click');
68
+ }
69
+ },
70
+
71
+ init: function () {
72
+ $(document).on('show.bs.tab', 'div[data-bs-toggle="tab"].btn-sm', this.loadTab);
73
+ $(document).on('click', 'button.btn-next', this.loadNextTab);
74
+ $(document).on('submit', '.tab-area form.js-tab-submit', this.formSubmit);
75
+ }
76
+ };
77
+
78
+ return TabManager;
79
+ });
@@ -12,6 +12,7 @@
12
12
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
13
13
  <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/simpleXML.css') }}" />
14
14
  <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/main.css') }}" />
15
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/navbar.css') }}" />
15
16
  </head>
16
17
 
17
18
  <body>
@@ -27,14 +28,14 @@
27
28
  </div>
28
29
  <ul class="sidebar-nav">
29
30
  <li class="sidebar-item">
30
- <a href={{ url_for("projects") }} class="sidebar-link" title="Projects">
31
+ <a href="{{ url_for('views.overview') }}" class="sidebar-link" title="Projects">
31
32
  <i class="bi bi-view-list"></i>
32
33
  <span>Overview</span>
33
34
  </a>
34
35
  </li>
35
36
  {% for project in get_projects() %}
36
- <li class="sidebar-item">
37
- <a href="{{ url_for('project', projectname=project.name) }}" class="sidebar-link"
37
+ <li class="sidebar-item project-btn">
38
+ <a href="{{ url_for('views.project_view', projectname=project.name) }}" class="sidebar-link"
38
39
  data-bs-target="#project_{{ loop.index }}" aria-expanded="false"
39
40
  aria-controls="project_{{ loop.index }}">
40
41
  {% if current_project == project.name %}
@@ -42,59 +43,19 @@
42
43
  {% else %}
43
44
  <i class="bi bi-circle"></i>
44
45
  {% endif %}
45
- <span>{{ project.name }}</span>
46
+ <span class="projectname">{{ project.name }}</span>
46
47
  </a>
47
- <ul id="project_{{ loop.index }}"
48
- class="sidebar-dropdown nav list-unstyled ps-3 {{ 'collapse' if current_project != project.name }}"
49
- data-bs-parent="#sidebar">
50
- <li class="sidebar-item">
51
- <a class="sidebar-link nav-link" data-bs-toggle="tab" href="#tab_storage" role="tab"
52
- aria-controls="tab_storage" aria-selected="false">
53
- <i class="bi bi-folder2-open"></i>
54
- Storage
55
- </a>
56
- </li>
57
- {% for c in project.collections %}
58
- {% set collection_config = project.collections[c]['config'] %}
59
- <li class="sidebar-item">
60
- <a class="sidebar-link nav-link collection-tab" data-bs-toggle="tab" href="#tab_{{ c }}"
61
- role="tab" aria-controls="tab_{{ c }} aria-selected=" false"
62
- data-url="{{ url_for('collection', projectname=project.name, name=c) }}"
63
- data-name="{{ c }}">
64
- <i class="spinner-border spinner-border-sm" role="status" style="display:none;"></i>
65
- <i class="bi bi-{{ loop.index }}-circle"></i>
66
- {{collection_config.short_title}}
67
- </a>
68
- </li>
69
- {% endfor %}
70
- <li class="sidebar-item">
71
- <a class="sidebar-link nav-link publication-tab" data-bs-toggle="tab" href="#tab_publish"
72
- role="tab" aria-controls="tab_publish" data-projectname="{{ project.name }}"
73
- aria-selected="false">
74
- <i class="bi bi-cloud-upload"></i>
75
- Publish
76
- </a>
77
- </li>
78
- </ul>
48
+
79
49
  </li>
80
50
  {% endfor %}
51
+ </ul>
81
52
  </aside>
82
53
  <div class="main p-3">
83
- <div class="text-center">
84
- <h1>
85
- <span>
86
- {{ sub_title }}
87
- </span>
88
- </h1>
89
- </div>
90
54
  {% block content %}{% endblock content %}
55
+ {% include 'modal/empty_container.html' %}
91
56
  </div>
92
57
  </div>
93
- <script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
94
- <script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
95
- <script src="{{ url_for('static', filename='js/simpleXML.js') }}"></script>
96
- <script src="{{ url_for('static', filename='js/main.js') }}"></script>
97
- </script>
58
+ <script data-main="/static/js/main" src="{{ url_for('static', filename='js/require.js') }}"></script>
98
59
  </body>
99
60
 
100
61
  </html>
@@ -1,94 +1,101 @@
1
- {% macro form_field_with_xpath(label, field_key, elem, has_next=False) %}
1
+ {% macro multi_input(_items, input_name) %}
2
+ {% set items = _items or [None] %}
3
+ {% for item in items %}
4
+ <div class="input-group mb-2">
5
+ <input type="text" class="form-control" title="Name" name="{{input_name}}_name" placeholder="e.g.: Jane Doe"
6
+ value="{{ item.fullname or item.name }}" required>
7
+ <input type="url" class="form-control" title="URL" name="{{input_name}}_url" value="{{ item.url or '' }}"
8
+ placeholder="e.g.: https://orcid.org/0000-0001-2345-6789">
9
+ {% if loop.first %}
10
+ <button class="input-group-text add-multi-input">
11
+ <i class="bi bi-plus-circle-dotted"></i>
12
+ </button>
13
+ {% else %}
14
+ <button class="input-group-text remove-multi-input">
15
+ <i class="bi bi-dash-circle-dotted"></i>
16
+ </button>
17
+ {% endif %}
18
+ </div>
19
+ {% endif %}
20
+ {% endfor %}
21
+ {% endmacro %}
22
+
23
+ {% macro form_field_with_xpath(type, label, field_key, elem, delete_button=False) %}
2
24
  <div class="mb-2 row">
3
- <div class="col-12 {{'pb-2 border-top' if has_next }}">
4
- <label class="form-label">{{label}}
5
- <a href="#" class="badge text-dark remove-multi-field">
6
- <span class="bi bi-trash"></span>
7
- </a>
25
+ <div class="col-12 d-flex justify-content-between align-items-center">
26
+ <label class="form-label mb-0">
27
+ {{ label }}
8
28
  </label>
29
+ {% if delete_button %}
30
+ <a href="#" class="badge text-dark remove-multi-field-classification ms-3">
31
+ <span class="bi bi-trash"></span>
32
+ </a>
33
+ {% endif %}
34
+ </div>
35
+ <div class="col-12">
9
36
  <div class="input-group">
10
37
  <input type="text" class="form-control" data-key="{{ field_key }}" data-type="value" placeholder="Value"
11
- value="{{ elem['value'] if elem and elem.get('value') }}">
38
+ value="{{ elem['value'] if elem and elem.get('value') }}" name="{{ type }}-{{ field_key }}-value">
12
39
  <input type="text" class="form-control" data-key="{{ field_key }}" data-type="xpath" placeholder="XPath"
13
- value="{{ elem['xpath'] if elem and elem.get('xpath') }}">
40
+ value="{{ elem['xpath'] if elem and elem.get('xpath') }}" name="{{ type }}-{{ field_key }}-xpath">
14
41
  </div>
15
42
  </div>
16
43
  </div>
17
44
  {% endmacro %}
18
45
 
19
- {% macro multi_input(type, heading, elements, version=1) %}
20
- <div class="border-top mb-3 row">
21
- <label class="col-sm-12 col-form-label">
22
- <div class="d-flex justify-content-between align-items-center">
23
- <b>{{ heading }}</b>
24
- <a href="#" class="add-multi-input btn btn-md" title="Add {{ heading }}">
25
- <span class="bi bi-plus-circle"></span>
26
- </a>
27
- </div>
28
- </label>
29
- </div>
30
- {% for item in elements or [None] %}
31
- {% if version == 1 %}
32
- <div class="multi-input-xpath" data-name="{{ type }}">
33
- {{ form_field_with_xpath('ID', 'id', item.get('id') if item) }}
34
- {{ form_field_with_xpath('URL', 'url', item.get('url') if item) }}
35
- {{ form_field_with_xpath('Value', 'value', item.get('value') if item, has_next=loop.nextitem) }}
36
- </div>
37
- {% elif version == 2 %}
38
- <div class="multi-input-fixed" data-name="{{ type }}">
39
- <div class="mb-3 row {{'pb-2 border-bottom' if not loop.last }}">
40
- <label class="col-lg-2 col-form-label">
41
- Fullname
46
+ {% macro classification_input(type, heading, elements) %}
47
+ <div class="border-bottom mb-3 row">
48
+ <div class="d-flex justify-content-between align-items-center">
49
+ <label class="form-label text-center flex-grow-1 mb-0">
50
+ <i>{{ heading }}</i>
42
51
  </label>
43
- <div class="col-lg-4">
44
- <input type="text" class="form-control" data-key="fullname"
45
- value="{{ item['fullname'] if item['fullname'] }}">
46
- </div>
47
- <label class="col-lg-1 col-form-label">URL</label>
48
- <div class="col-lg-4 ">
49
- <input type="text" class="form-control" data-key="url" value="{{ item['url'] if item['url'] }}">
50
- </div>
51
- <div class="col-lg-1 mt-lg-0 mt-2">
52
- <a href="#" class="btn btn-md remove-multi-field">
53
- <span class="bi bi-trash"></span>
54
- </a>
55
- </div>
52
+ <a href="#" class="add-multi-input-classifications btn btn-md ms-3" title="Add {{ heading }}">
53
+ <span class="bi bi-plus-circle"></span>
54
+ </a>
56
55
  </div>
57
56
  </div>
58
- {% endif %}
57
+ {% for item in elements or [None] %}
58
+ <div class="multi-input {{'mb-3 pb-3 border-bottom' if loop.nextitem }}" data-name="{{ type }}">
59
+ {{ form_field_with_xpath(type, 'ID', 'id', item.get('id') if item, delete_button=True) }}
60
+ {{ form_field_with_xpath(type, 'URL', 'url', item.get('url') if item) }}
61
+ {{ form_field_with_xpath(type, 'Value', 'value', item.get('value') if item) }}
62
+ </div>
59
63
  {% endfor %}
60
64
  {% endmacro %}
61
65
 
62
- {% macro xpath_or_value_input(heading, input_name, item, collection_parser, is_subsection=True) %}
63
- <div class="mb-1 row gap-1 align-items-center xpath-or-value-input">
64
- <label class="col-lg-2 col-form-label">
65
- {% if is_subsection %}
66
- {{ heading }}
67
- {% else %}
68
- <b>{{ heading }}</b>
69
- {% endif %}
66
+
67
+ {% macro xpath_or_value_input(heading, input_name, item, collection_parser, required=False) %}
68
+ <div class="mb-3">
69
+ <label class="form-label">
70
+ <span>{{ heading }}</span>
71
+ {% if required %}<span class="text-danger">*</span>{% endif %}
70
72
  </label>
71
- <div class="col-lg-5">
72
- <div class="form-floating">
73
- <input type="text" class="form-control" id="valueInput_{{ input_name }}" data-name="{{ input_name }}"
74
- data-type="value" value="{{ item['value'] if item['value'] }}" placeholder="Value">
75
- <label for="valueInput_{{ input_name }}">Value</label>
73
+ <div class="row g-2">
74
+ <!-- Value Input -->
75
+ <div class="col-lg-6">
76
+ <input type="text" class="form-control" id="valueInput_{{ input_name }}" name="{{ input_name }}_value"
77
+ value="{{ item['value'] if item['value'] }}" placeholder="Value">
76
78
  </div>
77
- </div>
78
- <div class="col-lg-4 position-relative">
79
- <div class="form-floating">
80
- <input type="text" class="form-control" id="xpathInput_{{ input_name }}" data-name="{{ input_name }}"
81
- data-type="xpath" value="{{ item['xpath'] if item['xpath'] }}" placeholder="XPath">
82
- <label for="xpathInput_{{ input_name }}">XPath</label>
79
+
80
+ <!-- XPath Input -->
81
+ <div class="col-lg-6 position-relative">
82
+ <input type="text" class="form-control" id="xpathInput_{{ input_name }}" name="{{ input_name }}_xpath"
83
+ value="{{ item['xpath'] if item['xpath'] }}" placeholder="XPath">
84
+ <a href="#" class="load-modal" title="Hitrate of xpath (in %) - CLICK to see results"
85
+ data-args_xpath="{{ item['xpath'] }}"
86
+ data-url="{{ url_for('collection.modal_xpath_parser', projectname=project.name, collectionname=collectionname) }}">
87
+ <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-success"
88
+ data-bs-placement="top" title="Hitrate of XPath in percentage">
89
+ {{ collection_parser.test_xpath(item['xpath'])['count']['percentage'] if item['xpath'] else '-' }}
90
+ </span>
91
+ </a>
83
92
  </div>
84
- <a href="#" class="show-xpath-in-tei-explorer" title="Hitrate of xpath (in %) - CLICK to see results"
85
- data-url="{{ url_for('xpath_parser_modal', projectname=project.name, title=collection_title) }}"
86
- data-xpath="{{ item['xpath'] }}">
87
- <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-success"
88
- data-bs-toggle="tooltip" data-bs-placement="top" title="Hitrate of XPath in percentage">
89
- {{ collection_parser.test_xpath(item['xpath'])['count']['percentage'] if item['xpath'] else '-' }}
90
- </span>
91
- </a>
92
93
  </div>
93
94
  </div>
95
+ {% endmacro %}
96
+
97
+ {% macro required_fields_disclaimer() %}
98
+ <div class="mb-3 mt-3">
99
+ <small class="text-muted"><span class="text-danger">*</span> indicates required fields</small>
100
+ </div>
94
101
  {% endmacro %}