diff --git a/.gitignore b/.gitignore index d5f19d8..948d314 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +.idea +cypress/screenshots node_modules package-lock.json diff --git a/README.md b/README.md index 1dbc665..8093a47 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,103 @@ # Codemeta Generator This repository contains a (client-side) web application to generate CodeMeta documents (aka. `codemeta.json`). The [CodeMeta initiative](https://github.com/codemeta/codemeta) is a Free and Open Source academic collaboration creating a minimal metadata schema for research software and code. The academic community recommands on adding a codemeta.json file in the root directory of your repository. With this linked data metadata file, you can easily declare the authorship, include contextual information and link to other research outputs (publications, data, etc.). Also, the `codemeta.json` file in your source code is indexed in the Software Heritage (SWH) archive, which will improve findability in searches. ### References - [SWH guidelines](https://www.softwareheritage.org/save-and-reference-research-software/) for research software. - [SWH blog post](https://www.softwareheritage.org/2019/05/28/mining-software-metadata-for-80-m-projects-and-even-more/) about metadata indexation. - [Dan S. Katz's blog post](https://danielskatzblog.wordpress.com/2017/09/25/software-heritage-and-repository-metadata-a-software-citation-solution/) about including metadata in your repository. - FORCE11's Software Citation Implementation WG [repository](https://github.com/force11/force11-sciwg) - RDA & FORCE11's joint Software Source Code Identification WG [repository](https://github.com/force11/force11-rda-scidwg) ## Specifications ### Use case 1. create a complete codemeta.json file from scratch 2. aggregate existing information and add complementary information to a codemeta.json file ### Functionalities - helpers while completing the form, for example a reference list of spdx licenses - a validation mechanism after submission - the possibility to use all the codeMeta terms and schema.org terms - accessible from multiple platforms (web browsers or OS) - (extra) the possibility to correct the output after validation as part of the creation process This tool was initially prepared for the [FORCE19 Hackathon](https://github.com/force11/force11-rda-scidwg/tree/master/hackathon/FORCE2019). ## Code contributions. This section only applies to developers who want to contribute to the Codemeta Generator. If you only want to use it, you can use [the hosted version](https://codemeta.github.io/codemeta-generator/) instead. ### Code guidelines This application is designed to work on popular modern browsers (Firefox, Chromium/Google Chrome, Edge, Safari). Check [Caniuse](https://caniuse.com/) for availability of features for these browsers. To keep the architecture simple, we serve javascript files directly to browsers, without a compiler or transpiler; and do not use third-party dependencies for now. ### Running local changes To run Codemeta Generator, you just need an HTTP server serving the files (nginx, apache2, etc.). The simplest way is probably to use Python's HTTP server: ``` git clone https://github.com/codemeta/codemeta-generator cd codemeta-generator python3 -m http.server ``` then open [http://localhost:8000/](http://localhost:8000/) in your web browser. ### Automatic testing In addition to manual testing, we have automated tests to check for bugs quickly, using [Cypress](https://www.cypress.io/). To run them, first install Cypress: ``` sudo apt install npm # or the equivalent on your system -npm install cypress -$(npm bin)/cypress install +npx cypress@9.7.0 install ``` Then, run the tests: ``` -$(npm bin)/cypress run +npx cypress@9.7.0 run ``` ## Contributed by ![Image description](https://annex.softwareheritage.org/public/logo/software-heritage-logo-title-motto.svg) diff --git a/cypress/integration/basics.js b/cypress/integration/basics.js index 3fbc7f6..3dafb40 100644 --- a/cypress/integration/basics.js +++ b/cypress/integration/basics.js @@ -1,243 +1,243 @@ /** * 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 basic features of the application. */ "use strict"; describe('JSON Generation', function() { beforeEach(function() { /* Clear the session storage, as it is used to restore field data; * and we don't want a test to load data from the previous test. */ cy.window().then((win) => { win.sessionStorage.clear() }) cy.visit('./index.html'); }); it('works just from the software name', 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", + "type": "SoftwareSourceCode", "name": "My Test Software", }); }); it('works just from all main fields when using only one license', function() { cy.get('#name').type('My Test Software'); cy.get('#description').type('This is a\ngreat piece of software'); cy.get('#dateCreated').type('2019-10-02'); cy.get('#datePublished').type('2020-01-01'); cy.get('#license').type('AGPL-3.0'); cy.get("#license").type('{enter}'); cy.get('#generateCodemeta').click(); cy.get("#license").should('have.value', ''); 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", + "type": "SoftwareSourceCode", "license": "https://spdx.org/licenses/AGPL-3.0", "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", }); }); it('works just from all main fields when using multiple licenses', function() { cy.get('#name').type('My Test Software'); cy.get('#description').type('This is a\ngreat piece of software'); cy.get('#dateCreated').type('2019-10-02'); cy.get('#datePublished').type('2020-01-01'); cy.get('#license').type('AGPL-3.0'); cy.get("#license").type('{enter}'); cy.get('#license').type('MIT'); cy.get("#license").type('{enter}'); cy.get('#generateCodemeta').click(); cy.get("#license").should('have.value', ''); 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", + "type": "SoftwareSourceCode", "license": ["https://spdx.org/licenses/AGPL-3.0", "https://spdx.org/licenses/MIT"], "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", }); }); it('works when choosing licenses without the keyboard', function() { cy.get('#name').type('My Test Software'); cy.get('#description').type('This is a\ngreat piece of software'); cy.get('#dateCreated').type('2019-10-02'); cy.get('#datePublished').type('2020-01-01'); cy.get('#license').type('AGPL-3.0'); // no cy.get("#license").type('{enter}'); here cy.get('#generateCodemeta').click(); cy.get("#license").should('have.value', ''); 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", + "type": "SoftwareSourceCode", "license": "https://spdx.org/licenses/AGPL-3.0", "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", }); }); }); describe('JSON Import', function() { it('works just from the software name', 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('#name').should('have.value', 'My Test Software'); }); it('works just from all main fields when using license as string', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "license": "https://spdx.org/licenses/AGPL-3.0", "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", })) ); cy.get('#importCodemeta').click(); cy.get('#name').should('have.value', 'My Test Software'); cy.get('#description').should('have.value', 'This is a\ngreat piece of software'); cy.get('#dateCreated').should('have.value', '2019-10-02'); cy.get('#datePublished').should('have.value', '2020-01-01'); cy.get('#license').should('have.value', ''); cy.get("#selected-licenses").children().should('have.length', 1); cy.get("#selected-licenses").children().first().children().first().should('have.text', 'AGPL-3.0'); }); it('works just from all main fields when using license as array', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "license": ["https://spdx.org/licenses/AGPL-3.0", "https://spdx.org/licenses/MIT"], "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", })) ); cy.get('#importCodemeta').click(); cy.get('#name').should('have.value', 'My Test Software'); cy.get('#description').should('have.value', 'This is a\ngreat piece of software'); cy.get('#dateCreated').should('have.value', '2019-10-02'); cy.get('#datePublished').should('have.value', '2020-01-01'); cy.get('#license').should('have.value', ''); cy.get("#selected-licenses").children().should('have.length', 2); cy.get("#selected-licenses").children().eq(0).children().first().should('have.text', 'AGPL-3.0'); cy.get("#selected-licenses").children().eq(1).children().first().should('have.text', 'MIT'); }); it('errors on invalid type', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "foo", "name": "My Test Software", })) ); cy.get('#importCodemeta').click(); // Should still be imported as much as possible cy.get('#name').should('have.value', 'My Test Software'); // But must display an error cy.get('#errorMessage').should('have.text', 'Wrong document type: must be "SoftwareSourceCode"/"SoftwareApplication", not "foo"'); }); it('allows singleton array as context', 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('#name').should('have.value', 'My Test Software'); }); it('errors on invalid context URL', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-100000", "@type": "SoftwareSourceCode", "name": "My Test Software", })) ); cy.get('#importCodemeta').click(); cy.get('#errorMessage').should('have.text', '@context must be "https://doi.org/10.5063/schema/codemeta-2.0", not "https://doi.org/10.5063/schema/codemeta-100000"'); }); it('errors on invalid context URL in array', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": ["https://doi.org/10.5063/schema/codemeta-100000"], "@type": "SoftwareSourceCode", "name": "My Test Software", })) ); cy.get('#importCodemeta').click(); cy.get('#errorMessage').should('have.text', '@context must be "https://doi.org/10.5063/schema/codemeta-2.0", not ["https://doi.org/10.5063/schema/codemeta-100000"]'); }); it('errors nicely when there are other contexts', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": [ "https://doi.org/10.5063/schema/codemeta-2.0", "https://schema.org/", ], "@type": "SoftwareSourceCode", "name": "My Test Software", })) ); cy.get('#importCodemeta').click(); cy.get('#errorMessage').should('have.text', 'Multiple values in @context are not supported (@context should be "https://doi.org/10.5063/schema/codemeta-2.0", not ["https://doi.org/10.5063/schema/codemeta-2.0","https://schema.org/"])'); }); }); diff --git a/cypress/integration/persons.js b/cypress/integration/persons.js index a296afc..e4e0182 100644 --- a/cypress/integration/persons.js +++ b/cypress/integration/persons.js @@ -1,424 +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", + "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", + "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { - "@type": "Person", - "@id": "http://example.org/~jdoe", + "type": "Person", + "id": "http://example.org/~jdoe", "givenName": "Jane", "familyName": "Doe", "email": "jdoe@example.org", "affiliation": { - "@type": "Organization", - "@id": "http://example.org/", + "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", + "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { - "@type": "Person", + "type": "Person", "givenName": "Jane", "affiliation": { - "@type": "Organization", - "@id": "http://example.org/", + "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", + "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { - "@type": "Person", + "type": "Person", "givenName": "Jane", "affiliation": { - "@type": "Organization", + "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_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_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_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", + "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { - "@type": "Person", + "type": "Person", "givenName": "Jane", "affiliation": { - "@type": "Organization", + "type": "Organization", "name": "Example Org", } }, { - "@type": "Person", + "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", + "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { - "@type": "Person", + "type": "Person", "givenName": "John", "familyName": "Doe", }, { - "@type": "Person", + "type": "Person", "givenName": "Jane", "affiliation": { - "@type": "Organization", + "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/cypress/integration/special_fields.js b/cypress/integration/special_fields.js index 569337e..62eb48d 100644 --- a/cypress/integration/special_fields.js +++ b/cypress/integration/special_fields.js @@ -1,90 +1,90 @@ /** * 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('Funder id', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#funder').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", + "type": "SoftwareSourceCode", "name": "My Test Software", "funder": { - "@type": "Organization", - "@id": "http://example.org/", + "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", "funder": { "@type": "Organization", "@id": "http://example.org/", }, })) ); cy.get('#importCodemeta').click(); cy.get('#funder').should('have.value', 'http://example.org/'); }); }); describe('Funder name', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#funder').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", + "type": "SoftwareSourceCode", "name": "My Test Software", "funder": { - "@type": "Organization", + "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", "funder": { "@type": "Organization", "name": "Example Org", } })) ); cy.get('#importCodemeta').click(); cy.get('#funder').should('have.value', 'Example Org'); }); }); diff --git a/data/contexts/codemeta-2.0.jsonld b/data/contexts/codemeta-2.0.jsonld new file mode 100644 index 0000000..5e19122 --- /dev/null +++ b/data/contexts/codemeta-2.0.jsonld @@ -0,0 +1,80 @@ +{ + "@context": { + "type": "@type", + "id": "@id", + "schema":"http://schema.org/", + "codemeta": "https://codemeta.github.io/terms/", + "Organization": {"@id": "schema:Organization"}, + "Person": {"@id": "schema:Person"}, + "SoftwareSourceCode": {"@id": "schema:SoftwareSourceCode"}, + "SoftwareApplication": {"@id": "schema:SoftwareApplication"}, + "Text": {"@id": "schema:Text"}, + "URL": {"@id": "schema:URL"}, + "address": { "@id": "schema:address"}, + "affiliation": { "@id": "schema:affiliation"}, + "applicationCategory": { "@id": "schema:applicationCategory", "@type": "@id"}, + "applicationSubCategory": { "@id": "schema:applicationSubCategory", "@type": "@id"}, + "citation": { "@id": "schema:citation"}, + "codeRepository": { "@id": "schema:codeRepository", "@type": "@id"}, + "contributor": { "@id": "schema:contributor"}, + "copyrightHolder": { "@id": "schema:copyrightHolder"}, + "copyrightYear": { "@id": "schema:copyrightYear"}, + "creator": { "@id": "schema:creator"}, + "dateCreated": {"@id": "schema:dateCreated", "@type": "schema:Date" }, + "dateModified": {"@id": "schema:dateModified", "@type": "schema:Date" }, + "datePublished": {"@id": "schema:datePublished", "@type": "schema:Date" }, + "description": { "@id": "schema:description"}, + "downloadUrl": { "@id": "schema:downloadUrl", "@type": "@id"}, + "email": { "@id": "schema:email"}, + "editor": { "@id": "schema:editor"}, + "encoding": { "@id": "schema:encoding"}, + "familyName": { "@id": "schema:familyName"}, + "fileFormat": { "@id": "schema:fileFormat", "@type": "@id"}, + "fileSize": { "@id": "schema:fileSize"}, + "funder": { "@id": "schema:funder"}, + "givenName": { "@id": "schema:givenName"}, + "hasPart": { "@id": "schema:hasPart" }, + "identifier": { "@id": "schema:identifier", "@type": "@id"}, + "installUrl": { "@id": "schema:installUrl", "@type": "@id"}, + "isAccessibleForFree": { "@id": "schema:isAccessibleForFree"}, + "isPartOf": { "@id": "schema:isPartOf"}, + "keywords": { "@id": "schema:keywords"}, + "license": { "@id": "schema:license", "@type": "@id"}, + "memoryRequirements": { "@id": "schema:memoryRequirements", "@type": "@id"}, + "name": { "@id": "schema:name"}, + "operatingSystem": { "@id": "schema:operatingSystem"}, + "permissions": { "@id": "schema:permissions"}, + "position": { "@id": "schema:position"}, + "processorRequirements": { "@id": "schema:processorRequirements"}, + "producer": { "@id": "schema:producer"}, + "programmingLanguage": { "@id": "schema:programmingLanguage"}, + "provider": { "@id": "schema:provider"}, + "publisher": { "@id": "schema:publisher"}, + "relatedLink": { "@id": "schema:relatedLink", "@type": "@id"}, + "releaseNotes": { "@id": "schema:releaseNotes", "@type": "@id"}, + "runtimePlatform": { "@id": "schema:runtimePlatform"}, + "sameAs": { "@id": "schema:sameAs", "@type": "@id"}, + "softwareHelp": { "@id": "schema:softwareHelp"}, + "softwareRequirements": { "@id": "schema:softwareRequirements", "@type": "@id"}, + "softwareVersion": { "@id": "schema:softwareVersion"}, + "sponsor": { "@id": "schema:sponsor"}, + "storageRequirements": { "@id": "schema:storageRequirements", "@type": "@id"}, + "supportingData": { "@id": "schema:supportingData"}, + "targetProduct": { "@id": "schema:targetProduct"}, + "url": { "@id": "schema:url", "@type": "@id"}, + "version": { "@id": "schema:version"}, + + "author": { "@id": "schema:author", "@container": "@list" }, + + "softwareSuggestions": { "@id": "codemeta:softwareSuggestions", "@type": "@id"}, + "contIntegration": { "@id": "codemeta:contIntegration", "@type": "@id"}, + "buildInstructions": { "@id": "codemeta:buildInstructions", "@type": "@id"}, + "developmentStatus": { "@id": "codemeta:developmentStatus", "@type": "@id"}, + "embargoDate": { "@id":"codemeta:embargoDate", "@type": "schema:Date" }, + "funding": { "@id": "codemeta:funding" }, + "readme": { "@id":"codemeta:readme", "@type": "@id" }, + "issueTracker": { "@id":"codemeta:issueTracker", "@type": "@id" }, + "referencePublication": { "@id": "codemeta:referencePublication", "@type": "@id"}, + "maintainer": { "@id": "codemeta:maintainer" } + } +} diff --git a/data/contexts/codemeta-2.0.txt b/data/contexts/codemeta-2.0.txt new file mode 100644 index 0000000..1f9acfc --- /dev/null +++ b/data/contexts/codemeta-2.0.txt @@ -0,0 +1,2 @@ +Matthew B. Jones, Carl Boettiger, Abby Cabunoc Mayes, Arfon Smith, Peter Slaughter, Kyle Niemeyer, Yolanda Gil, Martin Fenner, Krzysztof Nowak, Mark Hahnel, Luke Coy, Alice Allen, Mercè Crosas, Ashley Sands, Neil Chue Hong, Patricia Cruse, Daniel S. Katz, Carole Goble. 2017. CodeMeta: an exchange schema for software metadata. Version 2.0. KNB Data Repository. doi:10.5063/schema/codemeta-2.0 +https://archive.softwareheritage.org/swh:1:cnt:ecba88bee7bac07a81a6f45414f4f57ce5eb2d29;origin=https://github.com/codemeta/codemeta;visit=swh:1:snp:a35573dd8a59795b7d81055234352efd8b08a603;anchor=swh:1:rev:dbc58ffa8b088ae1b09ad335852d9767c402983d;path=/codemeta.jsonld \ No newline at end of file diff --git a/index.html b/index.html index 0ae9b8b..49290bc 100644 --- a/index.html +++ b/index.html @@ -1,359 +1,367 @@ CodeMeta generator

