Skip to content

Commit a6658b1

Browse files
Merge pull request #285 from NFDI4Energy/CFF-source-not-displayed-in-the-persons-table
281-Author metadata extracted from CFF source Bug
2 parents 91e7f6d + 9572723 commit a6658b1

5 files changed

Lines changed: 157 additions & 75 deletions

File tree

meta_creator/hermes_process.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def run_hermes_commands(url, token=None):
164164
"keywords": hermes_metadata_dict.get('keywords', ''),
165165
"downloadUrl": hermes_metadata_dict.get('downloadUrl', ''),
166166
"readme": hermes_metadata_dict.get('readme', ''),
167-
"version": hermes_metadata_dict.get('version', ''),
167+
"softwareVersion": hermes_metadata_dict.get('version', ''),
168168
"citation": hermes_metadata_dict.get('citation', ''),
169169
"author": authors_metadata,
170170
"contributor": contributors_metadata

meta_creator/templates/meta_creator/showdata.html

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ <h1>Extra Hints!</h1>
370370
{% elif forloop.first or not unique_tab %}
371371
<!-- small_table -->
372372
<div class="long_field copyright_table">
373-
{% with specific_types=type_metadata|get_array:key default_type=value.0|get:"@type"%}
373+
{% with specific_types=type_metadata|get_array:key default_type=value.0|get:"@type"|default:"Person"%}
374374
{% if not unique_tab %}
375375

