From 6ac40a6bbc4e025a23c0fa0488487be1181f8017 Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Wed, 24 Sep 2025 10:45:52 +0200 Subject: [PATCH 01/22] email validation --- static/foundation/js/vendor/table-utils.js | 150 +++++++++++---------- 1 file changed, 82 insertions(+), 68 deletions(-) diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 9cb4f80..fefdd78 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -223,6 +223,38 @@ export function setupTables() { const allEmpty = values.every((val) => val === ""); if (allEmpty) return; + // ✅ Combination Validation + const getInputVal = (col) => + Array.from(inputs) + .find((i) => i.getAttribute("data-col") === col) + ?.value.trim() || ""; + + const identifier = getInputVal("identifier"); + const givenName = getInputVal("givenName"); + const familyName = getInputVal("familyName"); + + let emailTagValue = ""; + const emailTagContainer = addRowControls.querySelector( + '.add-row-tags-container[data-col="email"]' + ); + if (emailTagContainer) { + const emailTag = emailTagContainer.querySelector(".tag"); + if (emailTag) emailTagValue = emailTag.dataset.tag.trim(); + } + // Condition 1: Identifier + Given Name + Family Name + const condition1 = identifier && givenName && familyName; + + // Condition 2: Given Name + Family Name + Email + const condition2 = givenName && familyName && emailTagValue; + + // If neither condition is satisfied → stop row creation + if (!condition1 && !condition2) { + alert( + "Please fill either: Identifier + Given Name + Family Name OR Given Name + Family Name + Email." + ); + return; + } + // Create new row const newRow = document.createElement("tr"); // Get column headers @@ -271,19 +303,7 @@ export function setupTables() { td.setAttribute("data-coltype", "element"); td.setAttribute("data-type", dataType); td.appendChild(checkbox); - } else if (colType === "dropdown") { - td.className = "table-tagging-cell"; - td.setAttribute("data-col", col); - td.setAttribute("data-coltype", "dropdown"); - td.setAttribute("data-type", dataType); - - // Show the selected value as plain text - const value = input ? input.value : ""; - td.textContent = value; - } else if ( - colType === "tagging" || - colType === "tagging_autocomplete" - ) { + } else if (colType === "tagging") { td.className = "table-tagging-cell"; td.setAttribute("data-col", col); td.setAttribute("data-coltype", "tagging"); @@ -317,17 +337,6 @@ export function setupTables() { .querySelectorAll(".tag") .forEach((tagEl) => tagEl.remove()); } - // If tagging_autocomplete, initialize autocomplete for this cell - if (colType === "tagging_autocomplete") { - getSchema().then((schema) => { - const autocompleteSource = - schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || - []; - if (autocompleteSource.length > 0) { - setupTableTagAutocomplete({ cell: td, autocompleteSource }); - } - }); - } } else { td.textContent = input ? input.value : ""; } @@ -355,11 +364,12 @@ export function setupTables() { // Clear input fields inputs.forEach((input) => { - if (input.tagName === "SELECT") { - input.selectedIndex = 0; - } else { - input.value = ""; - } + // if (input.tagName === "SELECT") { + // input.selectedIndex = 0; + // } else { + input.value = ""; + + // } }); // Update hidden input @@ -504,11 +514,21 @@ export function setupTables() { }); } - // Add tag on Enter input.addEventListener("keydown", function (e) { if (e.key === "Enter" && input.value.trim() !== "") { e.preventDefault(); const tag = input.value.trim(); + + // ✅ Check if this field is email column + if (col === "email") { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(tag)) { + alert("Please enter a valid Email address."); + input.value = ""; + return; // ❌ Stop tag creation + } + } + if (colType === "tagging_autocomplete") { if (autocompleteSource.includes(tag)) { if (!addRowTags[col].includes(tag)) { @@ -528,13 +548,7 @@ export function setupTables() { input.value = ""; if (suggestionsBox) suggestionsBox.style.display = "none"; } else { - showInvalidTagMessage( - container, - input, - "Please select a value from the list." - ); - input.classList.add("invalid"); - setTimeout(() => input.classList.remove("invalid"), 1000); + alert("Please select a value from the list."); input.value = ""; } } else { @@ -635,6 +649,18 @@ export function setupTables() { }); }); + document.addEventListener("keydown", function (e) { + if (e.key === "Enter") { + e.preventDefault(); + + const activeElement = document.activeElement; + + if (activeElement.classList.contains("checkbox-element")) { + activeElement.checked = !activeElement.checked; // toggle + } + } + }); + highlightEmptyAddRowControls(); } // Add function to color add items when element is required or recommended and empty @@ -692,16 +718,11 @@ export function initializeTableTaggingCells() { input.placeholder = "Add tag and press Enter"; cell.appendChild(input); } - // ------------------------------Contributors table ends----------------------------//// - - //--------------- --- Autocomplete logic: fetch source and setup --------------// - - // You can set data-autocomplete-source on the cell or column header, or fetch from schema const col = cell.getAttribute("data-col"); const colType = cell.getAttribute("data-coltype"); const dataType = cell.getAttribute("data-type"); - // Example: fetch from schema if available + if (colType == "tagging_autocomplete") { getSchema().then((schema) => { autocompleteSource = @@ -711,15 +732,12 @@ export function initializeTableTaggingCells() { } }); } else if (colType === "dropdown") { - // Show value as plain text initially const currentValue = cell.getAttribute("data-value") || cell.textContent.trim() || ""; cell.innerHTML = ""; cell.textContent = currentValue; - // Only show dropdown on cell click cell.addEventListener("click", function handleDropdownCellClick(e) { - // Prevent multiple dropdowns if (cell.querySelector("select")) return; getSchema().then((schema) => { const options = @@ -734,12 +752,10 @@ export function initializeTableTaggingCells() { .join(""); select.value = currentValue; - // Replace cell content with select cell.innerHTML = ""; cell.appendChild(select); select.focus(); - // On change or blur, update cell and data function finalizeSelection() { const selectedValue = select.value; cell.setAttribute("data-value", selectedValue); @@ -747,15 +763,11 @@ export function initializeTableTaggingCells() { cell.innerHTML = selectedValue; }, 0); - // Remove this event listener to avoid duplicate dropdowns cell.removeEventListener("click", handleDropdownCellClick); - - // Re-attach the click event for future edits setTimeout(() => { cell.addEventListener("click", handleDropdownCellClick); }, 0); - // Update the hidden input and main JSON const table = cell.closest("table"); if (table && table.id.endsWith("Table")) { const key = table.id.replace(/Table$/, ""); @@ -767,14 +779,13 @@ export function initializeTableTaggingCells() { select.addEventListener("blur", finalizeSelection); }); - // Remove this event listener to prevent re-entry until finished cell.removeEventListener("click", handleDropdownCellClick); }); - return; // Skip further tag logic for dropdowns + return; } - // Show input when cell is clicked (not on tag or remove) + // Show input when cell is clicked cell.addEventListener("click", function (e) { if ( e.target.classList.contains("remove-tag") || @@ -786,7 +797,6 @@ export function initializeTableTaggingCells() { e.stopPropagation(); }); - // Hide input when focus is lost input.addEventListener("blur", function () { setTimeout(function () { input.style.display = "none"; @@ -799,6 +809,17 @@ export function initializeTableTaggingCells() { e.preventDefault(); e.stopPropagation(); const tag = input.value.trim(); + + // ✅ Email Validation + if (col === "email") { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(tag)) { + alert("Please enter a valid Email address."); + input.value = ""; + return; // ❌ Stop tag creation + } + } + if ( [...tagsList.querySelectorAll(".tag")].some( (t) => t.textContent.trim() === tag + "×" @@ -807,29 +828,21 @@ export function initializeTableTaggingCells() { input.value = ""; return; } - // Get autocompleteSource for this cell/column + let autocompleteSource = []; - const col = cell.getAttribute("data-col"); - const dataType = cell.getAttribute("data-type"); const colType = cell.getAttribute("data-coltype"); if (colType === "tagging_autocomplete") { - // You may want to cache this for performance getSchema().then((schema) => { autocompleteSource = schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; if (!autocompleteSource.includes(tag)) { - showInvalidTagMessage( - cell, - input, - "Please select a value from the list." - ); - input.classList.add("invalid"); - setTimeout(() => input.classList.remove("invalid"), 1000); + alert("Please select a value from the list."); input.value = ""; return; } }); } + const span = document.createElement("span"); span.className = "tag"; span.setAttribute("data-tag", tag); @@ -837,6 +850,7 @@ export function initializeTableTaggingCells() { tag + ' ×'; tagsList.appendChild(span); input.value = ""; + const table = cell.closest("table"); if (table && table.id.endsWith("Table")) { const key = table.id.replace(/Table$/, ""); @@ -845,7 +859,7 @@ export function initializeTableTaggingCells() { } }); - // Remove tag on click (cell-local) + // Remove tag on click tagsList.addEventListener("click", function (e) { if (e.target.classList.contains("remove-tag")) { e.target.parentElement.remove(); From 80acdef63cf29c7dbbe5ba64c7ad041ab22ae1d7 Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Wed, 24 Sep 2025 13:30:58 +0200 Subject: [PATCH 02/22] Checkbox toggle from enter key --- static/foundation/js/vendor/table-utils.js | 177 +++++++++++---------- 1 file changed, 94 insertions(+), 83 deletions(-) diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index fefdd78..ebad98c 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -204,6 +204,7 @@ export function setupTables() { const key = btn.getAttribute("data-table-key"); const table = document.getElementById(key + "Table"); const hiddenInput = document.getElementById(key + "TableHiddenInput"); + if (!table || !hiddenInput) return; // Find the add-row-controls row @@ -241,18 +242,21 @@ export function setupTables() { const emailTag = emailTagContainer.querySelector(".tag"); if (emailTag) emailTagValue = emailTag.dataset.tag.trim(); } - // Condition 1: Identifier + Given Name + Family Name - const condition1 = identifier && givenName && familyName; - // Condition 2: Given Name + Family Name + Email - const condition2 = givenName && familyName && emailTagValue; + if (table.id !== "copyrightHolderTable") { + // Condition 1: Identifier + Given Name + Family Name + const condition1 = identifier && givenName && familyName; - // If neither condition is satisfied → stop row creation - if (!condition1 && !condition2) { - alert( - "Please fill either: Identifier + Given Name + Family Name OR Given Name + Family Name + Email." - ); - return; + // Condition 2: Given Name + Family Name + Email + const condition2 = givenName && familyName && emailTagValue; + + // If neither condition is satisfied → stop row creation + if (!condition1 && !condition2) { + alert( + "Please fill either: Identifier + Given Name + Family Name OR Given Name + Family Name + Email." + ); + return; + } } // Create new row @@ -362,16 +366,12 @@ export function setupTables() { initializeTableTaggingCells(); enableEditableTagsInTable(); - // Clear input fields - inputs.forEach((input) => { - // if (input.tagName === "SELECT") { - // input.selectedIndex = 0; - // } else { - input.value = ""; - - // } - }); - + // Clear input fields and checkboxes + inputs.forEach((input) => (input.value = "")); // reset text/tag inputs + const checkboxes = addRowControls.querySelectorAll( + 'input[type="checkbox"]' + ); + checkboxes.forEach((cb) => (cb.checked = false)); // Update hidden input updateTableHiddenInput(key); @@ -485,35 +485,36 @@ export function setupTables() { suggestionsBox.style.display = "none"; }, 200); }); - } else if (colType === "dropdown") { - getSchema().then((schema) => { - const options = - schema["$defs"]?.[dataType]?.properties?.[col]?.enum || []; - const select = document.createElement("select"); - select.className = "add-row-dropdown-select"; - select.name = "selectElement"; - select.setAttribute("data-col", col); - select.setAttribute("data-type", dataType); - select.setAttribute("data-coltype", "dropdown"); - select.innerHTML = - '' + - options - .map((opt) => ``) - .join(""); - // Replace the input with the select - if (input) { - input.style.display = "none"; - } - container.appendChild(select); - - // On change, update addRowTags or values as needed - select.addEventListener("change", function () { - addRowTags[col] = [select.value]; - console.log("Selected value:", select.value); - }); - }); } - + // else if (colType === "dropdown") { + // getSchema().then((schema) => { + // const options = + // schema["$defs"]?.[dataType]?.properties?.[col]?.enum || []; + // const select = document.createElement("select"); + // select.className = "add-row-dropdown-select"; + // select.name = "selectElement"; + // select.setAttribute("data-col", col); + // select.setAttribute("data-type", dataType); + // select.setAttribute("data-coltype", "dropdown"); + // select.innerHTML = + // '' + + // options + // .map((opt) => ``) + // .join(""); + // // Replace the input with the select + // if (input) { + // input.style.display = "none"; + // } + // container.appendChild(select); + + // // On change, update addRowTags or values as needed + // select.addEventListener("change", function () { + // addRowTags[col] = [select.value]; + // console.log("Selected value:", select.value); + // }); + // }); + // } + // Add tag on Enter input.addEventListener("keydown", function (e) { if (e.key === "Enter" && input.value.trim() !== "") { e.preventDefault(); @@ -525,45 +526,53 @@ export function setupTables() { if (!emailRegex.test(tag)) { alert("Please enter a valid Email address."); input.value = ""; - return; // ❌ Stop tag creation + return; } } - if (colType === "tagging_autocomplete") { - if (autocompleteSource.includes(tag)) { - if (!addRowTags[col].includes(tag)) { - addRowTags[col].push(tag); - - // Create tag element - const span = document.createElement("span"); - span.className = "tag"; - span.setAttribute("data-tag", tag); - span.innerHTML = - tag + - ' ×'; - container.insertBefore(span, input); - } - input.value = ""; - if (suggestionsBox) suggestionsBox.style.display = "none"; - } else { - alert("Please select a value from the list."); - input.value = ""; - } - } else { - // For plain tagging, just add the tag - if (!addRowTags[col].includes(tag)) { - addRowTags[col].push(tag); - const span = document.createElement("span"); - span.className = "tag"; - span.setAttribute("data-tag", tag); - span.innerHTML = - tag + ' ×'; - container.insertBefore(span, input); - } - input.value = ""; + // if (colType === "tagging_autocomplete") { + // if (autocompleteSource.includes(tag)) { + // if (!addRowTags[col].includes(tag)) { + // addRowTags[col].push(tag); + + // // Create tag element + // const span = document.createElement("span"); + // span.className = "tag"; + // span.setAttribute("data-tag", tag); + // span.innerHTML = + // tag + + // ' ×'; + // container.insertBefore(span, input); + // } + // input.value = ""; + // if (suggestionsBox) suggestionsBox.style.display = "none"; + // } else { + // showInvalidTagMessage( + // container, + // input, + // "Please select a value from the list." + // ); + // input.classList.add("invalid"); + // setTimeout(() => input.classList.remove("invalid"), 1000); + // input.value = ""; + // } + // } + // For plain tagging, just add the tag + if (!addRowTags[col].includes(tag)) { + addRowTags[col].push(tag); + const span = document.createElement("span"); + span.className = "tag"; + span.setAttribute("data-tag", tag); + span.innerHTML = + tag + ' ×'; + container.insertBefore(span, input); } + input.value = ""; + // else { + + // } } }); @@ -816,6 +825,8 @@ export function initializeTableTaggingCells() { if (!emailRegex.test(tag)) { alert("Please enter a valid Email address."); input.value = ""; + input.classList.add("invalid"); + setTimeout(() => input.classList.remove("invalid"), 2000); return; // ❌ Stop tag creation } } From ce1727697d202c4d2fe535267f69ca45716248d0 Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Mon, 29 Sep 2025 14:47:21 +0200 Subject: [PATCH 03/22] commit all unsed code --- static/foundation/css/foundation.css | 6 +- static/foundation/js/vendor/table-utils.js | 315 +++++++++++---------- 2 files changed, 161 insertions(+), 160 deletions(-) diff --git a/static/foundation/css/foundation.css b/static/foundation/css/foundation.css index b87027b..d81970c 100644 --- a/static/foundation/css/foundation.css +++ b/static/foundation/css/foundation.css @@ -317,7 +317,7 @@ form.data-form2 { } form.data-form3 { - overflow: auto; + /* overflow-y: auto; */ height: 90%; border-radius: 2px; margin-top: 0; @@ -488,9 +488,9 @@ select { } #metadata-json { - overflow: scroll; + /* overflow: scroll; */ width: 100%; - height: 92%; + height: 90%; padding: 8px; border-radius: 4px; margin-bottom: 2px; diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index ebad98c..541bf8a 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -391,101 +391,101 @@ export function setupTables() { const col = container.getAttribute("data-col"); addRowTags[col] = []; const input = container.querySelector(".add-row-tag-input"); - const colType = container.getAttribute("data-coltype"); - const dataType = container.getAttribute("data-type"); - // --- Autocomplete setup --- - let autocompleteSource = []; - let suggestionsBox = createSuggestionsBox(container); - - if (colType === "tagging_autocomplete") { - getSchema().then((schema) => { - autocompleteSource = - schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; - }); + // const colType = container.getAttribute("data-coltype"); + // const dataType = container.getAttribute("data-type"); + // // --- Autocomplete setup --- + // let autocompleteSource = []; + // let suggestionsBox = createSuggestionsBox(container); - input.addEventListener("input", function () { - const query = input.value.trim().toLowerCase(); - suggestionsBox.innerHTML = ""; - if (!query || autocompleteSource.length === 0) { - suggestionsBox.style.display = "none"; - return; - } - const selectedTags = addRowTags[col]; - const filtered = autocompleteSource.filter( - (tag) => - tag.toLowerCase().startsWith(query) && !selectedTags.includes(tag) - ); - if (filtered.length === 0) { - suggestionsBox.style.display = "none"; - return; - } - filtered.forEach((tag) => { - const div = document.createElement("div"); - div.className = "suggestion-item"; - div.textContent = tag; - div.style.cursor = "pointer"; - div.onclick = function () { - input.value = tag; - input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); - suggestionsBox.style.display = "none"; - }; - suggestionsBox.appendChild(div); - }); - // Position suggestions below the input - const inputRect = input.getBoundingClientRect(); - updateSuggestionsBoxPosition(input, suggestionsBox); - }); + // if (colType === "tagging_autocomplete") { + // getSchema().then((schema) => { + // autocompleteSource = + // schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; + // }); - input.addEventListener("focus", function () { - suggestionsBox.innerHTML = ""; - if (!autocompleteSource.length) { - suggestionsBox.style.display = "none"; - return; - } - const query = input.value.trim().toLowerCase(); - const selectedTags = addRowTags[col]; - const filtered = autocompleteSource.filter( - (tag) => - !selectedTags.includes(tag) && - (query === "" || tag.toLowerCase().startsWith(query)) - ); - if (filtered.length === 0) { - suggestionsBox.style.display = "none"; - return; - } - filtered.forEach((tag) => { - const div = document.createElement("div"); - div.className = "suggestion-item"; - div.textContent = tag; - div.style.cursor = "pointer"; - div.onclick = function () { - input.value = tag; - input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); - suggestionsBox.style.display = "none"; - }; - suggestionsBox.appendChild(div); - }); - // Position suggestions below the input - updateSuggestionsBoxPosition(input, suggestionsBox); - suggestionsBox.style.display = "block"; - }); + // input.addEventListener("input", function () { + // const query = input.value.trim().toLowerCase(); + // suggestionsBox.innerHTML = ""; + // if (!query || autocompleteSource.length === 0) { + // suggestionsBox.style.display = "none"; + // return; + // } + // const selectedTags = addRowTags[col]; + // const filtered = autocompleteSource.filter( + // (tag) => + // tag.toLowerCase().startsWith(query) && !selectedTags.includes(tag) + // ); + // if (filtered.length === 0) { + // suggestionsBox.style.display = "none"; + // return; + // } + // filtered.forEach((tag) => { + // const div = document.createElement("div"); + // div.className = "suggestion-item"; + // div.textContent = tag; + // div.style.cursor = "pointer"; + // div.onclick = function () { + // input.value = tag; + // input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); + // suggestionsBox.style.display = "none"; + // }; + // suggestionsBox.appendChild(div); + // }); + // // Position suggestions below the input + // const inputRect = input.getBoundingClientRect(); + // updateSuggestionsBoxPosition(input, suggestionsBox); + // }); - window.addEventListener( - "scroll", - () => updateSuggestionsBoxPosition(input, suggestionsBox), - true - ); - window.addEventListener("resize", () => - updateSuggestionsBoxPosition(input, suggestionsBox) - ); + // input.addEventListener("focus", function () { + // suggestionsBox.innerHTML = ""; + // if (!autocompleteSource.length) { + // suggestionsBox.style.display = "none"; + // return; + // } + // const query = input.value.trim().toLowerCase(); + // const selectedTags = addRowTags[col]; + // const filtered = autocompleteSource.filter( + // (tag) => + // !selectedTags.includes(tag) && + // (query === "" || tag.toLowerCase().startsWith(query)) + // ); + // if (filtered.length === 0) { + // suggestionsBox.style.display = "none"; + // return; + // } + // filtered.forEach((tag) => { + // const div = document.createElement("div"); + // div.className = "suggestion-item"; + // div.textContent = tag; + // div.style.cursor = "pointer"; + // div.onclick = function () { + // input.value = tag; + // input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); + // suggestionsBox.style.display = "none"; + // }; + // suggestionsBox.appendChild(div); + // }); + // // Position suggestions below the input + // updateSuggestionsBoxPosition(input, suggestionsBox); + // suggestionsBox.style.display = "block"; + // }); - // Hide suggestions on blur/click outside - input.addEventListener("blur", function () { - setTimeout(() => { - suggestionsBox.style.display = "none"; - }, 200); - }); - } + // window.addEventListener( + // "scroll", + // () => updateSuggestionsBoxPosition(input, suggestionsBox), + // true + // ); + // window.addEventListener("resize", () => + // updateSuggestionsBoxPosition(input, suggestionsBox) + // ); + + // // Hide suggestions on blur/click outside + // input.addEventListener("blur", function () { + // setTimeout(() => { + // suggestionsBox.style.display = "none"; + // }, 200); + // }); + // } // else if (colType === "dropdown") { // getSchema().then((schema) => { // const options = @@ -667,6 +667,9 @@ export function setupTables() { if (activeElement.classList.contains("checkbox-element")) { activeElement.checked = !activeElement.checked; // toggle } + if (activeElement.classList.contains("add-row-btn")) { + activeElement.click(); + } } }); @@ -733,63 +736,63 @@ export function initializeTableTaggingCells() { const dataType = cell.getAttribute("data-type"); if (colType == "tagging_autocomplete") { - getSchema().then((schema) => { - autocompleteSource = - schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; - if (autocompleteSource.length > 0) { - setupTableTagAutocomplete({ cell, autocompleteSource }); - } - }); + // getSchema().then((schema) => { + // autocompleteSource = + // schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; + // if (autocompleteSource.length > 0) { + // setupTableTagAutocomplete({ cell, autocompleteSource }); + // } + // }); } else if (colType === "dropdown") { const currentValue = cell.getAttribute("data-value") || cell.textContent.trim() || ""; cell.innerHTML = ""; cell.textContent = currentValue; - cell.addEventListener("click", function handleDropdownCellClick(e) { - if (cell.querySelector("select")) return; - getSchema().then((schema) => { - const options = - schema["$defs"]?.[dataType]?.properties?.[col]?.enum || []; - const select = document.createElement("select"); - select.className = "table-dropdown-select"; - select.name = "ChangingSelect"; - select.innerHTML = - '' + - options - .map((opt) => ``) - .join(""); - select.value = currentValue; - - cell.innerHTML = ""; - cell.appendChild(select); - select.focus(); - - function finalizeSelection() { - const selectedValue = select.value; - cell.setAttribute("data-value", selectedValue); - setTimeout(() => { - cell.innerHTML = selectedValue; - }, 0); - - cell.removeEventListener("click", handleDropdownCellClick); - setTimeout(() => { - cell.addEventListener("click", handleDropdownCellClick); - }, 0); - - const table = cell.closest("table"); - if (table && table.id.endsWith("Table")) { - const key = table.id.replace(/Table$/, ""); - updateTableHiddenInput(key); - } - } - - select.addEventListener("change", finalizeSelection); - select.addEventListener("blur", finalizeSelection); - }); - - cell.removeEventListener("click", handleDropdownCellClick); - }); + // cell.addEventListener("click", function handleDropdownCellClick(e) { + // if (cell.querySelector("select")) return; + // getSchema().then((schema) => { + // const options = + // schema["$defs"]?.[dataType]?.properties?.[col]?.enum || []; + // const select = document.createElement("select"); + // select.className = "table-dropdown-select"; + // select.name = "ChangingSelect"; + // select.innerHTML = + // '' + + // options + // .map((opt) => ``) + // .join(""); + // select.value = currentValue; + + // cell.innerHTML = ""; + // cell.appendChild(select); + // select.focus(); + + // function finalizeSelection() { + // const selectedValue = select.value; + // cell.setAttribute("data-value", selectedValue); + // setTimeout(() => { + // cell.innerHTML = selectedValue; + // }, 0); + + // cell.removeEventListener("click", handleDropdownCellClick); + // setTimeout(() => { + // cell.addEventListener("click", handleDropdownCellClick); + // }, 0); + + // const table = cell.closest("table"); + // if (table && table.id.endsWith("Table")) { + // const key = table.id.replace(/Table$/, ""); + // updateTableHiddenInput(key); + // } + // } + + // select.addEventListener("change", finalizeSelection); + // select.addEventListener("blur", finalizeSelection); + // }); + + // cell.removeEventListener("click", handleDropdownCellClick); + // }); return; } @@ -825,8 +828,6 @@ export function initializeTableTaggingCells() { if (!emailRegex.test(tag)) { alert("Please enter a valid Email address."); input.value = ""; - input.classList.add("invalid"); - setTimeout(() => input.classList.remove("invalid"), 2000); return; // ❌ Stop tag creation } } @@ -840,19 +841,19 @@ export function initializeTableTaggingCells() { return; } - let autocompleteSource = []; - const colType = cell.getAttribute("data-coltype"); - if (colType === "tagging_autocomplete") { - getSchema().then((schema) => { - autocompleteSource = - schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; - if (!autocompleteSource.includes(tag)) { - alert("Please select a value from the list."); - input.value = ""; - return; - } - }); - } + // let autocompleteSource = []; + // const colType = cell.getAttribute("data-coltype"); + // if (colType === "tagging_autocomplete") { + // getSchema().then((schema) => { + // autocompleteSource = + // schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; + // if (!autocompleteSource.includes(tag)) { + // alert("Please select a value from the list."); + // input.value = ""; + // return; + // } + // }); + // } const span = document.createElement("span"); span.className = "tag"; From e6f421737d469ab70a85dc922f3e86c4380a9a2a Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Tue, 30 Sep 2025 10:15:27 +0200 Subject: [PATCH 04/22] issue #236 Frontend: Second element right aligned --- static/foundation/css/foundation.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/foundation/css/foundation.css b/static/foundation/css/foundation.css index d81970c..cfcdcb9 100644 --- a/static/foundation/css/foundation.css +++ b/static/foundation/css/foundation.css @@ -317,7 +317,7 @@ form.data-form2 { } form.data-form3 { - /* overflow-y: auto; */ + overflow-y: auto; height: 90%; border-radius: 2px; margin-top: 0; @@ -386,7 +386,7 @@ select:focus { /* style for general single input elements with longer values */ .form-group .long_field { - width: 100%; + width: 94%; margin-bottom: 0.1em; float: left; box-sizing: border-box; @@ -402,7 +402,7 @@ select { } .form-group .big_field { - width: 100%; + width: 94%; margin-bottom: 0.1em; margin-right: 10%; float: left; From 071752924697b8bdc1f28c9d8e5819e7daaf5754 Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Tue, 7 Oct 2025 21:11:02 +0200 Subject: [PATCH 05/22] issue #239 Right-Left-Scrolling active for curation part --- static/foundation/css/foundation.css | 48 ++++++---- static/foundation/js/vendor/ui.js | 125 ++++++++++++++------------- 2 files changed, 97 insertions(+), 76 deletions(-) diff --git a/static/foundation/css/foundation.css b/static/foundation/css/foundation.css index cfcdcb9..ca4114b 100644 --- a/static/foundation/css/foundation.css +++ b/static/foundation/css/foundation.css @@ -330,7 +330,7 @@ form.data-form3 { } .tab .form-group { - width: 90%; + /* width: 90%; */ margin: 5% 0% 0% 5%; text-align: left; display: block; @@ -343,16 +343,16 @@ form.data-form3 { background: #f5f7fa; border-left: 4px solid #0078d4; color: #222; - padding: 0em 0em; - margin-bottom: 0em; + /* padding: 0em 0em; */ + /* margin-bottom: 0em; */ border-radius: 4px; font-size: 1.08em; font-style: italic; text-align: center; - width: 90%; - margin-left: 0; + /* width: 90%; */ + /* margin-left: 0; margin-right: 0; - margin: 0 auto 0 auto; + margin: 0 auto 0 auto; */ } .form-group label { @@ -386,7 +386,7 @@ select:focus { /* style for general single input elements with longer values */ .form-group .long_field { - width: 94%; + width: 100%; margin-bottom: 0.1em; float: left; box-sizing: border-box; @@ -490,12 +490,17 @@ select { #metadata-json { /* overflow: scroll; */ width: 100%; - height: 90%; + height: 93%; padding: 8px; border-radius: 4px; margin-bottom: 2px; font-size: medium; } +/* @media (width >= 2000px) { + #metadata-json { + height: 9%; + } +} */ .invalid { border: 1px solid rgba(226, 7, 7, 0.535); background-color: rgba(225, 94, 94, 0.377); @@ -524,7 +529,7 @@ button#new-url { .main-container { display: flex; transition: all 0.3s ease; - height: 100%; + height: 90%; } /* Error page / connection time out */ @@ -562,13 +567,19 @@ div.error-container a:hover { /* Flexible columns distribution */ .auto-property { margin-top: 8px; - width: 100%; /* Same as .long_field */ + /* width: 100%; Same as .long_field */ min-width: 500px; /* Optional: for usability */ table-layout: auto; margin-bottom: 1rem; - border-collapse: collapse; - margin-left: auto; - margin-right: auto; + /* border-collapse: collapse; */ + /* margin-left: auto; + margin-right: auto; */ + display: block; +} +@media (width>=2000px) { + .auto-property { + display: inline-table; + } } .table { @@ -732,6 +743,8 @@ td.table-tagging-cell select.table-dropdown-select { } .tab.unique-tab .unique-tab-inner { + width: 100%; + height: 100px; transform: scale(0.8); transform-origin: top center; display: inline-block; @@ -1035,8 +1048,7 @@ label > .fa-info-circle { } .fa-download, -.fa-copy, -.fa-trash-alt { +.fa-copy { border: none; float: right; margin-bottom: 0.5%; @@ -1046,7 +1058,11 @@ label > .fa-info-circle { background-color: none; margin-left: 2.5%; } - +.fa-trash-alt { + color: #055f82; + font-size: 1.2em; + background-color: none; +} .fa-download:hover, .fa-copy:hover, .fa-trash-alt:hover { diff --git a/static/foundation/js/vendor/ui.js b/static/foundation/js/vendor/ui.js index 1314cf8..4f51e7c 100644 --- a/static/foundation/js/vendor/ui.js +++ b/static/foundation/js/vendor/ui.js @@ -83,48 +83,48 @@ export function setupUI() { document .querySelectorAll(".custom-tooltip-metadata") .forEach(function (element) { - const tooltip = element.querySelector(".tooltip-text-metadata"); - const icon = element.querySelector("i"); - - // Helper to get scale factor from parent (default 1) - function getScaleFactor(el) { - let scale = 1; - let parent = el; - while (parent) { - const transform = window.getComputedStyle(parent).transform; - if (transform && transform !== "none") { - const match = transform.match(/matrix\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+\)/); - if (match) { - scale *= parseFloat(match[1]); - } - } - parent = parent.parentElement; + const tooltip = element.querySelector(".tooltip-text-metadata"); + const icon = element.querySelector("i"); + + // Helper to get scale factor from parent (default 1) + function getScaleFactor(el) { + let scale = 1; + let parent = el; + while (parent) { + const transform = window.getComputedStyle(parent).transform; + if (transform && transform !== "none") { + const match = transform.match( + /matrix\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+\)/ + ); + if (match) { + scale *= parseFloat(match[1]); } - return scale; + } + parent = parent.parentElement; } + return scale; + } - - element.addEventListener("mouseenter", function () { - tooltip.style.display = "block"; - tooltip.style.visibility = "visible"; - tooltip.style.opacity = "1"; - tooltip.style.position = "absolute"; - tooltip.style.zIndex = "9999"; - const rect = icon.getBoundingClientRect(); - const margin = 16; - - // Find the scale factor (if any) from the closest scaled parent - const scale = getScaleFactor(icon.parentElement); - console.info("Tooltip scale factor:", scale); - - // Adjust position for scale - //let left = rect.right * scale; - //let top = (rect.top + margin) * scale; - let left = 16; - let top = 16; - tooltip.style.left = left + "px"; - tooltip.style.top = top + "px"; - + element.addEventListener("mouseenter", function () { + tooltip.style.display = "block"; + tooltip.style.visibility = "visible"; + tooltip.style.opacity = "1"; + tooltip.style.position = "absolute"; + tooltip.style.zIndex = "9999"; + const rect = icon.getBoundingClientRect(); + const margin = 16; + + // Find the scale factor (if any) from the closest scaled parent + const scale = getScaleFactor(icon.parentElement); + console.info("Tooltip scale factor:", scale); + + // Adjust position for scale + //let left = rect.right * scale; + //let top = (rect.top + margin) * scale; + let left = 16; + let top = 16; + tooltip.style.left = left + "px"; + tooltip.style.top = top + "px"; }); element.addEventListener("mouseleave", function () { tooltip.style.display = "none"; @@ -178,7 +178,7 @@ function toggleSection() { } else if (window.screen.width <= 990 && toggleSwitch.checked) { formContainer.style.height = "50%"; } else { - formContainer.style.height = "100%"; + formContainer.style.height = "99%"; } if (toggleSwitch.checked) { metadataFormDisplay.style.display = "block"; @@ -255,11 +255,10 @@ export function validateInput(input) { ]; if (skipValidationIds.includes(input.id)) { return; // Skip validation for the specified inputs - } - - // Always remove highlight classes before validation - input.classList.remove("invalid", "invalid-required", "invalid-recommended"); + } + // Always remove highlight classes before validation + input.classList.remove("invalid", "invalid-required", "invalid-recommended"); // Fetch schema and validate only if field is required or recommended getSchema() @@ -281,20 +280,23 @@ export function validateInput(input) { : null; const key = getFieldKey(hiddenInput); - if (required.includes(key)) { if ( - (taggingType === "tagging_object" && isTaggingObjectEmpty(tagsContainer)) || - (taggingType !== "tagging_object" && hiddenInput.value.trim() === "") - ) { - input.classList.add("invalid-required"); + (taggingType === "tagging_object" && + isTaggingObjectEmpty(tagsContainer)) || + (taggingType !== "tagging_object" && + hiddenInput.value.trim() === "") + ) { + input.classList.add("invalid-required"); } } else if (recommended.includes(key)) { if ( - (taggingType === "tagging_object" && isTaggingObjectEmpty(tagsContainer)) || - (taggingType !== "tagging_object" && hiddenInput.value.trim() === "") - ) { - input.classList.add("invalid-recommended"); + (taggingType === "tagging_object" && + isTaggingObjectEmpty(tagsContainer)) || + (taggingType !== "tagging_object" && + hiddenInput.value.trim() === "") + ) { + input.classList.add("invalid-recommended"); } } else { input.classList.remove("invalid"); @@ -307,17 +309,21 @@ export function validateInput(input) { const key = getFieldKey(input); if (required.includes(key)) { if (input.value.trim() === "") { - input.classList.add("invalid-required"); + input.classList.add("invalid-required"); } } else if (recommended.includes(key)) { - if (input.value.trim() === "") { - input.classList.add("invalid-recommended"); - } + if (input.value.trim() === "") { + input.classList.add("invalid-recommended"); + } } }) .catch(() => { - // On schema load error, fallback to no validation - input.classList.remove("invalid", "invalid-required", "invalid-recommended"); + // On schema load error, fallback to no validation + input.classList.remove( + "invalid", + "invalid-required", + "invalid-recommended" + ); }); } @@ -381,4 +387,3 @@ function initAutoCloseCollapses(collapseSelector = ".collapsible-content") { } }); } - From b03ed8818ab2124e3b1c8e7f4555df93f6be9dcb Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Tue, 7 Oct 2025 21:26:08 +0200 Subject: [PATCH 06/22] copyright_table alligned --- meta_creator/templates/meta_creator/showdata.html | 2 +- static/foundation/css/foundation.css | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/meta_creator/templates/meta_creator/showdata.html b/meta_creator/templates/meta_creator/showdata.html index dfa285e..c65ef18 100644 --- a/meta_creator/templates/meta_creator/showdata.html +++ b/meta_creator/templates/meta_creator/showdata.html @@ -280,7 +280,7 @@

Extra Hints!

{% elif forloop.first or not unique_tab %} -
+
+
@@ -304,7 +305,15 @@

Extra Hints!

An "maintainer" in software authorship is someone who significantly maintainers to the creation and development of software, including roles like coding, project management, and documentation.

- ⚠️ Click on contributors, authors, or maintainers to see the descriptionGot it! +
+ ⚠️ Click on contributors, authors, or maintainers to see the descriptionGot it! + + + + + +
+
{% endif %} @@ -313,6 +322,7 @@

Extra Hints!

+ {% for col, value in specific_types.items %} + {% for col, value in specific_types.items %} {% if value == 'tagging' or value == 'tagging_autocomplete' or value == 'dropdown' %} {% endfor %} {% endif %} - {% endif %} {% endfor %} + {% for col, value in specific_types.items %} {% if value == 'tagging' or value == 'tagging_autocomplete' %} From 2845752971dee8ce69a4974266662fa04f239206 Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Thu, 23 Oct 2025 15:03:14 +0200 Subject: [PATCH 13/22] add button border fix --- meta_creator/templates/meta_creator/showdata.html | 10 +++++----- static/foundation/css/foundation.css | 8 ++++++-- static/foundation/js/vendor/table-utils.js | 2 ++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/meta_creator/templates/meta_creator/showdata.html b/meta_creator/templates/meta_creator/showdata.html index 5328c5a..6242da4 100644 --- a/meta_creator/templates/meta_creator/showdata.html +++ b/meta_creator/templates/meta_creator/showdata.html @@ -322,7 +322,7 @@

Extra Hints!

{% if key == "contributor" %}
{% endif %} -
Select {{ col|camel_to_spaces_lower }} @@ -347,6 +357,9 @@

Extra Hints!

{% for row in value %} {% if row|row_has_values:specific_types.keys %}
+ + @@ -380,14 +393,15 @@

Extra Hints!

+
diff --git a/static/foundation/css/foundation.css b/static/foundation/css/foundation.css index e037514..652bbea 100644 --- a/static/foundation/css/foundation.css +++ b/static/foundation/css/foundation.css @@ -349,9 +349,9 @@ form.data-form3 { font-size: 1.08em; font-style: italic; text-align: center; - /* width: 90%; */ - /* margin-left: 0; - margin-right: 0; + width: 90%; + margin-left: 5%; + /* margin-right: 0; margin: 0 auto 0 auto; */ } @@ -577,8 +577,16 @@ div.error-container a:hover { /* border-collapse: collapse; */ /* margin-left: auto; margin-right: auto; */ + /* display: block; */ +} +#contributorTable { display: block; } +/* @media (width<=1800px) { + .auto-property { + display: inline-block; + } +} */ @media (width>=2000px) { .auto-property { display: inline-table; @@ -748,7 +756,7 @@ td.table-tagging-cell select.table-dropdown-select { .tab.unique-tab .unique-tab-inner { width: 100%; height: 100px; - transform: scale(0.8); + transform: scale(0.97); transform-origin: top center; display: inline-block; } @@ -1061,14 +1069,18 @@ label > .fa-info-circle { background-color: none; margin-left: 2.5%; } -.fa-trash-alt { +.fa-trash-alt, +.fa-table-list { + float: right; color: #055f82; font-size: 1.2em; - background-color: none; + padding-top: 14px; + padding-right: 14px; } .fa-download:hover, .fa-copy:hover, -.fa-trash-alt:hover { +.fa-trash-alt:hover, +.fa-table-list:hover { color: #4ba0af; cursor: pointer; } @@ -1887,3 +1899,45 @@ select option:checked { .searchable-dropdown:focus::placeholder { color: transparent; } + +.action { + width: 100%; + position: relative; +} + +.selected-row { + background-color: #c8e6c9; /* halka green highlight */ +} + +table input[type="checkbox"] { + appearance: none; + width: 18px; + height: 18px; + border: 1px solid #cfcfcf; + border-radius: 4px; + cursor: pointer; + background-color: #ffffff; + /* display: grid; + place-content: center; */ +} + +table input[type="checkbox"]:checked { + background-color: #055f82; +} + +/* table input[type="checkbox"]:after { + + display: none; +} */ + +table input[type="checkbox"]:checked:after { + font-family: "Font Awesome 5 Free"; + content: "\f00c"; + font-weight: 900; + font-size: 12px; + color: white; + display: flex; + align-items: center; + justify-content: center; + /* text-align: center; */ +} diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 541bf8a..07327ca 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -346,13 +346,20 @@ export function setupTables() { } newRow.appendChild(td); } - const deleteTd = document.createElement("td"); - - deleteTd.className = "d-flex justify-content-center align-items-center"; - deleteTd.style.height = "50px"; - deleteTd.innerHTML = - ''; - newRow.appendChild(deleteTd); + // const deleteTd = document.createElement("td"); + + // deleteTd.className = "d-flex justify-content-center align-items-center"; + // deleteTd.style.height = "50px"; + // deleteTd.innerHTML = + // ''; + // newRow.appendChild(deleteTd); + if (newRow.firstElementChild) { + newRow.removeChild(newRow.firstElementChild); + } + const selectcheckbox = document.createElement("td"); + selectcheckbox.className = "text-center"; + selectcheckbox.innerHTML = ``; + newRow.prepend(selectcheckbox); // Insert new row above add-row-controls addRowControls.parentNode.insertBefore(newRow, addRowControls); @@ -674,7 +681,89 @@ export function setupTables() { }); highlightEmptyAddRowControls(); + + // deleteRow and MergeRow + const deleteIcon = document.querySelector(".action .delete-row-btn"); + const mergeRowIcon = document.querySelector(".action .fa-table-list"); + const deleteRowConfirm = document.querySelector(".action .delete-row"); + const mergeRowConfirm = document.querySelector(".action .merge-row"); + + // ✅ Use event delegation for checkbox handling + document.addEventListener("change", function (e) { + if (e.target.classList.contains("checkbox-select")) { + const checkbox = e.target; + const row = checkbox.closest("tr"); + + // Highlight selected row + if (checkbox.checked) { + row.classList.add("table-secondary"); + } else { + row.classList.remove("table-secondary"); + } + + // Count selected checkboxes + const selectedCount = document.querySelectorAll( + ".checkbox-select:checked" + ).length; + + // Show/hide icons based on selection count + deleteIcon.style.display = selectedCount >= 1 ? "" : "none"; + mergeRowIcon.style.display = selectedCount >= 2 ? "" : "none"; + } + }); + + // ✅ Handle delete icon click + deleteIcon.addEventListener("click", function () { + deleteRowConfirm.style.display = ""; + deleteIcon.style.display = "none"; + mergeRowIcon.style.display = "none"; + }); + + // ✅ Handle merge icon click + mergeRowIcon.addEventListener("click", function () { + mergeRowConfirm.style.display = ""; + deleteIcon.style.display = "none"; + mergeRowIcon.style.display = "none"; + }); + + // ✅ Handle delete confirmation (YES button) + document.querySelector(".deleteRow").addEventListener("click", function () { + const selectedCheckboxes = document.querySelectorAll( + ".checkbox-select:checked" + ); + + selectedCheckboxes.forEach((checkbox) => { + const row = checkbox.closest("tr"); + const table = row.closest("table"); + + if (row) row.remove(); + + // Update hidden input for that table + if (table && table.id && table.id.endsWith("Table")) { + const key = table.id.replace(/Table$/, ""); + if (typeof updateTableHiddenInput === "function") { + updateTableHiddenInput(key); + } + } + }); + + // Hide confirmation prompt + deleteRowConfirm.style.display = "none"; + + // Reset icons + deleteIcon.style.display = "none"; + mergeRowIcon.style.display = "none"; + }); + + // ✅ Optional: handle merge confirmation (YES button) + document.querySelector(".mergeRow").addEventListener("click", function () { + // Implement your merge logic here + mergeRowConfirm.style.display = "none"; + deleteIcon.style.display = "none"; + mergeRowIcon.style.display = "none"; + }); } + // Add function to color add items when element is required or recommended and empty export function highlightEmptyAddRowControls() { getSchema().then((schema) => { From eb35590608ff19f32a5ea751cd923ace9b25000d Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Mon, 13 Oct 2025 15:21:38 +0200 Subject: [PATCH 09/22] Merge row Function --- static/foundation/js/vendor/table-utils.js | 128 ++++++++++++++++++++- static/foundation/js/vendor/ui.js | 47 ++++++++ 2 files changed, 169 insertions(+), 6 deletions(-) diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 07327ca..be79630 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -16,6 +16,8 @@ import { enableEditableTagsInTable, } from "./tagging.js"; +import { showToast } from "./ui.js"; + const metadataJson = document.getElementById("metadata-json"); // New table @@ -252,8 +254,12 @@ export function setupTables() { // If neither condition is satisfied → stop row creation if (!condition1 && !condition2) { - alert( - "Please fill either: Identifier + Given Name + Family Name OR Given Name + Family Name + Email." + // alert( + // "Please fill either: Identifier + Given Name + Family Name OR Given Name + Family Name + Email." + // ); + showToast( + "Please fill either: Identifier + Given Name + Family Name OR Given Name + Family Name + Email.", + "error" ); return; } @@ -363,6 +369,7 @@ export function setupTables() { // Insert new row above add-row-controls addRowControls.parentNode.insertBefore(newRow, addRowControls); + showToast("New row has been added", "success"); // Check if this td contains a checkbox document.querySelectorAll("td").forEach((td) => { if (td.querySelector('input[type="checkbox"]')) { @@ -531,7 +538,8 @@ export function setupTables() { if (col === "email") { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(tag)) { - alert("Please enter a valid Email address."); + // alert("Please enter a valid Email address."); + showToast("Please enter a valid Email address.", "error"); input.value = ""; return; } @@ -755,12 +763,120 @@ export function setupTables() { mergeRowIcon.style.display = "none"; }); - // ✅ Optional: handle merge confirmation (YES button) + // ✅ Handle merge confirmation (YES button) document.querySelector(".mergeRow").addEventListener("click", function () { - // Implement your merge logic here + const selectedCheckboxes = document.querySelectorAll( + ".checkbox-select:checked" + ); + // if (selectedCheckboxes.length >= 2) { + // alert("Please select at least two rows to merge."); + // return; + // } + + const firstRow = selectedCheckboxes[0].closest("tr"); + const table = firstRow.closest("table"); + const headers = Array.from(table.querySelectorAll("thead th")).map((th) => + th.getAttribute("data-col") + ); + + const givenNameIdx = headers.indexOf("givenName"); + const familyNameIdx = headers.indexOf("familyName"); + const emailIdx = headers.indexOf("email"); + + // 🔹 Extract data for all selected rows + const selectedData = Array.from(selectedCheckboxes).map((checkbox) => { + const row = checkbox.closest("tr"); + const cells = row.querySelectorAll("td"); + + const givenName = cells[givenNameIdx]?.textContent.trim() || ""; + const familyName = cells[familyNameIdx]?.textContent.trim() || ""; + + // Collect all emails (from tags if available) + let emails = []; + if (emailIdx !== -1) { + const emailCell = cells[emailIdx]; + const tags = emailCell.querySelectorAll(".tag"); + if (tags.length > 0) { + emails = Array.from(tags).map((t) => t.dataset.tag); + } else if (emailCell.textContent.trim() !== "") { + emails = [emailCell.textContent.trim()]; + } + } + + return { row, givenName, familyName, emails }; + }); + + // 🔹 Group selected rows by Given Name + Family Name + const grouped = {}; + selectedData.forEach((item) => { + const key = `${item.givenName.toLowerCase()}-${item.familyName.toLowerCase()}`; + if (!grouped[key]) grouped[key] = []; + grouped[key].push(item); + }); + + let merged = false; + + // 🔹 Merge logic for rows with same Given + Family + Object.values(grouped).forEach((group) => { + if (group.length > 1) { + merged = true; + const mainRow = group[0].row; // keep first row + const allEmails = [...new Set(group.flatMap((g) => g.emails))]; // merge + dedupe + + // Update mainRow's email cell + if (emailIdx !== -1) { + const emailCell = mainRow.querySelectorAll("td")[emailIdx]; + const tagsList = emailCell.querySelector(".tags-list"); + + if (tagsList) { + tagsList.innerHTML = ""; + allEmails.forEach((email) => { + const span = document.createElement("span"); + span.className = "tag"; + span.setAttribute("data-tag", email); + span.innerHTML = + email + + ' ×'; + tagsList.appendChild(span); + }); + } else { + emailCell.textContent = allEmails.join(", "); + } + } + + // Remove other duplicate rows + group.slice(1).forEach((g) => g.row.remove()); + } + }); + + // ✅ Update hidden input JSON + if (table && typeof updateTableHiddenInput === "function") { + const key = table.id.replace(/Table$/, ""); + updateTableHiddenInput(key); + } + + // ✅ UI cleanup mergeRowConfirm.style.display = "none"; deleteIcon.style.display = "none"; mergeRowIcon.style.display = "none"; + selectedCheckboxes.forEach((cb) => (cb.checked = false)); + table + .querySelectorAll("tr") + .forEach((row) => row.classList.remove("table-secondary")); + + if (merged) { + showToast( + "Rows with matching names have been merged successfully!", + "success" + ); + } else { + showToast( + "No matching Given Name + Family Name found among selected rows.", + "error" + ); + } }); } @@ -915,7 +1031,7 @@ export function initializeTableTaggingCells() { if (col === "email") { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(tag)) { - alert("Please enter a valid Email address."); + showToast("Please enter a valid Email address.", "error"); input.value = ""; return; // ❌ Stop tag creation } diff --git a/static/foundation/js/vendor/ui.js b/static/foundation/js/vendor/ui.js index 4f51e7c..48e2918 100644 --- a/static/foundation/js/vendor/ui.js +++ b/static/foundation/js/vendor/ui.js @@ -387,3 +387,50 @@ function initAutoCloseCollapses(collapseSelector = ".collapsible-content") { } }); } + +export function showToast(message, type = "info") { + // Toast container create if not exist + let container = document.getElementById("toast-container"); + if (!container) { + container = document.createElement("div"); + container.id = "toast-container"; + container.style.position = "fixed"; + container.style.top = "20px"; + container.style.right = "20px"; + container.style.zIndex = "9999"; + document.body.appendChild(container); + } + + // Toast element + const toast = document.createElement("div"); + toast.textContent = message; + toast.style.padding = "10px 20px"; + toast.style.marginTop = "10px"; + toast.style.borderRadius = "6px"; + toast.style.color = "#fff"; + toast.style.fontSize = "14px"; + toast.style.fontFamily = "sans-serif"; + toast.style.boxShadow = "0 2px 6px rgba(0,0,0,0.2)"; + toast.style.opacity = "0"; + toast.style.transition = "opacity 0.5s"; + + // Type based color + if (type === "error") { + toast.style.background = "#e74c3c"; // red + } else if (type === "success") { + toast.style.background = "#2ecc71"; // green + } else { + toast.style.background = "#3498db"; // blue + } + + container.appendChild(toast); + + // Fade in + setTimeout(() => (toast.style.opacity = "1"), 100); + + // Auto remove after 3 sec + setTimeout(() => { + toast.style.opacity = "0"; + setTimeout(() => toast.remove(), 500); + }, 3000); +} From 9e9d1cee2be50120510e27c2c702bcc646e28dbb Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Mon, 13 Oct 2025 18:11:41 +0200 Subject: [PATCH 10/22] Copyright Holder updated --- static/foundation/js/vendor/table-utils.js | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index be79630..52c9d03 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -352,20 +352,24 @@ export function setupTables() { } newRow.appendChild(td); } - // const deleteTd = document.createElement("td"); - - // deleteTd.className = "d-flex justify-content-center align-items-center"; - // deleteTd.style.height = "50px"; - // deleteTd.innerHTML = - // ''; - // newRow.appendChild(deleteTd); - if (newRow.firstElementChild) { - newRow.removeChild(newRow.firstElementChild); + + if (table.id !== "copyrightHolderTable") { + if (newRow.firstElementChild) { + newRow.removeChild(newRow.firstElementChild); + } + const selectcheckbox = document.createElement("td"); + selectcheckbox.className = "text-center"; + selectcheckbox.innerHTML = ``; + newRow.prepend(selectcheckbox); + } else { + const deleteTd = document.createElement("td"); + + deleteTd.className = "d-flex justify-content-center align-items-center"; + deleteTd.style.height = "50px"; + deleteTd.innerHTML = + ''; + newRow.appendChild(deleteTd); } - const selectcheckbox = document.createElement("td"); - selectcheckbox.className = "text-center"; - selectcheckbox.innerHTML = ``; - newRow.prepend(selectcheckbox); // Insert new row above add-row-controls addRowControls.parentNode.insertBefore(newRow, addRowControls); From d01e0a813b438956a1acf2514bc574149e29d5c0 Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Mon, 20 Oct 2025 12:23:21 +0200 Subject: [PATCH 11/22] #210 Improve checkbox-role association for tables with many rows (Solved) --- .../templates/meta_creator/showdata.html | 11 +- static/foundation/css/foundation.css | 25 ++- static/foundation/js/vendor/table-utils.js | 166 ++++++++++-------- static/foundation/js/vendor/ui.js | 8 +- 4 files changed, 131 insertions(+), 79 deletions(-) diff --git a/meta_creator/templates/meta_creator/showdata.html b/meta_creator/templates/meta_creator/showdata.html index b052a4d..efb7980 100644 --- a/meta_creator/templates/meta_creator/showdata.html +++ b/meta_creator/templates/meta_creator/showdata.html @@ -319,10 +319,13 @@

Extra Hints!

{% endif %} + {% if key == "contributor" %} +
+ {% endif %} - + {% for col, value in specific_types.items %} {% endfor %} {% endif %} - + @@ -439,8 +442,12 @@

Extra Hints!

+
SelectSelect {{ col|camel_to_spaces_lower }} @@ -350,7 +353,7 @@

Extra Hints!

Row Control
+ {% if key == "contributor" %} +
+ {% endif %} {% endwith %} {% endif %} diff --git a/static/foundation/css/foundation.css b/static/foundation/css/foundation.css index 652bbea..f823ff9 100644 --- a/static/foundation/css/foundation.css +++ b/static/foundation/css/foundation.css @@ -579,9 +579,18 @@ div.error-container a:hover { margin-right: auto; */ /* display: block; */ } -#contributorTable { - display: block; +#contributorTable th { + position: sticky; + top: 0; + z-index: 10; } + +#contributorTable th:last-child, +#contributorTable td:last-child { + border: none !important; + border-collapse: collapse; +} + /* @media (width<=1800px) { .auto-property { display: inline-block; @@ -590,6 +599,7 @@ div.error-container a:hover { @media (width>=2000px) { .auto-property { display: inline-table; + width: 100%; } } @@ -611,6 +621,11 @@ div.error-container a:hover { max-width: 300px; } +.scrollable { + height: 500px; + overflow-y: auto; +} + /* Set fixed width for first row */ .auto-property th:first-child, .auto-property-table td:first-child { @@ -967,7 +982,7 @@ label > .fa-info-circle { font-size: 70%; min-width: 200px; max-width: 260px; - width: auto; + /* width: auto; */ background-color: #5e5b5b; color: #fff; text-align: left; @@ -975,8 +990,8 @@ label > .fa-info-circle { padding: 5px; z-index: 1000; position: fixed; - left: 0; /* Will be set dynamically */ - top: 0; /* Will be set dynamically */ + left: 0; + top: 0; transition: opacity 0.3s; pointer-events: none; opacity: 0; diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 52c9d03..91b6c1c 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -224,7 +224,10 @@ export function setupTables() { // Prevent adding if all fields are empty const allEmpty = values.every((val) => val === ""); - if (allEmpty) return; + if (allEmpty) { + showToast("Please fill the given fields before adding row", "error"); + return; + } // ✅ Combination Validation const getInputVal = (col) => @@ -293,7 +296,6 @@ export function setupTables() { // td.className = "text-center"; if (colType === "element") { // Find the checkbox in the add-row-controls row - console.log("Looking for checkbox with data-role:", col); const checkboxInput = addRowControls.querySelector( `input[type="checkbox"][data-role="${header}"]` ); @@ -302,8 +304,6 @@ export function setupTables() { checkbox.classList.add("checkbox-element"); checkbox.setAttribute("data-role", col); checkbox.name = `checkbox-${col}`; - console.log("Try to check for checkbox"); - console.log({ checkboxInput }); // Set checked state based on add-row-controls checkbox if (checkboxInput && checkboxInput.checked) { @@ -758,7 +758,9 @@ export function setupTables() { } } }); - + if (selectedCheckboxes.length >= 2) { + showToast("Selected Rows have been deleted", "success"); + } else showToast("Selected Row has been deleted", "success"); // Hide confirmation prompt deleteRowConfirm.style.display = "none"; @@ -772,10 +774,6 @@ export function setupTables() { const selectedCheckboxes = document.querySelectorAll( ".checkbox-select:checked" ); - // if (selectedCheckboxes.length >= 2) { - // alert("Please select at least two rows to merge."); - // return; - // } const firstRow = selectedCheckboxes[0].closest("tr"); const table = firstRow.closest("table"); @@ -882,6 +880,9 @@ export function setupTables() { ); } }); + // removeSelectColumn("copyrightHolderTable"); + removeColumnFromTable("copyrightHolderTable", "select"); + // removeColumnFromTable("contributorTable", "Row Control"); } // Add function to color add items when element is required or recommended and empty @@ -944,67 +945,68 @@ export function initializeTableTaggingCells() { const colType = cell.getAttribute("data-coltype"); const dataType = cell.getAttribute("data-type"); - if (colType == "tagging_autocomplete") { - // getSchema().then((schema) => { - // autocompleteSource = - // schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; - // if (autocompleteSource.length > 0) { - // setupTableTagAutocomplete({ cell, autocompleteSource }); - // } - // }); - } else if (colType === "dropdown") { - const currentValue = - cell.getAttribute("data-value") || cell.textContent.trim() || ""; - cell.innerHTML = ""; - cell.textContent = currentValue; - - // cell.addEventListener("click", function handleDropdownCellClick(e) { - // if (cell.querySelector("select")) return; - // getSchema().then((schema) => { - // const options = - // schema["$defs"]?.[dataType]?.properties?.[col]?.enum || []; - // const select = document.createElement("select"); - // select.className = "table-dropdown-select"; - // select.name = "ChangingSelect"; - // select.innerHTML = - // '' + - // options - // .map((opt) => ``) - // .join(""); - // select.value = currentValue; - - // cell.innerHTML = ""; - // cell.appendChild(select); - // select.focus(); - - // function finalizeSelection() { - // const selectedValue = select.value; - // cell.setAttribute("data-value", selectedValue); - // setTimeout(() => { - // cell.innerHTML = selectedValue; - // }, 0); - - // cell.removeEventListener("click", handleDropdownCellClick); - // setTimeout(() => { - // cell.addEventListener("click", handleDropdownCellClick); - // }, 0); - - // const table = cell.closest("table"); - // if (table && table.id.endsWith("Table")) { - // const key = table.id.replace(/Table$/, ""); - // updateTableHiddenInput(key); - // } - // } - - // select.addEventListener("change", finalizeSelection); - // select.addEventListener("blur", finalizeSelection); - // }); - - // cell.removeEventListener("click", handleDropdownCellClick); - // }); - - return; - } + // if (colType == "tagging_autocomplete") { + // // getSchema().then((schema) => { + // // autocompleteSource = + // // schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; + // // if (autocompleteSource.length > 0) { + // // setupTableTagAutocomplete({ cell, autocompleteSource }); + // // } + // // }); + // } else + // if (colType === "dropdown") { + // const currentValue = + // cell.getAttribute("data-value") || cell.textContent.trim() || ""; + // cell.innerHTML = ""; + // cell.textContent = currentValue; + + // // cell.addEventListener("click", function handleDropdownCellClick(e) { + // // if (cell.querySelector("select")) return; + // // getSchema().then((schema) => { + // // const options = + // // schema["$defs"]?.[dataType]?.properties?.[col]?.enum || []; + // // const select = document.createElement("select"); + // // select.className = "table-dropdown-select"; + // // select.name = "ChangingSelect"; + // // select.innerHTML = + // // '' + + // // options + // // .map((opt) => ``) + // // .join(""); + // // select.value = currentValue; + + // // cell.innerHTML = ""; + // // cell.appendChild(select); + // // select.focus(); + + // // function finalizeSelection() { + // // const selectedValue = select.value; + // // cell.setAttribute("data-value", selectedValue); + // // setTimeout(() => { + // // cell.innerHTML = selectedValue; + // // }, 0); + + // // cell.removeEventListener("click", handleDropdownCellClick); + // // setTimeout(() => { + // // cell.addEventListener("click", handleDropdownCellClick); + // // }, 0); + + // // const table = cell.closest("table"); + // // if (table && table.id.endsWith("Table")) { + // // const key = table.id.replace(/Table$/, ""); + // // updateTableHiddenInput(key); + // // } + // // } + + // // select.addEventListener("change", finalizeSelection); + // // select.addEventListener("blur", finalizeSelection); + // // }); + + // // cell.removeEventListener("click", handleDropdownCellClick); + // // }); + + // return; + // } // Show input when cell is clicked cell.addEventListener("click", function (e) { @@ -1096,3 +1098,27 @@ export function initializeTableTaggingCells() { }); }); } + +function removeColumnFromTable(tableId, columnName) { + const table = document.getElementById(tableId); + if (!table) return; + + const headers = Array.from(table.querySelectorAll("thead th")); + const headerIndex = headers.findIndex( + (th) => th.textContent.trim().toLowerCase() === columnName.toLowerCase() + ); + + // If header not found, stop + if (headerIndex === -1) return; + + // 🔹 Remove the header cell + headers[headerIndex].remove(); + + // 🔹 Remove the corresponding
in each row (same index) + table.querySelectorAll("tbody tr").forEach((row) => { + const cells = row.querySelectorAll("td"); + if (cells[headerIndex]) { + cells[headerIndex].remove(); + } + }); +} diff --git a/static/foundation/js/vendor/ui.js b/static/foundation/js/vendor/ui.js index 48e2918..c4f50c7 100644 --- a/static/foundation/js/vendor/ui.js +++ b/static/foundation/js/vendor/ui.js @@ -110,7 +110,7 @@ export function setupUI() { tooltip.style.visibility = "visible"; tooltip.style.opacity = "1"; tooltip.style.position = "absolute"; - tooltip.style.zIndex = "9999"; + // tooltip.style.zIndex = "9999"; const rect = icon.getBoundingClientRect(); const margin = 16; @@ -121,10 +121,12 @@ export function setupUI() { // Adjust position for scale //let left = rect.right * scale; //let top = (rect.top + margin) * scale; + let width = 1; let left = 16; let top = 16; tooltip.style.left = left + "px"; tooltip.style.top = top + "px"; + tooltip.style.width = width + "px"; }); element.addEventListener("mouseleave", function () { tooltip.style.display = "none"; @@ -420,7 +422,9 @@ export function showToast(message, type = "info") { } else if (type === "success") { toast.style.background = "#2ecc71"; // green } else { - toast.style.background = "#3498db"; // blue + toast.style.background = "#fef6da"; // yellow + toast.style.color = "#202020ff"; + toast.style.fontWeight = "bold"; } container.appendChild(toast); From 702331275820a98407bf49d76a03d636603dc30e Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Thu, 23 Oct 2025 15:01:48 +0200 Subject: [PATCH 12/22] table border fixed --- meta_creator/templates/meta_creator/showdata.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta_creator/templates/meta_creator/showdata.html b/meta_creator/templates/meta_creator/showdata.html index efb7980..5328c5a 100644 --- a/meta_creator/templates/meta_creator/showdata.html +++ b/meta_creator/templates/meta_creator/showdata.html @@ -353,7 +353,7 @@

Extra Hints!

{% endfor %} {% endif %} - +
+
@@ -340,7 +340,7 @@

Extra Hints!

{% if unique_tab %} {% for col, value in metadata_dict.items %} {% endif %} {% endfor %} diff --git a/static/foundation/css/foundation.css b/static/foundation/css/foundation.css index f823ff9..8a09ec2 100644 --- a/static/foundation/css/foundation.css +++ b/static/foundation/css/foundation.css @@ -578,17 +578,21 @@ div.error-container a:hover { /* margin-left: auto; margin-right: auto; */ /* display: block; */ + border-collapse: separate; + border-spacing: 0; } #contributorTable th { position: sticky; top: 0; z-index: 10; } - +#contributorTable td { + border: 1px solid #ddddddbd; +} #contributorTable th:last-child, #contributorTable td:last-child { border: none !important; - border-collapse: collapse; + /* border-collapse: collapse; */ } /* @media (width<=1800px) { diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 91b6c1c..73ff67b 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -358,9 +358,11 @@ export function setupTables() { newRow.removeChild(newRow.firstElementChild); } const selectcheckbox = document.createElement("td"); + const lastcolumn = document.createElement("td"); selectcheckbox.className = "text-center"; selectcheckbox.innerHTML = ``; newRow.prepend(selectcheckbox); + newRow.appendChild(lastcolumn); } else { const deleteTd = document.createElement("td"); From b6fec1c82b3b9a95403d56e8f1305d788f6311ea Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Thu, 23 Oct 2025 15:05:55 +0200 Subject: [PATCH 14/22] #241 Fixed: Selecting from suggestion list after typing does not work --- static/foundation/js/vendor/tagging.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/static/foundation/js/vendor/tagging.js b/static/foundation/js/vendor/tagging.js index 9e34a27..7afd70b 100644 --- a/static/foundation/js/vendor/tagging.js +++ b/static/foundation/js/vendor/tagging.js @@ -297,6 +297,7 @@ export function setupTagging({ // Add tag on pressing Enter key input.addEventListener("keydown", (e) => { + if (input.classList.contains("searchable-dropdown")) return; if (e.key === "Enter") { if (enterHandledBySuggestion) { enterHandledBySuggestion = false; @@ -310,16 +311,6 @@ export function setupTagging({ if (autocompleteSource.includes(newTag)) { e.preventDefault(); addTag(newTag); - } else { - e.preventDefault(); - showInvalidTagMessage( - container, - input, - "Please select a value from the list." - ); - input.classList.add("invalid"); - setTimeout(() => input.classList.remove("invalid"), 1000); - input.value = ""; } } else if (newTag) { e.preventDefault(); From 508795601f3768528190e1d1b1c019e022b42490 Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Thu, 23 Oct 2025 17:06:45 +0200 Subject: [PATCH 15/22] Uncheck selected checkboxes --- meta_creator/templates/meta_creator/showdata.html | 4 ++-- static/foundation/js/vendor/table-utils.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/meta_creator/templates/meta_creator/showdata.html b/meta_creator/templates/meta_creator/showdata.html index 6242da4..549e527 100644 --- a/meta_creator/templates/meta_creator/showdata.html +++ b/meta_creator/templates/meta_creator/showdata.html @@ -310,8 +310,8 @@

Extra Hints!

- - + + diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 73ff67b..6b396c5 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -701,6 +701,7 @@ export function setupTables() { const mergeRowIcon = document.querySelector(".action .fa-table-list"); const deleteRowConfirm = document.querySelector(".action .delete-row"); const mergeRowConfirm = document.querySelector(".action .merge-row"); + const rejects = document.querySelectorAll(".reject"); // ✅ Use event delegation for checkbox handling document.addEventListener("change", function (e) { @@ -882,6 +883,19 @@ export function setupTables() { ); } }); + + // Unselect checkboxes (No button) + rejects.forEach((reject) => { + reject.addEventListener("click", function () { + const selectedCheckboxes = document.querySelectorAll( + ".checkbox-select:checked" + ); + selectedCheckboxes.forEach((checkbox) => { + checkbox.checked = false; + }); + }); + }); + // removeSelectColumn("copyrightHolderTable"); removeColumnFromTable("copyrightHolderTable", "select"); // removeColumnFromTable("contributorTable", "Row Control"); From ef886f5aea92c8792b2a6e1dbdd6e99e2db2138e Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Thu, 23 Oct 2025 21:47:16 +0200 Subject: [PATCH 16/22] tooltip alignment fixed --- static/foundation/css/foundation.css | 19 ++-- static/foundation/js/vendor/table-utils.js | 2 + static/foundation/js/vendor/ui.js | 108 ++++++++++----------- 3 files changed, 66 insertions(+), 63 deletions(-) diff --git a/static/foundation/css/foundation.css b/static/foundation/css/foundation.css index 8a09ec2..debb38d 100644 --- a/static/foundation/css/foundation.css +++ b/static/foundation/css/foundation.css @@ -984,7 +984,7 @@ label > .fa-info-circle { .tooltip-text-metadata { visibility: hidden; font-size: 70%; - min-width: 200px; + min-width: 165px; max-width: 260px; /* width: auto; */ background-color: #5e5b5b; @@ -993,9 +993,9 @@ label > .fa-info-circle { border-radius: 6px; padding: 5px; z-index: 1000; - position: fixed; - left: 0; - top: 0; + position: absolute; + left: -20px; + top: 25px; transition: opacity 0.3s; pointer-events: none; opacity: 0; @@ -1003,7 +1003,8 @@ label > .fa-info-circle { white-space: normal; /* Allow text to wrap */ box-sizing: border-box; overflow-wrap: break-word; /* Ensure wrapping for long words */ - display: none; + display: block; + font-weight: 400; } .custom-tooltip-metadata:hover .tooltip-text-metadata { @@ -1029,7 +1030,7 @@ label > .fa-info-circle { pointer-events: none; } -.custom-tooltip-metadata .tooltip-text-metadata { +/* .custom-tooltip-metadata .tooltip-text-metadata { visibility: hidden; font-size: 70%; width: 100%; @@ -1042,7 +1043,7 @@ label > .fa-info-circle { top: 0; transition: opacity 0.3s; pointer-events: none; -} +} */ .custom-tooltip:hover .tooltip-text, .custom-tooltip .tooltip-text:hover { @@ -1051,10 +1052,10 @@ label > .fa-info-circle { pointer-events: auto; } -.custom-tooltip-metadata:hover .tooltip-text-metadata { +/* .custom-tooltip-metadata:hover .tooltip-text-metadata { visibility: visible; opacity: 1; -} +} */ .custom-tooltip .tooltip-text a { color: rgb(112, 155, 233); diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 6b396c5..517310b 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -892,6 +892,8 @@ export function setupTables() { ); selectedCheckboxes.forEach((checkbox) => { checkbox.checked = false; + if (!checkbox.checked) + checkbox.closest("tr").classList.remove("table-secondary"); }); }); }); diff --git a/static/foundation/js/vendor/ui.js b/static/foundation/js/vendor/ui.js index c4f50c7..c597e89 100644 --- a/static/foundation/js/vendor/ui.js +++ b/static/foundation/js/vendor/ui.js @@ -80,60 +80,60 @@ export function setupUI() { }); // custom tooltips - document - .querySelectorAll(".custom-tooltip-metadata") - .forEach(function (element) { - const tooltip = element.querySelector(".tooltip-text-metadata"); - const icon = element.querySelector("i"); - - // Helper to get scale factor from parent (default 1) - function getScaleFactor(el) { - let scale = 1; - let parent = el; - while (parent) { - const transform = window.getComputedStyle(parent).transform; - if (transform && transform !== "none") { - const match = transform.match( - /matrix\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+\)/ - ); - if (match) { - scale *= parseFloat(match[1]); - } - } - parent = parent.parentElement; - } - return scale; - } - - element.addEventListener("mouseenter", function () { - tooltip.style.display = "block"; - tooltip.style.visibility = "visible"; - tooltip.style.opacity = "1"; - tooltip.style.position = "absolute"; - // tooltip.style.zIndex = "9999"; - const rect = icon.getBoundingClientRect(); - const margin = 16; - - // Find the scale factor (if any) from the closest scaled parent - const scale = getScaleFactor(icon.parentElement); - console.info("Tooltip scale factor:", scale); - - // Adjust position for scale - //let left = rect.right * scale; - //let top = (rect.top + margin) * scale; - let width = 1; - let left = 16; - let top = 16; - tooltip.style.left = left + "px"; - tooltip.style.top = top + "px"; - tooltip.style.width = width + "px"; - }); - element.addEventListener("mouseleave", function () { - tooltip.style.display = "none"; - tooltip.style.visibility = "hidden"; - tooltip.style.opacity = "0"; - }); - }); + // document + // .querySelectorAll(".custom-tooltip-metadata") + // .forEach(function (element) { + // const tooltip = element.querySelector(".tooltip-text-metadata"); + // const icon = element.querySelector("i"); + + // // Helper to get scale factor from parent (default 1) + // function getScaleFactor(el) { + // let scale = 1; + // let parent = el; + // while (parent) { + // const transform = window.getComputedStyle(parent).transform; + // if (transform && transform !== "none") { + // const match = transform.match( + // /matrix\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+\)/ + // ); + // if (match) { + // scale *= parseFloat(match[1]); + // } + // } + // parent = parent.parentElement; + // } + // return scale; + // } + + // element.addEventListener("mouseenter", function () { + // tooltip.style.display = "block"; + // tooltip.style.visibility = "visible"; + // tooltip.style.opacity = "1"; + // tooltip.style.position = "absolute"; + // // tooltip.style.zIndex = "9999"; + // const rect = icon.getBoundingClientRect(); + // const margin = 16; + + // // Find the scale factor (if any) from the closest scaled parent + // const scale = getScaleFactor(icon.parentElement); + // console.info("Tooltip scale factor:", scale); + + // // Adjust position for scale + // //let left = rect.right * scale; + // //let top = (rect.top + margin) * scale; + // let width = 1; + // let left = 16; + // let top = 16; + // tooltip.style.left = left + "px"; + // tooltip.style.top = top + "px"; + // tooltip.style.width = width + "px"; + // }); + // element.addEventListener("mouseleave", function () { + // tooltip.style.display = "none"; + // tooltip.style.visibility = "hidden"; + // tooltip.style.opacity = "0"; + // }); + // }); // Initialize the state on page load window.onload = function () { From c44c86eef5317e950eb57d92956003714529133b Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Mon, 3 Nov 2025 12:53:04 +0100 Subject: [PATCH 17/22] #184 Merging of duplicate persons in tables with role consolidation --- meta_creator/hermes_process.py | 11 +++-- meta_creator/utils.py | 85 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 meta_creator/utils.py diff --git a/meta_creator/hermes_process.py b/meta_creator/hermes_process.py index 62ab48a..8d41b7e 100644 --- a/meta_creator/hermes_process.py +++ b/meta_creator/hermes_process.py @@ -3,6 +3,9 @@ import json import os from .token_handling_in_toml import update_token_to_toml, remove_token_from_toml +from .utils import merge_people_metadata + +# hermes_utils.py def run_hermes_commands(url, token=None): errors = [] @@ -121,7 +124,7 @@ def run_hermes_commands(url, token=None): # Authors author_info = hermes_metadata_dict.get('author', []) - authors_metadata = [ + authors_metadata = merge_people_metadata([ { "@type": "Person", "givenName": author.get("givenName", ""), @@ -129,11 +132,11 @@ def run_hermes_commands(url, token=None): "email": author.get("email", "") } for author in author_info - ] + ]) # Contributors contributor_info = hermes_metadata_dict.get('contributor', []) - contributors_metadata = [ + contributors_metadata = merge_people_metadata([ { "@type": "Person", "givenName": contributor.get("givenName", ""), @@ -141,7 +144,7 @@ def run_hermes_commands(url, token=None): "email": contributor.get("email", "") } for contributor in contributor_info - ] + ]) hermes_metadata_dict = { diff --git a/meta_creator/utils.py b/meta_creator/utils.py new file mode 100644 index 0000000..03d4ac3 --- /dev/null +++ b/meta_creator/utils.py @@ -0,0 +1,85 @@ +#utils.py + + +# hermes_utils.py + +def merge_people_metadata(people_list): + """ + Merge duplicate person entries (case-insensitive) based on givenName + familyName. + Collect all unique emails, but if all are the same, keep only one. + + Example: + Input: + [ + {"givenName": "Micheal", "familyName": "Jack", "email": "micheal@example.com"}, + {"givenName": "micheal", "familyName": "jack", "email": "MICHEAL@example.com"}, + {"givenName": "micheal", "familyName": "jack", "email": "micheal.jack@work.com"}, + {"givenName": "Jane", "familyName": "Smith", "email": ""} + ] + + Output: + [ + { + "@type": "Person", + "givenName": "Micheal", + "familyName": "jack", + "email": ["micheal@example.com", "micheal.jack@work.com"] + }, + { + "@type": "Person", + "givenName": "Jane", + "familyName": "Smith", + "email": "" + } + ] + """ + if not people_list: + return [] + + merged = {} + + for person in people_list: + given = person.get("givenName", "").strip().lower() + family = person.get("familyName", "").strip().lower() + email = person.get("email", "").strip().lower() + + if not given and not family: + # skip if no name info + continue + + key = (given, family) + + # Initialize entry if not present + if key not in merged: + merged[key] = { + "@type": "Person", + "givenName": person.get("givenName", "").strip().title(), + "familyName": person.get("familyName", "").strip().title(), + "emails": set() + } + + # Add email if present + if email: + merged[key]["emails"].add(email) + + # Format results + merged_people = [] + for person in merged.values(): + emails = list(person["emails"]) + if not emails: + email_field = "" + elif len(emails) == 1: + email_field = emails[0] + else: + # Check if all emails are identical ignoring case (e.g., same email written differently) + normalized = {e.lower() for e in emails} + email_field = emails[0] if len(normalized) == 1 else sorted(list(normalized)) + + merged_people.append({ + "@type": "Person", + "givenName": person["givenName"], + "familyName": person["familyName"], + "email": email_field + }) + + return merged_people From 5708ac5df93c083e21867bafcd563c95e6ae494c Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Wed, 5 Nov 2025 14:58:07 +0100 Subject: [PATCH 18/22] Issue 245 And issue 183 Solved --- static/foundation/js/vendor/table-utils.js | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 517310b..d2d9d29 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -267,6 +267,83 @@ export function setupTables() { return; } } + // 🧩 Duplicate check before adding new row + const existingRows = Array.from( + table.querySelectorAll("tbody tr") + ).filter( + (tr) => + !tr.classList.contains("add-row-controls") && tr.querySelector("td") + ); + console.log(existingRows); + + // Define which columns determine uniqueness + const columnsToCheck = ["identifier", "givenName", "familyName", "email"]; + + // Build new row data + const newRowData = {}; + inputs.forEach((input) => { + const col = input.getAttribute("data-col"); + if (columnsToCheck.includes(col)) { + newRowData[col] = (input.value || "").trim().toLowerCase(); + } + }); + + // Check tags (for email column, etc.) + const tagContainers = addRowControls.querySelectorAll( + ".add-row-tags-container" + ); + tagContainers.forEach((container) => { + const col = container.getAttribute("data-col"); + if (columnsToCheck.includes(col)) { + const tagEl = container.querySelector(".tag"); + if (tagEl) + newRowData[col] = (tagEl.dataset.tag || "").trim().toLowerCase(); + } + }); + + const keysToCompare = Object.keys(newRowData).filter( + (k) => newRowData[k] !== "" + ); + console.log(keysToCompare); + if (keysToCompare.length > 0) { + let isDuplicate = false; + + existingRows.forEach((row) => { + const cells = Array.from(row.querySelectorAll("td")); + let matches = 0; + + keysToCompare.forEach((col) => { + // Find the header index for this column + const headers = Array.from(table.querySelectorAll("thead th")).map( + (th) => th.getAttribute("data-col") + ); + console.log(headers); + const colIndex = headers.indexOf(col); + if (colIndex === -1) return; + + const cell = cells[colIndex]; + if (!cell) return; + + let cellValue = cell.textContent.trim().toLowerCase(); + const tagEl = cell.querySelector(".tag"); + console.log(cellValue); + console.log(newRowData[col]); + if (tagEl) + cellValue = (tagEl.dataset.tag || "").trim().toLowerCase(); + + if (cellValue === newRowData[col]) matches++; + }); + + if (matches === keysToCompare.length) { + isDuplicate = true; + } + }); + + if (isDuplicate) { + showToast("This row already exists in the table!", "error"); + return; // Stop adding the new row + } + } // Create new row const newRow = document.createElement("tr"); From cc267644cc6d77e2faf51be909f67e05a1bdc08c Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Tue, 25 Nov 2025 07:35:33 +0100 Subject: [PATCH 19/22] Download and invalid JSON output fixed --- meta_creator/templates/meta_creator/showdata.html | 4 ++-- static/foundation/js/vendor/table-utils.js | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/meta_creator/templates/meta_creator/showdata.html b/meta_creator/templates/meta_creator/showdata.html index 549e527..0dfa70f 100644 --- a/meta_creator/templates/meta_creator/showdata.html +++ b/meta_creator/templates/meta_creator/showdata.html @@ -325,7 +325,7 @@

Extra Hints!

Select - + @@ -396,9 +396,9 @@

Extra Hints!

{% endfor %} {% endif %} - +
+ +
- + {% for col, value in specific_types.items %} - {% for col, value in specific_types.items %} diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index d2d9d29..6a02f5c 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -43,12 +43,14 @@ export function updateTableHiddenInput(key) { // elements: all headers with data-coltype == 'element' const elements = headers - .filter((h) => h.coltype === "element") + .filter((h) => h.coltype === "element" && h.name) .map((h) => h.name); // subElements: all headers not 'delete' or 'element' const subElements = headers - .filter((h) => h.coltype !== "delete" && h.coltype !== "element") + .filter( + (h) => h.coltype !== "delete" && h.coltype !== "element" && h.name + ) .map((h) => h.name); // Find the table body @@ -300,7 +302,6 @@ export function setupTables() { newRowData[col] = (tagEl.dataset.tag || "").trim().toLowerCase(); } }); - const keysToCompare = Object.keys(newRowData).filter( (k) => newRowData[k] !== "" ); From 100c689e0e36ab7f548cff9b6b960c067b0106ea Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Wed, 10 Dec 2025 13:15:22 +0100 Subject: [PATCH 20/22] bugs fix --- .../templates/meta_creator/showdata.html | 14 +- static/foundation/js/vendor/table-utils.js | 258 +++++++++++++++--- static/foundation/js/vendor/tagging.js | 46 +++- 3 files changed, 268 insertions(+), 50 deletions(-) diff --git a/meta_creator/templates/meta_creator/showdata.html b/meta_creator/templates/meta_creator/showdata.html index 0dfa70f..5c13a26 100644 --- a/meta_creator/templates/meta_creator/showdata.html +++ b/meta_creator/templates/meta_creator/showdata.html @@ -284,6 +284,8 @@

Extra Hints!

{% if value == 'tagging' %} - + {% else %} {% endif %} @@ -410,7 +416,7 @@

Extra Hints!

SelectSelect {{ col|camel_to_spaces_lower }} @@ -360,7 +360,7 @@

Extra Hints!

{% for row in value %} {% if row|row_has_values:specific_types.keys %}
+ {% if value == "tagging" %} - + {% else %} {% endif %} diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 6a02f5c..f225b38 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -411,7 +411,7 @@ export function setupTables() { input.className = "tag-input"; input.type = "text"; input.style.display = "none"; - input.placeholder = "Add tag and press Enter"; + input.placeholder = "Add Email and press Enter"; td.appendChild(tagsList); td.appendChild(input); // Reset tags for next row @@ -689,18 +689,23 @@ export function setupTables() { document.querySelectorAll("table.auto-property").forEach(function (table) { table.addEventListener("click", function (e) { // Delete rows - if (e.target.classList.contains("delete-row-btn")) { - const row = e.target.closest("tr"); - if (row) { - row.remove(); - // Update the hidden input - const tableId = table.id; - if (tableId && tableId.endsWith("Table")) { - const key = tableId.replace(/Table$/, ""); - updateTableHiddenInput(key); - } - } - } + // if (e.target.classList.contains("delete-row-btn")) { + // const actionCell = e.target.closest(".action"); + // console.log(actionCell); + // const confirmBox = actionCell.querySelector(".delete-row"); + // confirmBox.style.display = ""; + // const row = e.target.closest("tr"); + + // if (row) { + // row.remove(); + // // Update the hidden input + // const tableId = table.id; + // if (tableId && tableId.endsWith("Table")) { + // const key = tableId.replace(/Table$/, ""); + // updateTableHiddenInput(key); + // } + // } + // } // Update other fields // Only allow editing on that is not the last column (delete icon) @@ -851,10 +856,121 @@ export function setupTables() { }); // ✅ Handle merge confirmation (YES button) + // document.querySelector(".mergeRow").addEventListener("click", function () { + // const selectedCheckboxes = document.querySelectorAll( + // ".checkbox-select:checked" + // ); + + // const firstRow = selectedCheckboxes[0].closest("tr"); + // const table = firstRow.closest("table"); + // const headers = Array.from(table.querySelectorAll("thead th")).map((th) => + // th.getAttribute("data-col") + // ); + + // const givenNameIdx = headers.indexOf("givenName"); + // const familyNameIdx = headers.indexOf("familyName"); + // const emailIdx = headers.indexOf("email"); + + // // 🔹 Extract data for all selected rows + // const selectedData = Array.from(selectedCheckboxes).map((checkbox) => { + // const row = checkbox.closest("tr"); + // const cells = row.querySelectorAll("td"); + + // const givenName = cells[givenNameIdx]?.textContent.trim() || ""; + // const familyName = cells[familyNameIdx]?.textContent.trim() || ""; + + // // Collect all emails (from tags if available) + // let emails = []; + // if (emailIdx !== -1) { + // const emailCell = cells[emailIdx]; + // const tags = emailCell.querySelectorAll(".tag"); + // if (tags.length > 0) { + // emails = Array.from(tags).map((t) => t.dataset.tag); + // } else if (emailCell.textContent.trim() !== "") { + // emails = [emailCell.textContent.trim()]; + // } + // } + + // return { row, givenName, familyName, emails }; + // }); + + // // 🔹 Group selected rows by Given Name + Family Name + // const grouped = {}; + // selectedData.forEach((item) => { + // const key = `${item.givenName.toLowerCase()}-${item.familyName.toLowerCase()}`; + // if (!grouped[key]) grouped[key] = []; + // grouped[key].push(item); + // }); + + // let merged = false; + + // // 🔹 Merge logic for rows with same Given + Family + // Object.values(grouped).forEach((group) => { + // if (group.length > 1) { + // merged = true; + // const mainRow = group[0].row; // keep first row + // const allEmails = [...new Set(group.flatMap((g) => g.emails))]; // merge + dedupe + + // // Update mainRow's email cell + // if (emailIdx !== -1) { + // const emailCell = mainRow.querySelectorAll("td")[emailIdx]; + // const tagsList = emailCell.querySelector(".tags-list"); + + // if (tagsList) { + // tagsList.innerHTML = ""; + // allEmails.forEach((email) => { + // const span = document.createElement("span"); + // span.className = "tag"; + // span.setAttribute("data-tag", email); + // span.innerHTML = + // email + + // ' ×'; + // tagsList.appendChild(span); + // }); + // } else { + // emailCell.textContent = allEmails.join(", "); + // } + // } + + // // Remove other duplicate rows + // group.slice(1).forEach((g) => g.row.remove()); + // } + // }); + + // // ✅ Update hidden input JSON + // if (table && typeof updateTableHiddenInput === "function") { + // const key = table.id.replace(/Table$/, ""); + // updateTableHiddenInput(key); + // } + + // // ✅ UI cleanup + // mergeRowConfirm.style.display = "none"; + // deleteIcon.style.display = "none"; + // mergeRowIcon.style.display = "none"; + // selectedCheckboxes.forEach((cb) => (cb.checked = false)); + // table + // .querySelectorAll("tr") + // .forEach((row) => row.classList.remove("table-secondary")); + + // if (merged) { + // showToast( + // "Rows with matching names have been merged successfully!", + // "success" + // ); + // } else { + // showToast( + // "No matching Given Name + Family Name found among selected rows.", + // "error" + // ); + // } + // }); document.querySelector(".mergeRow").addEventListener("click", function () { const selectedCheckboxes = document.querySelectorAll( ".checkbox-select:checked" ); + if (selectedCheckboxes.length === 0) return; const firstRow = selectedCheckboxes[0].closest("tr"); const table = firstRow.closest("table"); @@ -866,7 +982,7 @@ export function setupTables() { const familyNameIdx = headers.indexOf("familyName"); const emailIdx = headers.indexOf("email"); - // 🔹 Extract data for all selected rows + // 🔹 Extract data for all selected rows (including roles) const selectedData = Array.from(selectedCheckboxes).map((checkbox) => { const row = checkbox.closest("tr"); const cells = row.querySelectorAll("td"); @@ -874,11 +990,12 @@ export function setupTables() { const givenName = cells[givenNameIdx]?.textContent.trim() || ""; const familyName = cells[familyNameIdx]?.textContent.trim() || ""; - // Collect all emails (from tags if available) + // Extract emails let emails = []; if (emailIdx !== -1) { const emailCell = cells[emailIdx]; const tags = emailCell.querySelectorAll(".tag"); + if (tags.length > 0) { emails = Array.from(tags).map((t) => t.dataset.tag); } else if (emailCell.textContent.trim() !== "") { @@ -886,10 +1003,26 @@ export function setupTables() { } } - return { row, givenName, familyName, emails }; + // 🔹 Extract roles using data-role + const contributorChecked = + row.querySelector('[data-role="contributor"]')?.checked || false; + const authorChecked = + row.querySelector('[data-role="author"]')?.checked || false; + const maintainerChecked = + row.querySelector('[data-role="maintainer"]')?.checked || false; + + return { + row, + givenName, + familyName, + emails, + contributorChecked, + authorChecked, + maintainerChecked, + }; }); - // 🔹 Group selected rows by Given Name + Family Name + // 🔹 Group rows by (givenName + familyName) const grouped = {}; selectedData.forEach((item) => { const key = `${item.givenName.toLowerCase()}-${item.familyName.toLowerCase()}`; @@ -899,14 +1032,20 @@ export function setupTables() { let merged = false; - // 🔹 Merge logic for rows with same Given + Family + // 🔹 Merge logic Object.values(grouped).forEach((group) => { if (group.length > 1) { merged = true; - const mainRow = group[0].row; // keep first row - const allEmails = [...new Set(group.flatMap((g) => g.emails))]; // merge + dedupe - // Update mainRow's email cell + const mainRow = group[0].row; + const allEmails = [...new Set(group.flatMap((g) => g.emails))]; + + // 🔸 Aggregate roles (OR logic) + const groupContributor = group.some((g) => g.contributorChecked); + const groupAuthor = group.some((g) => g.authorChecked); + const groupMaintainer = group.some((g) => g.maintainerChecked); + + // 🔸 Update email cell in main row if (emailIdx !== -1) { const emailCell = mainRow.querySelectorAll("td")[emailIdx]; const tagsList = emailCell.querySelector(".tags-list"); @@ -916,12 +1055,8 @@ export function setupTables() { allEmails.forEach((email) => { const span = document.createElement("span"); span.className = "tag"; - span.setAttribute("data-tag", email); - span.innerHTML = - email + - ' ×'; + span.dataset.tag = email; + span.innerHTML = `${email} ×`; tagsList.appendChild(span); }); } else { @@ -929,36 +1064,45 @@ export function setupTables() { } } - // Remove other duplicate rows + // 🔸 Assign merged roles to the main row + const mainContributor = mainRow.querySelector( + '[data-role="contributor"]' + ); + if (mainContributor) mainContributor.checked = groupContributor; + + const mainAuthor = mainRow.querySelector('[data-role="author"]'); + if (mainAuthor) mainAuthor.checked = groupAuthor; + + const mainMaintainer = mainRow.querySelector( + '[data-role="maintainer"]' + ); + if (mainMaintainer) mainMaintainer.checked = groupMaintainer; + + // 🔸 Remove other rows in the group group.slice(1).forEach((g) => g.row.remove()); } }); - // ✅ Update hidden input JSON + // Update JSON if (table && typeof updateTableHiddenInput === "function") { const key = table.id.replace(/Table$/, ""); updateTableHiddenInput(key); } - // ✅ UI cleanup + // UI cleanup mergeRowConfirm.style.display = "none"; deleteIcon.style.display = "none"; mergeRowIcon.style.display = "none"; + selectedCheckboxes.forEach((cb) => (cb.checked = false)); table .querySelectorAll("tr") .forEach((row) => row.classList.remove("table-secondary")); if (merged) { - showToast( - "Rows with matching names have been merged successfully!", - "success" - ); + showToast("Rows merged successfully!", "success"); } else { - showToast( - "No matching Given Name + Family Name found among selected rows.", - "error" - ); + showToast("No rows with matching names found.", "error"); } }); @@ -1218,3 +1362,41 @@ function removeColumnFromTable(tableId, columnName) { } }); } +document.addEventListener("DOMContentLoaded", function () { + // === DELETE CONFIRMATION FOR COPYRIGHT HOLDER === + + const table = document.getElementById("copyrightHolderTable"); + const confirmBar = document.querySelector(".copyright_table .confirmBar"); + const yesBtn = document.querySelector(".copyright_table .yesBtn"); + const noBtn = document.querySelector(".copyright_table .noBtn"); + + if (!table || !confirmBar || !yesBtn || !noBtn) return; + + let rowToDelete = null; + + table.addEventListener("click", function (e) { + const icon = e.target.closest(".delete-row-btn"); + if (!icon) return; + + rowToDelete = icon.closest("tr"); + confirmBar.style.display = "inline-block"; + }); + + yesBtn.addEventListener("click", function () { + if (rowToDelete) { + rowToDelete.remove(); + + // Update hidden input + const key = table.id.replace(/Table$/, ""); + updateTableHiddenInput(key); + } + + confirmBar.style.display = "none"; + rowToDelete = null; + }); + + noBtn.addEventListener("click", function () { + confirmBar.style.display = "none"; + rowToDelete = null; + }); +}); diff --git a/static/foundation/js/vendor/tagging.js b/static/foundation/js/vendor/tagging.js index 7afd70b..62152c8 100644 --- a/static/foundation/js/vendor/tagging.js +++ b/static/foundation/js/vendor/tagging.js @@ -5,7 +5,7 @@ Highlights suggested input Provides reusable autocomplete logic for table cells and forms*/ import { getSchema } from "./schema-utils.js"; -import { validateInput } from "./ui.js"; +import { validateInput, showToast } from "./ui.js"; const SPDX_URL = "https://raw.githubusercontent.com/spdx/license-list-data/master/json/licenses.json"; const metadataJson = document.getElementById("metadata-json"); @@ -151,6 +151,7 @@ export function setupTagging({ // --- Position the suggestion box using getBoundingClientRect --- updateSuggestionsBoxPosition(input, suggestionsBox); suggestionsBox.style.display = "block"; + suggestionsBox.style.position = "fixed"; }); // 🔶 NEW: keyboard navigation for suggestions input.addEventListener("keydown", (e) => { @@ -174,6 +175,7 @@ export function setupTagging({ if (e.key === "Enter" && activeSuggestionIndex !== -1) { e.preventDefault(); items[activeSuggestionIndex].click(); + input.value = ""; enterHandledBySuggestion = true; setTimeout(() => input.focus(), 200); return; @@ -297,7 +299,6 @@ export function setupTagging({ // Add tag on pressing Enter key input.addEventListener("keydown", (e) => { - if (input.classList.contains("searchable-dropdown")) return; if (e.key === "Enter") { if (enterHandledBySuggestion) { enterHandledBySuggestion = false; @@ -306,11 +307,35 @@ export function setupTagging({ } const newTag = input.value.trim(); - + if (!newTag) return; + // Duplicate tag check + const isDuplicate = + taggingType === "tagging_object" + ? selectedTags.some((item) => item.identifier === newTag) + : selectedTags.includes(newTag); + + if (isDuplicate) { + e.preventDefault(); + showToast("Tag has been already added", "error"); + input.classList.add("invalid"); + setTimeout(() => input.classList.remove("invalid"), 1000); + input.value = ""; + return; + } if (useAutocomplete) { if (autocompleteSource.includes(newTag)) { e.preventDefault(); addTag(newTag); + } else { + e.preventDefault(); + showInvalidTagMessage( + container, + input, + "Please select a value from the list." + ); + input.classList.add("invalid"); + setTimeout(() => input.classList.remove("invalid"), 1000); + input.value = ""; } } else if (newTag) { e.preventDefault(); @@ -319,12 +344,17 @@ export function setupTagging({ setTimeout(() => input.focus(), 0); } }); + input.addEventListener("blur", () => { - const value = input.value.trim(); - if (value !== "") { - // Reuse Enter key handling - input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); - } + setTimeout(() => { + const value = input.value.trim(); + const suggestionsVisible = + suggestionsBox && suggestionsBox.style.display === "block"; + + if (value !== "" && !suggestionsVisible) { + input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); + } + }, 150); }); // Update hidden input and JSON From d641ff18a56e75b798f0295fa0b62bc36d4daf80 Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Thu, 11 Dec 2025 15:20:03 +0100 Subject: [PATCH 21/22] add email tag bug fixed --- static/foundation/css/foundation.css | 9 +- static/foundation/js/vendor/table-utils.js | 408 ++++++--------------- static/foundation/js/vendor/ui.js | 2 +- 3 files changed, 116 insertions(+), 303 deletions(-) diff --git a/static/foundation/css/foundation.css b/static/foundation/css/foundation.css index debb38d..4ae6dad 100644 --- a/static/foundation/css/foundation.css +++ b/static/foundation/css/foundation.css @@ -3,8 +3,12 @@ /******************** General ********************/ body { overflow: hidden; + caret-color: transparent; +} +input, +textarea { + caret-color: auto; /* or you can set a color like black */ } - [type="text"], [type="password"], [type="date"], @@ -586,7 +590,8 @@ div.error-container a:hover { top: 0; z-index: 10; } -#contributorTable td { +#contributorTable td, +#copyrightHolderTable td { border: 1px solid #ddddddbd; } #contributorTable th:last-child, diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index f225b38..91223d0 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -231,7 +231,7 @@ export function setupTables() { return; } - // ✅ Combination Validation + // Combination Validation const getInputVal = (col) => Array.from(inputs) .find((i) => i.getAttribute("data-col") === col) @@ -269,7 +269,7 @@ export function setupTables() { return; } } - // 🧩 Duplicate check before adding new row + // Duplicate check before adding new row const existingRows = Array.from( table.querySelectorAll("tbody tr") ).filter( @@ -489,193 +489,58 @@ export function setupTables() { const col = container.getAttribute("data-col"); addRowTags[col] = []; const input = container.querySelector(".add-row-tag-input"); - // const colType = container.getAttribute("data-coltype"); - // const dataType = container.getAttribute("data-type"); - // // --- Autocomplete setup --- - // let autocompleteSource = []; - // let suggestionsBox = createSuggestionsBox(container); - - // if (colType === "tagging_autocomplete") { - // getSchema().then((schema) => { - // autocompleteSource = - // schema["$defs"]?.[dataType]?.properties?.[col]?.items?.enum || []; - // }); - - // input.addEventListener("input", function () { - // const query = input.value.trim().toLowerCase(); - // suggestionsBox.innerHTML = ""; - // if (!query || autocompleteSource.length === 0) { - // suggestionsBox.style.display = "none"; - // return; - // } - // const selectedTags = addRowTags[col]; - // const filtered = autocompleteSource.filter( - // (tag) => - // tag.toLowerCase().startsWith(query) && !selectedTags.includes(tag) - // ); - // if (filtered.length === 0) { - // suggestionsBox.style.display = "none"; - // return; - // } - // filtered.forEach((tag) => { - // const div = document.createElement("div"); - // div.className = "suggestion-item"; - // div.textContent = tag; - // div.style.cursor = "pointer"; - // div.onclick = function () { - // input.value = tag; - // input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); - // suggestionsBox.style.display = "none"; - // }; - // suggestionsBox.appendChild(div); - // }); - // // Position suggestions below the input - // const inputRect = input.getBoundingClientRect(); - // updateSuggestionsBoxPosition(input, suggestionsBox); - // }); - - // input.addEventListener("focus", function () { - // suggestionsBox.innerHTML = ""; - // if (!autocompleteSource.length) { - // suggestionsBox.style.display = "none"; - // return; - // } - // const query = input.value.trim().toLowerCase(); - // const selectedTags = addRowTags[col]; - // const filtered = autocompleteSource.filter( - // (tag) => - // !selectedTags.includes(tag) && - // (query === "" || tag.toLowerCase().startsWith(query)) - // ); - // if (filtered.length === 0) { - // suggestionsBox.style.display = "none"; - // return; - // } - // filtered.forEach((tag) => { - // const div = document.createElement("div"); - // div.className = "suggestion-item"; - // div.textContent = tag; - // div.style.cursor = "pointer"; - // div.onclick = function () { - // input.value = tag; - // input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); - // suggestionsBox.style.display = "none"; - // }; - // suggestionsBox.appendChild(div); - // }); - // // Position suggestions below the input - // updateSuggestionsBoxPosition(input, suggestionsBox); - // suggestionsBox.style.display = "block"; - // }); - - // window.addEventListener( - // "scroll", - // () => updateSuggestionsBoxPosition(input, suggestionsBox), - // true - // ); - // window.addEventListener("resize", () => - // updateSuggestionsBoxPosition(input, suggestionsBox) - // ); - - // // Hide suggestions on blur/click outside - // input.addEventListener("blur", function () { - // setTimeout(() => { - // suggestionsBox.style.display = "none"; - // }, 200); - // }); - // } - // else if (colType === "dropdown") { - // getSchema().then((schema) => { - // const options = - // schema["$defs"]?.[dataType]?.properties?.[col]?.enum || []; - // const select = document.createElement("select"); - // select.className = "add-row-dropdown-select"; - // select.name = "selectElement"; - // select.setAttribute("data-col", col); - // select.setAttribute("data-type", dataType); - // select.setAttribute("data-coltype", "dropdown"); - // select.innerHTML = - // '' + - // options - // .map((opt) => ``) - // .join(""); - // // Replace the input with the select - // if (input) { - // input.style.display = "none"; - // } - // container.appendChild(select); - - // // On change, update addRowTags or values as needed - // select.addEventListener("change", function () { - // addRowTags[col] = [select.value]; - // console.log("Selected value:", select.value); - // }); - // }); - // } - // Add tag on Enter - input.addEventListener("keydown", function (e) { - if (e.key === "Enter" && input.value.trim() !== "") { - e.preventDefault(); - const tag = input.value.trim(); - // ✅ Check if this field is email column - if (col === "email") { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(tag)) { - // alert("Please enter a valid Email address."); - showToast("Please enter a valid Email address.", "error"); - input.value = ""; - return; - } - } + //add tag from current input value (with email validation support) + function addTagFromInput() { + const raw = input.value.trim(); + if (!raw) return; - // if (colType === "tagging_autocomplete") { - // if (autocompleteSource.includes(tag)) { - // if (!addRowTags[col].includes(tag)) { - // addRowTags[col].push(tag); - - // // Create tag element - // const span = document.createElement("span"); - // span.className = "tag"; - // span.setAttribute("data-tag", tag); - // span.innerHTML = - // tag + - // ' ×'; - // container.insertBefore(span, input); - // } - // input.value = ""; - // if (suggestionsBox) suggestionsBox.style.display = "none"; - // } else { - // showInvalidTagMessage( - // container, - // input, - // "Please select a value from the list." - // ); - // input.classList.add("invalid"); - // setTimeout(() => input.classList.remove("invalid"), 1000); - // input.value = ""; - // } - // } - // For plain tagging, just add the tag - if (!addRowTags[col].includes(tag)) { - addRowTags[col].push(tag); - const span = document.createElement("span"); - span.className = "tag"; - span.setAttribute("data-tag", tag); - span.innerHTML = - tag + ' ×'; - container.insertBefore(span, input); + const tag = raw; + + // If this is the email column, validate first + if (col === "email") { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(tag)) { + showToast("Please enter a valid Email address.", "error"); + input.value = ""; + return; } - input.value = ""; - // else { + } - // } + // Duplicate avoid + if (!addRowTags[col].includes(tag)) { + addRowTags[col].push(tag); + + const span = document.createElement("span"); + span.className = "tag"; + span.setAttribute("data-tag", tag); + span.innerHTML = + tag + ' ×'; + container.insertBefore(span, input); + showToast("Email has been added", "success"); + } else { + showToast("This email is already added", "error"); + } + + // Clear input after success + input.value = ""; + } + + // Add tag on Enter + input.addEventListener("keydown", function (e) { + if (e.key === "Enter") { + if (input.value.trim() === "") return; + e.preventDefault(); + addTagFromInput(); } }); - // Remove tag on click + // Add tag when input loses focus (blur) + input.addEventListener("blur", function () { + addTagFromInput(); + }); + + // Remove tag on click (same as before) container.addEventListener("click", function (e) { if (e.target.classList.contains("remove-tag")) { const tag = e.target.getAttribute("data-tag"); @@ -856,116 +721,6 @@ export function setupTables() { }); // ✅ Handle merge confirmation (YES button) - // document.querySelector(".mergeRow").addEventListener("click", function () { - // const selectedCheckboxes = document.querySelectorAll( - // ".checkbox-select:checked" - // ); - - // const firstRow = selectedCheckboxes[0].closest("tr"); - // const table = firstRow.closest("table"); - // const headers = Array.from(table.querySelectorAll("thead th")).map((th) => - // th.getAttribute("data-col") - // ); - - // const givenNameIdx = headers.indexOf("givenName"); - // const familyNameIdx = headers.indexOf("familyName"); - // const emailIdx = headers.indexOf("email"); - - // // 🔹 Extract data for all selected rows - // const selectedData = Array.from(selectedCheckboxes).map((checkbox) => { - // const row = checkbox.closest("tr"); - // const cells = row.querySelectorAll("td"); - - // const givenName = cells[givenNameIdx]?.textContent.trim() || ""; - // const familyName = cells[familyNameIdx]?.textContent.trim() || ""; - - // // Collect all emails (from tags if available) - // let emails = []; - // if (emailIdx !== -1) { - // const emailCell = cells[emailIdx]; - // const tags = emailCell.querySelectorAll(".tag"); - // if (tags.length > 0) { - // emails = Array.from(tags).map((t) => t.dataset.tag); - // } else if (emailCell.textContent.trim() !== "") { - // emails = [emailCell.textContent.trim()]; - // } - // } - - // return { row, givenName, familyName, emails }; - // }); - - // // 🔹 Group selected rows by Given Name + Family Name - // const grouped = {}; - // selectedData.forEach((item) => { - // const key = `${item.givenName.toLowerCase()}-${item.familyName.toLowerCase()}`; - // if (!grouped[key]) grouped[key] = []; - // grouped[key].push(item); - // }); - - // let merged = false; - - // // 🔹 Merge logic for rows with same Given + Family - // Object.values(grouped).forEach((group) => { - // if (group.length > 1) { - // merged = true; - // const mainRow = group[0].row; // keep first row - // const allEmails = [...new Set(group.flatMap((g) => g.emails))]; // merge + dedupe - - // // Update mainRow's email cell - // if (emailIdx !== -1) { - // const emailCell = mainRow.querySelectorAll("td")[emailIdx]; - // const tagsList = emailCell.querySelector(".tags-list"); - - // if (tagsList) { - // tagsList.innerHTML = ""; - // allEmails.forEach((email) => { - // const span = document.createElement("span"); - // span.className = "tag"; - // span.setAttribute("data-tag", email); - // span.innerHTML = - // email + - // ' ×'; - // tagsList.appendChild(span); - // }); - // } else { - // emailCell.textContent = allEmails.join(", "); - // } - // } - - // // Remove other duplicate rows - // group.slice(1).forEach((g) => g.row.remove()); - // } - // }); - - // // ✅ Update hidden input JSON - // if (table && typeof updateTableHiddenInput === "function") { - // const key = table.id.replace(/Table$/, ""); - // updateTableHiddenInput(key); - // } - - // // ✅ UI cleanup - // mergeRowConfirm.style.display = "none"; - // deleteIcon.style.display = "none"; - // mergeRowIcon.style.display = "none"; - // selectedCheckboxes.forEach((cb) => (cb.checked = false)); - // table - // .querySelectorAll("tr") - // .forEach((row) => row.classList.remove("table-secondary")); - - // if (merged) { - // showToast( - // "Rows with matching names have been merged successfully!", - // "success" - // ); - // } else { - // showToast( - // "No matching Given Name + Family Name found among selected rows.", - // "error" - // ); - // } - // }); document.querySelector(".mergeRow").addEventListener("click", function () { const selectedCheckboxes = document.querySelectorAll( ".checkbox-select:checked" @@ -981,8 +736,9 @@ export function setupTables() { const givenNameIdx = headers.indexOf("givenName"); const familyNameIdx = headers.indexOf("familyName"); const emailIdx = headers.indexOf("email"); + const identifierIdx = headers.indexOf("identifier"); - // 🔹 Extract data for all selected rows (including roles) + // 🔹 Extract data for all selected rows (including roles + identifier) const selectedData = Array.from(selectedCheckboxes).map((checkbox) => { const row = checkbox.closest("tr"); const cells = row.querySelectorAll("td"); @@ -990,7 +746,7 @@ export function setupTables() { const givenName = cells[givenNameIdx]?.textContent.trim() || ""; const familyName = cells[familyNameIdx]?.textContent.trim() || ""; - // Extract emails + // Emails let emails = []; if (emailIdx !== -1) { const emailCell = cells[emailIdx]; @@ -1003,7 +759,13 @@ export function setupTables() { } } - // 🔹 Extract roles using data-role + // Identifier (simple text based) + const identifier = + identifierIdx !== -1 + ? cells[identifierIdx]?.textContent.trim() || "" + : ""; + + // Roles by data-role const contributorChecked = row.querySelector('[data-role="contributor"]')?.checked || false; const authorChecked = @@ -1016,6 +778,7 @@ export function setupTables() { givenName, familyName, emails, + identifier, contributorChecked, authorChecked, maintainerChecked, @@ -1035,11 +798,40 @@ export function setupTables() { // 🔹 Merge logic Object.values(grouped).forEach((group) => { if (group.length > 1) { - merged = true; - const mainRow = group[0].row; const allEmails = [...new Set(group.flatMap((g) => g.emails))]; + // 🔸 Aggregate identifier + decide if we can merge + let mergedIdentifier = ""; + let canMergeThisGroup = true; + + if (identifierIdx !== -1) { + const identifiers = group + .map((g) => g.identifier && g.identifier.trim()) + .filter((id) => id); // non-empty only + + if (identifiers.length === 0) { + mergedIdentifier = ""; + } else { + const uniqueIds = [...new Set(identifiers)]; + + if (uniqueIds.length === 1) { + mergedIdentifier = uniqueIds[0]; + } else { + // multiple different non-empty identifiers → DON'T MERGE THIS GROUP + canMergeThisGroup = false; + } + } + } + + // Agar identifiers clash kar gaye → is group ko skip karo + if (!canMergeThisGroup) { + return; // no merged=true, no row removal, group as-is + } + + // Yahan tak pohanch gaye matlab merge allowed hai + merged = true; + // 🔸 Aggregate roles (OR logic) const groupContributor = group.some((g) => g.contributorChecked); const groupAuthor = group.some((g) => g.authorChecked); @@ -1064,7 +856,15 @@ export function setupTables() { } } - // 🔸 Assign merged roles to the main row + // Update identifier cell in main row (only if we got a non-empty one) + if (identifierIdx !== -1) { + const identifierCell = mainRow.querySelectorAll("td")[identifierIdx]; + if (mergedIdentifier) { + identifierCell.textContent = mergedIdentifier; + } + } + + // 🔸 Apply merged roles to the main row const mainContributor = mainRow.querySelector( '[data-role="contributor"]' ); @@ -1100,9 +900,16 @@ export function setupTables() { .forEach((row) => row.classList.remove("table-secondary")); if (merged) { - showToast("Rows merged successfully!", "success"); + showToast( + "Rows with matching names have been merged successfully!", + "success" + ); } else { - showToast("No rows with matching names found.", "error"); + // includes both: no name matches, or all blocked due to identifier mismatch + showToast( + "No rows were merged. Check Given/Family Name and Identifier values.", + "error" + ); } }); @@ -1389,6 +1196,7 @@ document.addEventListener("DOMContentLoaded", function () { // Update hidden input const key = table.id.replace(/Table$/, ""); updateTableHiddenInput(key); + showToast("Row has been deleted", "success"); } confirmBar.style.display = "none"; diff --git a/static/foundation/js/vendor/ui.js b/static/foundation/js/vendor/ui.js index c597e89..851bc89 100644 --- a/static/foundation/js/vendor/ui.js +++ b/static/foundation/js/vendor/ui.js @@ -436,5 +436,5 @@ export function showToast(message, type = "info") { setTimeout(() => { toast.style.opacity = "0"; setTimeout(() => toast.remove(), 500); - }, 3000); + }, 5000); } From aaecfb4c60e81b40919dfe550a29e3b2ce1f7184 Mon Sep 17 00:00:00 2001 From: Sundraiz-Shah Date: Mon, 12 Jan 2026 15:37:34 +0100 Subject: [PATCH 22/22] merging two newly added rows --- meta_creator/templates/meta_creator/showdata.html | 2 +- static/foundation/css/foundation.css | 1 + static/foundation/js/vendor/table-utils.js | 10 ++++------ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/meta_creator/templates/meta_creator/showdata.html b/meta_creator/templates/meta_creator/showdata.html index 5c13a26..f94fdf3 100644 --- a/meta_creator/templates/meta_creator/showdata.html +++ b/meta_creator/templates/meta_creator/showdata.html @@ -346,7 +346,7 @@

Extra Hints!

{% if unique_tab %} {% for col, value in metadata_dict.items %}
- + diff --git a/static/foundation/css/foundation.css b/static/foundation/css/foundation.css index 4ae6dad..2f7c809 100644 --- a/static/foundation/css/foundation.css +++ b/static/foundation/css/foundation.css @@ -1834,6 +1834,7 @@ div div p#contributor-explanation { font-size: 16px; font-weight: bold; text-align: left; + color: #055f82; } .collapsible-button:focus { background-color: #055f82; diff --git a/static/foundation/js/vendor/table-utils.js b/static/foundation/js/vendor/table-utils.js index 91223d0..05eacd1 100644 --- a/static/foundation/js/vendor/table-utils.js +++ b/static/foundation/js/vendor/table-utils.js @@ -380,14 +380,14 @@ export function setupTables() { const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.classList.add("checkbox-element"); - checkbox.setAttribute("data-role", col); - checkbox.name = `checkbox-${col}`; + checkbox.setAttribute("data-role", header); + checkbox.name = `checkbox-${header}`; // Set checked state based on add-row-controls checkbox if (checkboxInput && checkboxInput.checked) { checkbox.checked = true; } - td.setAttribute("data-col", col); + td.setAttribute("data-col", header); td.setAttribute("data-coltype", "element"); td.setAttribute("data-type", dataType); td.appendChild(checkbox); @@ -824,12 +824,10 @@ export function setupTables() { } } - // Agar identifiers clash kar gaye → is group ko skip karo if (!canMergeThisGroup) { - return; // no merged=true, no row removal, group as-is + return; } - // Yahan tak pohanch gaye matlab merge allowed hai merged = true; // 🔸 Aggregate roles (OR logic)