CodeMeta generator

Most fields are optional. Mandatory fields will be highlighted when generating Codemeta.

The software itself

the software title


from SPDX licence list

Discoverability and citation


such as ISBNs, GTIN codes, UUIDs etc.. http://schema.org/identifier


grant funding software development


organization funding software development

Authors and contributors can be added below
Development community / tools


Run-time environment


Current version of the software


Additional Info


see www.repostatus.org for details

Authors
Contributors

Order of contributors does not matter.

- - -

codemeta.json:


   
+ diff --git a/js/codemeta_generation.js b/js/codemeta_generation.js index 986e9a0..42ffecd 100644 --- a/js/codemeta_generation.js +++ b/js/codemeta_generation.js @@ -1,258 +1,295 @@ /** * Copyright (C) 2019-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 */ "use strict"; +const CODEMETA_CONTEXT_URL = 'https://doi.org/10.5063/schema/codemeta-2.0'; const SPDX_PREFIX = 'https://spdx.org/licenses/'; +const loadContextData = async () => { + const contextResponse = await fetch("../data/contexts/codemeta-2.0.jsonld"); + const context = await contextResponse.json(); + return { + [CODEMETA_CONTEXT_URL]: context + } +} + +const getJsonldCustomLoader = contexts => { + return url => { + const xhrDocumentLoader = jsonld.documentLoaders.xhr(); + if (url in contexts) { + return { + contextUrl: null, + document: contexts[url], + documentUrl: url + }; + } + return xhrDocumentLoader(url); + } +}; + +const initJsonldLoader = contexts => { + jsonld.documentLoader = getJsonldCustomLoader(contexts); +}; + function emptyToUndefined(v) { if (v == null || v == "") return undefined; else return v; } function getIfSet(query) { return emptyToUndefined(document.querySelector(query).value); } function setIfDefined(query, value) { if (value !== undefined) { document.querySelector(query).value = value; } } function getLicenses() { let selectedLicenses = Array.from(document.getElementById("selected-licenses").children); return selectedLicenses.map(licenseDiv => SPDX_PREFIX + licenseDiv.children[0].innerText); } // Names of codemeta properties with a matching HTML field name const directCodemetaFields = [ 'codeRepository', 'contIntegration', 'dateCreated', 'datePublished', 'dateModified', 'downloadUrl', 'issueTracker', 'name', 'version', 'identifier', 'description', 'applicationCategory', 'releaseNotes', 'funding', 'developmentStatus', 'isPartOf', 'referencePublication' ]; const splittedCodemetaFields = [ ['keywords', ','], ['programmingLanguage', ','], ['runtimePlatform', ','], ['operatingSystem', ','], ['softwareRequirements', '\n'], ['relatedLink', '\n'], ] // Names of codemeta properties with a matching HTML field name, // in a Person object const directPersonCodemetaFields = [ 'givenName', 'familyName', 'email', 'affiliation', ]; function generateShortOrg(fieldName) { var affiliation = getIfSet(fieldName); if (affiliation !== undefined) { if (isUrl(affiliation)) { return { "@type": "Organization", "@id": affiliation, }; } else { return { "@type": "Organization", "name": affiliation, }; } } else { return undefined; } } function generatePerson(idPrefix) { var doc = { "@type": "Person", - "@id": getIfSet(`#${idPrefix}_id`), + } + var id = getIfSet(`#${idPrefix}_id`); + if (id !== undefined) { + doc["@id"] = id; } directPersonCodemetaFields.forEach(function (item, index) { doc[item] = getIfSet(`#${idPrefix}_${item}`); }); doc["affiliation"] = generateShortOrg(`#${idPrefix}_affiliation`) return doc; } function generatePersons(prefix) { var persons = []; var nbPersons = getNbPersons(prefix); for (let personId = 1; personId <= nbPersons; personId++) { persons.push(generatePerson(`${prefix}_${personId}`)); } return persons; } -function generateCodemeta() { - var inputForm = document.querySelector('#inputForm'); - var codemetaText, errorHTML; - if (inputForm.checkValidity()) { - var doc = { - "@context": "https://doi.org/10.5063/schema/codemeta-2.0", - "@type": "SoftwareSourceCode", - }; - - let licenses = getLicenses(); - if (licenses.length > 0) { - doc["license"] = (licenses.length === 1) ? licenses[0] : licenses; - } +function buildDoc() { + var doc = { + "@context": CODEMETA_CONTEXT_URL, + "@type": "SoftwareSourceCode", + }; - // Generate most fields - directCodemetaFields.forEach(function (item, index) { - doc[item] = getIfSet('#' + item) - }); + let licenses = getLicenses(); + if (licenses.length > 0) { + doc["license"] = licenses; + } - doc["funder"] = generateShortOrg('#funder', doc["affiliation"]); + // Generate most fields + directCodemetaFields.forEach(function (item, index) { + doc[item] = getIfSet('#' + item) + }); - // Generate simple fields parsed simply by splitting - splittedCodemetaFields.forEach(function (item, index) { - const id = item[0]; - const separator = item[1]; - const value = getIfSet('#' + id); - if (value !== undefined) { - doc[id] = value.split(separator).map(trimSpaces); - } - }); + doc["funder"] = generateShortOrg('#funder', doc["affiliation"]); - // Generate dynamic fields - var authors = generatePersons('author'); - if (authors.length > 0) { - doc["author"] = authors; - } - var contributors = generatePersons('contributor'); - if (contributors.length > 0) { - doc["contributor"] = contributors; + // Generate simple fields parsed simply by splitting + splittedCodemetaFields.forEach(function (item, index) { + const id = item[0]; + const separator = item[1]; + const value = getIfSet('#' + id); + if (value !== undefined) { + doc[id] = value.split(separator).map(trimSpaces); } + }); - codemetaText = JSON.stringify(doc, null, 4); + // Generate dynamic fields + var authors = generatePersons('author'); + if (authors.length > 0) { + doc["author"] = authors; + } + var contributors = generatePersons('contributor'); + if (contributors.length > 0) { + doc["contributor"] = contributors; + } + return doc; +} + +async function generateCodemeta() { + var inputForm = document.querySelector('#inputForm'); + var codemetaText, errorHTML; + + if (inputForm.checkValidity()) { + var doc = buildDoc(); + const expanded = await jsonld.expand(doc); + const compacted = await jsonld.compact(expanded, CODEMETA_CONTEXT_URL); + codemetaText = JSON.stringify(compacted, null, 4); errorHTML = ""; } else { codemetaText = ""; errorHTML = "invalid input (see error above)"; inputForm.reportValidity(); } document.querySelector('#codemetaText').innerText = codemetaText; setError(errorHTML); // Run validator on the exported value, for extra validation. // If this finds a validation, it means there is a bug in our code (either // generation or validation), and the generation MUST NOT generate an // invalid codemeta file, regardless of user input. if (codemetaText && !validateDocument(JSON.parse(codemetaText))) { alert('Bug detected! The data you wrote is correct; but for some reason, it seems we generated an invalid codemeta.json. Please report this bug at https://github.com/codemeta/codemeta-generator/issues/new and copy-paste the generated codemeta.json file. Thanks!'); } if (codemetaText) { // For restoring the form state on page reload sessionStorage.setItem('codemetaText', codemetaText); } } // Imports a single field (name or @id) from an Organization. function importShortOrg(fieldName, doc) { if (doc !== undefined) { // Use @id if set, else use name setIfDefined(fieldName, doc["name"]); setIfDefined(fieldName, doc["@id"]); } } function importPersons(prefix, legend, docs) { if (docs === undefined) { return; } docs.forEach(function (doc, index) { var personId = addPerson(prefix, legend); setIfDefined(`#${prefix}_${personId}_id`, doc['@id']); directPersonCodemetaFields.forEach(function (item, index) { setIfDefined(`#${prefix}_${personId}_${item}`, doc[item]); }); importShortOrg(`#${prefix}_${personId}_affiliation`, doc['affiliation']) }) } function importCodemeta() { var inputForm = document.querySelector('#inputForm'); var doc = parseAndValidateCodemeta(false); resetForm(); 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); }); } directCodemetaFields.forEach(function (item, index) { setIfDefined('#' + item, doc[item]); }); importShortOrg('#funder', doc["funder"]); // Import simple fields by joining on their separator splittedCodemetaFields.forEach(function (item, index) { const id = item[0]; const separator = item[1]; let value = doc[id]; if (value !== undefined) { if (Array.isArray(value)) { value = value.join(separator); } setIfDefined('#' + id, value); } }); importPersons('author', 'Author', doc['author']) importPersons('contributor', 'Contributor', doc['contributor']) } function loadStateFromStorage() { var codemetaText = sessionStorage.getItem('codemetaText') if (codemetaText) { document.querySelector('#codemetaText').innerText = codemetaText; importCodemeta(); } } diff --git a/js/dynamic_form.js b/js/dynamic_form.js index 5af7c46..849f50a 100644 --- a/js/dynamic_form.js +++ b/js/dynamic_form.js @@ -1,188 +1,191 @@ /** * 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 */ "use strict"; // List of all HTML fields in a Person fieldset. const personFields = [ 'givenName', 'familyName', 'email', 'id', 'affiliation', ]; function createPersonFieldset(personPrefix, legend) { // Creates a fieldset containing inputs for informations about a person var fieldset = document.createElement("fieldset") var moveButtons; fieldset.classList.add("person"); fieldset.classList.add("leafFieldset"); fieldset.id = personPrefix; fieldset.innerHTML = ` ${legend}