376376
<div class="action" >
@@ -417,7 +417,7 @@ <h1>Extra Hints!</h1>
417417
{% if key == "contributor" %}
418418
<div class="scrollable">
419419
{% endif %}
420-
<table class="auto-property table table-responsive table-hover table-sortable" id="{{ key }}Table" data-at-type={{default_type}} unique-tab={{unique_tab}}>
420+
<table class="auto-property table table-responsive table-hover table-sortable" id="{{ key }}Table" data-at-type="{{ default_type }}" unique-tab={{unique_tab}}>
421421
<thead>
422422
<tr>
423423
<th class="text-center" data-col="" style="width: fit-content;">Select</th>
@@ -452,15 +452,15 @@ <h1>Extra Hints!</h1>
452452
</tr>
453453
</thead>
454454
<tbody>
455-
{% for row in value %}
455+
{% for row in value|get_rows:metadata_dict %}
456456
{% if row|row_has_values:specific_types.keys %}
457457
<tr>
458458
<td class="text-center" data-col="select" data-coltype="select">
459459
<input type="checkbox" class="checkbox-select" data-role="select" name="checkbox-select">
460460
</td>
461461
{% for col, value in specific_types.items %}
462462
{% if value == 'tagging' or value == 'tagging_autocomplete' or value == 'dropdown' %}
463-
<td class="table-tagging-cell" data-type={{default_type}} data-coltype="{{ value }}" data-col="{{ col }}" data-row="{{ forloop.parentloop.counter0 }}">
463+
<td class="table-tagging-cell" data-type="{{ default_type }}" data-coltype="{{ value }}" data-col="{{ col }}" data-row="{{ forloop.parentloop.counter0 }}">
464464
<div class="tags-list">
465465
{% for tag in row|get:col %}
466466
<span class="tag" data-tag="{{ tag }}">{{ tag }} <span class="remove-tag" data-tag="{{ tag }}">×</span></span>
@@ -479,15 +479,7 @@ <h1>Extra Hints!</h1>
479479
{% if unique_tab %}
480480
{% for col, value in metadata_dict.items %}
481481
<td data-col="{{ col }}" data-coltype="element" class="text-center">
482-
{% if col == key %}
483-
<input type="checkbox" class="checkbox-element" data-role="{{ col }}" name="checkbox-{{ col }}" checked>
484-
{% elif col == "author" %}
485-
<input type="checkbox" class="checkbox-element" data-role="{{ col }}" name="checkbox-{{ col }}" {% if metadata_dict|check_author:contributor %}checked{% endif %}>
486-
{% elif col == "maintainer" %}
487-
<input type="checkbox" class="checkbox-element" data-role="{{ col }}" name="checkbox-{{ col }}" {% if metadata_dict|check_maintainer:contributor %}checked{% endif %}>
488-
{% else %}
489-
<input type="checkbox" class="checkbox-element" data-role="{{ col }}" name="checkbox-{{ col }}">
490-
{% endif %}
482+
<input type="checkbox" class="checkbox-element" data-role="{{ col }}" name="checkbox-{{ col }}" {% if value|check_person_in_list:row %}checked{% endif %}>
491483
</td>
492484
{% endfor %}
493485
{% endif %}
@@ -502,16 +494,16 @@ <h1>Extra Hints!</h1>
502494
<td class="disabled"></td>
503495
{% for col, value in specific_types.items %}
504496
{% if value == 'tagging' or value == 'tagging_autocomplete' %}
505-
<td class="tags-table-container add-row-tags-container" data-type={{default_type}} data-coltype="{{ value }}" data-col="{{ col }}">
497+
<td class="tags-table-container add-row-tags-container" data-type="{{ default_type }}" data-coltype="{{ value }}" data-col="{{ col }}">
506498
<!-- Tags will be inserted here by JS -->
507499
{% if value == "tagging" %}
508-
<input type="text" class="add-row-tag-input" data-type={{default_type}} data-coltype="{{ value }}" data-col="{{ col }}" placeholder="Add {{col|camel_to_spaces_lower}} and press Enter" />
500+
<input type="text" class="add-row-tag-input" data-type="{{ default_type }}" data-coltype="{{ value }}" data-col="{{ col }}" placeholder="Add {{col|camel_to_spaces_lower}} and press Enter" />
509501
{% else %}
510-
<input type="text" class="add-row-tag-input" data-type={{default_type}} data-coltype="{{ value }}" data-col="{{ col }}" placeholder="Type and select from list for {{col|camel_to_spaces_lower}}" />
502+
<input type="text" class="add-row-tag-input" data-type="{{ default_type }}" data-coltype="{{ value }}" data-col="{{ col }}" placeholder="Type and select from list for {{col|camel_to_spaces_lower}}" />
511503
{% endif %}
512504
</td>
513505
{% elif value == 'dropdown' %}
514-
<td class="tags-table-container add-row-tags-container" data-type={{default_type}} data-coltype="{{ value }}" data-col="{{ col }}">
506+
<td class="tags-table-container add-row-tags-container" data-type="{{ default_type }}" data-coltype="{{ value }}" data-col="{{ col }}">
515507
<select class="add-row-dropdown-select"
516508
data-type="{{ default_type }}"
517509
data-coltype="{{ value }}"
@@ -521,8 +513,8 @@ <h1>Extra Hints!</h1>
521513
</select>
522514
</td>
523515
{% else %}
524-
<td class="tags-table-container" data-type={{default_type}} data-coltype="{{ value }}" data-col="{{ col }}">
525-
<input type="text" class="add-row-input" data-type={{default_type}} data-coltype="{{ value }}" data-col="{{ col }}" placeholder="{{ col|camel_to_spaces_lower }}"/>
516+
<td class="tags-table-container" data-type="{{ default_type }}" data-coltype="{{ value }}" data-col="{{ col }}">
517+
<input type="text" class="add-row-input" data-type="{{ default_type }}" data-coltype="{{ value }}" data-col="{{ col }}" placeholder="{{ col|camel_to_spaces_lower }}"/>
526518
</td>
527519
{% endif %}
528520
{% endfor %}

meta_creator/templatetags/custom_filters.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,73 @@ def row_has_values(row, columns):
8383
return True
8484
return False
8585

