From d23b3a7cec07ec838c06d6e59a0c01ccecb02c3c Mon Sep 17 00:00:00 2001 From: Possommi Date: Mon, 18 Nov 2024 10:32:48 +0100 Subject: [PATCH] MIR-1361 Allow to enter ROR as affiliation for authors (#1074) * Display ror id as of https://ror.readme.io/docs/display * Added id attribute to ror input * Added ror-search.js * Enable search in ROR registry --- .../mir-layout/scss/common/mir_metadata.scss | 3 + mir-module/GruntFile.js | 1 + .../resources/editor/editor-includes.xed | 3 +- .../images/ror/ror-icon-rgb-transparent.svg | 11 ++ .../META-INF/resources/js/mir/ror-search.js | 148 ++++++++++++++++++ .../config/mir/messages_de.properties | 2 + .../config/mir/messages_en.properties | 2 + .../resources/xsl/editor/mods2xeditor.xsl | 6 + .../resources/xsl/editor/xeditor2mods.xsl | 4 + .../xsl/layout/mir-common-layout.xsl | 1 + .../src/main/resources/xsl/mir-mods-utils.xsl | 15 +- 11 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 mir-module/src/main/resources/META-INF/resources/images/ror/ror-icon-rgb-transparent.svg create mode 100644 mir-module/src/main/resources/META-INF/resources/js/mir/ror-search.js diff --git a/mir-layout/src/main/resources/META-INF/resources/mir-layout/scss/common/mir_metadata.scss b/mir-layout/src/main/resources/META-INF/resources/mir-layout/scss/common/mir_metadata.scss index e3ccd17e45..1496764e5a 100644 --- a/mir-layout/src/main/resources/META-INF/resources/mir-layout/scss/common/mir_metadata.scss +++ b/mir-layout/src/main/resources/META-INF/resources/mir-layout/scss/common/mir_metadata.scss @@ -269,6 +269,9 @@ $grayDark: #ECF0F1 !default; height:200px; } +.mir-ror-logo { + height: 20px; +} .mir-toc-sections { list-style-type: none; padding-left: 0; diff --git a/mir-module/GruntFile.js b/mir-module/GruntFile.js index 061f8be738..cea4ec2c9c 100644 --- a/mir-module/GruntFile.js +++ b/mir-module/GruntFile.js @@ -69,6 +69,7 @@ module.exports = function(grunt) { '<%= globalConfig.moduleDirectory %>/target/classes/META-INF/resources/js/mir/relatedItem-modal.min.js': '<%= globalConfig.moduleDirectory %>/src/main/resources/META-INF/resources/js/mir/relatedItem-modal.js', '<%= globalConfig.moduleDirectory %>/target/classes/META-INF/resources/js/mir/relatedItem-autocomplete.min.js': '<%= globalConfig.moduleDirectory %>/src/main/resources/META-INF/resources/js/mir/relatedItem-autocomplete.js', '<%= globalConfig.moduleDirectory %>/target/classes/META-INF/resources/js/mir/xeditor-form.min.js': '<%= globalConfig.moduleDirectory %>/src/main/resources/META-INF/resources/js/mir/xeditor-form.js', + '<%= globalConfig.moduleDirectory %>/target/classes/META-INF/resources/js/mir/ror-search.min.js': '<%= globalConfig.moduleDirectory %>/src/main/resources/META-INF/resources/js/mir/ror-search.js', '<%= globalConfig.moduleDirectory %>/target/classes/META-INF/resources/js/mir/openaire.min.js': '<%= globalConfig.moduleDirectory %>/src/main/resources/META-INF/resources/js/mir/openaire.js', '<%= globalConfig.moduleDirectory %>/target/classes/META-INF/resources/js/mir/classification-modal-select.min.js': '<%= globalConfig.moduleDirectory %>/src/main/resources/META-INF/resources/js/mir/classification-modal-select.js', '<%= globalConfig.moduleDirectory %>/target/classes/META-INF/resources/js/mir/geo-coords.min.js': '<%= globalConfig.moduleDirectory %>/src/main/resources/META-INF/resources/js/mir/geo-coords.js', diff --git a/mir-module/src/main/resources/META-INF/resources/editor/editor-includes.xed b/mir-module/src/main/resources/META-INF/resources/editor/editor-includes.xed index e23f338280..b2ce523933 100644 --- a/mir-module/src/main/resources/META-INF/resources/editor/editor-includes.xed +++ b/mir-module/src/main/resources/META-INF/resources/editor/editor-includes.xed @@ -677,7 +677,8 @@ - + + diff --git a/mir-module/src/main/resources/META-INF/resources/images/ror/ror-icon-rgb-transparent.svg b/mir-module/src/main/resources/META-INF/resources/images/ror/ror-icon-rgb-transparent.svg new file mode 100644 index 0000000000..480100185b --- /dev/null +++ b/mir-module/src/main/resources/META-INF/resources/images/ror/ror-icon-rgb-transparent.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/mir-module/src/main/resources/META-INF/resources/js/mir/ror-search.js b/mir-module/src/main/resources/META-INF/resources/js/mir/ror-search.js new file mode 100644 index 0000000000..aea95ed377 --- /dev/null +++ b/mir-module/src/main/resources/META-INF/resources/js/mir/ror-search.js @@ -0,0 +1,148 @@ +const RORSearch = { + baseURL: "https://api.ror.org/", + + search: async function (text) { + const q = await fetch(RORSearch.baseURL + "organizations?query=" + encodeURIComponent(text)); + let json = await q.json(); + + let rorIdentifiers = []; + + json.items.forEach((item) => { + item.label = item.name + " (" + item.id + ")"; + rorIdentifiers.push(item); + }); + + rorIdentifiers.sort((a, b) => { + if (a.label < b.label) { + return -1; + } + if (a.label > b.label) { + return 1; + } + return 0; + }); + return rorIdentifiers; + }, + + /** + * Find the option (if any) matching the given label. + * + * @param dataListId the id of the datalist + * @param label the label to find in the given datalist + * + * @return the option element with the givel label or null + * */ + getOption: function (dataListId, label) { + let dataList = document.getElementById(dataListId); + + for (const option of dataList.childNodes) { + if (option.value === label) { + return option; + } + } + + return null; + }, + + /** + * Searches ROR for the given text at https://api.ror.org/ . + * */ + onInput: async function (event) { + let input = event.target; + let dataListId = input.getAttribute("list"); + + let option = RORSearch.getOption(dataListId, input.value); + + // User selects value from list + if (option != null) { + input.value = option.getAttribute("data-ror"); + input.title = option.getAttribute("value") + event.preventDefault(); + return; + } + + // Selected value is not in list, actually execute search at ROR + let result = await RORSearch.search(input.value); + let dataList = document.getElementById(dataListId); + dataList.textContent = ""; + + // Update datalist with results from ROR + for (const suggestion of result) { + let option = document.createElement("option"); + option.setAttribute("value", suggestion.label); + option.setAttribute("data-ror", suggestion.id); + dataList.appendChild(option); + } + }, + + debounce: (f, wait) => { + let timeoutId = null; + + return (...args) => { + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + f.apply(null, args); + }, wait); + }; + }, + + /** + * Resolve ror links in xEditor and update title attributes of ror inputs. + * */ + updateXEditorInputTitle: async function () { + let rorInputs = document.querySelectorAll("[id='mir-ror-input']"); + for (const rorInput of rorInputs) { + if (rorInput.value.length == 0) { + continue; + } + + RORSearch.search(rorInput.value).then(data => { + if (data.length > 0) { + rorInput.setAttribute("title", data[0].name + " (" + data[0].id + ")"); + } + }); + } + }, + + /** + * Resolve ror links on metadata page. + * */ + resolve: async function () { + let rorLinks = document.querySelectorAll("a.mir-ror-link"); + for (const rorLink of rorLinks) { + RORSearch.search(rorLink.href).then(data => { + if (data.length > 0) { + if (data[0].name.length > 25) { + rorLink.innerHTML = data[0].name.substring(0, 25).trim() + " …"; + } else { + rorLink.innerHTML = data[0].name; + } + rorLink.setAttribute("title", data[0].name + " | " + data[0].id); + } + }); + } + } +}; + +/** + * Adds event handler to every ror input element. + * */ +document.addEventListener('DOMContentLoaded', function () { + const debouncedInputHandler = RORSearch.debounce(RORSearch.onInput, 500); + + for (const input of document.querySelectorAll('[id="mir-ror-input"]')) { + // create datalist element with random uuid as element id + let dataList = document.createElement("datalist"); + dataList.id = self.crypto.randomUUID(); + + // set the previously created datalist as list to the ror input element + input.setAttribute("list", dataList.id); + input.parentElement.append(dataList); + + // add the actual event handler + input.addEventListener('input', debouncedInputHandler); + } + + RORSearch.resolve(); + RORSearch.updateXEditorInputTitle(); +}); diff --git a/mir-module/src/main/resources/config/mir/messages_de.properties b/mir-module/src/main/resources/config/mir/messages_de.properties index 66b3b81b92..a513c6ddb1 100644 --- a/mir-module/src/main/resources/config/mir/messages_de.properties +++ b/mir-module/src/main/resources/config/mir/messages_de.properties @@ -239,6 +239,7 @@ mir.actions.noaccess = Um das Dokument zu bearbeiten, mir.actions.norights = Sie besitzen nicht die ben\u00F6tigten Rechte, um das Dokument zu bearbeiten. mir.admineditor = Bearbeiten im Admin-Editor mir.affiliation = Zugeh\u00F6rigkeit +mir.affiliation.ror = Zugeh\u00F6rigkeit (ROR ID) mir.articlenumber = Artikelnummer: mir.articlenumber.short = Art.-Nr.: mir.breadcrumb.home = Start @@ -384,6 +385,7 @@ mir.help.abstract.language = Geben Sie die Sprache an, in d mir.help.abstract.ortext = Eine kurze Zusammenfassung des Dokumentinhalts. mir.help.access = Geben Sie hier an, wer auf die angehangenen Dokumente zugreifen darf. mir.help.affiliation = Organisation bzw. Institution der betreffenden Person. +mir.help.affiliation.ror = ROR ID der Organisation bzw. Institution der betreffenden Person. mir.help.articlenumber = Die Nummer eines Artikels, der in einer Online-Zeitschrift oder auf einer Publikationsplattform erschienen ist. mir.help.artist.photographer = K\u00FCnstler des Werkes oder Fotograf des Bildes. mir.help.authorSpecification = Genauere Beschreibung der Autor(innen)-Rolle, meist relevant f\u00C3\u00BCr Kontaktaufnahme oder statistische Auswertungen. diff --git a/mir-module/src/main/resources/config/mir/messages_en.properties b/mir-module/src/main/resources/config/mir/messages_en.properties index 3e78c44add..5f34c0d38d 100644 --- a/mir-module/src/main/resources/config/mir/messages_en.properties +++ b/mir-module/src/main/resources/config/mir/messages_en.properties @@ -226,6 +226,7 @@ mir.actions.noaccess = To edit the document, please lo mir.actions.norights = You don't have the rights to edit the document. mir.admineditor = Edit in admin editor mir.affiliation = Affiliation +mir.affiliation.ror = Affiliation (ROR ID) mir.articlenumber = Article number: mir.articlenumber.short = Art.-No.: mir.breadcrumb.home = Home @@ -362,6 +363,7 @@ mir.help.abstract.aslink = Weblink to abstract. mir.help.abstract.ortext = A short summary of the document content. mir.help.access = Specify who should be able to access the dataset after publication. The metadata will be public in any case. mir.help.affiliation = Organisation/Affiliation of the Person. +mir.help.affiliation.ror = ROR ID of the Organisation/Affiliation of the Person. mir.help.articlenumber = The number of an article published in an online journal or publication platform. mir.help.artist.photographer = Creator of the Picture/Image. mir.help.author.interviewee = A person, family or organization responsible for creating or contributing to a resource by responding to an interviewer, usually a reporter, pollster, or some other information gathering agent. diff --git a/mir-module/src/main/resources/xsl/editor/mods2xeditor.xsl b/mir-module/src/main/resources/xsl/editor/mods2xeditor.xsl index 6194732fd3..66cd21d423 100644 --- a/mir-module/src/main/resources/xsl/editor/mods2xeditor.xsl +++ b/mir-module/src/main/resources/xsl/editor/mods2xeditor.xsl @@ -165,6 +165,12 @@ + + + + + + diff --git a/mir-module/src/main/resources/xsl/editor/xeditor2mods.xsl b/mir-module/src/main/resources/xsl/editor/xeditor2mods.xsl index f1aeec434f..5b3ca5e07f 100644 --- a/mir-module/src/main/resources/xsl/editor/xeditor2mods.xsl +++ b/mir-module/src/main/resources/xsl/editor/xeditor2mods.xsl @@ -155,6 +155,10 @@ + + + + diff --git a/mir-module/src/main/resources/xsl/layout/mir-common-layout.xsl b/mir-module/src/main/resources/xsl/layout/mir-common-layout.xsl index 668dda5e92..fa425e268e 100644 --- a/mir-module/src/main/resources/xsl/layout/mir-common-layout.xsl +++ b/mir-module/src/main/resources/xsl/layout/mir-common-layout.xsl @@ -299,6 +299,7 @@ +