- 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'")
+ })
+ })
+})