86+
def _persons_are_same(p1, p2):
87+
"""Return True if two person dicts represent the same individual."""
88+
if not isinstance(p1, dict) or not isinstance(p2, dict):
89+
return False
90+
91+
def first_email(e):
92+
if isinstance(e, list):
93+
return e[0].strip().lower() if e else ""
94+
return (e or "").strip().lower()
95+
96+
e1, e2 = first_email(p1.get("email")), first_email(p2.get("email"))
97+
if e1 and e2 and e1 == e2:
98+
return True
99+
100+
g1 = (p1.get("givenName") or "").strip().lower()
101+
f1 = (p1.get("familyName") or "").strip().lower()
102+
g2 = (p2.get("givenName") or "").strip().lower()
103+
f2 = (p2.get("familyName") or "").strip().lower()
104+
return bool(g1 or f1) and g1 == g2 and f1 == f2
105+
106+
107+
@register.filter
108+
def check_person_in_list(person_list, person):
109+
"""
110+
Return True if person (dict) matches any entry in person_list.
111+
Used in the unique-tab person table to determine role checkbox state.
112+
"""
113+
if not isinstance(person_list, list) or not isinstance(person, dict):
114+
return False
115+
return any(_persons_are_same(person, p) for p in person_list)
116+
117+
118+
@register.filter
119+
def get_rows(value, metadata_dict):
120+
"""
121+
Return a deduplicated list of all persons from contributor, author, and
122+
maintainer when the tab contains all three role lists (i.e. RelatedPersons).
123+
For all other tabs the original value is returned unchanged.
124+
125+
Used as {{ value|get_rows:metadata_dict }} on the table row iteration so
126+
that persons who only appear in 'author' or 'maintainer' (e.g. from a CFF
127+
source) are also shown in the unified person table.
128+
"""
129+
if not isinstance(metadata_dict, dict):
130+
return value
131+
132+
role_keys = {"contributor", "author", "maintainer"}
133+
if not role_keys.issubset(set(metadata_dict.keys())):
134+
return value # Not the person tab — use the field's own list unchanged
135+
136+
result = []
137+
for role_key in ("contributor", "author", "maintainer"):
138+
for person in metadata_dict.get(role_key, []):
139+
if not isinstance(person, dict):
140+
continue
141+
given = (person.get("givenName") or "").strip()
142+
family = (person.get("familyName") or "").strip()
143+
if not given and not family:
144+
continue
145+
if not any(_persons_are_same(person, p) for p in result):
146+
result.append(person)
147+
148+
# If nothing found (all lists have only empty placeholders), fall back so
149+
# the add-row row still renders correctly
150+
return result if result else value
151+
152+
86153
@register.filter
87154
def all_types_same(type_metadata, metadata_dict):
88155
"""

static/foundation/css/foundation.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,9 @@ div.error-container a:hover {
661661
top: 0;
662662
z-index: 10;
663663
}
664+
#contributorTable thead th:hover {
665+
z-index: 20;
666+
}
664667
#contributorTable td,
665668
#copyrightHolderTable td {
666669
border: 1px solid #ddddddbd;
@@ -1099,6 +1102,39 @@ label > .fa-info-circle {
10991102
opacity: 1;
11001103
}
11011104

1105+
1106+
#contributorTable thead th .custom-tooltip-metadata:hover .tooltip-text-metadata {
1107+
visibility: hidden !important;
1108+
opacity: 0 !important;
1109+
}
1110+
1111+
#contributor-th-fixed-tooltip {
1112+
position: fixed;
1113+
z-index: 99999;
1114+
visibility: hidden;
1115+
opacity: 0;
1116+
font-size: 70%;
1117+
min-width: 165px;
1118+
max-width: 260px;
1119+
background-color: #5e5b5b;
1120+
color: #fff;
1121+
text-align: left;
1122+
border-radius: 6px;
1123+
padding: 5px;
1124+
pointer-events: none;
1125+
transition: opacity 0.2s;
1126+
word-break: break-word;
1127+
white-space: normal;
1128+
box-sizing: border-box;
1129+
overflow-wrap: break-word;
1130+
font-weight: 400;
1131+
}
1132+
1133+
#contributor-th-fixed-tooltip.active {
1134+
visibility: visible;
1135+
opacity: 1;
1136+
}
1137+
11021138
.custom-tooltip .tooltip-text {
11031139
visibility: hidden;
11041140
font-size: 65%;

static/foundation/js/vendor/ui.js

Lines changed: 42 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -93,61 +93,48 @@ export function setupUI() {
9393
});
9494
});
9595

96-
// custom tooltips
97-
// document
98-
// .querySelectorAll(".custom-tooltip-metadata")
99-
// .forEach(function (element) {
100-
// const tooltip = element.querySelector(".tooltip-text-metadata");
101-
// const icon = element.querySelector("i");
102-
103-
// // Helper to get scale factor from parent (default 1)
104-
// function getScaleFactor(el) {
105-
// let scale = 1;
106-
// let parent = el;
107-
// while (parent) {
108-
// const transform = window.getComputedStyle(parent).transform;
109-
// if (transform && transform !== "none") {
110-
// const match = transform.match(
111-
// /matrix\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+\)/
112-
// );
113-
// if (match) {
114-
// scale *= parseFloat(match[1]);
115-
// }
116-
// }
117-
// parent = parent.parentElement;
118-
// }
119-
// return scale;
120-
// }
121-
122-
// element.addEventListener("mouseenter", function () {
123-
// tooltip.style.display = "block";
124-
// tooltip.style.visibility = "visible";
125-
// tooltip.style.opacity = "1";
126-
// tooltip.style.position = "absolute";
127-
// // tooltip.style.zIndex = "9999";
128-
// const rect = icon.getBoundingClientRect();
129-
// const margin = 16;
130-
131-
// // Find the scale factor (if any) from the closest scaled parent
132-
// const scale = getScaleFactor(icon.parentElement);
133-
// console.info("Tooltip scale factor:", scale);
134-
135-
// // Adjust position for scale
136-
// //let left = rect.right * scale;
137-
// //let top = (rect.top + margin) * scale;
138-
// let width = 1;
139-
// let left = 16;
140-
// let top = 16;
141-
// tooltip.style.left = left + "px";
142-
// tooltip.style.top = top + "px";
143-
// tooltip.style.width = width + "px";
144-
// });
145-
// element.addEventListener("mouseleave", function () {
146-
// tooltip.style.display = "none";
147-
// tooltip.style.visibility = "hidden";
148-
// tooltip.style.opacity = "0";
149-
// });
150-
// });
96+
97+
(function () {
98+
const tip = document.createElement("div");
99+
tip.id = "contributor-th-fixed-tooltip";
100+
document.body.appendChild(tip);
101+
102+
document
103+
.querySelectorAll(
104+
"#contributorTable thead th .custom-tooltip-metadata"
105+
)
106+
.forEach(function (trigger) {
107+
const span = trigger.querySelector(".tooltip-text-metadata");
108+
if (!span) return;
109+
const text = span.textContent.trim();
110+
if (!text) return;
111+
112+
trigger.addEventListener("mouseenter", function () {
113+
tip.textContent = text;
114+
115+
const icon = trigger.querySelector("i") || trigger;
116+
const r = icon.getBoundingClientRect();
117+
118+
// Start to the right of the icon, vertically centred
119+
tip.style.left = r.right + 8 + "px";
120+
tip.style.top = r.top + r.height / 2 + "px";
121+
tip.style.transform = "translateY(-50%)";
122+
tip.classList.add("active");
123+
124+
// After render: flip left if the tooltip clips the right viewport edge
125+
requestAnimationFrame(function () {
126+
const tr = tip.getBoundingClientRect();
127+
if (tr.right > window.innerWidth - 8) {
128+
tip.style.left = r.left - tr.width - 8 + "px";
129+
}
130+
});
131+
});
132+
133+
trigger.addEventListener("mouseleave", function () {
134+
tip.classList.remove("active");
135+
});
136+
});
137+
})();
151138

152139
// Initialize the state on page load
153140
window.onload = function () {

0 commit comments

Comments
 (0)