diff --git a/cypress/integration/persons.js b/cypress/integration/persons.js
index f86426e..20f7799 100644
--- a/cypress/integration/persons.js
+++ b/cypress/integration/persons.js
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2020 The Software Heritage developers
+ * Copyright (C) 2020-2025 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
@@ -395,31 +395,62 @@ describe('Author order change', function() {
});
});
+ it('moves roles with person', function() {
+ cy.get('#name').type('My Test Software');
+
+ cy.get('#author_add').click();
+ cy.get('#author_1_givenName').type('Jane');
+
+ cy.get('#author_add').click();
+ cy.get('#author_2_givenName').type('John');
+
+ cy.get('#author_1_role_add').click();
+ cy.get('#author_1_roleName_0').type('Developer');
+ cy.get('#author_1_roleName_0').should('have.value', 'Developer');
+
+ // Move author 1 to the right (swap with author 2)
+ cy.get('#author_1_moveToRight').click();
+
+ // After the swap, Jane (and her role) should be at author_2
+ cy.get('#author_2_givenName').should('have.value', 'Jane');
+ cy.get('#author_2_roleName_0').should('have.value', 'Developer');
+
+ // John should now be at author_1 and should not have the role
+ cy.get('#author_1_givenName').should('have.value', 'John');
+ cy.get('#author_1_roleName_0').should('not.exist');
+ });
+
it('wraps around to the right', function() {
cy.get('#name').type('My Test Software');
cy.get('#author_add').click();
cy.get('#author_add').click();
cy.get('#author_add').click();
- cy.get('#author_1_givenName').type('Jane');
+ cy.get('#author_add').click();
+ cy.get('#author_1_givenName').type('One');
cy.get('#author_1_affiliation').type('Example Org');
- cy.get('#author_2_givenName').type('John');
- cy.get('#author_2_familyName').type('Doe');
- cy.get('#author_3_givenName').type('Alex');
+ cy.get('#author_2_givenName').type('Two');
+ cy.get('#author_2_familyName').type('Too');
+ cy.get('#author_3_givenName').type('Three');
+ cy.get('#author_4_givenName').type('Four');
cy.get('#author_1_moveToLeft').click()
- cy.get('#author_1_givenName').should('have.value', 'Alex');
- cy.get('#author_1_familyName').should('have.value', '');
+ cy.get('#author_1_givenName').should('have.value', 'Two');
+ cy.get('#author_1_familyName').should('have.value', 'Too');
cy.get('#author_1_affiliation').should('have.value', '');
- cy.get('#author_2_givenName').should('have.value', 'John');
- cy.get('#author_2_familyName').should('have.value', 'Doe');
+ cy.get('#author_2_givenName').should('have.value', 'Three');
+ cy.get('#author_2_familyName').should('have.value', '');
cy.get('#author_2_affiliation').should('have.value', '');
- cy.get('#author_3_givenName').should('have.value', 'Jane');
+ cy.get('#author_3_givenName').should('have.value', 'Four');
cy.get('#author_3_familyName').should('have.value', '');
- cy.get('#author_3_affiliation').should('have.value', 'Example Org');
+ cy.get('#author_3_affiliation').should('have.value', '');
+
+ cy.get('#author_4_givenName').should('have.value', 'One');
+ cy.get('#author_4_familyName').should('have.value', '');
+ cy.get('#author_4_affiliation').should('have.value', 'Example Org');
});
it('wraps around to the left', function() {
@@ -428,25 +459,31 @@ describe('Author order change', function() {
cy.get('#author_add').click();
cy.get('#author_add').click();
cy.get('#author_add').click();
- cy.get('#author_1_givenName').type('Jane');
+ cy.get('#author_add').click();
+ cy.get('#author_1_givenName').type('One');
cy.get('#author_1_affiliation').type('Example Org');
- cy.get('#author_2_givenName').type('John');
- cy.get('#author_2_familyName').type('Doe');
- cy.get('#author_3_givenName').type('Alex');
+ cy.get('#author_2_givenName').type('Two');
+ cy.get('#author_2_familyName').type('Too');
+ cy.get('#author_3_givenName').type('Three');
+ cy.get('#author_4_givenName').type('Four');
- cy.get('#author_3_moveToRight').click()
+ cy.get('#author_4_moveToRight').click()
- cy.get('#author_1_givenName').should('have.value', 'Alex');
+ cy.get('#author_1_givenName').should('have.value', 'Four');
cy.get('#author_1_familyName').should('have.value', '');
cy.get('#author_1_affiliation').should('have.value', '');
- cy.get('#author_2_givenName').should('have.value', 'John');
- cy.get('#author_2_familyName').should('have.value', 'Doe');
- cy.get('#author_2_affiliation').should('have.value', '');
+ cy.get('#author_2_givenName').should('have.value', 'One');
+ cy.get('#author_2_familyName').should('have.value', '');
+ cy.get('#author_2_affiliation').should('have.value', 'Example Org');
- cy.get('#author_3_givenName').should('have.value', 'Jane');
- cy.get('#author_3_familyName').should('have.value', '');
- cy.get('#author_3_affiliation').should('have.value', 'Example Org');
+ cy.get('#author_3_givenName').should('have.value', 'Two');
+ cy.get('#author_3_familyName').should('have.value', 'Too');
+ cy.get('#author_3_affiliation').should('have.value', '');
+
+ cy.get('#author_4_givenName').should('have.value', 'Three');
+ cy.get('#author_4_familyName').should('have.value', '');
+ cy.get('#author_4_affiliation').should('have.value', '');
});
});
@@ -889,6 +926,78 @@ describe('Multiple authors', function () {
cy.get('#author_1_endDate_0').should('have.value', '2024-04-03');
cy.get('#author_2_givenName').should('have.value', 'Joe');
});
+
+ it('can remove the first one and reindexes remaining ones', function() {
+ cy.get('#name').type('My Test Software');
+
+ cy.get('#author_add').click();
+ cy.get('#author_add').click();
+ cy.get('#author_nb').should('have.value', '2');
+
+ cy.get('#author_1_givenName').type('Alice');
+ cy.get('#author_2_givenName').type('Bob');
+
+ cy.get('#author_1_remove').click();
+
+ cy.get('#author_nb').should('have.value', '1');
+ cy.get('#author_1_givenName').should('have.value', 'Bob');
+
+ cy.get('#generateCodemetaV2').click();
+ cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
+ .should('deep.equal', {
+ "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
+ "type": "SoftwareSourceCode",
+ "name": "My Test Software",
+ "author": [
+ {
+ "id": "_:author_1",
+ "type": "Person",
+ "givenName": "Bob"
+ }
+ ],
+ });
+ });
+
+ it('can remove a middle one and reindexes remaining ones', function() {
+ cy.get('#name').type('My Test Software');
+
+ cy.get('#author_add').click();
+ cy.get('#author_add').click();
+ cy.get('#author_add').click();
+ cy.get('#author_nb').should('have.value', '3');
+
+ cy.get('#author_1_givenName').type('Alice');
+ cy.get('#author_2_givenName').type('Bob');
+ cy.get('#author_3_givenName').type('Carol');
+
+ cy.get('#author_2_remove').click();
+
+ cy.get('#author_nb').should('have.value', '2');
+ cy.get('#author_1_givenName').should('have.value', 'Alice');
+ cy.get('#author_2_givenName').should('have.value', 'Carol');
+
+ cy.get('#generateCodemetaV2').click();
+ cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
+ .should('deep.equal', {
+ "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
+ "type": "SoftwareSourceCode",
+ "name": "My Test Software",
+ "author": [
+ {
+ "id": "_:author_1",
+ "type": "Person",
+ "givenName": "Alice"
+ },
+ {
+ "id": "_:author_2",
+ "type": "Person",
+ "givenName": "Carol"
+ }
+ ],
+ });
+ });
+
+
});
describe('Contributors', function () {
diff --git a/index.html b/index.html
index acaa027..eaf98fd 100644
--- a/index.html
+++ b/index.html
@@ -372,6 +372,8 @@
CodeMeta Generator v3.0
+
+
diff --git a/js/codemeta_generation.js b/js/codemeta_generation.js
index b4e3c81..5f2e4b9 100644
--- a/js/codemeta_generation.js
+++ b/js/codemeta_generation.js
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2019-2020 The Software Heritage developers
+ * Copyright (C) 2019-2025 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
@@ -7,6 +7,8 @@
"use strict";
+let codemetaGenerationEnabled = true;
+
const CODEMETA_CONTEXTS = {
"2.0": {
path: "./data/contexts/codemeta-2.0.jsonld",
@@ -291,6 +293,12 @@ async function buildExpandedDocWithAllContexts() {
// v2.0 is still default version for generation, for now
async function generateCodemeta(codemetaVersion = "2.0") {
+ if (!codemetaGenerationEnabled) {
+ // Avoid regenerating a document while we are importing it.
+ // This avoid resetting the input in case there is an error in it.
+ return false;
+ }
+
var inputForm = document.querySelector('#inputForm');
var codemetaText, errorHTML;
@@ -427,68 +435,73 @@ async function recompactDocWithAllContexts(doc) {
}
async function importCodemeta() {
- var inputForm = document.querySelector('#inputForm');
- var doc = parseAndValidateCodemeta(false);
+ // Don't wipe the codemeta text (if any) in case of error
+ codemetaGenerationEnabled = false;
- // Re-compact document with all contexts
- // to allow importing property from any context
- doc = await recompactDocWithAllContexts(doc);
+ try {
+ var doc = parseAndValidateCodemeta(false);
+ // Re-compact document with all contexts
+ // to allow importing property from any context
+ doc = await recompactDocWithAllContexts(doc);
- resetForm();
+ resetForm();
- if (doc['license'] !== undefined) {
- if (typeof doc['license'] === 'string') {
- doc['license'] = [doc['license']];
+ if (doc['license'] !== undefined) {
+ if (typeof doc['license'] === 'string') {
+ doc['license'] = [doc['license']];
+ }
+
+ doc['license'].forEach(l => {
+ if (l.indexOf(SPDX_PREFIX) !== 0) { return; }
+ let licenseId = l.substring(SPDX_PREFIX.length);
+ insertLicenseElement(licenseId);
+ });
}
- doc['license'].forEach(l => {
- if (l.indexOf(SPDX_PREFIX) !== 0) { return; }
- let licenseId = l.substring(SPDX_PREFIX.length);
- insertLicenseElement(licenseId);
+ directCodemetaFields.forEach(function (item, index) {
+ setIfDefined('#' + item, doc[item]);
});
- }
-
- directCodemetaFields.forEach(function (item, index) {
- setIfDefined('#' + item, doc[item]);
- });
- importShortOrg('#funder', doc["funder"]);
- importReview(doc["review"]);
-
- // Import simple fields by joining on their separator
- splittedCodemetaFields.forEach(function (item, index) {
- const id = item[0];
- const separator = item[1];
- const serializer = item[2];
- const deserializer = item[3];
- let value = doc[id];
- if (value !== undefined) {
- if (Array.isArray(value)) {
- if (deserializer !== undefined) {
- value = value.map((item) => deserializer(id, item));
- }
- value = value.join(separator);
- } else {
- if (deserializer !== undefined) {
- value = deserializer(id, value);
+ importShortOrg('#funder', doc["funder"]);
+ importReview(doc["review"]);
+
+ // Import simple fields by joining on their separator
+ splittedCodemetaFields.forEach(function (item, index) {
+ const id = item[0];
+ const separator = item[1];
+ const deserializer = item[3];
+ let value = doc[id];
+ if (value !== undefined) {
+ if (Array.isArray(value)) {
+ if (deserializer !== undefined) {
+ value = value.map((item) => deserializer(id, item));
+ }
+ value = value.join(separator);
+ } else {
+ if (deserializer !== undefined) {
+ value = deserializer(id, value);
+ }
}
+ setIfDefined('#' + id, value);
}
- setIfDefined('#' + id, value);
- }
- });
-
- for (const [key, items] of Object.entries(crossCodemetaFields)) {
- let value = "";
- items.forEach(item => {
- value = doc[item] || value;
});
- setIfDefined(`#${key}`, value);
- }
- importPersons('author', 'Author', doc['author']);
- if (doc['contributor']) {
- // If only one contributor, it is compacted to an object
- const contributors = Array.isArray(doc['contributor'])? doc['contributor'] : [doc['contributor']];
- importPersons('contributor', 'Contributor', contributors);
+ for (const [key, items] of Object.entries(crossCodemetaFields)) {
+ let value = "";
+ items.forEach(item => {
+ value = doc[item] || value;
+ });
+ setIfDefined(`#${key}`, value);
+ }
+
+ importPersons('author', 'Author', doc['author']);
+ if (doc['contributor']) {
+ // If only one contributor, it is compacted to an object
+ const contributors = Array.isArray(doc['contributor']) ? doc['contributor'] : [doc['contributor']];
+ importPersons('contributor', 'Contributor', contributors);
+ }
+ } finally {
+ // Re-enable codemeta generation
+ codemetaGenerationEnabled = true;
}
}
diff --git a/js/dynamic_form.js b/js/dynamic_form.js
index 8e79b4f..6d507ca 100644
--- a/js/dynamic_form.js
+++ b/js/dynamic_form.js
@@ -18,8 +18,7 @@ const personFields = [
function createPersonFieldset(personPrefix, legend) {
// Creates a fieldset containing inputs for informations about a person
- var fieldset = document.createElement("fieldset")
- var moveButtons;
+ const fieldset = document.createElement("fieldset")
fieldset.classList.add("person");
fieldset.classList.add("leafFieldset");
fieldset.id = personPrefix;
@@ -29,6 +28,8 @@ function createPersonFieldset(personPrefix, legend) {
+
@@ -65,94 +66,183 @@ function createPersonFieldset(personPrefix, legend) {
}
function addPersonWithId(container, prefix, legend, id) {
- var personPrefix = `${prefix}_${id}`;
- var fieldset = createPersonFieldset(personPrefix, `${legend} #${id}`);
+ const personPrefix = `${prefix}_${id}`;
+ const fieldset = createPersonFieldset(personPrefix, legend);
container.appendChild(fieldset);
- document.querySelector(`#${personPrefix}_moveToLeft`)
- .addEventListener('click', () => movePerson(prefix, id, "left"));
- document.querySelector(`#${personPrefix}_moveToRight`)
- .addEventListener('click', () => movePerson(prefix, id, "right"));
- document.querySelector(`#${personPrefix}_role_add`)
- .addEventListener('click', () => addRole(personPrefix));
+ // Use ID selector to attach handlers that compute the current fieldset
+ // ID at click time, so renaming the IDs (when renumbering persons)
+ // won't break the handlers.
+ fieldset.querySelector('[id$="_moveToLeft"]')
+ .addEventListener('click', () => movePerson(prefix, fieldset.id, 'left'));
+ fieldset.querySelector('[id$="_moveToRight"]')
+ .addEventListener('click', () => movePerson(prefix, fieldset.id, 'right'));
+ fieldset.querySelector('[id$="_remove"]')
+ .addEventListener('click', () => removePerson(prefix, fieldset.id));
+ fieldset.querySelector('[id$="_role_add"]')
+ .addEventListener('click', () => addRole(fieldset.id));
}
-function movePerson(prefix, id1, direction) {
- var nbPersons = getNbPersons(prefix);
- var id2;
+function movePerson(prefix, personPrefix, direction) {
+ const container = document.getElementById(`${prefix}_list`);
+ if (!container) return;
- // Computer id2, the id of the person to flip id1 with (wraps around the
- // end of the list of persons)
- if (direction == "left") {
- id2 = id1 - 1;
- if (id2 <= 0) {
- id2 = nbPersons;
+ const persons = Array.from(container.querySelectorAll('.person'));
+ const len = persons.length;
+ const currentIndex = persons.findIndex(function (p) { return p.id === personPrefix; });
+ if (currentIndex === -1 || len <= 1) return;
+
+ const currentElement = document.getElementById(personPrefix);
+
+ function swapAdjacent(a, b) {
+ const parent = a && a.parentNode;
+ if (!parent) return;
+ if (a.nextElementSibling === b) {
+ parent.insertBefore(b, a);
+ } else if (b.nextElementSibling === a) {
+ parent.insertBefore(a, b);
}
}
- else {
- id2 = id1 + 1;
- if (id2 > nbPersons) {
- id2 = 1;
+
+ const prev = currentElement.previousElementSibling;
+ const next = currentElement.nextElementSibling;
+
+ if (direction === 'left') {
+ if (!prev) {
+ // Current is first element -> move to end (wrap-around)
+ container.appendChild(currentElement);
+ } else {
+ // Swap with previous element
+ swapAdjacent(prev, currentElement);
+ }
+ } else {
+ if (!next) {
+ // Current is last element -> move to beginning (wrap-around)
+ const firstEl = container.firstElementChild;
+ if (firstEl) container.insertBefore(currentElement, firstEl);
+ } else {
+ // Swap with next element
+ swapAdjacent(currentElement, next);
}
}
- // Flip the field values, one by one
- personFields.forEach((fieldName) => {
- var field1 = document.querySelector(`#${prefix}_${id1}_${fieldName}`);
- var field2 = document.querySelector(`#${prefix}_${id2}_${fieldName}`);
- var value1 = field1.value;
- var value2 = field2.value;
- field2.value = value1;
- field1.value = value2;
- });
-
- // Form was changed; regenerate
+ renumberPersons(prefix);
generateCodemeta();
}
function addPerson(prefix, legend) {
- var container = document.querySelector(`#${prefix}_container`);
- var personId = getNbPersons(prefix) + 1;
+ const container = document.getElementById(`${prefix}_list`);
+ const personId = getNbPersons(prefix) + 1;
addPersonWithId(container, prefix, legend, personId);
+ renumberPersons(prefix);
+ return personId;
+}
- setNbPersons(prefix, personId);
+function removePerson(prefix, personPrefix) {
+ const container = document.getElementById(`${prefix}_list`);
+ if (!container) return;
+
+ if (personPrefix) {
+ const fs = document.getElementById(personPrefix);
+ if (!fs) return;
+ fs.remove();
+ } else {
+ // If no personPrefix is provided, remove the last person
+ const last = container.lastElementChild;
+ if (!last) return;
+ last.remove();
+ }
- return personId;
+ renumberPersons(prefix);
+ generateCodemeta();
}
-function removePerson(prefix) {
- var personId = getNbPersons(prefix);
+function renumberPersons(prefix) {
+ // Assume id pattern of "prefix_index_suffix"
+ function idSuffix(id) {
+ if (!id) return '';
+ const parts = id.split('_');
+ if (parts.length <= 2) return '';
+ return parts.slice(2).join('_');
+ }
- document.querySelector(`#${prefix}_${personId}`).remove();
+ const container = document.getElementById(`${prefix}_list`);
+ if (!container) return;
- setNbPersons(prefix, personId - 1);
+ const persons = Array.from(container.querySelectorAll('.person'));
+ for (let i = 0; i < persons.length; i++) {
+ const fs = persons[i];
+ const n = i + 1;
+ const newPersonPrefix = `${prefix}_${n}`;
+
+ fs.id = newPersonPrefix;
+
+ const legend = fs.querySelector('legend');
+ if (legend) {
+ let base = legend.textContent.split('#')[0].trim();
+ if (base === '') base = legend.textContent;
+ legend.textContent = base + ' #' + n;
+ }
+
+ // Update descendant ids and names
+ Array.from(fs.querySelectorAll('[id]')).forEach(function (el) {
+ const oldId = el.id;
+ const suffix = idSuffix(oldId);
+
+ el.id = `${newPersonPrefix}_${suffix}`;
+
+ if (el.name) {
+ const nameSuffix = idSuffix(el.name);
+ el.name = `${newPersonPrefix}_${nameSuffix}`;
+ }
+ });
+
+ // Update label 'for' attributes
+ Array.from(fs.querySelectorAll('label[for]')).forEach(function (label) {
+ if (!label.htmlFor) return;
+ const forSuffix = idSuffix(label.htmlFor);
+ label.htmlFor = `${newPersonPrefix}_${forSuffix}`;
+ });
+ }
+
+ setNbPersons(prefix, persons.length);
}
// Initialize a group of persons (authors, contributors) on page load.
// Useful if the page is reloaded.
function initPersons(prefix, legend) {
- var nbPersons = getNbPersons(prefix);
- var personContainer = document.querySelector(`#${prefix}_container`)
+ const container = document.getElementById(`${prefix}_list`);
+ if (!container) return;
+
+ // If there are already persons, do not add new ones,
+ // renumber them if needed.
+ const existing = Array.from(container.querySelectorAll('.person'));
+ if (existing.length > 0) {
+ renumberPersons(prefix);
+ return;
+ }
- for (let personId = 1; personId <= nbPersons; personId++) {
- addPersonWithId(personContainer, prefix, legend, personId);
+ // If no persons, add empty ones
+ const nbPersons = getNbPersons(prefix);
+ for (let i = 0; i < nbPersons; i++) {
+ addPerson(prefix, legend);
}
}
function removePersons(prefix) {
- var nbPersons = getNbPersons(prefix);
- var personContainer = document.querySelector(`#${prefix}_container`)
+ const container = document.getElementById(`${prefix}_list`);
+ if (!container) return;
- for (let personId = 1; personId <= nbPersons; personId++) {
- removePerson(prefix)
- }
+ container.innerHTML = '';
+ setNbPersons(prefix, 0);
+ generateCodemeta();
}
function addRole(personPrefix) {
- const roleButtonGroup = document.querySelector(`#${personPrefix}_role_add`);
- const roleIndexNode = document.querySelector(`#${personPrefix}_role_index`);
+ const roleButtonGroup = document.getElementById(`${personPrefix}_role_add`);
+ const roleIndexNode = document.getElementById(`${personPrefix}_role_index`);
const roleIndex = parseInt(roleIndexNode.value, 10);
const ul = document.createElement("ul")
@@ -171,8 +261,11 @@ function addRole(personPrefix) {
`;
roleButtonGroup.after(ul);
- document.querySelector(`#${personPrefix}_role_remove_${roleIndex}`)
- .addEventListener('click', () => removeRole(personPrefix, roleIndex));
+ document.getElementById(`${personPrefix}_role_remove_${roleIndex}`)
+ .addEventListener('click', (e) => {
+ const pid = e.currentTarget.closest?.('.person')?.id;
+ removeRole(pid, roleIndex);
+ });
roleIndexNode.value = roleIndex + 1;
@@ -180,7 +273,8 @@ function addRole(personPrefix) {
}
function removeRole(personPrefix, roleIndex) {
- document.querySelector(`#${personPrefix}_role_${roleIndex}`).remove();
+ document.getElementById(`${personPrefix}_role_${roleIndex}`).remove();
+ generateCodemeta();
}
function resetForm() {
@@ -191,7 +285,7 @@ function resetForm() {
// Reset the form after deleting elements, so nbPersons doesn't get
// reset before it's read.
- document.querySelector('#inputForm').reset();
+ document.getElementById('inputForm').reset();
}
function fieldToLower(event) {
@@ -207,41 +301,41 @@ function initCallbacks() {
// In Firefox datalist selection without Enter press does not trigger
// 'change' event, so we need to listen to 'input' event to catch
// a selection with mouse click.
- document.querySelector('#license')
+ document.getElementById('license')
.addEventListener('input', validateLicense);
- document.querySelector('#license')
+ document.getElementById('license')
.addEventListener('change', validateLicense);
// Safari needs 'keydown' to catch Enter press when datalist is shown
- document.querySelector('#license')
+ document.getElementById('license')
.addEventListener('keydown', validateLicense);
- document.querySelector('#generateCodemetaV2').disabled = false;
- document.querySelector('#generateCodemetaV2')
+ document.getElementById('generateCodemetaV2').disabled = false;
+ document.getElementById('generateCodemetaV2')
.addEventListener('click', () => generateCodemeta("2.0"));
- document.querySelector('#generateCodemetaV3').disabled = false;
- document.querySelector('#generateCodemetaV3')
+ document.getElementById('generateCodemetaV3').disabled = false;
+ document.getElementById('generateCodemetaV3')
.addEventListener('click', () => generateCodemeta("3.0"));
- document.querySelector('#resetForm')
+ document.getElementById('resetForm')
.addEventListener('click', resetForm);
- document.querySelector('#validateCodemeta').disabled = false;
- document.querySelector('#validateCodemeta')
+ document.getElementById('validateCodemeta').disabled = false;
+ document.getElementById('validateCodemeta')
.addEventListener('click', () => parseAndValidateCodemeta(true));
- document.querySelector('#importCodemeta').disabled = false;
- document.querySelector('#importCodemeta')
+ document.getElementById('importCodemeta').disabled = false;
+ document.getElementById('importCodemeta')
.addEventListener('click', importCodemeta);
document.querySelector('#downloadCodemeta input').disabled = false;
document.querySelector('#downloadCodemeta input')
.addEventListener('click', downloadCodemeta);
- document.querySelector('#inputForm')
+ document.getElementById('inputForm')
.addEventListener('change', () => generateCodemeta());
- document.querySelector('#developmentStatus')
+ document.getElementById('developmentStatus')
.addEventListener('change', fieldToLower);
initPersons('author', 'Author');
diff --git a/js/fields_data.js b/js/fields_data.js
index 0d67894..8828fdb 100644
--- a/js/fields_data.js
+++ b/js/fields_data.js
@@ -65,7 +65,7 @@ function validateLicense(e) {
'insertReplacementText', // from datalist selection
]);
if (!(e.inputType && CONFIRM_INPUT_TYPES.has(e.inputType))) {
- // Typing characters, pasting, deletions - don't proceed
+ // Typing characters, pasting, deletions - don't proceed
return;
}
} else {
@@ -75,7 +75,7 @@ function validateLicense(e) {
// Correct casing to the canonical SPDX license ID when possible.
// This will allow user to type in any casing and hit Enter once
- // to insert the license immediately.
+ // to insert the license immediately.
const match = SPDX_LICENSE_IDS.find(id =>
id.toLowerCase() === license.toLowerCase());
if (match) {
diff --git a/js/validation/index.js b/js/validation/index.js
index 815a3b7..3519145 100644
--- a/js/validation/index.js
+++ b/js/validation/index.js
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2020 The Software Heritage developers
+ * Copyright (C) 2020-2025 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
@@ -8,7 +8,7 @@
/*
* Reads a CodeMeta file and shows human-friendly errors on it.
*
- * This validator intentionaly does not use a schema, in order to show errors
+ * This validator intentionally does not use a schema, in order to show errors
* that are easy to understand for users with no understanding of JSON-LD.
*/
diff --git a/main.css b/main.css
index e4cfaab..42abb3b 100644
--- a/main.css
+++ b/main.css
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2019 The Software Heritage developers
+ * Copyright (C) 2019-2025 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
@@ -51,6 +51,7 @@ p input, p textarea {
width: 100%;
display: flex;
justify-content: space-between;
+ align-items: center;
}
#license {