`; return fieldset; } function addPersonWithId(container, prefix, 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 list of selected licenses document.getElementById("selected-licenses").innerHTML = ''; // 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').disabled = false; document.querySelector('#generateCodemeta') .addEventListener('click', generateCodemeta); document.querySelector('#resetForm') .addEventListener('click', resetForm); + document.querySelector('#validateCodemeta').disabled = false; document.querySelector('#validateCodemeta') .addEventListener('click', () => parseAndValidateCodemeta(true)); + document.querySelector('#importCodemeta').disabled = false; 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/js/fields_data.js b/js/fields_data.js index de21e4a..dbf04a7 100644 --- a/js/fields_data.js +++ b/js/fields_data.js @@ -1,80 +1,74 @@ /** * 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 */ "use strict"; var SPDX_LICENSES = null; var SPDX_LICENSE_IDS = null; +const loadSpdxData = async () => { + const licensesResponse = await fetch("./data/spdx/licenses.json"); + const licenseList = await licensesResponse.json(); + return licenseList.licenses; +} function initSpdx() { - var xhr = new XMLHttpRequest(); - xhr.open('GET', './data/spdx/licenses.json', true); - xhr.onload = function () { - if (xhr.status === 200) { - SPDX_LICENSES = JSON.parse(xhr.response)['licenses']; + var datalist = document.getElementById('licenses'); - var datalist = document.getElementById('licenses'); + SPDX_LICENSES.forEach(function (license) { + var option = document.createElement('option'); + option.value = license['licenseId']; + option.label = `${license['licenseId']}: ${license['name']}`; + datalist.appendChild(option); + }); - SPDX_LICENSES.forEach(function (license) { - var option = document.createElement('option'); - option.value = license['licenseId']; - option.label = `${license['licenseId']}: ${license['name']}`; - datalist.appendChild(option); - }); - SPDX_LICENSE_IDS = SPDX_LICENSES.map(function (license) { - return license['licenseId']; - }) - } - } - xhr.send(); } function insertLicenseElement(licenseId) { let selectedLicenses = document.getElementById("selected-licenses"); let newLicense = document.createElement("div"); newLicense.className = "selected-license"; newLicense.innerHTML = ` ${licenseId} `; selectedLicenses.appendChild(newLicense); return newLicense; } function validateLicense(e) { // continue only if Enter/Tab key is pressed if (e.keyCode && e.keyCode !== 13 && e.keyCode !== 9) { return; } // Note: For some reason e.keyCode is undefined when Enter/Tab key is pressed. // Maybe it's because of the datalist. But the above condition should // work in either case. var licenseField = document.getElementById('license'); var license = licenseField.value; if (SPDX_LICENSE_IDS !== null && SPDX_LICENSE_IDS.indexOf(license) == -1) { licenseField.setCustomValidity('Unknown license id'); } else { insertLicenseElement(license); licenseField.value = ""; licenseField.setCustomValidity(''); generateCodemeta(); } } function removeLicense(btn) { btn.parentElement.remove(); generateCodemeta(); } -function initFieldsData() { +function initFields() { initSpdx(); } diff --git a/js/libs/jsonld/LICENSE b/js/libs/jsonld/LICENSE new file mode 100644 index 0000000..5a8c53e --- /dev/null +++ b/js/libs/jsonld/LICENSE @@ -0,0 +1,40 @@ +You may use the jsonld.js project under the terms of the BSD License. + +You are free to use this project in commercial projects as long as the +copyright header is left intact. + +If you are a commercial entity and use this set of libraries in your +commercial software then reasonable payment to Digital Bazaar, if you can +afford it, is not required but is expected and would be appreciated. If this +library saves you time, then it's saving you money. The cost of developing +JSON-LD was on the order of several months of work and tens of +thousands of dollars. We are attempting to strike a balance between helping +the development community while not being taken advantage of by lucrative +commercial entities for our efforts. + +------------------------------------------------------------------------------- +New BSD License (3-clause) +Copyright (c) 2010, Digital Bazaar, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Digital Bazaar, Inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL DIGITAL BAZAAR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/js/libs/jsonld/jsonld.min.js b/js/libs/jsonld/jsonld.min.js new file mode 100644 index 0000000..1f9f663 --- /dev/null +++ b/js/libs/jsonld/jsonld.min.js @@ -0,0 +1,72 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.jsonld=t():e.jsonld=t()}(window,(function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var a=t[n]={i:n,l:!1,exports:{}};return e[n].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)r.d(n,a,function(t){return e[t]}.bind(null,a));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=228)}([function(e,t,r){"use strict";e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,r){"use strict";var n=r(3),a=r(52).f,o=r(50),i=r(31),s=r(135),u=r(175),c=r(114);e.exports=function(e,t){var r,l,f,d,p,v=e.target,h=e.global,y=e.stat;if(r=h?n:y?n[v]||s(v,{}):(n[v]||{}).prototype)for(l in t){if(d=t[l],f=e.dontCallGetSet?(p=a(r,l))&&p.value:r[l],!c(h?l:v+(y?".":"#")+l,e.forced)&&void 0!==f){if(typeof d==typeof f)continue;u(d,f)}(e.sham||f&&f.sham)&&o(d,"sham",!0),i(r,l,d,e)}}},function(e,t,r){"use strict";var n=r(88),a=Function.prototype,o=a.call,i=n&&a.bind.bind(o,o);e.exports=n?i:function(e){return function(){return o.apply(e,arguments)}}},function(e,t,r){"use strict";(function(t){var r=function(e){return e&&e.Math===Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof t&&t)||r("object"==typeof this&&this)||function(){return this}()||Function("return this")()}).call(this,r(168))},function(e,t,r){"use strict";var n=r(138),a=r(31),o=r(251);n||a(Object.prototype,"toString",o,{unsafe:!0})},function(e,t,r){"use strict";var n=r(170),a=n.all;e.exports=n.IS_HTMLDDA?function(e){return"function"==typeof e||e===a}:function(e){return"function"==typeof e}},function(e,t,r){"use strict";var n,a,o,i=r(210),s=r(10),u=r(3),c=r(5),l=r(12),f=r(13),d=r(61),p=r(66),v=r(50),h=r(31),y=r(57),g=r(54),x=r(99),b=r(83),m=r(8),w=r(91),k=r(41),j=k.enforce,S=k.get,O=u.Int8Array,I=O&&O.prototype,E=u.Uint8ClampedArray,A=E&&E.prototype,T=O&&x(O),N=I&&x(I),R=Object.prototype,_=u.TypeError,D=m("toStringTag"),L=w("TYPED_ARRAY_TAG"),C=i&&!!b&&"Opera"!==d(u.opera),M=!1,P={Int8Array:1,Uint8Array:1,Uint8ClampedArray:1,Int16Array:2,Uint16Array:2,Int32Array:4,Uint32Array:4,Float32Array:4,Float64Array:8},F={BigInt64Array:8,BigUint64Array:8},B=function(e){var t=x(e);if(l(t)){var r=S(t);return r&&f(r,"TypedArrayConstructor")?r.TypedArrayConstructor:B(t)}},J=function(e){if(!l(e))return!1;var t=d(e);return f(P,t)||f(F,t)};for(n in P)(o=(a=u[n])&&a.prototype)?j(o).TypedArrayConstructor=a:C=!1;for(n in F)(o=(a=u[n])&&a.prototype)&&(j(o).TypedArrayConstructor=a);if((!C||!c(T)||T===Function.prototype)&&(T=function(){throw new _("Incorrect invocation")},C))for(n in P)u[n]&&b(u[n],T);if((!C||!N||N===R)&&(N=T.prototype,C))for(n in P)u[n]&&b(u[n].prototype,N);if(C&&x(A)!==N&&b(A,N),s&&!f(N,D))for(n in M=!0,y(N,D,{configurable:!0,get:function(){return l(this)?this[L]:void 0}}),P)u[n]&&v(u[n],L,n);e.exports={NATIVE_ARRAY_BUFFER_VIEWS:C,TYPED_ARRAY_TAG:M&&L,aTypedArray:function(e){if(J(e))return e;throw new _("Target is not a typed array")},aTypedArrayConstructor:function(e){if(c(e)&&(!b||g(T,e)))return e;throw new _(p(e)+" is not a typed array constructor")},exportTypedArrayMethod:function(e,t,r,n){if(s){if(r)for(var a in P){var o=u[a];if(o&&f(o.prototype,e))try{delete o.prototype[e]}catch(r){try{o.prototype[e]=t}catch(e){}}}N[e]&&!r||h(N,e,r?t:C&&I[e]||t,n)}},exportTypedArrayStaticMethod:function(e,t,r){var n,a;if(s){if(b){if(r)for(n in P)if((a=u[n])&&f(a,e))try{delete a[e]}catch(e){}if(T[e]&&!r)return;try{return h(T,e,r?t:C&&T[e]||t)}catch(e){}}for(n in P)!(a=u[n])||a[e]&&!r||h(a,e,t)}},getTypedArrayConstructor:B,isView:function(e){if(!l(e))return!1;var t=d(e);return"DataView"===t||f(P,t)||f(F,t)},isTypedArray:J,TypedArray:T,TypedArrayPrototype:N}},function(e,t,r){"use strict";var n=r(88),a=Function.prototype.call;e.exports=n?a.bind(a):function(){return a.apply(a,arguments)}},function(e,t,r){"use strict";var n=r(3),a=r(79),o=r(13),i=r(91),s=r(76),u=r(171),c=n.Symbol,l=a("wks"),f=u?c.for||c:c&&c.withoutSetter||i;e.exports=function(e){return o(l,e)||(l[e]=s&&o(c,e)?c[e]:f("Symbol."+e)),l[e]}},function(e,t){e.exports=function(e){return e&&e.__esModule?e:{default:e}},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,r){"use strict";var n=r(0);e.exports=!n((function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]}))},function(e,t,r){"use strict";var n=r(35),a=r(141),o=r(98),i=r(41),s=r(30).f,u=r(145),c=r(146),l=r(48),f=r(10),d=i.set,p=i.getterFor("Array Iterator");e.exports=u(Array,"Array",(function(e,t){d(this,{type:"Array Iterator",target:n(e),index:0,kind:t})}),(function(){var e=p(this),t=e.target,r=e.index++;if(!t||r>=t.length)return e.target=void 0,c(void 0,!0);switch(e.kind){case"keys":return c(r,!1);case"values":return c(t[r],!1)}return c([r,t[r]],!1)}),"values");var v=o.Arguments=o.Array;if(a("keys"),a("values"),a("entries"),!l&&f&&"values"!==v.name)try{s(v,"name",{value:"values"})}catch(e){}},function(e,t,r){"use strict";var n=r(5),a=r(170),o=a.all;e.exports=a.IS_HTMLDDA?function(e){return"object"==typeof e?null!==e:n(e)||e===o}:function(e){return"object"==typeof e?null!==e:n(e)}},function(e,t,r){"use strict";var n=r(2),a=r(21),o=n({}.hasOwnProperty);e.exports=Object.hasOwn||function(e,t){return o(a(e),t)}},function(e,t,r){"use strict";var n=r(3),a=r(184),o=r(185),i=r(11),s=r(50),u=r(62),c=r(8)("iterator"),l=i.values,f=function(e,t){if(e){if(e[c]!==l)try{s(e,c,l)}catch(t){e[c]=l}if(u(e,t,!0),a[t])for(var r in i)if(e[r]!==i[r])try{s(e,r,i[r])}catch(t){e[r]=i[r]}}};for(var d in a)f(n[d]&&n[d].prototype,d);f(o,"DOMTokenList")},function(e,t,r){"use strict";var n=r(12),a=String,o=TypeError;e.exports=function(e){if(n(e))return e;throw new o(a(e)+" is not an object")}},function(e,t,r){"use strict";var n=r(61),a=String;e.exports=function(e){if("Symbol"===n(e))throw new TypeError("Cannot convert a Symbol value to a string");return a(e)}},function(e,t,r){"use strict";var n=r(193).charAt,a=r(16),o=r(41),i=r(145),s=r(146),u=o.set,c=o.getterFor("String Iterator");i(String,"String",(function(e){u(this,{type:"String Iterator",string:a(e),index:0})}),(function(){var e,t=c(this),r=t.string,a=t.index;return a>=r.length?s(void 0,!0):(e=n(r,a),t.index+=e.length,s(e,!1))}))},function(e,t,r){"use strict";var n=r(1),a=r(126);n({target:"RegExp",proto:!0,forced:/./.exec!==a},{exec:a})},function(e,t,r){"use strict";r(230),r(237),r(238),r(117),r(240)},function(e,t,r){"use strict";var n=r(49);e.exports=function(e){return n(e.length)}},function(e,t,r){"use strict";var n=r(39),a=Object;e.exports=function(e){return a(n(e))}},function(e,t,r){"use strict";var n=r(1),a=r(96),o=r(116),i=r(12),s=r(68),u=r(20),c=r(35),l=r(69),f=r(8),d=r(97),p=r(70),v=d("slice"),h=f("species"),y=Array,g=Math.max;n({target:"Array",proto:!0,forced:!v},{slice:function(e,t){var r,n,f,d=c(this),v=u(d),x=s(e,v),b=s(void 0===t?v:t,v);if(a(d)&&(r=d.constructor,(o(r)&&(r===y||a(r.prototype))||i(r)&&null===(r=r[h]))&&(r=void 0),r===y||void 0===r))return p(d,x,b);for(n=new(void 0===r?y:r)(g(b-x,0)),f=0;x3)){if(v)return!0;if(y)return y<603;var e,t,r,n,a="";for(e=65;e<76;e++){switch(t=String.fromCharCode(e),e){case 66:case 69:case 70:case 72:r=3;break;case 68:case 71:r=4;break;default:r=2}for(n=0;n<47;n++)g.push({k:t+n,v:r})}for(g.sort((function(e,t){return t.v-e.v})),n=0;nc(r)?1:-1}}(e)),r=s(a),n=0;nS;S++)if((p||S in w)&&(b=j(x=w[S],S,m),e))if(t)I[S]=b;else if(b)switch(e){case 3:return!0;case 5:return x;case 6:return S;case 2:c(I,x)}else switch(e){case 4:return!1;case 7:c(I,x)}return f?-1:a||l?l:I}};e.exports={forEach:l(0),map:l(1),filter:l(2),some:l(3),every:l(4),find:l(5),findIndex:l(6),filterReject:l(7)}},function(e,t,r){var n=r(244)();e.exports=n;try{regeneratorRuntime=n}catch(e){"object"==typeof globalThis?globalThis.regeneratorRuntime=n:Function("r","regeneratorRuntime = r")(n)}},function(e,t,r){"use strict";r(4),r(23),r(32);var n={};e.exports=n,n.isArray=Array.isArray,n.isBoolean=function(e){return"boolean"==typeof e||"[object Boolean]"===Object.prototype.toString.call(e)},n.isDouble=function(e){return n.isNumber(e)&&(-1!==String(e).indexOf(".")||Math.abs(e)>=1e21)},n.isEmptyObject=function(e){return n.isObject(e)&&0===Object.keys(e).length},n.isNumber=function(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)},n.isNumeric=function(e){return!isNaN(parseFloat(e))&&isFinite(e)},n.isObject=function(e){return"[object Object]"===Object.prototype.toString.call(e)},n.isString=function(e){return"string"==typeof e||"[object String]"===Object.prototype.toString.call(e)},n.isUndefined=function(e){return void 0===e}},function(e,t,r){"use strict";var n=r(53),a=TypeError;e.exports=function(e){if(n(e))throw new a("Can't call method on "+e);return e}},function(e,t,r){"use strict";var n=r(5),a=r(66),o=TypeError;e.exports=function(e){if(n(e))return e;throw new o(a(e)+" is not a function")}},function(e,t,r){"use strict";var n,a,o,i=r(232),s=r(3),u=r(12),c=r(50),l=r(13),f=r(134),d=r(111),p=r(93),v=s.TypeError,h=s.WeakMap;if(i||f.state){var y=f.state||(f.state=new h);y.get=y.get,y.has=y.has,y.set=y.set,n=function(e,t){if(y.has(e))throw new v("Object already initialized");return t.facade=e,y.set(e,t),t},a=function(e){return y.get(e)||{}},o=function(e){return y.has(e)}}else{var g=d("state");p[g]=!0,n=function(e,t){if(l(e,g))throw new v("Object already initialized");return t.facade=e,c(e,g,t),t},a=function(e){return l(e,g)?e[g]:{}},o=function(e){return l(e,g)}}e.exports={set:n,get:a,has:o,enforce:function(e){return o(e)?a(e):n(e,{})},getterFor:function(e){return function(t){var r;if(!u(t)||(r=a(t)).type!==e)throw new v("Incompatible receiver, "+e+" required");return r}}}},function(e,t,r){"use strict";var n=r(233);e.exports=function(e){var t=+e;return t!=t||0===t?0:n(t)}},function(e,t){function r(e,t,r,n,a,o,i){try{var s=e[o](i),u=s.value}catch(e){return void r(e)}s.done?t(u):Promise.resolve(u).then(n,a)}e.exports=function(e){return function(){var t=this,n=arguments;return new Promise((function(a,o){var i=e.apply(t,n);function s(e){r(i,a,o,s,u,"next",e)}function u(e){r(i,a,o,s,u,"throw",e)}s(void 0)}))}},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,r){"use strict";r(22),r(27),r(29),r(24),r(19),r(25),r(26);var n=r(9);r(11),r(51),r(4),r(17),r(14),r(84),r(23),r(32),r(153),r(18),r(86),r(87),r(71),r(28);var a=n(r(132)),o=n(r(59));function i(e,t){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=function(e,t){if(!e)return;if("string"==typeof e)return s(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);"Object"===r&&e.constructor&&(r=e.constructor.name);if("Map"===r||"Set"===r)return Array.from(e);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return s(e,t)}(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0,a=function(){};return{s:a,n:function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,u=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return i=e.done,e},e:function(e){u=!0,o=e},f:function(){try{i||null==r.return||r.return()}finally{if(u)throw o}}}}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r]*?>|"[^"]*?"|[^,])+/g,p=/\s*<([^>]*?)>\s*(?:;\s*(.*))?/,v=/(.*?)=(?:(?:"([^"]*?)")|([^"]*?))\s*(?:(?:;\s*)|$)/g,h={accept:"application/ld+json, application/json"},y={};e.exports=y,y.IdentifierIssuer=l,y.REGEX_BCP47=/^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/,y.REGEX_KEYWORD=/^@[a-zA-Z]+$/,y.clone=function(e){if(e&&"object"===(0,o.default)(e)){var t;if(c.isArray(e)){t=[];for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},t=Object.keys(e).some((function(e){return"accept"===e.toLowerCase()}));if(t)throw new RangeError('Accept header may not be specified; only "'+h.accept+'" is supported.');return Object.assign({Accept:h.accept},e)},y.parseLinkHeader=function(e){for(var t={},r=e.match(d),n=0;n0}return!1},y.hasValue=function(e,t,r){if(y.hasProperty(e,t)){var n=e[t],a=u.isList(n);if(c.isArray(n)||a){a&&(n=n["@list"]);for(var o=0;o0&&void 0!==arguments[0]?arguments[0]:"An unspecified JSON-LD error occurred.",a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"jsonld.Error",i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return(0,o.default)(this,r),(e=t.call(this,n)).name=a,e.message=n,e.details=i,e}return(0,a.default)(r)}((0,c.default)(Error))},function(e,t,r){"use strict";var n=r(2),a=n({}.toString),o=n("".slice);e.exports=function(e){return o(a(e),8,-1)}},function(e,t,r){"use strict";var n=r(3),a=r(5),o=function(e){return a(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?o(n[e]):n[e]&&n[e][t]}},function(e,t,r){"use strict";e.exports=!1},function(e,t,r){"use strict";var n=r(42),a=Math.min;e.exports=function(e){return e>0?a(n(e),9007199254740991):0}},function(e,t,r){"use strict";var n=r(10),a=r(30),o=r(74);e.exports=n?function(e,t,r){return a.f(e,t,o(1,r))}:function(e,t,r){return e[t]=r,e}},function(e,t,r){"use strict";r(269)},function(e,t,r){"use strict";var n=r(10),a=r(7),o=r(109),i=r(74),s=r(35),u=r(89),c=r(13),l=r(172),f=Object.getOwnPropertyDescriptor;t.f=n?f:function(e,t){if(e=s(e),t=u(t),l)try{return f(e,t)}catch(e){}if(c(e,t))return i(!a(o.f,e,t),e[t])}},function(e,t,r){"use strict";e.exports=function(e){return null==e}},function(e,t,r){"use strict";var n=r(2);e.exports=n({}.isPrototypeOf)},function(e,t,r){"use strict";e.exports="undefined"!=typeof navigator&&String(navigator.userAgent)||""},function(e,t,r){"use strict";var n,a=r(15),o=r(178),i=r(137),s=r(93),u=r(179),c=r(110),l=r(111),f=l("IE_PROTO"),d=function(){},p=function(e){return"