Most fields are optional. Mandatory fields will be highlighted when generating Codemeta.
codemeta.json:
diff --git a/cypress/integration/persons.js b/cypress/integration/persons.js index d32016b..a296afc 100644 --- a/cypress/integration/persons.js +++ b/cypress/integration/persons.js @@ -1,264 +1,424 @@ /** * Copyright (C) 2020 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 */ /* * Tests the author/contributor dynamic fieldsets */ "use strict"; describe('Zero author', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#generateCodemeta').click(); cy.get('#errorMessage').should('have.text', ''); 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", }); }); it('can be imported from no list', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '0'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('not.exist'); }); it('can be imported from empty list', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", "author": [], })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '0'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('not.exist'); }); }); describe('One full author', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#author_nb').should('have.value', '0'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('not.exist'); cy.get('#author_1_givenName').should('not.exist'); cy.get('#author_add').click(); cy.get('#author_nb').should('have.value', '1'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('exist'); cy.get('#author_2').should('not.exist'); cy.get('#author_1_givenName').should('have.value', ''); cy.get('#author_1_familyName').should('have.value', ''); cy.get('#author_1_email').should('have.value', ''); cy.get('#author_1_id').should('have.value', ''); cy.get('#author_1_affiliation').should('have.value', ''); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_familyName').type('Doe'); cy.get('#author_1_email').type('jdoe@example.org'); cy.get('#author_1_id').type('http://example.org/~jdoe'); cy.get('#author_1_affiliation').type('http://example.org/'); cy.get('#generateCodemeta').click(); cy.get('#errorMessage').should('have.text', ''); 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": [ { "@type": "Person", "@id": "http://example.org/~jdoe", "givenName": "Jane", "familyName": "Doe", "email": "jdoe@example.org", "affiliation": { "@type": "Organization", "@id": "http://example.org/", } } ], }); }); it('can be imported', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "@type": "Person", "@id": "http://example.org/~jdoe", "givenName": "Jane", "familyName": "Doe", "email": "jdoe@example.org", "affiliation": { "@type": "Organization", "@id": "http://example.org/", } } ], })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '1'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('exist'); cy.get('#author_2').should('not.exist'); cy.get('#author_1_givenName').should('have.value', 'Jane'); cy.get('#author_1_familyName').should('have.value', 'Doe'); cy.get('#author_1_email').should('have.value', 'jdoe@example.org'); cy.get('#author_1_id').should('have.value', 'http://example.org/~jdoe'); cy.get('#author_1_affiliation').should('have.value', 'http://example.org/'); }); }); describe('Affiliation id', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#author_add').click(); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_affiliation').type('http://example.org/'); cy.get('#generateCodemeta').click(); cy.get('#errorMessage').should('have.text', ''); 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": [ { "@type": "Person", "givenName": "Jane", "affiliation": { "@type": "Organization", "@id": "http://example.org/", } } ], }); }); it('can be imported', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "@type": "Person", "@id": "http://example.org/~jdoe", "givenName": "Jane", "familyName": "Doe", "email": "jdoe@example.org", "affiliation": { "@type": "Organization", "@id": "http://example.org/", } } ], })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '1'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('exist'); cy.get('#author_2').should('not.exist'); cy.get('#author_1_affiliation').should('have.value', 'http://example.org/'); }); }); describe('Affiliation name', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#author_add').click(); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_affiliation').type('Example Org'); cy.get('#generateCodemeta').click(); cy.get('#errorMessage').should('have.text', ''); 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": [ { "@type": "Person", "givenName": "Jane", "affiliation": { "@type": "Organization", "name": "Example Org", } } ], }); }); it('can be imported', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "@type": "Person", "@id": "http://example.org/~jdoe", "givenName": "Jane", "familyName": "Doe", "email": "jdoe@example.org", "affiliation": { "@type": "Organization", "name": "Example Org", } } ], })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '1'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('exist'); cy.get('#author_2').should('not.exist'); cy.get('#author_1_affiliation').should('have.value', 'Example Org'); }); }); + +describe('Author order change', function() { + it('is a noop with a single author', function() { + cy.get('#name').type('My Test Software'); + + cy.get('#author_add').click(); + cy.get('#author_1_givenName').type('Jane'); + cy.get('#author_1_affiliation').type('Example Org'); + + cy.get('#author_1_moveToRight').click() + + cy.get('#author_1_givenName').should('have.value', 'Jane'); + cy.get('#author_1_affiliation').should('have.value', 'Example Org'); + + cy.get('#author_1_moveToLeft').click() + + cy.get('#author_1_givenName').should('have.value', 'Jane'); + cy.get('#author_1_affiliation').should('have.value', 'Example Org'); + }); + + it('flips two authors', 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_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_1_moveToRight').click() + + cy.get('#author_1_givenName').should('have.value', 'John'); + cy.get('#author_1_familyName').should('have.value', 'Doe'); + cy.get('#author_1_affiliation').should('have.value', ''); + + cy.get('#author_2_givenName').should('have.value', 'Jane'); + 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', 'Alex'); + cy.get('#author_3_familyName').should('have.value', ''); + cy.get('#author_3_affiliation').should('have.value', ''); + }); + + it('updates generated Codemeta', function() { + cy.get('#name').type('My Test Software'); + + cy.get('#author_add').click(); + cy.get('#author_add').click(); + cy.get('#author_1_givenName').type('Jane'); + cy.get('#author_1_affiliation').type('Example Org'); + cy.get('#author_2_givenName').type('John'); + cy.get('#author_2_familyName').type('Doe'); + + cy.get('#generateCodemeta').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": [ + { + "@type": "Person", + "givenName": "Jane", + "affiliation": { + "@type": "Organization", + "name": "Example Org", + } + }, + { + "@type": "Person", + "givenName": "John", + "familyName": "Doe", + }, + ], + }); + + cy.get('#author_1_moveToRight').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": [ + { + "@type": "Person", + "givenName": "John", + "familyName": "Doe", + }, + { + "@type": "Person", + "givenName": "Jane", + "affiliation": { + "@type": "Organization", + "name": "Example Org", + } + }, + ], + }); + }); + + 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_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_1_moveToLeft').click() + + cy.get('#author_1_givenName').should('have.value', 'Alex'); + 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_3_givenName').should('have.value', 'Jane'); + cy.get('#author_3_familyName').should('have.value', ''); + cy.get('#author_3_affiliation').should('have.value', 'Example Org'); + }); + + it('wraps around to the left', 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_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_3_moveToRight').click() + + cy.get('#author_1_givenName').should('have.value', 'Alex'); + 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_3_givenName').should('have.value', 'Jane'); + cy.get('#author_3_familyName').should('have.value', ''); + cy.get('#author_3_affiliation').should('have.value', 'Example Org'); + }); +}) diff --git a/index.html b/index.html index 691f4ec..38f7cbe 100644 --- a/index.html +++ b/index.html @@ -1,357 +1,355 @@
Most fields are optional. Mandatory fields will be highlighted when generating Codemeta.
codemeta.json:
`; return fieldset; } function addPersonWithId(container, prefix, legend, id) { - var fieldset = createPersonFieldset(`${prefix}_${id}`, `${legend} #${id}`); + var personPrefix = `${prefix}_${id}`; + var fieldset = createPersonFieldset(personPrefix, `${legend} #${id}`); container.appendChild(fieldset); + + document.querySelector(`#${personPrefix}_moveToLeft`) + .addEventListener('click', () => movePerson(prefix, id, "left")); + document.querySelector(`#${personPrefix}_moveToRight`) + .addEventListener('click', () => movePerson(prefix, id, "right")); +} + +function movePerson(prefix, id1, direction) { + var nbPersons = getNbPersons(prefix); + var id2; + + // 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; + } + } + else { + id2 = id1 + 1; + if (id2 > nbPersons) { + id2 = 1; + } + } + + // 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 + generateCodemeta(); } function addPerson(prefix, legend) { var container = document.querySelector(`#${prefix}_container`); var personId = getNbPersons(prefix) + 1; addPersonWithId(container, prefix, legend, personId); setNbPersons(prefix, personId); return personId; } function removePerson(prefix) { var personId = getNbPersons(prefix); document.querySelector(`#${prefix}_${personId}`).remove(); setNbPersons(prefix, personId-1); } // 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`) for (let personId = 1; personId <= nbPersons; personId++) { addPersonWithId(personContainer, prefix, legend, personId); } } function removePersons(prefix) { var nbPersons = getNbPersons(prefix); var personContainer = document.querySelector(`#${prefix}_container`) for (let personId = 1; personId <= nbPersons; personId++) { removePerson(prefix) } } function resetForm() { removePersons('author'); removePersons('contributor'); // Reset the form after deleting elements, so nbPersons doesn't get // reset before it's read. document.querySelector('#inputForm').reset(); } function fieldToLower(event) { event.target.value = event.target.value.toLowerCase(); } function initCallbacks() { document.querySelector('#license') .addEventListener('change', validateLicense); document.querySelector('#generateCodemeta') .addEventListener('click', generateCodemeta); document.querySelector('#resetForm') .addEventListener('click', resetForm); document.querySelector('#validateCodemeta') .addEventListener('click', () => parseAndValidateCodemeta(true)); document.querySelector('#importCodemeta') .addEventListener('click', importCodemeta); document.querySelector('#inputForm') .addEventListener('change', generateCodemeta); document.querySelector('#developmentStatus') .addEventListener('change', fieldToLower); initPersons('author', 'Author'); initPersons('contributor', 'Contributor'); } diff --git a/main.css b/main.css index 3cdd50f..dfa7c62 100644 --- a/main.css +++ b/main.css @@ -1,72 +1,78 @@ /** * Copyright (C) 2019 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 */ /* This file contains the main CSS to make the form/application usable, * without being especially pretty. */ #noscriptError { color: red; } .person { display: inline-block; } #inputForm { max-width: 100%; display: flex; flex-wrap: wrap; } /* A fieldset that contains only label/input pairs */ .leafFieldset { flex: auto; } p input, p textarea { width: 100%; box-sizing: border-box; } .dynamicFields { width: 100%; } +.dynamicFields .moveButtons { + width: 100%; + display: flex; + justify-content: space-between; +} + #license { /* License names are long */ min-width: 20em; } #funding { /* Funding names are long */ min-width: 20em; } input[type=URL] { /* URLs are longer than the other fields */ min-width: 20em; } .field-description { color : rgb(100, 104, 103); font-size: small; } #codemetaText { width: 100%; min-height: 10em; border: 1px solid black; } #errorMessage { color: red; } input:invalid { color: red; }