diff --git a/.babelrc b/.babelrc
index 18c19dbc..5c8e1b0a 100644
--- a/.babelrc
+++ b/.babelrc
@@ -2,7 +2,7 @@
"plugins": ["@babel/plugin-syntax-dynamic-import"],
"env": {
"test": {
- "plugins": ["dynamic-import-node"],
+ "plugins": ["dynamic-import-node", "istanbul"],
"presets": [
[
"@babel/preset-env",
diff --git a/.eslintrc.js b/.eslintrc.js
index 6825aa09..5df211d1 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -42,6 +42,8 @@ module.exports = {
'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
+ 'plugin:cypress/recommended',
+
'standard'
],
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index a8612dcf..53a3c690 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,8 +1,15 @@
# Pull request details
+As a contributor I confirm
+- [ ] I read and followed the instructions in [CONTRIBUTING.md](CONTRIBUTING.md)
+- [ ] The developer documentation is up to date with the changes introduced in this Pull Request
+- [ ] Tests are passing
+- [ ] All the workflows are passing
+
## List of related issues or pull requests
-Refs: #ISSUE_NUMBER
+Refs:
+- #ISSUE_NUMBER
## Describe the changes made in this pull request
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 79d53c6a..a3aa2d5d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,17 +10,15 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- - name: Use Node.js 14
- uses: actions/setup-node@v2
+ - name: Use Node.js 18
+ uses: actions/setup-node@v3
with:
- node-version: '14'
+ node-version: '18'
cache: 'npm'
- name: Run npm clean-install
run: npm clean-install
- name: Run build
run: npm run build
-
-
diff --git a/.github/workflows/cffconvert.yml b/.github/workflows/cffconvert.yml
index 707a71c4..6851c52d 100644
--- a/.github/workflows/cffconvert.yml
+++ b/.github/workflows/cffconvert.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out a copy of the repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Check whether the citation metadata from CITATION.cff is valid
uses: citation-file-format/cffconvert-github-action@2.0.0
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 6506a620..7c96fb99 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -11,13 +11,13 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- - name: Use Node.js 14
- uses: actions/setup-node@v2
+ - name: Use Node.js 18
+ uses: actions/setup-node@v3
with:
- node-version: '14'
+ node-version: '18'
cache: 'npm'
- name: Run npm clean-install
run: npm clean-install
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
index 4d7a9a55..04ec52dd 100644
--- a/.github/workflows/preview.yml
+++ b/.github/workflows/preview.yml
@@ -7,6 +7,7 @@ on:
pull_request:
branches:
- main
+ release:
workflow_dispatch:
jobs:
@@ -14,13 +15,13 @@ jobs:
if: github.repository_owner == 'citation-file-format'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- - name: Use Node.js 14
- uses: actions/setup-node@v2
+ - name: Use Node.js 18
+ uses: actions/setup-node@v3
with:
- node-version: '14'
+ node-version: '18'
cache: 'npm'
- name: Run npm clean-install
run: npm clean-install
@@ -56,4 +57,3 @@ jobs:
script: |
const { issue: { number: issue_number }, repo: { owner, repo } } = context;
github.issues.createComment({ issue_number, owner, repo, body: 'Once the build has completed, you can preview your PR at this URL: https://cffinit.netlify.app/PR${{ github.event.number }}/' });
-
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 95911937..29db4426 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -9,13 +9,13 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- - name: Use Node.js 14
- uses: actions/setup-node@v2
+ - name: Use Node.js 18
+ uses: actions/setup-node@v3
with:
- node-version: '14'
+ node-version: '18'
cache: 'npm'
- name: Run npm clean-install
run: npm clean-install
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 95a0f2cd..23524534 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,17 +11,30 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- - name: Use Node.js 14
- uses: actions/setup-node@v2
+ - name: Use Node.js 18
+ uses: actions/setup-node@v3
with:
- node-version: '14'
+ node-version: '18'
cache: 'npm'
- name: Run npm clean-install
run: npm clean-install
- name: Run unit tests
- run: npm run test:unit:ci
-
-
+ run: npm run test:unit:coverage
+ - name: Cypress run
+ uses: cypress-io/github-action@v4
+ with:
+ start: npm run dev
+ wait-on: 'http://localhost:8080/cff-initializer-javascript/#'
+ - name: Upload Screenshot of Cypress when errors occur
+ if: failure()
+ uses: actions/upload-artifact@v3
+ with:
+ name: cypress-screenshots
+ path: cypress/screenshots
+ - name: Code coverage
+ uses: codecov/codecov-action@v3
+ with:
+ files: ./test/jest/coverage/coverage-final.json,./coverage/coverage-final.json
diff --git a/.github/workflows/zenodraft.yml b/.github/workflows/zenodraft.yml
index 11c2783e..02b7091e 100644
--- a/.github/workflows/zenodraft.yml
+++ b/.github/workflows/zenodraft.yml
@@ -4,13 +4,14 @@ on:
release:
types:
- published
+ workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout the contents of your repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Create a draft snapshot of your repository contents as a new
version in collection 1404735 on Zenodo using metadata
from repository file .zenodo.json
@@ -21,7 +22,7 @@ jobs:
with:
collection: 1404735
metadata: .zenodo.json
- publish: false
+ publish: true
sandbox: false
upsert-doi: true
upsert-location: identifiers[0]
diff --git a/.gitignore b/.gitignore
index 37c9e486..28311d17 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,12 @@ yarn-error.log*
*.njsproj
*.sln
node_modules
+
+# Cypress
+cypress/downloads
+cypress/screenshots
+cypress/videos
+
+# Coverage related
+coverage
+.nyc_output
diff --git a/.nycrc b/.nycrc
new file mode 100644
index 00000000..a963a1b7
--- /dev/null
+++ b/.nycrc
@@ -0,0 +1,4 @@
+{
+ "extends": "@istanbuljs/nyc-config-babel",
+ "extension": [".js", ".ts", ".vue"]
+}
diff --git a/.zenodo.json b/.zenodo.json
index a57f36f0..c5c1f232 100644
--- a/.zenodo.json
+++ b/.zenodo.json
@@ -29,6 +29,11 @@
"affiliation": "Netherlands eScience Center",
"name": "Garcia Gonzalez, Jesus",
"orcid": "0000-0002-2170-3253"
+ },
+ {
+ "affiliation": "Netherlands eScience Center",
+ "name": "Cushing, Reggie",
+ "orcid": "0000-0002-5967-7302"
}
],
"description": "Web form to initialize CITATION.cff files.",
@@ -41,7 +46,7 @@
"license": {
"id": "Apache-2.0"
},
- "publication_date": "2022-06-14",
+ "publication_date": "2023-01-17",
"title": "cffinit",
- "version": "2.0.3"
+ "version": "2.2.0"
}
diff --git a/CITATION.cff b/CITATION.cff
index 113cd228..9b7d00a2 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -24,11 +24,15 @@ authors:
family-names: Garcia Gonzalez
given-names: Jesus
orcid: https://orcid.org/0000-0002-2170-3253
+ - affiliation: Netherlands eScience Center
+ family-names: Cushing
+ given-names: Reggie
+ orcid: https://orcid.org/0000-0002-5967-7302
cff-version: 1.2.0
-date-released: 2022-06-14
+date-released: 2023-01-17
identifiers:
- type: doi
- value: 10.5281/zenodo.6641931
+ value: 10.5281/zenodo.7543718
description: >-
This is the identifier used to uniquely identify the version of the
software.
@@ -46,4 +50,4 @@ license: Apache-2.0
message: If you use this software, please cite it using these metadata.
repository-code: https://github.com/citation-file-format/cff-initializer-javascript
title: cffinit
-version: 2.0.3
+version: 2.2.0
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..c64d5a96
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,73 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+education, socio-economic status, nationality, personal appearance, race,
+religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at generalization@esciencecenter.nl. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+
+This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..2b5c1475
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,41 @@
+# Contributing guidelines
+
+We welcome any kind of contribution to our software, from simple comment or question to a full fledged [pull request](https://help.github.com/articles/about-pull-requests/). Please read and follow our Code of Conduct [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
+
+A contribution can be one of the following cases:
+
+- you have a question;
+- you think you may have found a bug (including unexpected behavior);
+- you want to make some kind of change to the code base (e.g. to fix a bug, to add a new feature, to update documentation).
+
+The sections below outline the steps in each case.
+
+## You have a question
+
+- use the search functionality [here](https://github.com/citation-file-format/cff-initializer-javascript/issues) to see if someone already filed the same issue;
+- if your issue search did not yield any relevant results, make a new issue;
+
+## You think you may have found a bug
+
+- use the search functionality [here](https://github.com/citation-file-format/cff-initializer-javascript/issues) to see if someone already filed the same issue;
+- if your issue search did not yield any relevant results, make a new issue, making sure to provide enough information to the rest of the community to understand the cause and context of the problem. Depending on the issue, you may want to include:
+ - the version of cffinit (bottom right in the app);
+ - if you are using a development version, the [SHA hashcode](https://help.github.com/articles/autolinked-references-and-urls/#commit-shas) of the commit that is causing your problem;
+ - some identifying information (name and version number) for dependencies you're using;
+ - information about the operating system;
+
+## You want to make some kind of change to the code base
+
+- (**important**) announce your plan to the rest of the community *before you start working*. This announcement should be in the form of a (new) issue;
+- (**important**) wait until some kind of consensus is reached about your idea being implemented;
+- if needed, fork the repository to your own Github profile and create your own feature branch off of the latest main commit. While working on your feature branch, make sure to stay up to date with the main branch by pulling in changes, possibly from the 'upstream' repository (follow the instructions [here](https://help.github.com/articles/configuring-a-remote-for-a-fork/) and [here](https://help.github.com/articles/syncing-a-fork/);
+- read the [developer documentation](README.dev.md);
+- make sure the existing tests still work;
+- add your own tests (if necessary);
+- update or expand the developer documentation;
+- [push](http://rogerdudler.github.io/git-guide/) your feature branch to (your fork of) the cff-initializer-javascript repository on GitHub;
+- create the pull request, e.g. following the instructions [here](https://help.github.com/articles/creating-a-pull-request/);
+- verify that the GitHub workflows passed (if they don't, mark the PR as a draft while you fix it);
+- don't request reviews, the reviewer will assign them themselves.
+
+If you don't know how to write or run tests or generate documentation, don't let this discourage you; we can help! Ask for help on the relevant issue so we can decide how to proceed.
diff --git a/README.dev.md b/README.dev.md
index da2f9050..5a69871f 100644
--- a/README.dev.md
+++ b/README.dev.md
@@ -51,6 +51,8 @@ npm run build
## Tests
+### Unit tests
+
We use `Jest` for unit tests. The unit tests can be found under `test/jest/__tests__/` folder.
You can run the test with
@@ -65,6 +67,33 @@ You can also use the Majestic web interface to run the unit tests in your browse
npm run test:unit:ui
```
+### End-to-end tests
+
+We use [Cypress](https://cypress.io) for end-to-end tests. These tests can be found under the `cypress/e2e` folder.
+
+You can start Cypress with
+
+```shell
+npm run cypress:open
+```
+
+Select "E2E Testing", which should be configured already and select the desired browser and click the start button.
+
+A browser will open with the list of tests, which can be explored.
+It should look like this:
+
+
+
+
+ Cypress E2E specs list. Image is from https://docs.cypress.io/guides/end-to-end-testing/writing-your-first-end-to-end-test
+
- Your CITATION.cff does not have the minimum fields
-
-
- Your CITATION.cff is {{ isValidCFF ? "valid" : "not valid" }}
-
-
+
+
+ Your CITATION.cff does not have the minimum fields. Make sure the title has been filled and that at least one author was added.
+
+
+ Your CITATION.cff is {{ isValidCFF ? "valid" : "not valid" }}
+
+
+
-
-
diff --git a/src/components/RawErrorList.vue b/src/components/RawErrorList.vue
new file mode 100644
index 00000000..7305818e
--- /dev/null
+++ b/src/components/RawErrorList.vue
@@ -0,0 +1,61 @@
+
+
+ The CFF schema supports more fields than this form implements, for instance preferred-citation, or references.
+ These extra fields can be pasted directly here.
+ They will be validated, but the error messages will not be parsed, so they may not be helpful.
+
+ The values here identify a specific point in the work lifetime. Remember to update them, for instance, when making a new release.
+
-
-
- What is the commit identifier of the work?
-
-
-
+
+
+
+
+
-
- What is the version of the work?
-
-
-
+
+
+
+
+
-
- When was the version released?
+
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/src/components/Stepper.vue b/src/components/Stepper.vue
index bf465062..c4c5882a 100644
--- a/src/components/Stepper.vue
+++ b/src/components/Stepper.vue
@@ -1,142 +1,115 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
diff --git a/src/components/StepperActions.vue b/src/components/StepperActions.vue
index f409c444..df501fd9 100644
--- a/src/components/StepperActions.vue
+++ b/src/components/StepperActions.vue
@@ -1,39 +1,47 @@
-
-
-
-
+
+
+
-
+
-
-
diff --git a/src/css/app.css b/src/css/app.css
index aad92e81..ec67c330 100644
--- a/src/css/app.css
+++ b/src/css/app.css
@@ -9,174 +9,127 @@
:root {
--fgcolor: #efefef;
--bgcolor: #ddd;
- --primary: #3E7BFA;
+ --white: #efefef;
}
body {
background-color: var(--bgcolor);
+ color: #333;
+ font-family: "Inter", sans-serif;
+ font-size: 14pt;
}
-#app {
- background-color: var(--bgcolor, tan);
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
+#main-block {
+ max-width: 1044px;
+ min-height: 500px;
+ position: relative;
}
-#header-inner {
- background-color: purple;
- color: white;
- display: flex;
- flex-direction: row;
- height: 50px;
-}
-#middle {
- background-color: var(--bgcolor, lightblue);
- column-gap: 0px;
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: center;
- row-gap: 20px;
-}
-#form {
- background-color: var(--fgcolor, plum);
- display: flex;
- flex-direction: column;
- flex-grow: 0.5;
- padding-left: 30px;
- padding-right: 30px;
- padding-top: 30px;
-}
-#form-title {
- background-color: var(--fgcolor, tomato);
- height: 120px;
-}
-#form-content {
- background-color: var(--fgcolor, lightslategray);
- flex-grow: 1;
- padding-bottom: 30px;
-}
-#form-button-bar {
- align-items: center;
- background-color: var(--fgcolor, khaki);
- display: flex;
- flex-direction: row;
- justify-content: center;
- max-height: 80px;
- min-height: 80px;
-}
-#preview-drawer, #preview-static {
- background-color: var(--fgcolor, limegreen);
- display: flex;
- flex-direction: column;
- flex-grow: 0.5;
- padding-left: 30px;
- padding-right: 30px;
- padding-top: 30px;
- border-radius: 0px 7px 7px 0px;
-}
-#preview-button-close {
- align-items: center;
- display: flex;
- flex-direction: row;
- justify-content: center;
- max-height: 80px;
- min-height: 80px;
-}
-#preview-content {
- background-color: var(--fgcolor, lightcoral);
- display: flex;
- flex-direction: column;
- flex-grow: 1;
-}
-#preview-button-bar {
- align-items: center;
- background-color: var(--fgcolor, darkorange);
- display: flex;
- flex-direction: row;
- justify-content: center;
- max-height: 80px;
- min-height: 80px;
-}
-#footer-inner {
- background-color: deepskyblue;
- display: flex;
- flex-direction: column;
- height: 50px;
- justify-content: center;
- padding-left: 10px;
- padding-right: 10px;
-}
-#logo {
- display: flex;
- flex-direction: column;
- justify-content: center;
- padding-left: 16px;
- width: 245px;
+
+#preview-block {
+ max-width: 600px;
}
-@font-face {
- font-family: "Roboto";
- src: url("./fonts/Roboto/Roboto-Regular.ttf");
+#header {
+ background-color: #f8f8f8;
}
-@font-face {
- font-family: "Roboto Condensed";
- src: url("./fonts/Roboto_Condensed/RobotoCondensed-Regular.ttf");
+#footer {
+ background-color: #b3c5db;
}
-@font-face {
- font-family: "Roboto Mono";
- src: url("./fonts/Roboto_Mono/static/RobotoMono-Regular.ttf");
+a:not(.q-btn) {
+ color: var(--q-primary);
+ text-decoration: none
}
-p,
-body {
- font-family: "Roboto Condensed", sans-serif;
+a:not(.q-btn):hover{
+ text-decoration: underline
}
-.page-title,
-.finish-title {
- font-family: "Roboto", sans-serif;
- color: var(--q-prose);
- font-size: 1.4rem;
- font-weight: 400;
- margin-top: 10px;
- margin-bottom: 60px;
+.elevated {
+ box-shadow: 0px 8px 22px -6px rgba(24, 39, 75, 0.12), 0px 14px 64px -4px rgba(24, 39, 75, 0.12);
}
-.question {
- font-family: "Roboto Condensed", sans-serif;
- color: var(--q-prose);
- font-size: 1.1rem;
- font-weight: 400;
- margin-top: 12px;
- margin-bottom: 7px;
- line-height: normal;
+
+.floating-preview-button {
+ position: absolute;
+ top: 16px;
+ right: 16px;
}
-.subquestion {
- font-family: "Roboto Condensed", sans-serif;
- color: var(--q-prose);
- font-size: 1.1rem;
- font-weight: 400;
- margin: 0px;
+
+.rounded-borders {
+ border-radius: 10px;
+}
+
+.bg-formcard {
+ background-color: #f8f8f8;
+ border-radius: 5px;
+}
+
+h1 {
+ font-size: 32pt;
+ font-weight: bold;
+}
+h2 {
+ font-size: 26pt;
+}
+h3 {
+ font-size: 22pt;
+}
+h4 {
+ font-size: 20pt;
+}
+h1, h2, h3, h4 {
line-height: normal;
- font-size: 0.9rem;
+ margin: 1rem 0rem;
+}
+
+.q-drawer {
+ background-color: transparent !important;
}
-.finish-paragraph {
- font-family: "Roboto Condensed", sans-serif;
- color: var(--q-prose);
- font-size: 1rem;
+
+@font-face {
+ font-family: "Inter";
font-weight: 400;
- line-height: 1.7;
- letter-spacing: normal;
+ src: url("./fonts/Inter/Inter-Regular.ttf");
+}
+
+@font-face {
+ font-family: "Inter";
+ font-weight: bold;
+ src: url("./fonts/Inter/Inter-Bold.ttf");
+}
+
+@font-face {
+ font-family: "Fira Code";
+ src: url("./fonts/Fira_Code/FiraCode-Regular.ttf");
+}
+
+p,
+body {
+ font-family: "Inter", sans-serif;
}
-pre,
-.mono {
- font-family: "Roboto Mono", sans-serif;
+.cffstr {
+ font-family: "Fira Code", monospace;
+ font-size: 11pt;
}
.red-border {
border-color: #c10015;
- border-width: 2px;
+}
+
+.skip-to-main-content-link {
+ position: absolute;
+ left: -9999px;
+ z-index: 999;
+ padding: 1em;
+ background-color: black;
+ color: white;
+ opacity: 0;
+}
+
+.skip-to-main-content-link:focus {
+ left: 50%;
+ transform: translateX(-50%);
+ opacity: 1;
}
diff --git a/src/css/fonts/Fira_Code/FiraCode-Bold.ttf b/src/css/fonts/Fira_Code/FiraCode-Bold.ttf
new file mode 100644
index 00000000..c0aa0f51
Binary files /dev/null and b/src/css/fonts/Fira_Code/FiraCode-Bold.ttf differ
diff --git a/src/css/fonts/Fira_Code/FiraCode-Light.ttf b/src/css/fonts/Fira_Code/FiraCode-Light.ttf
new file mode 100644
index 00000000..84801e1b
Binary files /dev/null and b/src/css/fonts/Fira_Code/FiraCode-Light.ttf differ
diff --git a/src/css/fonts/Fira_Code/FiraCode-Medium.ttf b/src/css/fonts/Fira_Code/FiraCode-Medium.ttf
new file mode 100644
index 00000000..570b4d14
Binary files /dev/null and b/src/css/fonts/Fira_Code/FiraCode-Medium.ttf differ
diff --git a/src/css/fonts/Fira_Code/FiraCode-Regular.ttf b/src/css/fonts/Fira_Code/FiraCode-Regular.ttf
new file mode 100644
index 00000000..82baafc3
Binary files /dev/null and b/src/css/fonts/Fira_Code/FiraCode-Regular.ttf differ
diff --git a/src/css/fonts/Fira_Code/FiraCode-SemiBold.ttf b/src/css/fonts/Fira_Code/FiraCode-SemiBold.ttf
new file mode 100644
index 00000000..f3a34faf
Binary files /dev/null and b/src/css/fonts/Fira_Code/FiraCode-SemiBold.ttf differ
diff --git a/src/css/fonts/Fira_Code/FiraCode-VariableFont_wght.ttf b/src/css/fonts/Fira_Code/FiraCode-VariableFont_wght.ttf
new file mode 100644
index 00000000..f75b2a2e
Binary files /dev/null and b/src/css/fonts/Fira_Code/FiraCode-VariableFont_wght.ttf differ
diff --git a/src/css/fonts/Fira_Code/OFL.txt b/src/css/fonts/Fira_Code/OFL.txt
new file mode 100644
index 00000000..19f42988
--- /dev/null
+++ b/src/css/fonts/Fira_Code/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2014-2020 The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/src/css/fonts/Roboto_Mono/README.txt b/src/css/fonts/Fira_Code/README.txt
similarity index 56%
rename from src/css/fonts/Roboto_Mono/README.txt
rename to src/css/fonts/Fira_Code/README.txt
index ad6998b3..e70b485c 100644
--- a/src/css/fonts/Roboto_Mono/README.txt
+++ b/src/css/fonts/Fira_Code/README.txt
@@ -1,32 +1,22 @@
-Roboto Mono Variable Font
-=========================
+Fira Code Variable Font
+=======================
-This download contains Roboto Mono as both variable fonts and static fonts.
+This download contains Fira Code as both a variable font and static fonts.
-Roboto Mono is a variable font with this axis:
+Fira Code is a variable font with this axis:
wght
-This means all the styles are contained in these files:
- Roboto_Mono/RobotoMono-VariableFont_wght.ttf
- Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf
+This means all the styles are contained in a single file:
+ FiraCode-VariableFont_wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that aren’t available as static fonts. Not all apps support variable fonts, and
-in those cases you can use the static font files for Roboto Mono:
- Roboto_Mono/static/RobotoMono-Thin.ttf
- Roboto_Mono/static/RobotoMono-ExtraLight.ttf
- Roboto_Mono/static/RobotoMono-Light.ttf
- Roboto_Mono/static/RobotoMono-Regular.ttf
- Roboto_Mono/static/RobotoMono-Medium.ttf
- Roboto_Mono/static/RobotoMono-SemiBold.ttf
- Roboto_Mono/static/RobotoMono-Bold.ttf
- Roboto_Mono/static/RobotoMono-ThinItalic.ttf
- Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf
- Roboto_Mono/static/RobotoMono-LightItalic.ttf
- Roboto_Mono/static/RobotoMono-Italic.ttf
- Roboto_Mono/static/RobotoMono-MediumItalic.ttf
- Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf
- Roboto_Mono/static/RobotoMono-BoldItalic.ttf
+in those cases you can use the static font files for Fira Code:
+ static/FiraCode-Light.ttf
+ static/FiraCode-Regular.ttf
+ static/FiraCode-Medium.ttf
+ static/FiraCode-SemiBold.ttf
+ static/FiraCode-Bold.ttf
Get started
-----------
@@ -67,10 +57,10 @@ Android Apps
License
-------
-Please read the full license text (LICENSE.txt) to understand the permissions,
+Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
-You can use them freely in your products & projects - print or digital,
+You can use them in your products & projects – print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
diff --git a/src/css/fonts/Inter/Inter-Black.ttf b/src/css/fonts/Inter/Inter-Black.ttf
new file mode 100644
index 00000000..5aecf7dc
Binary files /dev/null and b/src/css/fonts/Inter/Inter-Black.ttf differ
diff --git a/src/css/fonts/Inter/Inter-Bold.ttf b/src/css/fonts/Inter/Inter-Bold.ttf
new file mode 100644
index 00000000..8e82c70d
Binary files /dev/null and b/src/css/fonts/Inter/Inter-Bold.ttf differ
diff --git a/src/css/fonts/Inter/Inter-ExtraBold.ttf b/src/css/fonts/Inter/Inter-ExtraBold.ttf
new file mode 100644
index 00000000..cb4b8217
Binary files /dev/null and b/src/css/fonts/Inter/Inter-ExtraBold.ttf differ
diff --git a/src/css/fonts/Inter/Inter-ExtraLight.ttf b/src/css/fonts/Inter/Inter-ExtraLight.ttf
new file mode 100644
index 00000000..64aee30a
Binary files /dev/null and b/src/css/fonts/Inter/Inter-ExtraLight.ttf differ
diff --git a/src/css/fonts/Inter/Inter-Light.ttf b/src/css/fonts/Inter/Inter-Light.ttf
new file mode 100644
index 00000000..9e265d89
Binary files /dev/null and b/src/css/fonts/Inter/Inter-Light.ttf differ
diff --git a/src/css/fonts/Inter/Inter-Medium.ttf b/src/css/fonts/Inter/Inter-Medium.ttf
new file mode 100644
index 00000000..b53fb1c4
Binary files /dev/null and b/src/css/fonts/Inter/Inter-Medium.ttf differ
diff --git a/src/css/fonts/Inter/Inter-Regular.ttf b/src/css/fonts/Inter/Inter-Regular.ttf
new file mode 100644
index 00000000..8d4eebf2
Binary files /dev/null and b/src/css/fonts/Inter/Inter-Regular.ttf differ
diff --git a/src/css/fonts/Inter/Inter-SemiBold.ttf b/src/css/fonts/Inter/Inter-SemiBold.ttf
new file mode 100644
index 00000000..c6aeeb16
Binary files /dev/null and b/src/css/fonts/Inter/Inter-SemiBold.ttf differ
diff --git a/src/css/fonts/Inter/Inter-Thin.ttf b/src/css/fonts/Inter/Inter-Thin.ttf
new file mode 100644
index 00000000..7aed55d5
Binary files /dev/null and b/src/css/fonts/Inter/Inter-Thin.ttf differ
diff --git a/src/css/fonts/Inter/Inter-VariableFont_slnt,wght.ttf b/src/css/fonts/Inter/Inter-VariableFont_slnt,wght.ttf
new file mode 100644
index 00000000..ec3164ef
Binary files /dev/null and b/src/css/fonts/Inter/Inter-VariableFont_slnt,wght.ttf differ
diff --git a/src/css/fonts/Inter/OFL.txt b/src/css/fonts/Inter/OFL.txt
new file mode 100644
index 00000000..ad214842
--- /dev/null
+++ b/src/css/fonts/Inter/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/src/css/fonts/Inter/README.txt b/src/css/fonts/Inter/README.txt
new file mode 100644
index 00000000..3078f199
--- /dev/null
+++ b/src/css/fonts/Inter/README.txt
@@ -0,0 +1,72 @@
+Inter Variable Font
+===================
+
+This download contains Inter as both a variable font and static fonts.
+
+Inter is a variable font with these axes:
+ slnt
+ wght
+
+This means all the styles are contained in a single file:
+ Inter-VariableFont_slnt,wght.ttf
+
+If your app fully supports variable fonts, you can now pick intermediate styles
+that aren’t available as static fonts. Not all apps support variable fonts, and
+in those cases you can use the static font files for Inter:
+ static/Inter-Thin.ttf
+ static/Inter-ExtraLight.ttf
+ static/Inter-Light.ttf
+ static/Inter-Regular.ttf
+ static/Inter-Medium.ttf
+ static/Inter-SemiBold.ttf
+ static/Inter-Bold.ttf
+ static/Inter-ExtraBold.ttf
+ static/Inter-Black.ttf
+
+Get started
+-----------
+
+1. Install the font files you want to use
+
+2. Use your app's font picker to view the font family and all the
+available styles
+
+Learn more about variable fonts
+-------------------------------
+
+ https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
+ https://variablefonts.typenetwork.com
+ https://medium.com/variable-fonts
+
+In desktop apps
+
+ https://theblog.adobe.com/can-variable-fonts-illustrator-cc
+ https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
+
+Online
+
+ https://developers.google.com/fonts/docs/getting_started
+ https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
+ https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
+
+Installing fonts
+
+ MacOS: https://support.apple.com/en-us/HT201749
+ Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
+ Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
+
+Android Apps
+
+ https://developers.google.com/fonts/docs/android
+ https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
+
+License
+-------
+Please read the full license text (OFL.txt) to understand the permissions,
+restrictions and requirements for usage, redistribution, and modification.
+
+You can use them in your products & projects – print or digital,
+commercial or otherwise.
+
+This isn't legal advice, please consider consulting a lawyer and see the full
+license for all details.
diff --git a/src/css/fonts/Roboto/LICENSE.txt b/src/css/fonts/Roboto/LICENSE.txt
deleted file mode 100644
index 75b52484..00000000
--- a/src/css/fonts/Roboto/LICENSE.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/src/css/fonts/Roboto/Roboto-Black.ttf b/src/css/fonts/Roboto/Roboto-Black.ttf
deleted file mode 100644
index 43a00e0d..00000000
Binary files a/src/css/fonts/Roboto/Roboto-Black.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-BlackItalic.ttf b/src/css/fonts/Roboto/Roboto-BlackItalic.ttf
deleted file mode 100644
index 5082cdc4..00000000
Binary files a/src/css/fonts/Roboto/Roboto-BlackItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-Bold.ttf b/src/css/fonts/Roboto/Roboto-Bold.ttf
deleted file mode 100644
index 37424579..00000000
Binary files a/src/css/fonts/Roboto/Roboto-Bold.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-BoldItalic.ttf b/src/css/fonts/Roboto/Roboto-BoldItalic.ttf
deleted file mode 100644
index e85e7fb9..00000000
Binary files a/src/css/fonts/Roboto/Roboto-BoldItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-Italic.ttf b/src/css/fonts/Roboto/Roboto-Italic.ttf
deleted file mode 100644
index c9df607a..00000000
Binary files a/src/css/fonts/Roboto/Roboto-Italic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-Light.ttf b/src/css/fonts/Roboto/Roboto-Light.ttf
deleted file mode 100644
index 0e977514..00000000
Binary files a/src/css/fonts/Roboto/Roboto-Light.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-LightItalic.ttf b/src/css/fonts/Roboto/Roboto-LightItalic.ttf
deleted file mode 100644
index 3ad14fa7..00000000
Binary files a/src/css/fonts/Roboto/Roboto-LightItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-Medium.ttf b/src/css/fonts/Roboto/Roboto-Medium.ttf
deleted file mode 100644
index e89b0b79..00000000
Binary files a/src/css/fonts/Roboto/Roboto-Medium.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-MediumItalic.ttf b/src/css/fonts/Roboto/Roboto-MediumItalic.ttf
deleted file mode 100644
index a5a41d3d..00000000
Binary files a/src/css/fonts/Roboto/Roboto-MediumItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-Regular.ttf b/src/css/fonts/Roboto/Roboto-Regular.ttf
deleted file mode 100644
index 3d6861b4..00000000
Binary files a/src/css/fonts/Roboto/Roboto-Regular.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-Thin.ttf b/src/css/fonts/Roboto/Roboto-Thin.ttf
deleted file mode 100644
index 7d084aed..00000000
Binary files a/src/css/fonts/Roboto/Roboto-Thin.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto/Roboto-ThinItalic.ttf b/src/css/fonts/Roboto/Roboto-ThinItalic.ttf
deleted file mode 100644
index c1733896..00000000
Binary files a/src/css/fonts/Roboto/Roboto-ThinItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Condensed/LICENSE.txt b/src/css/fonts/Roboto_Condensed/LICENSE.txt
deleted file mode 100644
index 75b52484..00000000
--- a/src/css/fonts/Roboto_Condensed/LICENSE.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/src/css/fonts/Roboto_Condensed/RobotoCondensed-Bold.ttf b/src/css/fonts/Roboto_Condensed/RobotoCondensed-Bold.ttf
deleted file mode 100644
index 7fe31289..00000000
Binary files a/src/css/fonts/Roboto_Condensed/RobotoCondensed-Bold.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Condensed/RobotoCondensed-BoldItalic.ttf b/src/css/fonts/Roboto_Condensed/RobotoCondensed-BoldItalic.ttf
deleted file mode 100644
index 52ef6f37..00000000
Binary files a/src/css/fonts/Roboto_Condensed/RobotoCondensed-BoldItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Condensed/RobotoCondensed-Italic.ttf b/src/css/fonts/Roboto_Condensed/RobotoCondensed-Italic.ttf
deleted file mode 100644
index 12216d67..00000000
Binary files a/src/css/fonts/Roboto_Condensed/RobotoCondensed-Italic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Condensed/RobotoCondensed-Light.ttf b/src/css/fonts/Roboto_Condensed/RobotoCondensed-Light.ttf
deleted file mode 100644
index 43dd8f42..00000000
Binary files a/src/css/fonts/Roboto_Condensed/RobotoCondensed-Light.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Condensed/RobotoCondensed-LightItalic.ttf b/src/css/fonts/Roboto_Condensed/RobotoCondensed-LightItalic.ttf
deleted file mode 100644
index 99d491b9..00000000
Binary files a/src/css/fonts/Roboto_Condensed/RobotoCondensed-LightItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Condensed/RobotoCondensed-Regular.ttf b/src/css/fonts/Roboto_Condensed/RobotoCondensed-Regular.ttf
deleted file mode 100644
index 62dd61e5..00000000
Binary files a/src/css/fonts/Roboto_Condensed/RobotoCondensed-Regular.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/LICENSE.txt b/src/css/fonts/Roboto_Mono/LICENSE.txt
deleted file mode 100644
index 75b52484..00000000
--- a/src/css/fonts/Roboto_Mono/LICENSE.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/src/css/fonts/Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf b/src/css/fonts/Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf
deleted file mode 100644
index d30055a9..00000000
Binary files a/src/css/fonts/Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/RobotoMono-VariableFont_wght.ttf b/src/css/fonts/Roboto_Mono/RobotoMono-VariableFont_wght.ttf
deleted file mode 100644
index d2b47461..00000000
Binary files a/src/css/fonts/Roboto_Mono/RobotoMono-VariableFont_wght.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-Bold.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-Bold.ttf
deleted file mode 100644
index 900fce68..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-Bold.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-BoldItalic.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-BoldItalic.ttf
deleted file mode 100644
index 4bfe29ae..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-BoldItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-ExtraLight.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-ExtraLight.ttf
deleted file mode 100644
index d5358845..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-ExtraLight.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf
deleted file mode 100644
index b28960a0..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-Italic.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-Italic.ttf
deleted file mode 100644
index 4ee4dc49..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-Italic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-Light.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-Light.ttf
deleted file mode 100644
index 276af4c5..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-Light.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-LightItalic.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-LightItalic.ttf
deleted file mode 100644
index a2801c21..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-LightItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-Medium.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-Medium.ttf
deleted file mode 100644
index 8461be77..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-Medium.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-MediumItalic.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-MediumItalic.ttf
deleted file mode 100644
index a3bfaa11..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-MediumItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-Regular.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-Regular.ttf
deleted file mode 100644
index 7c4ce36a..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-Regular.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-SemiBold.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-SemiBold.ttf
deleted file mode 100644
index 15ee6c6e..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-SemiBold.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf
deleted file mode 100644
index 8e214977..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-Thin.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-Thin.ttf
deleted file mode 100644
index ee8a3fd4..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-Thin.ttf and /dev/null differ
diff --git a/src/css/fonts/Roboto_Mono/static/RobotoMono-ThinItalic.ttf b/src/css/fonts/Roboto_Mono/static/RobotoMono-ThinItalic.ttf
deleted file mode 100644
index 40b01e40..00000000
Binary files a/src/css/fonts/Roboto_Mono/static/RobotoMono-ThinItalic.ttf and /dev/null differ
diff --git a/src/error-filtering.ts b/src/error-filtering.ts
index 95479937..f28048dc 100644
--- a/src/error-filtering.ts
+++ b/src/error-filtering.ts
@@ -30,6 +30,10 @@ const defaultMatcher: Comparator = (error, query) => {
return true
}
+export const instancePathStartsWithMatcher: Comparator = (error, query) => {
+ return error.instancePath.startsWith(query.find.instancePath as string)
+}
+
export const duplicateMatcher = (index: number) => {
return (error: ErrorObject) => error.params.i === index || error.params.j === index
}
@@ -51,7 +55,7 @@ export const authorsQueries: ErrorQuery[] = [{
message: 'must NOT have fewer than 1 items'
},
replace: {
- message: 'Use the button to add an author.'
+ message: 'Add at least one author.'
}
}, {
find: {
@@ -69,7 +73,7 @@ export const dateReleasedQueries: ErrorQuery[] = [{
schemaPath: '#/definitions/date/pattern'
},
replace: {
- message: 'Use the YYYY-MM-DD format.'
+ message: 'Wrong format. Use YYYY-MM-DD.'
}
}]
@@ -110,7 +114,7 @@ export const emailQueries = (index: number) => {
schemaPath: '#/definitions/email/pattern'
},
replace: {
- message: 'Something like bob@gmail.com, akira@yahoo.co.jp, or t.achebe@live.org.za'
+ message: 'E-mail format is invalid'
}
}] as ErrorQuery[]
}
@@ -124,7 +128,7 @@ export const identifierValueQueries = (index: number, typeIndex: number) => {
schemaPath: '#/definitions/doi/pattern'
},
replace: {
- message: 'e.g. \'10.5281/zenodo.1003149\' or \'10.7717/peerj-cs.86\'. Does not include the resolver URL.'
+ message: 'DOI format is wrong. It should be like \'10.1234/zenodo.4321\'. Click the info button for details.'
}
}
], [
@@ -134,7 +138,7 @@ export const identifierValueQueries = (index: number, typeIndex: number) => {
schemaPath: '#/definitions/url/pattern'
},
replace: {
- message: 'e.g. \'https://www.example.com\' (http, ftp, sftp hyperlinks are also supported)'
+ message: 'URL format is wrong. Do not forget the URL resolver (http, etc.)'
}
},
{
@@ -143,7 +147,7 @@ export const identifierValueQueries = (index: number, typeIndex: number) => {
schemaPath: '#/definitions/url/format'
},
replace: {
- message: 'e.g. \'https://www.example.com\' (http, ftp, sftp hyperlinks are also supported)'
+ message: 'URL format is wrong. Do not forget the URL resolver (http, etc.)'
}
}
], [
@@ -153,7 +157,7 @@ export const identifierValueQueries = (index: number, typeIndex: number) => {
schemaPath: '#/definitions/swh-identifier/pattern'
},
replace: {
- message: 'e.g. \'swh:1:rev:309cf2674ee7a0749978cf8265ab91a60aea0f7d\'. Besides \'rev\', other allowed values are: \'snp\', \'rel\', \'dir\', and \'cnt\'.'
+ message: 'SWH format is wrong. Click the info button for details.'
}
}
], [
@@ -163,7 +167,7 @@ export const identifierValueQueries = (index: number, typeIndex: number) => {
schemaPath: '#/anyOf/3/properties/value/minLength'
},
replace: {
- message: 'Zero-length identifier values are not allowed. Please type an identifier value or remove the identifier entirely.'
+ message: 'Value cannot be empty.'
}
}
]
@@ -187,7 +191,7 @@ export const keywordQueries = (index: number) => {
schemaPath: '#/properties/keywords/items/minLength'
},
replace: {
- message: 'Zero-length keywords are not allowed. Please type a keyword or remove the field entirely.'
+ message: 'Keyword cannot be empty.'
}
}] as ErrorQuery[]
}
@@ -217,7 +221,7 @@ export const messageQueries: ErrorQuery[] = [{
schemaPath: '#/properties/message/minLength'
},
replace: {
- message: '\'message\' needs to be at least 1 character long.'
+ message: 'Message cannot be empty.'
}
}]
@@ -228,7 +232,7 @@ export const orcidQueries = (index: number) => {
schemaPath: '#/definitions/orcid/pattern'
},
replace: {
- message: 'Expected format is: https://orcid.org/0000-0000-0000-0000'
+ message: 'ORCID format is invalid'
}
}] as ErrorQuery[]
}
@@ -287,6 +291,74 @@ export const repositoryQueries: ErrorQuery[] = [{
}
}]
+export const screenAuthorQueries: ErrorQuery[] = [{
+ find: {
+ instancePath: '/authors'
+ },
+ replace: {
+ message: 'Screen Authors has errors'
+ }
+}]
+
+export const screenIdentifiersQueries: ErrorQuery[] = [{
+ find: {
+ instancePath: '/identifiers'
+ },
+ replace: {
+ message: 'Screen Identifiers has errors'
+ }
+}]
+
+export const screenKeywordsQueries: ErrorQuery[] = [{
+ find: {
+ instancePath: '/keywords'
+ },
+ replace: {
+ message: 'Screen Keywords has errors'
+ }
+}]
+
+export const screenRelatedResourcesQueries: ErrorQuery[] = [{
+ find: {
+ instancePath: '/repository'
+ },
+ replace: {
+ message: 'Screen Related Resources has errors'
+ }
+}, {
+ find: {
+ instancePath: '/url'
+ },
+ replace: {
+ message: 'Screen Related Resources has errors'
+ }
+}]
+
+export const screenStartQueries: ErrorQuery[] = [{
+ find: {
+ instancePath: '/title'
+ },
+ replace: {
+ message: 'Screen Start has errors'
+ }
+}, {
+ find: {
+ instancePath: '/message'
+ },
+ replace: {
+ message: 'Screen Start has errors'
+ }
+}]
+
+export const screenVersionSpecificQueries: ErrorQuery[] = [{
+ find: {
+ instancePath: '/date-released'
+ },
+ replace: {
+ message: 'Screen Version Specific has errors'
+ }
+}]
+
export const titleQueries: ErrorQuery[] = [{
find: {
instancePath: '',
@@ -302,7 +374,7 @@ export const titleQueries: ErrorQuery[] = [{
schemaPath: '#/properties/title/minLength'
},
replace: {
- message: '\'title\' needs to be at least 1 character long.'
+ message: 'Title cannot be empty.'
}
}]
diff --git a/src/index.template.html b/src/index.template.html
index 9cddaf6b..b91e4dbf 100644
--- a/src/index.template.html
+++ b/src/index.template.html
@@ -7,7 +7,7 @@
-
+
diff --git a/src/router/routes.ts b/src/router/routes.ts
index 57f21d71..8a06c54d 100644
--- a/src/router/routes.ts
+++ b/src/router/routes.ts
@@ -9,6 +9,10 @@ const routes: RouteRecordRaw[] = [
path: '/landing',
component: () => import('src/components/LayoutLanding.vue')
},
+ {
+ path: '/update',
+ component: () => import('src/components/LayoutUpdate.vue')
+ },
{
path: '/start',
component: () => import('src/components/LayoutStepper.vue'),
@@ -19,11 +23,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('src/components/LayoutStepper.vue'),
children: [{ path: '', component: () => import('src/components/ScreenAuthors.vue') }]
},
- {
- path: '/finish-minimum',
- component: () => import('src/components/LayoutStepper.vue'),
- children: [{ path: '', component: () => import('src/components/ScreenFinishMinimum.vue') }]
- },
{
path: '/identifiers',
component: () => import('src/components/LayoutStepper.vue'),
@@ -55,9 +54,14 @@ const routes: RouteRecordRaw[] = [
children: [{ path: '', component: () => import('src/components/ScreenVersionSpecific.vue') }]
},
{
- path: '/finish-advanced',
+ path: '/extra-cff-fields',
+ component: () => import('src/components/LayoutStepper.vue'),
+ children: [{ path: '', component: () => import('src/components/ScreenExtraCffFields.vue') }]
+ },
+ {
+ path: '/finish',
component: () => import('src/components/LayoutStepper.vue'),
- children: [{ path: '', component: () => import('src/components/ScreenFinishAdvanced.vue') }]
+ children: [{ path: '', component: () => import('src/components/ScreenFinish.vue') }]
},
{
path: '/404',
diff --git a/src/scroll-to-bottom.ts b/src/scroll-to-bottom.ts
index 3f18d80e..c1c3c6f8 100644
--- a/src/scroll-to-bottom.ts
+++ b/src/scroll-to-bottom.ts
@@ -1,7 +1,7 @@
export const scrollToBottom = (targetClass = 'bottom') => {
document.getElementsByClassName(targetClass)[0].scrollIntoView({
behavior: 'smooth',
- block: 'nearest',
+ block: 'start',
inline: 'nearest'
})
}
diff --git a/src/store/app.ts b/src/store/app.ts
index 061d0ed9..0c2d4ab3 100644
--- a/src/store/app.ts
+++ b/src/store/app.ts
@@ -1,82 +1,87 @@
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
-export type StepNameType = 'start' | 'authors' | 'finish-minimum' | 'identifiers' | 'related-resources' |
- 'abstract' | 'keywords' | 'license' | 'version-specific' | 'finish-advanced'
-
-const state = ref({
- showAdvanced: false,
- stepIndex: 0
-})
+export type StepNameType = 'start' | 'authors' | 'identifiers' | 'related-resources' |
+ 'abstract' | 'keywords' | 'license' | 'version-specific' | 'extra-cff-fields' | 'finish'
const stepNames = [
'start',
'authors',
- 'finish-minimum',
'identifiers',
'related-resources',
'abstract',
'keywords',
'license',
'version-specific',
- 'finish-advanced'
+ 'extra-cff-fields',
+ 'finish'
] as Array
-const advancedStepNames = new Set([
- 'identifiers',
- 'related-resources',
- 'abstract',
- 'keywords',
- 'license',
- 'version-specific'
-])
+const state = ref({
+ stepIndex: 0,
+ screenVisited: Array(stepNames.length).fill(false) as Array
+})
const firstStepIndex = 0
-const lastStepIndex = computed(() => state.value.showAdvanced ? stepNames.indexOf('finish-advanced') : stepNames.indexOf('finish-minimum'))
+const lastStepIndex = computed(() => stepNames.length - 1)
const stepName = computed(() => stepNames[state.value.stepIndex])
export const useApp = () => {
const router = useRouter()
+ const focusFormTitle = () => {
+ const element = window.document.getElementById('form-title')
+ if (!element) return
+ element.focus()
+ }
+ const visitScreen = (screenName: StepNameType) => {
+ for (let i = 0; i <= stepNames.indexOf(screenName); i++) {
+ state.value.screenVisited[i] = true
+ }
+ }
return {
cannotGoBack: computed(() => state.value.stepIndex === firstStepIndex),
cannotGoForward: computed(() => state.value.stepIndex === lastStepIndex.value),
+ currentStepIndex: computed(() => state.value.stepIndex),
lastStepIndex,
- showAdvanced: computed(() => state.value.showAdvanced),
stepName,
+ stepNames,
navigateDirect: (newStepName: StepNameType) => {
- if (!stepNames.includes(newStepName)) {
+ if (![...stepNames, 'finish'].includes(newStepName)) {
return
}
- if (advancedStepNames.has(newStepName)) {
- state.value.showAdvanced = true
- }
state.value.stepIndex = stepNames.indexOf(newStepName)
- },
- setStepName: async (newStepName: StepNameType) => {
- state.value.stepIndex = stepNames.indexOf(newStepName)
- await router.push({ path: `/${stepName.value}` })
+ visitScreen(newStepName)
},
navigateNext: async () => {
- if (state.value.showAdvanced === true && stepName.value === 'authors') {
- // extra increment to step past finish-minimum
- state.value.stepIndex++
- }
if (state.value.stepIndex < lastStepIndex.value) {
state.value.stepIndex++
+ visitScreen(stepName.value)
await router.push({ path: `/${stepName.value}` })
+ focusFormTitle()
}
},
navigatePrevious: async () => {
- if (state.value.showAdvanced === true && stepName.value === 'identifiers') {
- // extra decrement to step past finish-minimum
- state.value.stepIndex--
- }
if (state.value.stepIndex > firstStepIndex) {
state.value.stepIndex--
+ visitScreen(stepName.value)
await router.push({ path: `/${stepName.value}` })
+ focusFormTitle()
}
},
- setShowAdvanced: (newShowAdvanced: boolean) => { state.value.showAdvanced = newShowAdvanced }
+ resetState: () => {
+ state.value.stepIndex = 0
+ state.value.screenVisited.fill(false)
+ },
+ screenVisited: (screenName: StepNameType) => {
+ return state.value.screenVisited[stepNames.indexOf(screenName)]
+ },
+ setStepName: async (newStepName: StepNameType) => {
+ state.value.stepIndex = stepNames.indexOf(newStepName)
+ visitScreen(stepName.value)
+ await router.push({ path: `/${stepName.value}` })
+ focusFormTitle()
+ },
+ visitScreen
}
}
diff --git a/src/store/cff.ts b/src/store/cff.ts
index 290811f9..83667622 100644
--- a/src/store/cff.ts
+++ b/src/store/cff.ts
@@ -1,5 +1,7 @@
-import { AuthorsType, CffType, IdentifiersType, KeywordsType, TypeType } from 'src/types'
+import * as yaml from 'js-yaml'
+import { AuthorType, AuthorsType, CffType, IdentifierType, IdentifierTypeType, IdentifiersType, KeywordsType, TypeType } from 'src/types'
import { computed, ref } from 'vue'
+import camelCase from 'camelcase'
const getInitialData = () => {
return {
@@ -23,6 +25,138 @@ const getInitialData = () => {
}
const cff = ref(getInitialData())
+const extraCffFields = ref('')
+const authorProperties = [
+ 'affiliation',
+ 'email',
+ 'familyNames',
+ 'givenNames',
+ 'nameParticle',
+ 'nameSuffix',
+ 'orcid'
+]
+const identifierProperties = ['type', 'value', 'description']
+
+export const updateCff = (newCffstr: string) => {
+ let msg = [] as string[]
+ let success = false
+ try {
+ const existingCff = yaml.load(newCffstr)
+ extraCffFields.value = ''
+
+ if (!existingCff) {
+ throw new Error('Error: CFF is empty.')
+ } else if (Array.isArray(existingCff) || typeof existingCff === 'string') {
+ throw new Error('Error: CFF is invalid. It should be a YAML map.')
+ }
+
+ const existingCffProperties = Object.getOwnPropertyNames(existingCff) as Array
+ if (existingCffProperties.length === 0) {
+ throw new Error('Error: CFF is empty.')
+ } else if (existingCffProperties.filter(x => x === '[object Object]').length > 0) {
+ throw new Error('Error: invalid object in keys (did you use {} as key?).')
+ } else if (existingCffProperties.filter(x => !x).length > 0) {
+ throw new Error('Error: invalid null property.')
+ }
+
+ cff.value = getInitialData()
+ const cffProperties = Object.getOwnPropertyNames(cff.value) as Array
+ existingCffProperties.forEach((property) => {
+ const camelCaseProperty = camelCase(property) as keyof CffType
+ const value = existingCff[property]
+
+ if (cffProperties.includes(camelCaseProperty)) {
+ // Treating all special cases
+ if (property === 'type') {
+ if (value !== 'software' && value !== 'dataset') {
+ msg.push(`Invalid type '${value as string}'. Using 'software' instead.`)
+ cff.value.type = 'software'
+ } else {
+ cff.value.type = value
+ }
+ } else if (property === 'cff-version') {
+ cff.value.cffVersion = '1.2.0'
+ if (value !== '1.2.0') {
+ msg.push('cff-version was updated to 1.2.0. This might led to some issues, so verify before downloading.')
+ }
+ } else if (property === 'authors') {
+ cff.value.authors = [] as AuthorsType
+
+ const existingCffAuthors = value as Array>
+ existingCffAuthors.forEach((existingAuthor) => {
+ const newAuthor: AuthorType = {}
+ Object.getOwnPropertyNames(existingAuthor)
+ .forEach((authorProperty) => {
+ const camelCaseAuthorProperty = camelCase(authorProperty) as keyof typeof newAuthor
+ const value = existingAuthor[authorProperty]
+ if (authorProperties.includes(camelCaseAuthorProperty)) {
+ newAuthor[camelCaseAuthorProperty] = value
+ } else {
+ msg.push(`Property '${authorProperty}: ${value}' inside 'authors' was ignored. Check if the key is correct.`)
+ }
+ })
+ cff.value.authors.push(newAuthor)
+ })
+ } else if (property === 'identifiers') {
+ cff.value.identifiers = [] as IdentifiersType
+
+ const existingCffIdentifiers = value as Array>
+ existingCffIdentifiers.forEach((existingIdentifier) => {
+ const newIdentifier: IdentifierType = { type: 'other', value: '' }
+ Object.getOwnPropertyNames(existingIdentifier)
+ .forEach((identifierProperty) => {
+ const camelCaseIdentifierProperty = camelCase(identifierProperty) as keyof typeof newIdentifier
+ const value = existingIdentifier[identifierProperty]
+ if (identifierProperties.includes(camelCaseIdentifierProperty)) {
+ if (camelCaseIdentifierProperty !== 'type' ||
+ ['doi', 'url', 'swh', 'other'].includes(value)) {
+ newIdentifier[camelCaseIdentifierProperty] = existingIdentifier[identifierProperty] as IdentifierTypeType
+ } else if (camelCaseIdentifierProperty === 'type') {
+ msg.push(`Invalid value '${value}' for identifier type. Using 'other' instead.`)
+ newIdentifier.type = 'other'
+ }
+ } else {
+ msg.push(`Property '${identifierProperty}: ${value}' inside 'identifiers' was ignored. Check if the key is correct.`)
+ }
+ })
+ cff.value.identifiers?.push(newIdentifier)
+ })
+ } else if (property === 'date-released') {
+ const value = existingCff[property]
+ if (typeof value === 'string') {
+ cff.value.dateReleased = value
+ } else {
+ cff.value.dateReleased = (value as Date).toISOString().slice(0, 10)
+ }
+ } else {
+ // No special treatment, just add to cff
+ cff.value[camelCaseProperty] = existingCff[property]
+ }
+ } else {
+ // Existing CFF property is not part of cff, so add it to extra
+ const thisYamlElement = {}
+ thisYamlElement[property] = existingCff[property]
+ extraCffFields.value += yaml.dump(thisYamlElement)
+ msg.push(`Property '${property as string}' was not identified as a basic field, so it was passed as an extra cff field.`)
+ }
+ })
+
+ success = true
+ } catch (error) {
+ if (error instanceof yaml.YAMLException) {
+ msg = ['Error: could not parse CFF because of the following YAML error: ' + error.message]
+ } else if (error instanceof Error) {
+ msg = [error.message]
+ } else {
+ msg = ['Uncaught error. Please report this issue.']
+ }
+ }
+
+ return {
+ success,
+ msg
+ }
+}
export const useCff = () => {
return {
@@ -31,6 +165,7 @@ export const useCff = () => {
commit: computed(() => cff.value.commit),
cffVersion: computed(() => cff.value.cffVersion),
dateReleased: computed(() => cff.value.dateReleased),
+ extraCffFields: computed(() => extraCffFields.value),
identifiers: computed(() => cff.value.identifiers),
keywords: computed(() => cff.value.keywords),
license: computed(() => cff.value.license),
@@ -46,6 +181,7 @@ export const useCff = () => {
setAuthors: (newAuthors: AuthorsType) => { cff.value.authors = newAuthors },
setCommit: (newCommit: string) => { cff.value.commit = newCommit === '' ? undefined : newCommit },
setDateReleased: (newDateReleased: string) => { cff.value.dateReleased = newDateReleased === '' ? undefined : newDateReleased },
+ setExtraCffFields: (newExtraCffFields: string) => { extraCffFields.value = newExtraCffFields },
setIdentifiers: (newIdentifiers: IdentifiersType) => { cff.value.identifiers = newIdentifiers === [] ? undefined : newIdentifiers },
setKeywords: (newKeywords: KeywordsType) => { cff.value.keywords = newKeywords === [] ? undefined : newKeywords },
setLicense: (newLicense: string) => { cff.value.license = newLicense === '' ? undefined : newLicense },
@@ -59,6 +195,7 @@ export const useCff = () => {
setVersion: (newVersion: string) => { cff.value.version = newVersion === '' ? undefined : newVersion },
reset: () => {
cff.value = getInitialData()
+ extraCffFields.value = ''
}
}
}
diff --git a/src/store/cffstr.ts b/src/store/cffstr.ts
index ed7ef8a4..629cb710 100644
--- a/src/store/cffstr.ts
+++ b/src/store/cffstr.ts
@@ -26,7 +26,8 @@ export const useCffstr = () => {
title,
type,
url,
- version
+ version,
+ extraCffFields
} = useCff()
const notEmpty = (value: unknown, prop: unknown, subject: unknown) => {
@@ -59,9 +60,9 @@ export const useCffstr = () => {
const makeCffstr = () => {
const kebabed = makeJavascriptObject()
- const yamlString = yaml.dump(kebabed, { indent: 2, lineWidth: 53 })
+ const yamlString = yaml.dump(kebabed, { indent: 2, lineWidth: 60 })
const generatedBy = '# This CITATION.cff file was generated with cffinit.\n# Visit https://bit.ly/cffinit to generate yours today!\n\n'
- return generatedBy + yamlString
+ return generatedBy + yamlString + extraCffFields.value
}
return {
jsObject: computed(makeJavascriptObject),
diff --git a/src/store/help-data.ts b/src/store/help-data.ts
new file mode 100644
index 00000000..6739fbbf
--- /dev/null
+++ b/src/store/help-data.ts
@@ -0,0 +1,397 @@
+export const helpData = {
+ abstract: {
+ title: 'abstract',
+ url: [
+ {
+ text: 'Documentation for abstract.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#abstract'
+ }
+ ],
+ description: 'A description or summary of the work.'
+ },
+ authorAffiliation: {
+ title: 'affiliation',
+ url: [
+ {
+ text: 'Documentation for affiliation.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersonaffiliation'
+ }
+ ],
+ description: 'The person\'s affiliation.',
+ examples: [
+ 'Netherlands eScience Center',
+ 'German Aerospace Center (DLR)'
+ ]
+ },
+ authorEmail: {
+ title: 'email',
+ url: [
+ {
+ text: 'Documentation for email.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersonemail'
+ }
+ ],
+ description: 'The person\'s email address.',
+ examples: [
+ 'mail@research-project.org'
+ ]
+ },
+ authorNames: {
+ title: 'given-names, name-particle, family-names, name-suffix',
+ url: [
+ {
+ text: 'Documentation for given-names.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersongiven-names'
+ },
+ {
+ text: 'Documentation for name-particle.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersonname-particle'
+ },
+ {
+ text: 'Documentation for family-name.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersonfamily-names'
+ },
+ {
+ text: 'Documentation for name-suffix.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersonname-suffix'
+ }
+ ],
+ description: 'The person\'s full name, split into fours parts: The given names, a possible name particle, the family names, and a possible name suffix',
+ examples: [
+ 'given-name: John',
+ 'name-particle: von',
+ 'family-name: Doe',
+ 'name-suffix: Jr.'
+ ]
+ },
+ authorGivenNames: {
+ title: 'given-names',
+ url: [
+ {
+ text: 'Documentation for given-names.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersongiven-names'
+ }
+ ],
+ description: 'The person\'s given names.',
+ examples: [
+ 'Jane',
+ 'John'
+ ]
+ },
+ authorLastNames: {
+ title: 'name-particle, family-names, name-suffix',
+ url: [
+ {
+ text: 'Documentation for name-particle.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersonname-particle'
+ },
+ {
+ text: 'Documentation for family-name.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersonfamily-names'
+ },
+ {
+ text: 'Documentation for name-suffix.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersonname-suffix'
+ }
+ ],
+ description: 'The person\'s last names, split into parts.',
+ examples: [
+ 'name-particle: von',
+ 'family-name: Doe',
+ 'name-suffix: Jr.'
+ ]
+ },
+ authorOrcid: {
+ title: 'orcid',
+ url: [
+ {
+ text: 'Documentation for orcid.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionspersonorcid'
+ },
+ {
+ text: 'https://orcid.org',
+ link: 'https://orcid.org'
+ }
+ ],
+ description: 'The person\'s ORCID identifier.',
+ examples: [
+ 'https://orcid.org/0000-0003-4925-7248'
+ ]
+ },
+ authors: {
+ title: 'authors',
+ url: [
+ {
+ text: 'Documentation for authors.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#authors'
+ },
+ {
+ text: 'How to deal with unknown individual authors?',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#how-to-deal-with-unknown-individual-authors'
+ }
+ ],
+ description: 'The authors of a software or dataset. TIP: you can use the ORCID of the author to autocomplete author information.',
+ examples: [
+ ' given-names: Jane\n family-names: Doe',
+ ' name: "The Research Software project"',
+ ' given-names: John\n family-names: Doe\n name: "The Research Software project"'
+ ]
+ },
+ commit: {
+ title: 'commit',
+ url: [
+ {
+ text: 'Documentation for commit.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#commit'
+ }
+ ],
+ description: 'The commit hash or revision number of the software version.',
+ examples: [
+ '1ff847d81f29c45a3a1a5ce73d38e45c2f319bba',
+ 'Revision: 8612'
+ ]
+ },
+ dateReleased: {
+ title: 'date-released',
+ url: [
+ {
+ text: 'Documentation for date-released.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#date-released'
+ }
+ ],
+ description: 'The date the work has been released.'
+ },
+ identifierDescription: {
+ title: 'description',
+ url: [
+ {
+ text: 'Documentation for description.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionsidentifier-description'
+ }
+ ],
+ description: 'A description of the identifier.',
+ examples: [
+ 'The concept DOI of the work.',
+ 'The URL of version 1.1.0 of the software',
+ 'The Software Heritage link for version 1.1.0.',
+ 'The ArXiv deposit of the encompassing paper.'
+ ]
+ },
+ identifierDoi: {
+ title: 'doi',
+ url: [
+ {
+ text: 'Documentation for doi.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionsdoi'
+ },
+ {
+ text: 'Wikipedia page for DOI',
+ link: 'https://en.wikipedia.org/wiki/Digital_object_identifier'
+ }
+ ],
+ description: 'The DOI of the work. Do not include the resolver URL.',
+ examples: [
+ '10.5281/zenodo.1003150',
+ '10.7717/peerj-cs.86'
+ ]
+ },
+ identifierOther: {
+ title: 'other',
+ url: [
+ {
+ text: 'Documentation for other.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionsidentifier'
+ }
+ ],
+ description: 'An identifier that does not fit in the other categories.',
+ examples: [
+ 'arXiv:2103.06681'
+ ]
+ },
+ identifierSwh: {
+ title: 'swh',
+ url: [
+ {
+ text: 'Documentation for swh.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionsswh-identifier'
+ },
+ {
+ text: 'Software Heritage site.',
+ link: 'https://www.softwareheritage.org/'
+ }
+ ],
+ description: 'The Software Heritage identifier. Besides \'rev\', other allowed values are: \'snp\', \'rel\', \'dir\', and \'cnt\'.',
+ examples: [
+ 'swh:1:rev:309cf2674ee7a0749978cf8265ab91a60aea0f7d'
+ ]
+ },
+ identifierUrl: {
+ title: 'url',
+ url: [
+ {
+ text: 'Documentation for url.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#definitionsurl'
+ }
+ ],
+ description: 'A URL.',
+ examples: [
+ 'https://research-software-project.org',
+ 'http://research-software-project.org',
+ 'sftp://files.research-software-project.org',
+ 'ftp://files.research-software-project.org'
+ ]
+ },
+ identifiers: {
+ title: 'identifiers',
+ url: [
+ {
+ text: 'Documentation for identifiers.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#identifiers'
+ }
+ ],
+ description: 'The identifiers of the work, such as DOIs, Software Heritage deposits, and URLs for relevant objects.',
+ examples: [
+ 'DOI: 10.5281/zenodo.1003149 - The concept DOI of the work',
+ 'SWH: swh:1:dir:bc286860f423ea7ced246ba7458eef4b4541cf2d - The Software Heritage for version 1.1.0',
+ 'URL: https://github.com/citation-file-format/citation-file-format/releases/tag/1.1.0 - The GitHub release URL of tag 1.1.0',
+ 'OTHER: arXiv:2103.06681 - The ArXiv preprint of the paper'
+ ]
+ },
+ keywords: {
+ title: 'keywords',
+ url: [
+ {
+ text: 'Documentation for keywords.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#keywords'
+ }
+ ],
+ description: 'Keywords that describe the work.',
+ examples: [
+ 'keyword',
+ 'other-keyword',
+ 'Yet Another Keyword'
+ ]
+ },
+ license: {
+ title: 'license',
+ url: [
+ {
+ text: 'Documentation for license.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#license'
+ }
+ ],
+ description: 'The SPDX license identifier for the license under which the work is available.',
+ examples: [
+ 'Apache-2.0',
+ 'MIT',
+ 'GPL-3.0'
+ ]
+ },
+ message: {
+ title: 'message',
+ url: [
+ {
+ text: 'Documentation for message.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#message'
+ }
+ ],
+ description: 'A message to the human reader of the CITATION.cff file to let them know what to do with the citation metadata.',
+ examples: [
+ 'If you use this software, please cite it using the metadata from this file.',
+ 'Please cite this software using these metadata.',
+ 'Please cite this software using the metadata from "preferred-citation".'
+ ]
+ },
+ repository: {
+ title: 'repository',
+ url: [
+ {
+ text: 'Documentation for repository.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#repository'
+ }
+ ],
+ description: 'URL of the work in a repository/archive that is neither a source code repository nor a build artifact repository',
+ examples: [
+ 'https://ascl.net/2105.013'
+ ]
+ },
+ repositoryArtifact: {
+ title: 'repository-artifact',
+ url: [
+ {
+ text: 'Documentation for repository-artifact.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#repository-artifact'
+ }
+ ],
+ description: 'URL of the work in a build artifact/binary repository',
+ examples: [
+ 'https://search.maven.org/artifact/org.corpus-tools/cff-maven-plugin/0.4.0/maven-plugin'
+ ]
+ },
+ repositoryCode: {
+ title: 'repository-code',
+ url: [
+ {
+ text: 'Documentation for repository-code.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#repository-code'
+ }
+ ],
+ description: 'URL of the work in a source code repository',
+ examples: [
+ 'https://github.com/citation-file-format/citation-file-format'
+ ]
+ },
+ title: {
+ title: 'title',
+ url: [
+ {
+ text: 'Documentation for title.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#title'
+ }
+ ],
+ description: 'The name of the software or dataset.',
+ examples: [
+ 'cffconvert',
+ 'Firefox',
+ 'LibreOffice'
+ ]
+ },
+ type: {
+ title: 'type',
+ url: [
+ {
+ text: 'Documentation for type.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#type'
+ }
+ ],
+ description: 'The type of the work that is being described by this CITATION.cff file.'
+ },
+ url: {
+ title: 'url',
+ url: [
+ {
+ text: 'Documentation for url.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#url'
+ }
+ ],
+ description: 'URL of the landing page/website for the work',
+ examples: [
+ 'https://citation-file-format.github.io/'
+ ]
+ },
+ version: {
+ title: 'version',
+ url: [
+ {
+ text: 'Documentation for version.',
+ link: 'https://github.com/citation-file-format/citation-file-format/blob/1.2.0/schema-guide.md#version'
+ }
+ ],
+ description: 'The version of the software or dataset.',
+ examples: [
+ '1.2.0',
+ '1.2',
+ '21.10 (Impish Indri)'
+ ]
+ }
+}
diff --git a/src/store/stepper-errors.ts b/src/store/stepper-errors.ts
index 15cb89b6..5ffc5ffc 100644
--- a/src/store/stepper-errors.ts
+++ b/src/store/stepper-errors.ts
@@ -1,32 +1,60 @@
-import { computed, ref } from 'vue'
+import {
+ byError,
+ instancePathStartsWithMatcher,
+ screenAuthorQueries,
+ screenIdentifiersQueries,
+ screenKeywordsQueries,
+ screenRelatedResourcesQueries,
+ screenStartQueries,
+ screenVersionSpecificQueries
+} from 'src/error-filtering'
+import { computed } from 'vue'
+import { useCff } from 'src/store/cff'
+import { useValidation } from 'src/store/validation'
-const reset = () => {
- return {
- authors: true,
- identifiers: false,
- keywords: false,
- relatedResources: false,
- start: true,
- versionSpecific: false
- }
-}
+const { errors } = useValidation()
+const { extraCffFields } = useCff()
-const state = ref(reset())
+const errorStateScreenAuthors = computed(() => {
+ return screenAuthorQueries
+ .filter(byError(errors.value, instancePathStartsWithMatcher))
+ .length > 0
+})
+const errorStateScreenIdentifiers = computed(() => {
+ return screenIdentifiersQueries
+ .filter(byError(errors.value, instancePathStartsWithMatcher))
+ .length > 0
+})
+const errorStateScreenKeywords = computed(() => {
+ return screenKeywordsQueries
+ .filter(byError(errors.value, instancePathStartsWithMatcher))
+ .length > 0
+})
+const errorStateScreenRelatedResources = computed(() => {
+ return screenRelatedResourcesQueries
+ .filter(byError(errors.value, instancePathStartsWithMatcher))
+ .length > 0
+})
+const errorStateScreenStart = computed(() => {
+ return screenStartQueries
+ .filter(byError(errors.value)) // One of the possible errors is instancePath == '', so we use a traditional approach here
+ .length > 0
+})
+const errorStateScreenVersionSpecific = computed(() => {
+ return screenVersionSpecificQueries
+ .filter(byError(errors.value, instancePathStartsWithMatcher))
+ .length > 0
+})
-export const useStepperErrors = () => {
- return {
- errorStateScreenAuthors: computed(() => state.value.authors),
- errorStateScreenIdentifiers: computed(() => state.value.identifiers),
- errorStateScreenKeywords: computed(() => state.value.keywords),
- errorStateScreenRelatedResources: computed(() => state.value.relatedResources),
- errorStateScreenStart: computed(() => state.value.start),
- errorStateScreenVersionSpecific: computed(() => state.value.versionSpecific),
- reset: () => (state.value = reset()),
- setErrorStateScreenAuthors: (hasErrors: boolean) => (state.value.authors = hasErrors),
- setErrorStateScreenIdentifiers: (hasErrors: boolean) => (state.value.identifiers = hasErrors),
- setErrorStateScreenKeywords: (hasErrors: boolean) => (state.value.keywords = hasErrors),
- setErrorStateScreenRelatedResources: (hasErrors: boolean) => (state.value.relatedResources = hasErrors),
- setErrorStateScreenStart: (hasErrors: boolean) => (state.value.start = hasErrors),
- setErrorStateScreenVersionSpecific: (hasErrors: boolean) => (state.value.versionSpecific = hasErrors)
- }
+export const errorPerStep = {
+ start: errorStateScreenStart,
+ authors: errorStateScreenAuthors,
+ identifiers: errorStateScreenIdentifiers,
+ 'related-resources': errorStateScreenRelatedResources,
+ abstract: computed(() => false),
+ keywords: errorStateScreenKeywords,
+ license: computed(() => false),
+ 'version-specific': errorStateScreenVersionSpecific,
+ 'extra-cff-fields': computed(() => { return extraCffFields.value.length > 0 && errors.value.length > 0 }),
+ finish: computed(() => false)
}
diff --git a/src/store/validation.ts b/src/store/validation.ts
index d1c64764..71aafdc7 100644
--- a/src/store/validation.ts
+++ b/src/store/validation.ts
@@ -1,8 +1,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
+import * as yaml from 'js-yaml'
import Ajv, { ErrorObject } from 'ajv'
import addFormats from 'ajv-formats'
import { computed } from 'vue'
import schema from 'src/schemas/1.2.0/schema.json'
+import { useCff } from 'src/store/cff'
import { useCffstr } from 'src/store/cffstr'
const ajv = new Ajv({ allErrors: true })
@@ -10,17 +12,56 @@ addFormats(ajv)
ajv.addSchema(schema)
type ajvErrorType = ErrorObject, unknown>
+const { extraCffFields } = useCff()
const { jsObject } = useCffstr()
-export const useValidation = () => {
- return {
- errors: computed(() => {
+const errors = computed(() => {
+ try {
+ if (extraCffFields.value.trim() === '') {
ajv.validate(schema.$id, jsObject.value)
- if (ajv.errors) {
- return ajv.errors
- } else {
- return [] as ajvErrorType[]
+ } else {
+ const extraYaml = yaml.load(extraCffFields.value) as Record
+ const duplicateKeys = Object.keys(extraYaml).filter(x => x in jsObject.value)
+ if (duplicateKeys.length > 0) {
+ throw Error(`Duplicate keys: '${duplicateKeys.join("', '")}'`)
}
+ ajv.validate(schema.$id, {
+ ...jsObject.value,
+ ...extraYaml
+ })
+ }
+ } catch (error) {
+ ajv.validate(schema.$id, {
+ ...jsObject.value
})
+ let msg = ''
+ if (error instanceof yaml.YAMLException) {
+ msg = 'YAML Error: ' + error.message
+ } else if (error instanceof Error) {
+ msg = error.message
+ } else {
+ msg = 'Uncaught error. Please report this issue.'
+ }
+ if (!(ajv.errors)) {
+ ajv.errors = [] as ErrorObject[]
+ }
+ ajv.errors.push({
+ keyword: 'Extra CFF Fields error',
+ instancePath: '',
+ schemaPath: '',
+ params: [],
+ message: msg
+ } as ErrorObject)
+ }
+ if (ajv.errors) {
+ return ajv.errors
+ } else {
+ return [] as ajvErrorType[]
+ }
+})
+
+export const useValidation = () => {
+ return {
+ errors: errors
}
}
diff --git a/test/jest/__tests__/store/app.jest.spec.ts b/test/jest/__tests__/store/app.jest.spec.ts
new file mode 100644
index 00000000..dbec4f0f
--- /dev/null
+++ b/test/jest/__tests__/store/app.jest.spec.ts
@@ -0,0 +1,69 @@
+import { StepNameType, useApp } from 'src/store/app'
+import { describe, expect, it, jest } from '@jest/globals'
+import { useCff } from 'src/store/cff'
+
+const routerPushMock = jest.fn()
+
+jest.mock('vue-router', () => ({
+ useRouter: () => ({
+ push: routerPushMock
+ })
+}))
+
+describe('useApp', () => {
+ const {
+ cannotGoBack,
+ cannotGoForward,
+ lastStepIndex,
+ navigateNext,
+ navigatePrevious,
+ setStepName,
+ stepName
+ } = useApp()
+ const { reset: resetCffData } = useCff()
+ const stepNames = [
+ 'start',
+ 'authors',
+ 'identifiers',
+ 'related-resources',
+ 'abstract',
+ 'keywords',
+ 'license',
+ 'version-specific',
+ 'extra-cff-fields'
+ ] as Array
+
+ beforeEach(() => {
+ resetCffData()
+ void setStepName('start')
+ })
+
+ it('should start with state on step 0', () => {
+ expect(stepName.value).toBe('start')
+ })
+
+ describe('navigation', () => {
+ it(`should have ${stepNames.length} as lastIndex`, () => {
+ expect(lastStepIndex.value).toBe(stepNames.length)
+ })
+ it('should be navigable with next and previous (except at boundaries)', () => {
+ expect(cannotGoBack.value).toBe(true)
+ stepNames.forEach((step) => {
+ expect(stepName.value).toBe(step)
+ expect(cannotGoForward.value).toBe(false)
+ void navigateNext()
+ expect(cannotGoBack.value).toBe(false)
+ })
+ expect(stepName.value).toBe('finish')
+ expect(cannotGoForward.value).toBe(true)
+ Array.from(stepNames).reverse().forEach((step) => {
+ expect(cannotGoBack.value).toBe(false)
+ void navigatePrevious()
+ expect(stepName.value).toBe(step)
+ expect(cannotGoForward.value).toBe(false)
+ })
+ expect(stepName.value).toBe('start')
+ expect(cannotGoBack.value).toBe(true)
+ })
+ })
+})
diff --git a/test/jest/__tests__/store/cffstr.jest.spec.ts b/test/jest/__tests__/store/cffstr.jest.spec.ts
index f9647180..66d102f0 100644
--- a/test/jest/__tests__/store/cffstr.jest.spec.ts
+++ b/test/jest/__tests__/store/cffstr.jest.spec.ts
@@ -1,52 +1,67 @@
+import * as yaml from 'js-yaml'
import { beforeEach, describe, expect, it } from '@jest/globals'
import { useCff } from 'src/store/cff'
import { useCffstr } from 'src/store/cffstr'
describe('useCffstr', () => {
const cff = useCff()
- const generatedBy = '# This CITATION.cff file was generated with cffinit.\n# Visit https://bit.ly/cffinit to generate yours today!\n\n'
const { cffstr } = useCffstr()
+ const parsedCffStr = () => { return yaml.load(cffstr.value) }
+ const cffMinimumFields = {
+ 'cff-version': '1.2.0',
+ type: 'software',
+ title: '',
+ message: 'If you use this software, please cite it using the metadata from this file.',
+ authors: []
+ }
+ const cffstrFields = [
+ { field: 'abstract', value: 'Description', cffFunction: cff.setAbstract },
+ { field: 'authors', value: [{ 'given-names': 'John', 'family-names': 'Doe', orcid: 'https://1234-1234-1234-123X' }], cffFunction: cff.setAuthors },
+ { field: 'commit', value: '1234567890abcde', cffFunction: cff.setCommit },
+ { field: 'date-released', value: '2022-01-01', cffFunction: cff.setDateReleased },
+ { field: 'identifiers', value: [{ type: 'doi', value: '10.5281/zenodo.5171937' }], cffFunction: cff.setIdentifiers },
+ { field: 'keywords', value: ['kw1', 'kw2'], cffFunction: cff.setKeywords },
+ { field: 'license', value: 'Apache-2.0', cffFunction: cff.setLicense },
+ { field: 'message', value: 'Cite me!', cffFunction: cff.setMessage },
+ { field: 'repository-artifact', value: 'https://a', cffFunction: cff.setRepositoryArtifact },
+ { field: 'repository-code', value: 'https://a', cffFunction: cff.setRepositoryCode },
+ { field: 'repository', value: 'https://a', cffFunction: cff.setRepository },
+ { field: 'title', value: 'Title', cffFunction: cff.setTitle },
+ { field: 'type', value: 'dataset', cffFunction: cff.setType },
+ { field: 'url', value: 'https://a', cffFunction: cff.setUrl },
+ { field: 'version', value: '1.2.3', cffFunction: cff.setVersion }
+ ]
beforeEach(() => {
cff.reset()
})
describe('initial content', () => {
it('should only have fields with defaults', () => {
- const expected = generatedBy + "cff-version: 1.2.0\ntitle: ''\nmessage: >-\n If you use this software, please cite it using the\n metadata from this file.\ntype: software\nauthors: []\n"
- expect(cffstr.value).toEqual(expected)
+ const expected = cffMinimumFields
+ expect(parsedCffStr()).toEqual(expected)
})
})
- describe('with title', () => {
- beforeEach(() => {
- cff.setTitle('sometitle')
- })
-
- it('should have title', () => {
- const expected = generatedBy + 'cff-version: 1.2.0\ntitle: sometitle\nmessage: >-\n If you use this software, please cite it using the\n metadata from this file.\ntype: software\nauthors: []\n'
- expect(cffstr.value).toEqual(expected)
- })
- })
-
- describe('with keyword', () => {
- beforeEach(() => {
- cff.setKeywords(['keyword1'])
- })
-
- it('should have a keyword', () => {
- const expected = generatedBy + "cff-version: 1.2.0\ntitle: ''\nmessage: >-\n If you use this software, please cite it using the\n metadata from this file.\ntype: software\nauthors: []\nkeywords:\n - keyword1\n"
- expect(cffstr.value).toEqual(expected)
- })
+ describe('changing only a single field at a time', () => {
+ for (const fieldData of cffstrFields) {
+ const { field, value, cffFunction } = fieldData
+ it(`should work for field '${field}'`, () => {
+ cffFunction(value as never)
+ const expected = { ...cffMinimumFields, [field]: value }
+ expect(parsedCffStr()).toEqual(expected)
+ })
+ }
})
- describe('with identifier', () => {
- beforeEach(() => {
- cff.setIdentifiers([{ type: 'doi', value: '10.5281/zenodo.5171937' }])
- })
-
- it('should have a identifier', () => {
- const expected = generatedBy + "cff-version: 1.2.0\ntitle: ''\nmessage: >-\n If you use this software, please cite it using the\n metadata from this file.\ntype: software\nauthors: []\nidentifiers:\n - type: doi\n value: 10.5281/zenodo.5171937\n"
- expect(cffstr.value).toEqual(expected)
+ describe('adding every field', () => {
+ it('should work', () => {
+ let expected = { ...cffMinimumFields }
+ for (const fieldData of cffstrFields) {
+ const { field, value, cffFunction } = fieldData
+ cffFunction(value as never)
+ expected = { ...expected, [field]: value }
+ }
+ expect(parsedCffStr()).toEqual(expected)
})
})
})
diff --git a/test/jest/__tests__/store/cffupdate.jest.spec.ts b/test/jest/__tests__/store/cffupdate.jest.spec.ts
new file mode 100644
index 00000000..89901664
--- /dev/null
+++ b/test/jest/__tests__/store/cffupdate.jest.spec.ts
@@ -0,0 +1,208 @@
+import * as yaml from 'js-yaml'
+import { beforeEach, describe, expect, it } from '@jest/globals'
+import { updateCff, useCff } from 'src/store/cff'
+import { useCffstr } from 'src/store/cffstr'
+
+describe('Update of existing CFF', () => {
+ const cff = useCff()
+ const { cffstr } = useCffstr()
+ const parsedCffStr = () => { return yaml.load(cffstr.value) }
+ const cffMinimumFields = {
+ 'cff-version': '1.2.0',
+ type: 'software',
+ title: '',
+ message: 'If you use this software, please cite it using the metadata from this file.',
+ authors: []
+ }
+ const cffstrFields = [
+ { field: 'abstract', value: 'Description' },
+ {
+ field: 'authors',
+ value: [
+ {
+ 'given-names': 'John',
+ 'family-names': 'Doe',
+ orcid: 'https://1234-1234-1234-123X',
+ email: 'john@doe.com'
+ }
+ ]
+ },
+ { field: 'commit', value: '1234567890abcde' },
+ { field: 'date-released', value: '2022-01-01' },
+ { field: 'identifiers', value: [{ type: 'doi', value: '10.5281/zenodo.5171937' }] },
+ { field: 'keywords', value: ['kw1', 'kw2'] },
+ { field: 'license', value: 'Apache-2.0' },
+ { field: 'message', value: 'Cite me!' },
+ { field: 'repository-artifact', value: 'https://a' },
+ { field: 'repository-code', value: 'https://a' },
+ { field: 'repository', value: 'https://a' },
+ { field: 'title', value: 'Title' },
+ { field: 'type', value: 'dataset' },
+ { field: 'url', value: 'https://a' },
+ { field: 'version', value: '1.2.3' },
+ {
+ field: 'preferred-citation',
+ value: {
+ authors: [{ 'given-names': 'John', 'family-names': 'Doe' }],
+ title: 'The paper',
+ type: 'article'
+ }
+ }
+ ]
+
+ beforeEach(() => {
+ cff.reset()
+ })
+ describe('Should returns errors for', () => {
+ test('empty field', () => {
+ expect(updateCff('')).toEqual({ msg: ['Error: CFF is empty.'], success: false })
+ })
+ test('array instead of list', () => {
+ expect(updateCff('- a\n- b\n- c')).toEqual({
+ msg: ['Error: CFF is invalid. It should be a YAML map.'],
+ success: false
+ })
+ })
+ test('string instead of list', () => {
+ expect(updateCff('bad')).toEqual({
+ msg: ['Error: CFF is invalid. It should be a YAML map.'],
+ success: false
+ })
+ })
+ test('catch YAML errors', () => {
+ const { msg, success } = updateCff('a: -')
+ expect(msg[0]).toContain('Error: could not parse CFF because of the following YAML error:')
+ expect(success).toBe(false)
+ })
+ })
+ it('should work for the minimum information', () => {
+ expect(updateCff('cff-version: 1.2.0')).toEqual({ msg: [], success: true })
+ expect(parsedCffStr()).toEqual(cffMinimumFields)
+ })
+ it('should work for full cff', () => {
+ let expected = { ...cffMinimumFields }
+ for (const fieldData of cffstrFields) {
+ const { field, value } = fieldData
+ expected = { ...expected, [field]: value }
+ }
+ const updateStr = yaml.dump(cffstrFields.reduce((acc, { field, value }) => {
+ return { ...acc, [field]: value }
+ }, {}))
+ const { msg, success } = updateCff(updateStr)
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe("Property 'preferred-citation' was not identified as a basic field, so it was passed as an extra cff field.")
+ expect(success).toBe(true)
+ expect(parsedCffStr()).toEqual(expected)
+ })
+ describe('should work for each field of a full cff', () => {
+ for (const fieldData of cffstrFields) {
+ const { field, value } = fieldData
+ test(`field ${field}`, () => {
+ const expected = { ...cffMinimumFields, [field]: value }
+ const { msg, success } = updateCff(yaml.dump({ [field]: value }))
+ expect(msg).toHaveLength(field === 'preferred-citation' ? 1 : 0)
+ expect(success).toBe(true)
+ expect(parsedCffStr()).toEqual(expected)
+ })
+ }
+ })
+ test('keys at root level that are not part of the part of cff are kept as extra', () => {
+ const { msg, success } = updateCff('bad: value')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe("Property 'bad' was not identified as a basic field, so it was passed as an extra cff field.")
+ expect(success).toBe(true)
+ const expected = { ...cffMinimumFields, bad: 'value' }
+ expect(parsedCffStr()).toEqual(expected)
+ })
+ test('keys at authors level that are not part of the part of cff are ignored', () => {
+ const { msg, success } = updateCff('authors:\n- bad: value')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe("Property 'bad: value' inside 'authors' was ignored. Check if the key is correct.")
+ expect(success).toBe(true)
+ const expected = { ...cffMinimumFields, authors: [{}] }
+ expect(parsedCffStr()).toEqual(expected)
+ })
+ test('keys at identifiers level that are not part of the part of cff are ignored', () => {
+ const { msg, success } = updateCff('identifiers:\n- bad: value')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe("Property 'bad: value' inside 'identifiers' was ignored. Check if the key is correct.")
+ expect(success).toBe(true)
+ const expected = { ...cffMinimumFields, identifiers: [{ type: 'other', value: '' }] }
+ expect(parsedCffStr()).toEqual(expected)
+ })
+ test('type should be software or dataset', () => {
+ const { msg, success } = updateCff('type: potato')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe("Invalid type 'potato'. Using 'software' instead.")
+ expect(success).toBe(true)
+ expect(parsedCffStr()).toEqual(cffMinimumFields)
+ })
+ test('identifier type should be valid', () => {
+ const { msg, success } = updateCff('identifiers:\n- type: potato\n value: fried')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe("Invalid value 'potato' for identifier type. Using 'other' instead.")
+ expect(success).toBe(true)
+ const expected = { ...cffMinimumFields, identifiers: [{ type: 'other', value: 'fried' }] }
+ expect(parsedCffStr()).toEqual(expected)
+ })
+ test('fix old cff-version', () => {
+ const { msg, success } = updateCff('cff-version: 0.0.1')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe('cff-version was updated to 1.2.0. This might led to some issues, so verify before downloading.')
+ expect(success).toBe(true)
+ expect(parsedCffStr()).toEqual(cffMinimumFields)
+ })
+ test('date-released should work for non-string', () => {
+ const { msg, success } = updateCff('date-released: 2023-01-01')
+ expect(msg).toHaveLength(0)
+ expect(success).toBe(true)
+ const expected = { ...cffMinimumFields, 'date-released': '2023-01-01' }
+ expect(parsedCffStr()).toEqual(expected)
+ })
+ test('ignore bad values validated a posteriori', () => {
+ const { msg, success } = updateCff("title: ''\nmessage: ''\nauthors: [{ orcid: bad }, { orcid: bad }]\nidentifiers:\n- type: doi\n value: bad")
+ expect(msg).toHaveLength(0)
+ expect(success).toBe(true)
+ })
+ test('catches {}', () => {
+ const { msg, success } = updateCff('{}')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe('Error: CFF is empty.')
+ expect(success).toBe(false)
+ })
+ test('catches {{}}', () => {
+ const { msg, success } = updateCff('{{}}')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe('Error: invalid object in keys (did you use {} as key?).')
+ expect(success).toBe(false)
+ })
+ test('catches {[]}', () => {
+ const { msg, success } = updateCff('{[]}')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe('Error: invalid null property.')
+ expect(success).toBe(false)
+ })
+ test('allow {title: Title, message: CITE ME}', () => {
+ const { msg, success } = updateCff('{title: Title, message: CITE ME}')
+ expect(msg).toHaveLength(0)
+ expect(success).toBe(true)
+ })
+ test('catches {}: 1', () => {
+ const { msg, success } = updateCff('{}: 1\nb: 2')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe('Error: invalid object in keys (did you use {} as key?).')
+ expect(success).toBe(false)
+ })
+ test('catches []: 1', () => {
+ const { msg, success } = updateCff('[]: 1\nb: 2')
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe('Error: invalid null property.')
+ expect(success).toBe(false)
+ })
+ test("catches '': 1", () => {
+ const { msg, success } = updateCff("'': 1\nb: 2")
+ expect(msg).toHaveLength(1)
+ expect(msg[0]).toBe('Error: invalid null property.')
+ expect(success).toBe(false)
+ })
+})
diff --git a/test/jest/__tests__/store/validation.jest.spec.ts b/test/jest/__tests__/store/validation.jest.spec.ts
new file mode 100644
index 00000000..d4053579
--- /dev/null
+++ b/test/jest/__tests__/store/validation.jest.spec.ts
@@ -0,0 +1,147 @@
+import { describe, expect, it } from '@jest/globals'
+import { useCff } from 'src/store/cff'
+import { useValidation } from 'src/store/validation'
+
+describe('useValidation', () => {
+ const cff = useCff()
+ const { errors } = useValidation()
+ const mappedErrors = () => { return errors.value.map(error => [error.instancePath, error.schemaPath]) }
+
+ cff.reset()
+ it('should start with errors in title and authors', () => {
+ expect(errors.value.length).toEqual(2)
+ expect(errors.value[0].instancePath).toEqual('/authors')
+ expect(errors.value[0].schemaPath).toEqual('#/properties/authors/minItems')
+ expect(errors.value[1].instancePath).toEqual('/title')
+ expect(errors.value[1].schemaPath).toEqual('#/properties/title/minLength')
+ })
+
+ it('should have no more errors after title and authors are fixed', () => {
+ cff.setTitle('Title')
+ cff.setAuthors([{}])
+ expect(errors.value.length).toEqual(0)
+ })
+
+ it('should complain about missing message', () => {
+ cff.setMessage('')
+ expect(errors.value.length).toEqual(1)
+ expect(errors.value[0].instancePath).toEqual('/message')
+ expect(errors.value[0].schemaPath).toEqual('#/properties/message/minLength')
+ })
+
+ describe('catches', () => {
+ const relatedResources = [
+ { key: 'repository', foo: cff.setRepository },
+ { key: 'repository-artifact', foo: cff.setRepositoryArtifact },
+ { key: 'repository-code', foo: cff.setRepositoryCode },
+ { key: 'url', foo: cff.setUrl }
+ ]
+
+ beforeEach(() => {
+ cff.reset()
+ cff.setTitle('Title')
+ cff.setAuthors([{}])
+ })
+
+ test('missing author', () => {
+ cff.setAuthors([])
+ expect(errors.value[0].instancePath).toEqual('/authors')
+ expect(errors.value[0].schemaPath).toEqual('#/properties/authors/minItems')
+ })
+ test('bad release date', () => {
+ cff.setDateReleased('bad')
+ expect(errors.value[0].instancePath).toEqual('/date-released')
+ expect(errors.value[0].schemaPath).toEqual('#/definitions/date/pattern')
+ })
+ test('duplicate author', () => {
+ cff.setAuthors([{}, {}])
+ expect(errors.value[0].instancePath).toEqual('/authors')
+ expect(errors.value[0].schemaPath).toEqual('#/properties/authors/uniqueItems')
+ })
+ test('duplicate identifier', () => {
+ cff.setIdentifiers([{ type: 'other', value: '1' }, { type: 'other', value: '1' }])
+ expect(mappedErrors()).toContainEqual(['/identifiers', '#/properties/identifiers/uniqueItems'])
+ })
+ test('duplicate keyword', () => {
+ cff.setKeywords(['a', 'a'])
+ expect(errors.value[0].instancePath).toEqual('/keywords')
+ expect(errors.value[0].schemaPath).toEqual('#/properties/keywords/uniqueItems')
+ })
+ test('bad e-mail on author', () => {
+ cff.setAuthors([{ email: 'bad' }])
+ expect(errors.value[0].instancePath).toEqual('/authors/0/email')
+ expect(errors.value[0].schemaPath).toEqual('#/definitions/email/pattern')
+ })
+ test('bad DOI identifier', () => {
+ cff.setIdentifiers([{ type: 'doi', value: 'bad' }])
+ expect(mappedErrors()).toContainEqual([
+ '/identifiers/0/value',
+ '#/definitions/doi/pattern'
+ ])
+ })
+ test('bad URL identifier', () => {
+ cff.setIdentifiers([{ type: 'url', value: 'bad' }])
+ expect(mappedErrors()).toContainEqual([
+ '/identifiers/0/value',
+ '#/definitions/url/pattern'
+ ])
+ // Trailing white spaces on URLs are a different kind of error. See #605
+ cff.setIdentifiers([{ type: 'url', value: 'https:// ' }])
+ expect(mappedErrors()).toContainEqual([
+ '/identifiers/0/value',
+ '#/definitions/url/format'
+ ])
+ })
+ test('bad SWH identifier', () => {
+ cff.setIdentifiers([{ type: 'swh', value: 'bad' }])
+ expect(mappedErrors()).toContainEqual([
+ '/identifiers/0/value',
+ '#/definitions/swh-identifier/pattern'
+ ])
+ })
+ test('bad other identifier', () => {
+ cff.setIdentifiers([{ type: 'other', value: '' }])
+ expect(mappedErrors()).toContainEqual([
+ '/identifiers/0/value',
+ '#/anyOf/3/properties/value/minLength'
+ ])
+ })
+ test('bad keyword', () => {
+ cff.setKeywords([''])
+ expect(errors.value[0].instancePath).toEqual('/keywords/0')
+ expect(errors.value[0].schemaPath).toEqual('#/properties/keywords/items/minLength')
+ })
+ test('bad ORCID', () => {
+ cff.setAuthors([{ orcid: 'bad' }])
+ expect(errors.value[0].instancePath).toEqual('/authors/0/orcid')
+ expect(errors.value[0].schemaPath).toEqual('#/definitions/orcid/pattern')
+ })
+ for (const relatedResource of relatedResources) {
+ const { key, foo } = relatedResource
+ test(`bad ${key}`, () => {
+ foo('bad')
+ expect(errors.value[0].instancePath).toEqual(`/${key}`)
+ expect(errors.value[0].schemaPath).toEqual('#/definitions/url/pattern')
+ // Trailing white spaces on URLs are a different kind of error. See #605
+ foo('https:// ')
+ expect(errors.value[0].instancePath).toEqual(`/${key}`)
+ expect(errors.value[0].schemaPath).toEqual('#/definitions/url/format')
+ })
+ }
+ test('bad YAML in extra CFF', () => {
+ cff.setExtraCffFields('a: -')
+ expect(errors.value[0].keyword).toEqual('Extra CFF Fields error')
+ expect(errors.value[0].message).toContain('end of the stream')
+ })
+ test('wrong extra field in extra CFF', () => {
+ cff.setExtraCffFields('extra: field')
+ expect(errors.value[0].keyword).toEqual('additionalProperties')
+ })
+ test('preferred-citation missing title', () => {
+ cff.setExtraCffFields('preferred-citation:\n authors: [{}]\n type: article')
+ expect(errors.value.length).toBe(1)
+ expect(errors.value[0].instancePath).toEqual('/preferred-citation')
+ expect(errors.value[0].message).toEqual("must have required property 'title'")
+ })
+ })
+})