├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── update-version.sh └── workflows │ ├── build.yml │ ├── cffconvert.yml │ ├── lint.yml │ ├── preview.yml │ ├── publish.yml │ ├── test.yml │ └── zenodraft.yml ├── .gitignore ├── .husky └── pre-commit ├── .nycrc ├── .postcssrc.js ├── .vscode ├── extensions.json └── settings.json ├── .zenodo.json ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.dev.md ├── README.md ├── babel.config.js ├── cypress.config.ts ├── cypress ├── e2e │ ├── errors.cy.ts │ ├── info-dialog.cy.ts │ ├── navigation.cy.ts │ ├── spec.cy.ts │ ├── specific.cy.ts │ ├── update.cy.ts │ └── yaml-examples │ │ ├── bad-authors.yml │ │ ├── bad-cff-version.yml │ │ ├── bad-date-released.yml │ │ ├── bad-identifiers.yml │ │ ├── bad-type.yml │ │ ├── clean-authors.yml │ │ ├── clean-cff-version.yml │ │ ├── clean-date-released.yml │ │ ├── clean-identifiers.yml │ │ ├── clean-type.yml │ │ ├── passing-basic.yml │ │ ├── passing-full.yml │ │ ├── warning-authors.txt │ │ ├── warning-cff-version.txt │ │ ├── warning-date-released.txt │ │ ├── warning-identifiers.txt │ │ └── warning-type.txt ├── support │ ├── e2e.ts │ └── index.ts └── tsconfig.json ├── jest.config.js ├── package-lock.json ├── package.json ├── public └── favicon.png ├── quasar.conf.js ├── quasar.extensions.json ├── quasar.testing.json ├── src ├── App.vue ├── assets │ ├── cff-logo.svg │ ├── landing-image.svg │ ├── landing-image.webp │ └── nlesc-logo.svg ├── boot │ └── .gitkeep ├── components │ ├── BannerErrorMessages.vue │ ├── DownloadButton.vue │ ├── EntityCardEditing.vue │ ├── EntityCardViewing.vue │ ├── Error404.vue │ ├── Footer.vue │ ├── Header.vue │ ├── IdentifierCardEditing.vue │ ├── IdentifierCardViewing.vue │ ├── InfoDialog.vue │ ├── Keyword.vue │ ├── LayoutLanding.vue │ ├── LayoutStepper.vue │ ├── LayoutUpdate.vue │ ├── PersonCardEditing.vue │ ├── PersonCardViewing.vue │ ├── Preview.vue │ ├── RawErrorList.vue │ ├── ScreenAbstract.vue │ ├── ScreenAuthors.vue │ ├── ScreenExtraCffFields.vue │ ├── ScreenFinish.vue │ ├── ScreenIdentifiers.vue │ ├── ScreenKeywords.vue │ ├── ScreenLicense.vue │ ├── ScreenRelatedResources.vue │ ├── ScreenStart.vue │ ├── ScreenVersionSpecific.vue │ ├── Stepper.vue │ └── StepperActions.vue ├── css │ ├── app.css │ └── fonts │ │ ├── Fira_Code │ │ ├── FiraCode-Bold.ttf │ │ ├── FiraCode-Light.ttf │ │ ├── FiraCode-Medium.ttf │ │ ├── FiraCode-Regular.ttf │ │ ├── FiraCode-SemiBold.ttf │ │ ├── FiraCode-VariableFont_wght.ttf │ │ ├── OFL.txt │ │ └── README.txt │ │ └── Inter │ │ ├── Inter-Black.ttf │ │ ├── Inter-Bold.ttf │ │ ├── Inter-ExtraBold.ttf │ │ ├── Inter-ExtraLight.ttf │ │ ├── Inter-Light.ttf │ │ ├── Inter-Medium.ttf │ │ ├── Inter-Regular.ttf │ │ ├── Inter-SemiBold.ttf │ │ ├── Inter-Thin.ttf │ │ ├── Inter-VariableFont_slnt,wght.ttf │ │ ├── OFL.txt │ │ └── README.txt ├── deep-filter.d.ts ├── env.d.ts ├── error-filtering.ts ├── index.template.html ├── kebabcase-keys.d.ts ├── router │ ├── index.ts │ └── routes.ts ├── schemas │ └── 1.2.0 │ │ └── schema.json ├── scroll-to-bottom.ts ├── shims-vue.d.ts ├── store │ ├── app.ts │ ├── cff.ts │ ├── cffstr.ts │ ├── help-data.ts │ ├── stepper-errors.ts │ ├── store-flag.d.ts │ └── validation.ts ├── types │ └── index.ts └── updown.ts ├── test └── jest │ ├── .eslintrc.js │ ├── .gitignore │ └── __tests__ │ ├── .gitkeep │ ├── pages │ └── Start.jest.spec.ts │ └── store │ ├── app.jest.spec.ts │ ├── cffstr.jest.spec.ts │ ├── cffupdate.jest.spec.ts │ └── validation.jest.spec.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/plugin-syntax-dynamic-import"], 3 | "env": { 4 | "test": { 5 | "plugins": ["dynamic-import-node", "istanbul"], 6 | "presets": [ 7 | [ 8 | "@babel/preset-env", 9 | { 10 | "modules": "commonjs", 11 | "targets": { 12 | "node": "current" 13 | } 14 | } 15 | ] 16 | ] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /src-bex/www 3 | /src-capacitor 4 | /src-cordova 5 | /.quasar 6 | /node_modules 7 | .eslintrc.js 8 | babel.config.js 9 | /src-ssr 10 | /docs 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | module.exports = { 3 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy 4 | // This option interrupts the configuration hierarchy at this file 5 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) 6 | root: true, 7 | 8 | // https://eslint.vuejs.org/user-guide/#how-to-use-custom-parser 9 | // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working 10 | // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted 11 | parserOptions: { 12 | // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#configuration 13 | // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#eslint 14 | // Needed to make the parser take into account 'vue' files 15 | extraFileExtensions: ['.vue'], 16 | parser: '@typescript-eslint/parser', 17 | project: resolve(__dirname, './tsconfig.json'), 18 | tsconfigRootDir: __dirname, 19 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 20 | sourceType: 'module' // Allows for the use of imports 21 | }, 22 | 23 | env: { 24 | browser: true 25 | }, 26 | 27 | // Rules order is important, please avoid shuffling them 28 | extends: [ 29 | // Base ESLint recommended rules 30 | // 'eslint:recommended', 31 | 32 | // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage 33 | // ESLint typescript rules 34 | 'plugin:@typescript-eslint/recommended', 35 | // consider disabling this class of rules if linting takes too long 36 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 37 | 38 | // Uncomment any of the lines below to choose desired strictness, 39 | // but leave only one uncommented! 40 | // See https://eslint.vuejs.org/rules/#available-rules 41 | // 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention) 42 | 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) 43 | // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) 44 | 45 | 'plugin:cypress/recommended', 46 | 47 | 'standard' 48 | 49 | ], 50 | 51 | plugins: [ 52 | // required to apply rules which need type information 53 | '@typescript-eslint', 54 | 55 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file 56 | // required to lint *.vue files 57 | 'vue', 58 | 59 | ], 60 | 61 | globals: { 62 | ga: 'readonly', // Google Analytics 63 | cordova: 'readonly', 64 | __statics: 'readonly', 65 | __QUASAR_SSR__: 'readonly', 66 | __QUASAR_SSR_SERVER__: 'readonly', 67 | __QUASAR_SSR_CLIENT__: 'readonly', 68 | __QUASAR_SSR_PWA__: 'readonly', 69 | process: 'readonly', 70 | Capacitor: 'readonly', 71 | chrome: 'readonly' 72 | }, 73 | 74 | // add your custom rules here 75 | rules: { 76 | 'vue/v-on-style': ['error', 'longform'], 77 | 'vue/v-bind-style': ['error', 'longform'], 78 | 'vue/v-slot-style': ['error', 'longform'], 79 | 'vue/no-unused-properties': ['error', { 80 | 'groups': ['props', 'data', 'computed', 'methods', 'setup'], 81 | 'deepData': true, 82 | 'ignorePublicMembers': false 83 | }], 84 | 'indent': ['error', 4], 85 | 'vue/html-indent': ['error', 4, { 86 | 'attribute': 1, 87 | 'baseIndent': 1, 88 | 'closeBracket': 0, 89 | 'alignAttributesVertically': true, 90 | 'ignores': [] 91 | }], 92 | // allow async-await 93 | 'generator-star-spacing': 'off', 94 | // allow paren-less arrow functions 95 | 'arrow-parens': 'off', 96 | 'one-var': 'off', 97 | 'no-void': 'off', 98 | 'multiline-ternary': 'off', 99 | 100 | 'import/first': 'off', 101 | 'import/named': 'error', 102 | 'import/namespace': 'error', 103 | 'import/default': 'error', 104 | 'import/export': 'error', 105 | 'import/extensions': 'off', 106 | 'import/no-unresolved': 'off', 107 | 'import/no-extraneous-dependencies': 'off', 108 | 'prefer-promise-reject-errors': 'off', 109 | 'no-restricted-imports': ['error', { 110 | 'patterns': ['.*'] 111 | }], 112 | 'sort-imports': ['error', { 113 | 'ignoreCase': false, 114 | 'ignoreDeclarationSort': false, 115 | 'ignoreMemberSort': false, 116 | 'memberSyntaxSortOrder': ['none', 'all', 'multiple', 'single'], 117 | 'allowSeparatedGroups': false 118 | }], 119 | 120 | // TypeScript 121 | quotes: ['warn', 'single', { avoidEscape: true }], 122 | '@typescript-eslint/explicit-function-return-type': 'off', 123 | '@typescript-eslint/explicit-module-boundary-types': 'off', 124 | 125 | // allow debugger during development only 126 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull request details 2 | 3 | As a contributor I confirm 4 | - [ ] I read and followed the instructions in [CONTRIBUTING.md](CONTRIBUTING.md) 5 | - [ ] The developer documentation is up to date with the changes introduced in this Pull Request 6 | - [ ] Tests are passing 7 | - [ ] All the workflows are passing 8 | 9 | ## List of related issues or pull requests 10 | 11 | Refs: 12 | - #ISSUE_NUMBER 13 | 14 | 15 | ## Describe the changes made in this pull request 16 | 17 | 18 | 19 | 20 | ## Instructions to review the pull request 21 | 22 | 36 | -------------------------------------------------------------------------------- /.github/update-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function usage { 4 | echo -e "Usage:\n\n\tbash $0 X.Y.Z\n 5 | cffconvert will be required. If the command exists, it will be used. 6 | Otherwise, if you have docker installed, set USE_DOCKER=\"yes\" to use the docker image. 7 | Alternatively, you can set CFFCONVERT to specify the binary path." 8 | } 9 | 10 | if [ ! $# -eq 1 ]; then 11 | usage 12 | exit 1 13 | fi 14 | 15 | VERSION=$1 16 | 17 | if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 18 | echo "ERROR: Wrong input format" 19 | usage 20 | exit 1 21 | fi 22 | 23 | if [ -n "$CFFCONVERT" ]; then 24 | if ! command -v "$CFFCONVERT" &> /dev/null; then 25 | echo "ERROR: CFFCONVERT was set to '$CFFCONVERT' but command does not exist" 26 | usage 27 | exit 1 28 | fi 29 | fi 30 | 31 | if [ -z "$CFFCONVERT" ]; then 32 | if command -v cffconvert &> /dev/null; then 33 | echo "cffconvert found" 34 | CFFCONVERT=cffconvert 35 | elif command -v docker &> /dev/null && [ "$USE_DOCKER" == "yes" ]; then 36 | CFFCONVERT="docker run --rm -v $PWD:/app citationcff/cffconvert" 37 | else 38 | echo "ERROR: cffconvert was not found, install it first, possibly using" 39 | echo -e "\n\tpython -m venv env\n\t. env/bin/activate\n\tpip install --upgrade pip setuptools cffconvert\n" 40 | usage 41 | exit 1 42 | fi 43 | fi 44 | 45 | sed -i "s/^date-released: .*$/date-released: $(date --iso)/" CITATION.cff 46 | sed -i "s/^version: .*$/version: $VERSION/" CITATION.cff 47 | echo "CITATION.cff updated" 48 | $CFFCONVERT -f zenodo -o .zenodo.json 49 | echo ".zenodo.json updated" 50 | sed -i "s/^ \"version.*$/ \"version\": \"$VERSION\",/" package.json 51 | sed -i "s/^ \"version.*$/ \"version\": \"$VERSION\",/" package-lock.json 52 | echo "package.json and package-lock.json updated" 53 | sed -i "s/Version .*$/Version $VERSION/" src/components/LayoutLanding.vue 54 | sed -i "s/Version .*$/Version $VERSION/" src/components/Footer.vue 55 | echo "LayoutLanding.vue and Footer.vue updated" 56 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: # rebuild any PRs and main branch changes 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Use Node.js 18 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: '18' 18 | cache: 'npm' 19 | - name: Run npm clean-install 20 | run: npm clean-install 21 | - name: Run build 22 | run: npm run build 23 | -------------------------------------------------------------------------------- /.github/workflows/cffconvert.yml: -------------------------------------------------------------------------------- 1 | name: cffconvert 2 | 3 | on: 4 | push: 5 | paths: 6 | - CITATION.cff 7 | 8 | jobs: 9 | validate: 10 | name: "validate" 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out a copy of the repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Check whether the citation metadata from CITATION.cff is valid 17 | uses: citation-file-format/cffconvert-github-action@2.0.0 18 | with: 19 | args: "--validate" 20 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: # rebuild any PRs and main branch changes 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - 'releases/*' 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Use Node.js 18 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: '18' 19 | cache: 'npm' 20 | - name: Run npm clean-install 21 | run: npm clean-install 22 | - name: Run linting 23 | run: npm run lint 24 | -------------------------------------------------------------------------------- /.github/workflows/preview.yml: -------------------------------------------------------------------------------- 1 | name: Preview 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | release: 11 | workflow_dispatch: 12 | 13 | jobs: 14 | preview: 15 | if: github.repository == 'citation-file-format/cff-initializer-javascript' 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Use Node.js 18 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: '18' 23 | cache: 'npm' 24 | - name: Run npm clean-install 25 | run: npm clean-install 26 | - name: Run build on PR 27 | run: | 28 | if [[ ${{ github.event_name }} == "pull_request" ]]; then 29 | export PUBLICPATH=PR${{ github.event.number }} 30 | else 31 | export PUBLICPATH=$GITHUB_REF_NAME 32 | fi 33 | echo "PUBLICPATH=$PUBLICPATH" >> $GITHUB_ENV 34 | sed -i "s|publicPath: 'cff-initializer-javascript'|publicPath: '$PUBLICPATH'|" quasar.conf.js 35 | npm run build 36 | - name: Deploy to gh-preview 37 | uses: peaceiris/actions-gh-pages@v3 38 | with: 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | publish_branch: gh-preview 41 | destination_dir: "${{ env.PUBLICPATH }}" 42 | publish_dir: ./dist 43 | user_name: 'cffinit[bot]' 44 | user_email: 'cffinit[bot]@users.noreply.github.com' 45 | commit_message: ':robot: Create preview of ${{ env.PUBLICPATH }}' 46 | pr_comment: 47 | if: github.event_name == 'pull_request' && github.repository == 'citation-file-format/cff-initializer-javascript' 48 | needs: preview 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: 'Comment PR' 52 | uses: actions/github-script@0.3.0 53 | with: 54 | github-token: ${{ secrets.GITHUB_TOKEN }} 55 | script: | 56 | const { issue: { number: issue_number }, repo: { owner, repo } } = context; 57 | github.issues.createComment({ issue_number, owner, repo, body: 'Once the build has completed, you can preview your PR at this URL: https://cffinit.netlify.app/PR${{ github.event.number }}/' }); 58 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: # publish when main branch changes 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Use Node.js 18 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: '18' 17 | cache: 'npm' 18 | - name: Run npm clean-install 19 | run: npm clean-install 20 | - name: Run build 21 | run: npm run build 22 | - name: Deploy to gh-pages 23 | uses: peaceiris/actions-gh-pages@v3 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | publish_dir: ./dist 27 | user_name: 'cffinit[bot]' 28 | user_email: 'cffinit[bot]@users.noreply.github.com' 29 | commit_message: ':robot: Update github.io page' 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: # rebuild any PRs and main branch changes 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - 'releases/*' 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Use Node.js 18 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: '18' 19 | cache: 'npm' 20 | - name: Run npm clean-install 21 | run: npm clean-install 22 | - name: Run unit tests 23 | run: npm run test:unit:coverage 24 | - name: Cypress run 25 | uses: cypress-io/github-action@v4 26 | with: 27 | start: npm run dev 28 | wait-on: 'http://localhost:8080/cff-initializer-javascript/#' 29 | - name: Upload Screenshot of Cypress when errors occur 30 | if: failure() 31 | uses: actions/upload-artifact@v3 32 | with: 33 | name: cypress-screenshots 34 | path: cypress/screenshots 35 | - name: Code coverage 36 | uses: codecov/codecov-action@v3 37 | with: 38 | files: ./test/jest/coverage/coverage-final.json,./coverage/coverage-final.json 39 | -------------------------------------------------------------------------------- /.github/workflows/zenodraft.yml: -------------------------------------------------------------------------------- 1 | name: zenodraft 2 | on: 3 | # Trigger when you publish a release via GitHub's release page 4 | release: 5 | types: 6 | - published 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout the contents of your repository 14 | uses: actions/checkout@v3 15 | - name: Create a draft snapshot of your repository contents as a new 16 | version in collection 1404735 on Zenodo using metadata 17 | from repository file .zenodo.json 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | ZENODO_ACCESS_TOKEN: ${{ secrets.ZENODO_ACCESS_TOKEN }} 21 | uses: zenodraft/action@0.10.0 22 | with: 23 | collection: 1404735 24 | metadata: .zenodo.json 25 | publish: true 26 | sandbox: false 27 | upsert-doi: true 28 | upsert-location: identifiers[0] 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | 5 | # Quasar core related directories 6 | .quasar 7 | /dist 8 | 9 | # Cordova related directories and files 10 | /src-cordova/node_modules 11 | /src-cordova/platforms 12 | /src-cordova/plugins 13 | /src-cordova/www 14 | 15 | # Capacitor related directories and files 16 | /src-capacitor/www 17 | /src-capacitor/node_modules 18 | 19 | # BEX related directories and files 20 | /src-bex/www 21 | /src-bex/js/core 22 | 23 | # Log files 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Editor directories and files 29 | .idea 30 | *.suo 31 | *.ntvs* 32 | *.njsproj 33 | *.sln 34 | node_modules 35 | 36 | # Cypress 37 | cypress/downloads 38 | cypress/screenshots 39 | cypress/videos 40 | 41 | # Coverage related 42 | coverage 43 | .nyc_output 44 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@istanbuljs/nyc-config-babel", 3 | "extension": [".js", ".ts", ".vue"] 4 | } 5 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "Vue.volar" 5 | ], 6 | "unwantedRecommendations": [ 7 | "hookyqr.beautify", 8 | "dbaeumer.jshint", 9 | "ms-vscode.vscode-typescript-tslint-plugin" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"], 3 | "typescript.tsdk": "node_modules/typescript/lib" 4 | } 5 | -------------------------------------------------------------------------------- /.zenodo.json: -------------------------------------------------------------------------------- 1 | { 2 | "creators": [ 3 | { 4 | "affiliation": "Netherlands eScience Center", 5 | "name": "Spaaks, Jurriaan H.", 6 | "orcid": "0000-0002-7064-4069" 7 | }, 8 | { 9 | "affiliation": "Netherlands eScience Center", 10 | "name": "Verhoeven, Stefan", 11 | "orcid": "0000-0002-5821-2060" 12 | }, 13 | { 14 | "affiliation": "Netherlands eScience Center", 15 | "name": "Diblen, Faruk", 16 | "orcid": "0000-0002-0989-929X" 17 | }, 18 | { 19 | "affiliation": "German Aerospace Center", 20 | "name": "Druskat, Stephan", 21 | "orcid": "0000-0003-4925-7248" 22 | }, 23 | { 24 | "affiliation": "Netherlands eScience Center", 25 | "name": "Soares Siqueira, Abel", 26 | "orcid": "0000-0003-4451-281X" 27 | }, 28 | { 29 | "affiliation": "Netherlands eScience Center", 30 | "name": "Garcia Gonzalez, Jesus", 31 | "orcid": "0000-0002-2170-3253" 32 | }, 33 | { 34 | "affiliation": "Netherlands eScience Center", 35 | "name": "Cushing, Reggie", 36 | "orcid": "0000-0002-5967-7302" 37 | } 38 | ], 39 | "description": "Web form to initialize CITATION.cff files.", 40 | "keywords": [ 41 | "citation", 42 | "CITATION.cff", 43 | "credit", 44 | "research software engineering" 45 | ], 46 | "license": { 47 | "id": "Apache-2.0" 48 | }, 49 | "publication_date": "2023-08-08", 50 | "title": "cffinit", 51 | "version": "2.3.1" 52 | } 53 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | abstract: Web form to initialize CITATION.cff files. 2 | authors: 3 | - affiliation: Netherlands eScience Center 4 | family-names: Spaaks 5 | given-names: Jurriaan H. 6 | orcid: https://orcid.org/0000-0002-7064-4069 7 | - affiliation: Netherlands eScience Center 8 | family-names: Verhoeven 9 | given-names: Stefan 10 | orcid: https://orcid.org/0000-0002-5821-2060 11 | - affiliation: Netherlands eScience Center 12 | family-names: Diblen 13 | given-names: Faruk 14 | orcid: https://orcid.org/0000-0002-0989-929X 15 | - affiliation: German Aerospace Center 16 | family-names: Druskat 17 | given-names: Stephan 18 | orcid: https://orcid.org/0000-0003-4925-7248 19 | - affiliation: Netherlands eScience Center 20 | family-names: Soares Siqueira 21 | given-names: Abel 22 | orcid: https://orcid.org/0000-0003-4451-281X 23 | - affiliation: Netherlands eScience Center 24 | family-names: Garcia Gonzalez 25 | given-names: Jesus 26 | orcid: https://orcid.org/0000-0002-2170-3253 27 | - affiliation: Netherlands eScience Center 28 | family-names: Cushing 29 | given-names: Reggie 30 | orcid: https://orcid.org/0000-0002-5967-7302 31 | cff-version: 1.2.0 32 | date-released: 2023-08-08 33 | identifiers: 34 | - type: doi 35 | value: 10.5281/zenodo.8224012 36 | description: >- 37 | This is the identifier used to uniquely identify the version of the 38 | software. 39 | - type: doi 40 | value: 10.5281/zenodo.1404735 41 | description: >- 42 | This is the identifier used to uniquely identify the software as a concept 43 | (i.e., version-agnostic). 44 | keywords: 45 | - citation 46 | - CITATION.cff 47 | - credit 48 | - research software engineering 49 | license: Apache-2.0 50 | message: If you use this software, please cite it using these metadata. 51 | repository-code: https://github.com/citation-file-format/cff-initializer-javascript 52 | title: cffinit 53 | version: 2.3.1 54 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | 49 | This Code of Conduct applies both within project spaces and in public spaces 50 | when an individual is representing the project or its community. Examples of 51 | representing a project or community include using an official project e-mail 52 | address, posting via an official social media account, or acting as an appointed 53 | representative at an online or offline event. Representation of a project may be 54 | further defined and clarified by project maintainers. 55 | 56 | ## Enforcement 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported by contacting the project team at generalization@esciencecenter.nl. All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | 72 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 73 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | We welcome any kind of contribution to our software, from simple comment or question to a full fledged [pull request](https://help.github.com/articles/about-pull-requests/). Please read and follow our Code of Conduct [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). 4 | 5 | A contribution can be one of the following cases: 6 | 7 | - you have a question; 8 | - you think you may have found a bug (including unexpected behavior); 9 | - you want to make some kind of change to the code base (e.g. to fix a bug, to add a new feature, to update documentation). 10 | 11 | The sections below outline the steps in each case. 12 | 13 | ## You have a question 14 | 15 | - use the search functionality [here](https://github.com/citation-file-format/cff-initializer-javascript/issues) to see if someone already filed the same issue; 16 | - if your issue search did not yield any relevant results, make a new issue; 17 | 18 | ## You think you may have found a bug 19 | 20 | - use the search functionality [here](https://github.com/citation-file-format/cff-initializer-javascript/issues) to see if someone already filed the same issue; 21 | - if your issue search did not yield any relevant results, make a new issue, making sure to provide enough information to the rest of the community to understand the cause and context of the problem. Depending on the issue, you may want to include: 22 | - the version of cffinit (bottom right in the app); 23 | - if you are using a development version, the [SHA hashcode](https://help.github.com/articles/autolinked-references-and-urls/#commit-shas) of the commit that is causing your problem; 24 | - some identifying information (name and version number) for dependencies you're using; 25 | - information about the operating system; 26 | 27 | ## You want to make some kind of change to the code base 28 | 29 | - (**important**) announce your plan to the rest of the community *before you start working*. This announcement should be in the form of a (new) issue; 30 | - (**important**) wait until some kind of consensus is reached about your idea being implemented; 31 | - if needed, fork the repository to your own Github profile and create your own feature branch off of the latest main commit. While working on your feature branch, make sure to stay up to date with the main branch by pulling in changes, possibly from the 'upstream' repository (follow the instructions [here](https://help.github.com/articles/configuring-a-remote-for-a-fork/) and [here](https://help.github.com/articles/syncing-a-fork/); 32 | - read the [developer documentation](README.dev.md); 33 | - make sure the existing tests still work; 34 | - add your own tests (if necessary); 35 | - update or expand the developer documentation; 36 | - [push](http://rogerdudler.github.io/git-guide/) your feature branch to (your fork of) the cff-initializer-javascript repository on GitHub; 37 | - create the pull request, e.g. following the instructions [here](https://help.github.com/articles/creating-a-pull-request/); 38 | - verify that the GitHub workflows passed (if they don't, mark the PR as a draft while you fix it); 39 | - don't request reviews, the reviewer will assign them themselves. 40 | 41 | If you don't know how to write or run tests or generate documentation, don't let this discourage you; we can help! Ask for help on the relevant issue so we can decide how to proceed. 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cffinit: a web application to create CITATION.cff files 2 | 3 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1404735.svg)](https://doi.org/10.5281/zenodo.1404735) 4 | [![Build](https://github.com/citation-file-format/cff-initializer-javascript/actions/workflows/ghpages.yml/badge.svg)](https://github.com/citation-file-format/cff-initializer-javascript/actions/workflows/ghpages.yml) 5 | [![Lint](https://github.com/citation-file-format/cff-initializer-javascript/actions/workflows/lint.yml/badge.svg)](https://github.com/citation-file-format/cff-initializer-javascript/actions/workflows/lint.yml) 6 | [![Test](https://github.com/citation-file-format/cff-initializer-javascript/actions/workflows/test.yml/badge.svg)](https://github.com/citation-file-format/cff-initializer-javascript/actions/workflows/test.yml) 7 | [![codecov.io](https://codecov.io/github/citation-file-format/cff-initializer-javascript/coverage.svg?branch=main)](https://codecov.io/github/citation-file-format/cff-initializer-javascript?branch=main) 8 | 9 | ## What cffinit can do for you? 10 | 11 | When you made some software and you want to include instructions on how to cite it, CITATION.cff files are the answer. However, sometimes it's tricky to ensure you write valid CFF. This tool helps mitigate that problem by generating the CFF text using a web form with form validation and user feedback. 12 | 13 | ## Information for users 14 | 15 | - Check out the **live version** at . 16 | - For the rationale behind CITATION.cff files, you can read more here: 17 | - . 18 | - 19 | - 20 | - For the Citation File Format specification, go [here](https://github.com/citation-file-format/citation-file-format) (latest) or [here](https://doi.org/10.5281/zenodo.1003149) (stable). 21 | - For the Citation File Format home page, go [here](https://citation-file-format.github.io). 22 | 23 | ## Information for developers 24 | 25 | - If you want to know how you can contribute, please have a look at [the current issues](https://github.com/citation-file-format/cff-initializer-javascript/issues) and read [CONTRIBUTING.md](CONTRIBUTING.md) 26 | - Developer documentation can be found in [README.dev.md](README.dev.md) 27 | - Check out the **development branch preview** at . 28 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const fs = require('fs-extra'); 4 | let extend = undefined; 5 | 6 | /** 7 | * The .babelrc file has been created to assist Jest for transpiling. 8 | * You should keep your application's babel rules in this file. 9 | */ 10 | 11 | if (fs.existsSync('./.babelrc')) { 12 | extend = './.babelrc'; 13 | } 14 | 15 | module.exports = { 16 | presets: ['@quasar/babel-preset-app'], 17 | plugins: ['babel-plugin-istanbul'], 18 | extends: extend, 19 | }; 20 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | import { defineConfig } from 'cypress' 4 | 5 | export default defineConfig({ 6 | e2e: { 7 | baseUrl: 'http://localhost:8080/cff-initializer-javascript/#', 8 | setupNodeEvents (on, config) { 9 | require('@cypress/code-coverage/task')(on, config) 10 | 11 | return config 12 | }, 13 | supportFile: 'cypress/support/index.ts', 14 | video: false 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /cypress/e2e/info-dialog.cy.ts: -------------------------------------------------------------------------------- 1 | const infoDialogs = [ 2 | { 3 | screen: 'start', 4 | values: ['type', 'title', 'message'] 5 | }, 6 | { 7 | screen: 'authors', 8 | values: ['authors', 'given-names, name-particle, family-names, name-suffix', 'email', 'affiliation', 'orcid'], 9 | before: () => { cy.dataCy('btn-add-person').click() } 10 | }, 11 | { 12 | screen: 'authors', 13 | values: ['authors', 'name', 'address', 'city', 'country', 'post-code', 'location', 'region', 'alias', 'email', 'date-start', 'date-end', 'tel', 'fax', 'website', 'orcid'], 14 | before: () => { cy.dataCy('btn-add-entity').click() } 15 | }, 16 | { 17 | screen: 'identifiers', 18 | values: ['identifiers', 'doi', 'description'], 19 | before: () => { cy.dataCy('btn-add-identifier').click() } 20 | }, 21 | { 22 | screen: 'identifiers', 23 | values: ['url', 'description'], 24 | before: () => { 25 | cy.dataCy('btn-add-identifier').click() 26 | cy.dataCy('radio-identifier-url').click() 27 | } 28 | }, 29 | { 30 | screen: 'identifiers', 31 | values: ['swh', 'description'], 32 | before: () => { 33 | cy.dataCy('btn-add-identifier').click() 34 | cy.dataCy('radio-identifier-swh').click() 35 | } 36 | }, 37 | { 38 | screen: 'identifiers', 39 | values: ['other', 'description'], 40 | before: () => { 41 | cy.dataCy('btn-add-identifier').click() 42 | cy.dataCy('radio-identifier-other').click() 43 | } 44 | }, 45 | { 46 | screen: 'related-resources', 47 | values: ['repository-code', 'url', 'repository', 'repository-artifact'] 48 | }, 49 | { 50 | screen: 'abstract', 51 | values: ['abstract'] 52 | }, 53 | { 54 | screen: 'keywords', 55 | values: ['keywords'] 56 | }, 57 | { 58 | screen: 'license', 59 | values: ['license'] 60 | }, 61 | { 62 | screen: 'version-specific', 63 | values: ['commit', 'version', 'date-released'] 64 | } 65 | ] 66 | 67 | describe('InfoDialog', () => { 68 | it('exists for all we need', () => { 69 | for (const infoDialog of infoDialogs) { 70 | cy.visit(`/${infoDialog.screen}`) 71 | if (infoDialog.before) { 72 | infoDialog.before() 73 | } 74 | for (const value of infoDialog.values) { 75 | cy.dataCy(`info-icon-${value}`) 76 | .click() 77 | cy.dataCy(`info-dialog-${value}`) 78 | .find('button') 79 | .contains('close') 80 | .click() 81 | } 82 | } 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /cypress/e2e/specific.cy.ts: -------------------------------------------------------------------------------- 1 | describe('Issue 637 - ORCID freeze', () => { 2 | it('should work when writing a full ORCID with dashes', () => { 3 | cy.visit('/authors') 4 | cy.dataCy('btn-add-person') 5 | .click() 6 | cy.dataCy('input-orcid') 7 | .type('1234-1234-1234-1234') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /cypress/e2e/update.cy.ts: -------------------------------------------------------------------------------- 1 | const passingCffFiles = ['passing-basic.yml', 'passing-full.yml'] 2 | const badCffFiles = ['authors', 'cff-version', 'date-released', 'identifiers', 'type'] 3 | 4 | describe('On the update screen', () => { 5 | beforeEach(() => { 6 | cy.visit('/update') 7 | }) 8 | describe('Should parse passing files correctly', () => { 9 | passingCffFiles.forEach((fileName) => { 10 | it(`file ${fileName}`, () => { 11 | cy.readFile(`cypress/e2e/yaml-examples/${fileName}`, 'binary', { timeout: 400 }) 12 | .then((str) => { 13 | cy.dataCy('input-existing-cff') 14 | .invoke('val', str) 15 | .trigger('input') 16 | cy.dataCy('btn-parse') 17 | .click() 18 | cy.dataCy('text-validation-msg') 19 | .should('include.text', 'Parsed CFF successfully') 20 | cy.dataCy('btn-start') 21 | .click() 22 | cy.url().should('include', '/start') 23 | cy.dataCy('ta-cff-preview') 24 | .should('include.value', str) 25 | }) 26 | }) 27 | }) 28 | }) 29 | 30 | describe('Should sanitize salvageable files', () => { 31 | badCffFiles.forEach((fileName) => { 32 | it(`file bad-${fileName}.yml`, () => { 33 | cy.readFile(`cypress/e2e/yaml-examples/bad-${fileName}.yml`, 'binary', { timeout: 400 }) 34 | .then((str) => { 35 | cy.dataCy('input-existing-cff') 36 | .invoke('val', str) 37 | .trigger('input') 38 | cy.dataCy('btn-parse') 39 | .click() 40 | cy.dataCy('text-validation-msg') 41 | .should('include.text', 'Parsed CFF successfully') 42 | }) 43 | cy.readFile(`cypress/e2e/yaml-examples/warning-${fileName}.txt`, 'binary', { timeout: 400 }) 44 | .then((str: string) => { 45 | str.split('\n').forEach((line) => { 46 | cy.dataCy('text-validation-msg') 47 | .should('include.text', line) 48 | }) 49 | }) 50 | cy.readFile(`cypress/e2e/yaml-examples/clean-${fileName}.yml`, 'binary', { timeout: 400 }) 51 | .then((str) => { 52 | cy.dataCy('btn-start') 53 | .click() 54 | cy.url().should('include', '/start') 55 | cy.dataCy('ta-cff-preview') 56 | .should('include.value', str) 57 | }) 58 | }) 59 | }) 60 | }) 61 | 62 | describe('Catch the following errors', () => { 63 | it('should error for empty input', () => { 64 | ['', '# nothing'].forEach((str) => { 65 | cy.dataCy('input-existing-cff') 66 | .invoke('val', str) 67 | .trigger('input') 68 | cy.dataCy('btn-parse') 69 | .click() 70 | cy.dataCy('text-validation-msg') 71 | .should('include.text', 'Error: CFF is empty.') 72 | }) 73 | }) 74 | it('should error for list instead of map', () => { 75 | cy.dataCy('input-existing-cff') 76 | .invoke('val', '- a: 1') 77 | .trigger('input') 78 | cy.dataCy('btn-parse') 79 | .click() 80 | cy.dataCy('text-validation-msg') 81 | .should('include.text', 'Error: CFF is invalid. It should be a YAML map.') 82 | }) 83 | it('should error for string instead of map', () => { 84 | cy.dataCy('input-existing-cff') 85 | .invoke('val', 'bad') 86 | .trigger('input') 87 | cy.dataCy('btn-parse') 88 | .click() 89 | cy.dataCy('text-validation-msg') 90 | .should('include.text', 'Error: CFF is invalid. It should be a YAML map.') 91 | }) 92 | it('should error for general invalid YAML', () => { 93 | ['y : :', 'title: Software: the return'].forEach((str) => { 94 | cy.dataCy('input-existing-cff') 95 | .invoke('val', str) 96 | .trigger('input') 97 | cy.dataCy('btn-parse') 98 | .click() 99 | cy.dataCy('text-validation-msg') 100 | .should('include.text', 'Error: could not parse CFF because of the following YAML error:') 101 | }) 102 | }) 103 | }) 104 | 105 | it('should warn when fields are passed to extra', () => { 106 | cy.dataCy('input-existing-cff') 107 | .invoke('val', 'extra: field') 108 | .trigger('input') 109 | cy.dataCy('btn-parse') 110 | .click() 111 | cy.dataCy('text-validation-msg') 112 | .should('include.text', "Property 'extra' was not identified as a basic field, so it was passed as an extra cff field") 113 | }) 114 | }) 115 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/bad-authors.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | - name: Bad 11 | given-names: Bad 12 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/bad-cff-version.yml: -------------------------------------------------------------------------------- 1 | cff-version: 0.0.1 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/bad-date-released.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | date-released: 2023-01-01 11 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/bad-identifiers.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | identifiers: 11 | - tijpe: doi 12 | describtion: 1 13 | - type: dio 14 | description: 2 15 | - type: doi 16 | description: 3 17 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/bad-type.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: potato 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/clean-authors.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/clean-cff-version.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/clean-date-released.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | date-released: '2023-01-01' 11 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/clean-identifiers.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | identifiers: 11 | - type: other 12 | value: '' 13 | - type: other 14 | value: '' 15 | description: 2 16 | - type: doi 17 | value: '' 18 | description: 3 19 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/clean-type.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/passing-basic.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My title 3 | message: >- 4 | If you use this software, please cite it using the 5 | metadata from this file. 6 | type: software 7 | authors: 8 | - given-names: John 9 | family-names: Doe 10 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/passing-full.yml: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: My Title 3 | message: New message 4 | type: dataset 5 | authors: 6 | - given-names: John 7 | name-particle: von 8 | family-names: Doe 9 | name-suffix: Sr. 10 | email: john@doe.com 11 | affiliation: UU 12 | orcid: 'https://orcid.org/1234-1234-1234-123X' 13 | - address: Some street 14 | alias: NLeSC 15 | city: Amsterdam 16 | country: NL 17 | date-end: '2022-01-01' 18 | date-start: '2021-01-01' 19 | email: nl@esc.com 20 | fax: +31 02 1234 1234 21 | location: Science Park 22 | name: Netherlands eScience Center 23 | orcid: 'https://orcid.org/1234-1234-1234-1234' 24 | post-code: 1234AM 25 | region: Oost 26 | tel: +31 02 1234 5678 27 | website: 'https://nlesc.org' 28 | identifiers: 29 | - type: doi 30 | value: 10.1234/x 31 | description: Some DOI 32 | - type: url 33 | value: 'https://id' 34 | description: Some URL 35 | - type: swh 36 | value: 'swh:1:rev:0123456789abcdef0123456789abcdef01234567' 37 | description: Some SWH 38 | - type: other 39 | value: Other 40 | description: Some other thing 41 | repository-code: 'https://rc' 42 | url: 'https://url' 43 | repository: 'https://r' 44 | repository-artifact: 'https://ra' 45 | abstract: Lorem ipsum 46 | keywords: 47 | - kw0 48 | - kw1 49 | - kw2 50 | license: Apache-2.0 51 | commit: '123' 52 | version: v1.2.3 53 | date-released: '2022-01-01' 54 | preferred-citation: 55 | authors: 56 | - given-names: John 57 | family-names: Doe 58 | title: My Paper 59 | type: article 60 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/warning-authors.txt: -------------------------------------------------------------------------------- 1 | Could not add author. It is not a Person due to fields name and not an Entity due to fields given-names. Skipping 2 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/warning-cff-version.txt: -------------------------------------------------------------------------------- 1 | cff-version was updated to 1.2.0. This might led to some issues, so verify before downloading. 2 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/warning-date-released.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/cypress/e2e/yaml-examples/warning-date-released.txt -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/warning-identifiers.txt: -------------------------------------------------------------------------------- 1 | Property 'tijpe: doi' inside 'identifiers' was ignored. Check if the key is correct. 2 | Invalid value 'dio' for identifier type. Using 'other' instead. 3 | -------------------------------------------------------------------------------- /cypress/e2e/yaml-examples/warning-type.txt: -------------------------------------------------------------------------------- 1 | Invalid type 'potato'. Using 'software' instead. 2 | -------------------------------------------------------------------------------- /cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-namespace */ 2 | 3 | export {} 4 | 5 | declare global { 6 | namespace Cypress { 7 | interface Chainable { 8 | /** 9 | * Custom command to select element using data-cy 10 | * @example cy.dataCy('btn-next') 11 | */ 12 | dataCy(value: string): Chainable 13 | 14 | /** 15 | * Custom command to check whether the step has .q-stepper__tab.q-stepper__tab--error 16 | * @example cy.checkThatStepperValidityIs(true, 'start') 17 | */ 18 | checkThatStepperValidityIs(passing: boolean, step: string): Chainable 19 | 20 | /** 21 | * Custom command to find the input's error message 22 | * @example cy.findInputError('title').should('exist') 23 | */ 24 | findInputError(input: string): Chainable 25 | 26 | /** 27 | * Custom command to find the input's error message 28 | * @example cy.checkInputErrorState(true, 'title') 29 | */ 30 | checkThatInputValidityIs(passing: boolean, input: string): Chainable 31 | 32 | /** 33 | * Custom command to check general errors in the app, like the preview 34 | * @example cy.checkThatAppValidityIs(true) 35 | */ 36 | checkThatAppValidityIs(passing: boolean): Chainable 37 | } 38 | } 39 | } 40 | 41 | const prependIf = (passing: boolean, str: string) => { 42 | if (passing) { 43 | return 'not.' + str 44 | } else { 45 | return str 46 | } 47 | } 48 | 49 | Cypress.Commands.add('dataCy', (text) => { 50 | cy.get(`[data-cy="${text}"]`) 51 | }) 52 | 53 | Cypress.Commands.add('checkThatStepperValidityIs', (passing, step) => { 54 | cy.dataCy(`step-${step}`) 55 | .find('.q-stepper__tab') 56 | .should(prependIf(passing, 'have.class'), 'q-stepper__tab--error') 57 | }) 58 | 59 | Cypress.Commands.add('findInputError', (input) => { 60 | cy.dataCy(`input-${input}`) 61 | .parents('.q-field') 62 | .find('.q-field__messages > div') 63 | }) 64 | 65 | Cypress.Commands.add('checkThatInputValidityIs', (passing, input) => { 66 | cy.dataCy(`input-${input}`) 67 | .parents('.q-field') 68 | .should(prependIf(passing, 'have.class'), 'q-field--error') 69 | .find('.q-field__messages > div') 70 | .should(prependIf(passing, 'exist')) 71 | }) 72 | 73 | Cypress.Commands.add('checkThatAppValidityIs', (passing) => { 74 | cy.dataCy('text-validation-msg') 75 | .should(prependIf(!passing, 'contain.text'), 'Your CITATION.cff is valid') 76 | .should(prependIf(passing, 'contain.text'), 'not') 77 | }) 78 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es5", "dom"], 5 | "types": ["cypress", "node"] 6 | }, 7 | "include": ["**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const esModules = ['quasar/lang', 'lodash-es'].join('|') 2 | 3 | /* eslint-env node */ 4 | module.exports = { 5 | globals: { 6 | __DEV__: true, 7 | // TODO: Remove if resolved natively 8 | // See https://github.com/vuejs/vue-jest/issues/175 9 | 'vue-jest': { 10 | pug: { doctype: 'html' } 11 | } 12 | }, 13 | // noStackTrace: true, 14 | // bail: true, 15 | // cache: false, 16 | // verbose: true, 17 | // watch: true, 18 | collectCoverage: true, 19 | coverageDirectory: '/test/jest/coverage', 20 | collectCoverageFrom: [ 21 | '/src/**/*.vue', 22 | '/src/**/*.js', 23 | '/src/**/*.ts', 24 | '/src/**/*.jsx', 25 | '/src/**/*.tsx' 26 | ], 27 | coveragePathIgnorePatterns: ['/node_modules/', '.d.ts$'], 28 | coverageThreshold: { 29 | global: { 30 | // branches: 50, 31 | // functions: 50, 32 | // lines: 50, 33 | // statements: 50 34 | } 35 | }, 36 | testMatch: [ 37 | // Matches tests in any subfolder of 'src' or into 'test/jest/__tests__' 38 | // Matches all files with extension 'js', 'jsx', 'ts' and 'tsx' 39 | '/test/jest/__tests__/**/*.(spec|test).+(ts|js)?(x)', 40 | '/src/**/*.jest.(spec|test).+(ts|js)?(x)' 41 | ], 42 | // Extension-less imports of components are resolved to .ts files by TS, 43 | // grating correct type-checking in test files. 44 | // Being 'vue' the first moduleFileExtension option, the very same imports 45 | // will be resolved to .vue files by Jest, if both .vue and .ts files are 46 | // in the same folder. 47 | // This guarantee a great dev experience both for testing and type-checking. 48 | // See https://github.com/vuejs/vue-jest/issues/188#issuecomment-620750728 49 | moduleFileExtensions: ['vue', 'js', 'jsx', 'json', 'ts', 'tsx'], 50 | moduleNameMapper: { 51 | '^~/(.*)$': '/$1', 52 | '^src/(.*)$': '/src/$1', 53 | '^app/(.*)$': '/$1', 54 | '^components/(.*)$': '/src/components/$1', 55 | '^assets/(.*)$': '/src/assets/$1', 56 | '^boot/(.*)$': '/src/boot/$1', 57 | '.*css$': '@quasar/quasar-app-extension-testing-unit-jest/stub.css' 58 | }, 59 | transform: { 60 | // See https://jestjs.io/docs/en/configuration.html#transformignorepatterns-array-string 61 | [`^(${esModules}).+\\.js$`]: 'babel-jest', 62 | '^.+\\.(ts|js|html)$': 'ts-jest', 63 | // vue-jest uses find-babel-file, which searches by this order: 64 | // (async) .babelrc, .babelrc.js, package.json, babel.config.js 65 | // (sync) .babelrc, .babelrc.js, babel.config.js, package.json 66 | // https://github.com/tleunen/find-babel-config/issues/33 67 | '.*\\.vue$': 'vue-jest', 68 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 69 | 'jest-transform-stub' 70 | }, 71 | transformIgnorePatterns: [`node_modules/(?!(${esModules}))`], 72 | snapshotSerializers: ['/node_modules/jest-serializer-vue'] 73 | } 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cffinit", 3 | "version": "2.3.1", 4 | "description": "Web form to assist people in creating a CITATION.cff file for their projects.", 5 | "productName": "cffinit", 6 | "author": "Netherlands eScience Center", 7 | "private": true, 8 | "scripts": { 9 | "dev": "export NODE_OPTIONS=--openssl-legacy-provider && quasar dev", 10 | "build": "export NODE_OPTIONS=--openssl-legacy-provider && quasar build", 11 | "lint": "eslint --ext .js,.ts,.vue --ignore-pattern node_modules ./", 12 | "lint-fix": "eslint --ext .js,.ts,.vue --ignore-pattern node_modules ./ --fix", 13 | "test:unit:ui": "majestic", 14 | "test:unit": "jest --updateSnapshot", 15 | "test:unit:ci": "jest --ci --collectCoverage=false", 16 | "test:unit:coverage": "jest --coverage", 17 | "test:unit:watch": "jest --watch --collectCoverage=false", 18 | "test:unit:watchAll": "jest --watchAll", 19 | "cypress:open": "cypress open", 20 | "cypress:run": "cypress run", 21 | "serve:test:coverage": "quasar serve test/jest/coverage/lcov-report/ --port 8788", 22 | "concurrently:dev:jest": "concurrently \"quasar dev\" \"jest --watch\"" 23 | }, 24 | "dependencies": { 25 | "@quasar/extras": "^1.0.0", 26 | "ajv": "^8.6.2", 27 | "ajv-formats": "^2.1.1", 28 | "core-js": "^3.6.5", 29 | "deep-filter": "^1.0.2", 30 | "js-yaml": "^3.14.1", 31 | "kebabcase-keys": "^1.0.0", 32 | "quasar": "^2.10.1" 33 | }, 34 | "devDependencies": { 35 | "@babel/eslint-parser": "^7.13.14", 36 | "@cypress/code-coverage": "^3.10.0", 37 | "@istanbuljs/nyc-config-babel": "^3.0.0", 38 | "@quasar/app": "^3.0.0", 39 | "@quasar/quasar-app-extension-testing": "^2.0.0-beta.2", 40 | "@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.3", 41 | "@types/cypress": "^1.1.3", 42 | "@types/js-yaml": "^4.0.2", 43 | "@types/node": "^10.17.15", 44 | "@typescript-eslint/eslint-plugin": "^4.16.1", 45 | "@typescript-eslint/parser": "^4.16.1", 46 | "babel-plugin-istanbul": "^6.1.1", 47 | "cypress": "^10.3.1", 48 | "eslint": "^7.14.0", 49 | "eslint-config-standard": "^16.0.2", 50 | "eslint-plugin-cypress": "^2.12.1", 51 | "eslint-plugin-import": "^2.19.1", 52 | "eslint-plugin-jest": "^24.3.6", 53 | "eslint-plugin-node": "^11.0.0", 54 | "eslint-plugin-promise": "^5.1.0", 55 | "eslint-plugin-vue": "^7.0.0", 56 | "husky": "^7.0.1", 57 | "majestic": "^1.7.0", 58 | "nyc": "^15.1.0" 59 | }, 60 | "browserslist": [ 61 | "last 10 Chrome versions", 62 | "last 10 Firefox versions", 63 | "last 4 Edge versions", 64 | "last 7 Safari versions", 65 | "last 8 Android versions", 66 | "last 8 ChromeAndroid versions", 67 | "last 8 FirefoxAndroid versions", 68 | "last 10 iOS versions", 69 | "last 5 Opera versions" 70 | ], 71 | "engines": { 72 | "node": ">= 12.22.1", 73 | "npm": ">= 6.13.4", 74 | "yarn": ">= 1.21.1" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/public/favicon.png -------------------------------------------------------------------------------- /quasar.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only 3 | * the ES6 features that are supported by your Node version. https://node.green/ 4 | */ 5 | 6 | // Configuration for your app 7 | // https://v2.quasar.dev/quasar-cli/quasar-conf-js 8 | 9 | /* eslint-env node */ 10 | /* eslint-disable @typescript-eslint/no-var-requires */ 11 | const { configure } = require('quasar/wrappers') 12 | 13 | module.exports = configure(function () { 14 | return { 15 | // https://v2.quasar.dev/quasar-cli/supporting-ts 16 | supportTS: { 17 | tsCheckerConfig: { 18 | eslint: { 19 | enabled: true, 20 | files: './src/**/*.{ts,tsx,js,jsx,vue}' 21 | } 22 | } 23 | }, 24 | 25 | // https://v2.quasar.dev/quasar-cli/prefetch-feature 26 | // preFetch: true, 27 | 28 | // app boot file (/src/boot) 29 | // --> boot files are part of "main.js" 30 | // https://v2.quasar.dev/quasar-cli/boot-files 31 | boot: [ 32 | ], 33 | 34 | // https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css 35 | css: [ 36 | 'app.css' 37 | ], 38 | 39 | // https://github.com/quasarframework/quasar/tree/dev/extras 40 | extras: [ 41 | 'ionicons-v4', 42 | // 'mdi-v5', 43 | // 'fontawesome-v5', 44 | // 'eva-icons', 45 | // 'themify', 46 | // 'line-awesome', 47 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 48 | 49 | 'roboto-font', // optional, you are not bound to it 50 | 'material-icons' // optional, you are not bound to it 51 | ], 52 | 53 | // Full list of options: https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build 54 | build: { 55 | vueRouterMode: 'hash', // available values: 'hash', 'history' 56 | publicPath: 'cff-initializer-javascript', 57 | // transpile: false, 58 | distDir: 'dist', 59 | 60 | // Add dependencies for transpiling with Babel (Array of string/regex) 61 | // (from node_modules, which are by default not transpiled). 62 | // Applies only if "transpile" is set to true. 63 | // transpileDependencies: [], 64 | 65 | // rtl: true, // https://v2.quasar.dev/options/rtl-support 66 | // preloadChunks: true, 67 | // showProgress: false, 68 | // gzip: true, 69 | // analyze: true, 70 | 71 | // Options below are automatically set depending on the env, set them if you want to override 72 | // extractCSS: false, 73 | 74 | // https://v2.quasar.dev/quasar-cli/handling-webpack 75 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 76 | chainWebpack (/* chain */) { 77 | // 78 | } 79 | }, 80 | 81 | // Full list of options: https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer 82 | devServer: { 83 | https: false, 84 | port: 8080, 85 | open: false // do not open browser window automatically 86 | }, 87 | 88 | // https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework 89 | framework: { 90 | config: { 91 | brand: { 92 | primary: '#346FEF', 93 | secondary: '#555770', 94 | accent: '#fdac42', 95 | 96 | formcard: '#efefef', 97 | prose: '#333', 98 | 99 | dark: '#333', 100 | 101 | positive: '#39d98a', 102 | negative: '#D5362C', 103 | info: '#B3C5DB', 104 | warning: '#fddd48' 105 | } 106 | }, 107 | 108 | // iconSet: 'material-icons', // Quasar icon set 109 | // lang: 'en-US', // Quasar language pack 110 | 111 | // For special cases outside of where the auto-import strategy can have an impact 112 | // (like functional components as one of the examples), 113 | // you can manually specify Quasar components/directives to be available everywhere: 114 | // 115 | // components: [], 116 | // directives: [], 117 | 118 | // Quasar plugins 119 | plugins: [ 120 | 'Dialog', 121 | 'Notify' 122 | ] 123 | }, 124 | 125 | // animations: 'all', // --- includes all animations 126 | // https://v2.quasar.dev/options/animations 127 | animations: [] 128 | } 129 | }) 130 | -------------------------------------------------------------------------------- /quasar.extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "@quasar/testing": { 3 | "harnesses": [ 4 | "unit-jest@alpha" 5 | ] 6 | }, 7 | "@quasar/testing-unit-jest": { 8 | "babel": "babelrc", 9 | "options": [ 10 | "scripts", 11 | "typescript", 12 | "majestic" 13 | ] 14 | } 15 | } -------------------------------------------------------------------------------- /quasar.testing.json: -------------------------------------------------------------------------------- 1 | { 2 | "unit-jest": { 3 | "runnerCommand": "jest --ci" 4 | } 5 | } -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 11 | -------------------------------------------------------------------------------- /src/assets/cff-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/landing-image.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/assets/landing-image.webp -------------------------------------------------------------------------------- /src/boot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/boot/.gitkeep -------------------------------------------------------------------------------- /src/components/BannerErrorMessages.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 34 | -------------------------------------------------------------------------------- /src/components/DownloadButton.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 39 | -------------------------------------------------------------------------------- /src/components/EntityCardViewing.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 148 | -------------------------------------------------------------------------------- /src/components/Error404.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 34 | -------------------------------------------------------------------------------- /src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 30 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 56 | -------------------------------------------------------------------------------- /src/components/IdentifierCardEditing.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 150 | -------------------------------------------------------------------------------- /src/components/IdentifierCardViewing.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 110 | -------------------------------------------------------------------------------- /src/components/InfoDialog.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 104 | -------------------------------------------------------------------------------- /src/components/Keyword.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 91 | 98 | -------------------------------------------------------------------------------- /src/components/LayoutLanding.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 95 | 96 | 206 | -------------------------------------------------------------------------------- /src/components/LayoutStepper.vue: -------------------------------------------------------------------------------- 1 | 117 | 118 | 172 | -------------------------------------------------------------------------------- /src/components/LayoutUpdate.vue: -------------------------------------------------------------------------------- 1 | 121 | 122 | 171 | -------------------------------------------------------------------------------- /src/components/PersonCardEditing.vue: -------------------------------------------------------------------------------- 1 | 150 | 151 | 193 | 198 | -------------------------------------------------------------------------------- /src/components/PersonCardViewing.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 126 | -------------------------------------------------------------------------------- /src/components/Preview.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 97 | -------------------------------------------------------------------------------- /src/components/RawErrorList.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 62 | -------------------------------------------------------------------------------- /src/components/ScreenAbstract.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 49 | -------------------------------------------------------------------------------- /src/components/ScreenExtraCffFields.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 46 | -------------------------------------------------------------------------------- /src/components/ScreenFinish.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 80 | -------------------------------------------------------------------------------- /src/components/ScreenKeywords.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 113 | -------------------------------------------------------------------------------- /src/components/ScreenLicense.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 82 | -------------------------------------------------------------------------------- /src/components/ScreenRelatedResources.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 136 | -------------------------------------------------------------------------------- /src/components/ScreenStart.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 125 | -------------------------------------------------------------------------------- /src/components/ScreenVersionSpecific.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 129 | -------------------------------------------------------------------------------- /src/components/Stepper.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 116 | -------------------------------------------------------------------------------- /src/components/StepperActions.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 68 | -------------------------------------------------------------------------------- /src/css/app.css: -------------------------------------------------------------------------------- 1 | /* app global css */ 2 | 3 | @media only screen and (min-width: 2090px) and (min-height: 1039px) { 4 | .scale-container { 5 | overflow: hidden; 6 | } 7 | } 8 | 9 | :root { 10 | --fgcolor: #efefef; 11 | --bgcolor: #ddd; 12 | --white: #efefef; 13 | } 14 | 15 | body { 16 | background-color: var(--bgcolor); 17 | color: #333; 18 | font-family: "Inter", sans-serif; 19 | font-size: 14pt; 20 | } 21 | 22 | #main-block { 23 | max-width: 1044px; 24 | min-height: 500px; 25 | position: relative; 26 | } 27 | 28 | #preview-block { 29 | max-width: 600px; 30 | } 31 | 32 | #header { 33 | background-color: #f8f8f8; 34 | } 35 | 36 | #footer { 37 | background-color: #b3c5db; 38 | } 39 | 40 | a:not(.q-btn) { 41 | color: var(--q-primary); 42 | text-decoration: none 43 | } 44 | 45 | a:not(.q-btn):hover{ 46 | text-decoration: underline 47 | } 48 | 49 | .elevated { 50 | box-shadow: 0px 8px 22px -6px rgba(24, 39, 75, 0.12), 0px 14px 64px -4px rgba(24, 39, 75, 0.12); 51 | } 52 | 53 | .floating-preview-button { 54 | position: absolute; 55 | top: 16px; 56 | right: 16px; 57 | } 58 | 59 | .rounded-borders { 60 | border-radius: 10px; 61 | } 62 | 63 | .bg-formcard { 64 | background-color: #f8f8f8; 65 | border-radius: 5px; 66 | } 67 | 68 | h1 { 69 | font-size: 32pt; 70 | font-weight: bold; 71 | } 72 | h2 { 73 | font-size: 26pt; 74 | } 75 | h3 { 76 | font-size: 22pt; 77 | } 78 | h4 { 79 | font-size: 20pt; 80 | } 81 | h1, h2, h3, h4 { 82 | line-height: normal; 83 | margin: 1rem 0rem; 84 | } 85 | 86 | .q-drawer { 87 | background-color: transparent !important; 88 | } 89 | 90 | @font-face { 91 | font-family: "Inter"; 92 | font-weight: 400; 93 | src: url("./fonts/Inter/Inter-Regular.ttf"); 94 | } 95 | 96 | @font-face { 97 | font-family: "Inter"; 98 | font-weight: bold; 99 | src: url("./fonts/Inter/Inter-Bold.ttf"); 100 | } 101 | 102 | @font-face { 103 | font-family: "Fira Code"; 104 | src: url("./fonts/Fira_Code/FiraCode-Regular.ttf"); 105 | } 106 | 107 | p, 108 | body { 109 | font-family: "Inter", sans-serif; 110 | } 111 | 112 | .cffstr { 113 | font-family: "Fira Code", monospace; 114 | font-size: 11pt; 115 | } 116 | 117 | .red-border { 118 | border-color: #c10015; 119 | } 120 | 121 | .skip-to-main-content-link { 122 | position: absolute; 123 | left: -9999px; 124 | z-index: 999; 125 | padding: 1em; 126 | background-color: black; 127 | color: white; 128 | opacity: 0; 129 | } 130 | 131 | .skip-to-main-content-link:focus { 132 | left: 50%; 133 | transform: translateX(-50%); 134 | opacity: 1; 135 | } 136 | -------------------------------------------------------------------------------- /src/css/fonts/Fira_Code/FiraCode-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Fira_Code/FiraCode-Bold.ttf -------------------------------------------------------------------------------- /src/css/fonts/Fira_Code/FiraCode-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Fira_Code/FiraCode-Light.ttf -------------------------------------------------------------------------------- /src/css/fonts/Fira_Code/FiraCode-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Fira_Code/FiraCode-Medium.ttf -------------------------------------------------------------------------------- /src/css/fonts/Fira_Code/FiraCode-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Fira_Code/FiraCode-Regular.ttf -------------------------------------------------------------------------------- /src/css/fonts/Fira_Code/FiraCode-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Fira_Code/FiraCode-SemiBold.ttf -------------------------------------------------------------------------------- /src/css/fonts/Fira_Code/FiraCode-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Fira_Code/FiraCode-VariableFont_wght.ttf -------------------------------------------------------------------------------- /src/css/fonts/Fira_Code/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014-2020 The Fira Code Project Authors (https://github.com/tonsky/FiraCode) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /src/css/fonts/Fira_Code/README.txt: -------------------------------------------------------------------------------- 1 | Fira Code Variable Font 2 | ======================= 3 | 4 | This download contains Fira Code as both a variable font and static fonts. 5 | 6 | Fira Code is a variable font with this axis: 7 | wght 8 | 9 | This means all the styles are contained in a single file: 10 | FiraCode-VariableFont_wght.ttf 11 | 12 | If your app fully supports variable fonts, you can now pick intermediate styles 13 | that aren’t available as static fonts. Not all apps support variable fonts, and 14 | in those cases you can use the static font files for Fira Code: 15 | static/FiraCode-Light.ttf 16 | static/FiraCode-Regular.ttf 17 | static/FiraCode-Medium.ttf 18 | static/FiraCode-SemiBold.ttf 19 | static/FiraCode-Bold.ttf 20 | 21 | Get started 22 | ----------- 23 | 24 | 1. Install the font files you want to use 25 | 26 | 2. Use your app's font picker to view the font family and all the 27 | available styles 28 | 29 | Learn more about variable fonts 30 | ------------------------------- 31 | 32 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 33 | https://variablefonts.typenetwork.com 34 | https://medium.com/variable-fonts 35 | 36 | In desktop apps 37 | 38 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 39 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 40 | 41 | Online 42 | 43 | https://developers.google.com/fonts/docs/getting_started 44 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 45 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 46 | 47 | Installing fonts 48 | 49 | MacOS: https://support.apple.com/en-us/HT201749 50 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 51 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 52 | 53 | Android Apps 54 | 55 | https://developers.google.com/fonts/docs/android 56 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 57 | 58 | License 59 | ------- 60 | Please read the full license text (OFL.txt) to understand the permissions, 61 | restrictions and requirements for usage, redistribution, and modification. 62 | 63 | You can use them in your products & projects – print or digital, 64 | commercial or otherwise. 65 | 66 | This isn't legal advice, please consider consulting a lawyer and see the full 67 | license for all details. 68 | -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-Black.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-Bold.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-ExtraBold.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-ExtraLight.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-Light.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-Medium.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-Regular.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-SemiBold.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-Thin.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/Inter-VariableFont_slnt,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/src/css/fonts/Inter/Inter-VariableFont_slnt,wght.ttf -------------------------------------------------------------------------------- /src/css/fonts/Inter/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /src/css/fonts/Inter/README.txt: -------------------------------------------------------------------------------- 1 | Inter Variable Font 2 | =================== 3 | 4 | This download contains Inter as both a variable font and static fonts. 5 | 6 | Inter is a variable font with these axes: 7 | slnt 8 | wght 9 | 10 | This means all the styles are contained in a single file: 11 | Inter-VariableFont_slnt,wght.ttf 12 | 13 | If your app fully supports variable fonts, you can now pick intermediate styles 14 | that aren’t available as static fonts. Not all apps support variable fonts, and 15 | in those cases you can use the static font files for Inter: 16 | static/Inter-Thin.ttf 17 | static/Inter-ExtraLight.ttf 18 | static/Inter-Light.ttf 19 | static/Inter-Regular.ttf 20 | static/Inter-Medium.ttf 21 | static/Inter-SemiBold.ttf 22 | static/Inter-Bold.ttf 23 | static/Inter-ExtraBold.ttf 24 | static/Inter-Black.ttf 25 | 26 | Get started 27 | ----------- 28 | 29 | 1. Install the font files you want to use 30 | 31 | 2. Use your app's font picker to view the font family and all the 32 | available styles 33 | 34 | Learn more about variable fonts 35 | ------------------------------- 36 | 37 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 38 | https://variablefonts.typenetwork.com 39 | https://medium.com/variable-fonts 40 | 41 | In desktop apps 42 | 43 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 44 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 45 | 46 | Online 47 | 48 | https://developers.google.com/fonts/docs/getting_started 49 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 50 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 51 | 52 | Installing fonts 53 | 54 | MacOS: https://support.apple.com/en-us/HT201749 55 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 56 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 57 | 58 | Android Apps 59 | 60 | https://developers.google.com/fonts/docs/android 61 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 62 | 63 | License 64 | ------- 65 | Please read the full license text (OFL.txt) to understand the permissions, 66 | restrictions and requirements for usage, redistribution, and modification. 67 | 68 | You can use them in your products & projects – print or digital, 69 | commercial or otherwise. 70 | 71 | This isn't legal advice, please consider consulting a lawyer and see the full 72 | license for all details. 73 | -------------------------------------------------------------------------------- /src/deep-filter.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module 'deep-filter' { 3 | const deepfilter: any 4 | export default deepfilter 5 | } 6 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | interface ProcessEnv { 3 | NODE_ENV: string; 4 | VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined; 5 | VUE_ROUTER_BASE: string | undefined; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /src/kebabcase-keys.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module 'kebabcase-keys' { 3 | const kebabcaseKeys: any 4 | export default kebabcaseKeys 5 | } 6 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { StepNameType, useApp } from 'src/store/app' 2 | import { 3 | createMemoryHistory, 4 | createRouter, 5 | createWebHashHistory, 6 | createWebHistory 7 | } from 'vue-router' 8 | import { route } from 'quasar/wrappers' 9 | import routes from 'src/router/routes' 10 | 11 | /* 12 | * If not building with SSR mode, you can 13 | * directly export the Router instantiation; 14 | * 15 | * The function below can be async too; either use 16 | * async/await or return a Promise which resolves 17 | * with the Router instance. 18 | */ 19 | 20 | export default route((/* { store, ssrContext } */) => { 21 | const createHistory = process.env.SERVER 22 | ? createMemoryHistory 23 | : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory) 24 | 25 | const Router = createRouter({ 26 | scrollBehavior: () => ({ left: 0, top: 0 }), 27 | routes, 28 | 29 | // Leave this as is and make changes in quasar.conf.js instead! 30 | // quasar.conf.js -> build -> vueRouterMode 31 | // quasar.conf.js -> build -> publicPath 32 | history: createHistory( 33 | process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE 34 | ) 35 | }) 36 | Router.beforeEach((to) => { 37 | const { navigateDirect } = useApp() 38 | const newStepName = to.path.replace('/', '') as StepNameType 39 | navigateDirect(newStepName) 40 | }) 41 | return Router 42 | }) 43 | -------------------------------------------------------------------------------- /src/router/routes.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router' 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: '/', 6 | component: () => import('src/components/LayoutLanding.vue') 7 | }, 8 | { 9 | path: '/landing', 10 | component: () => import('src/components/LayoutLanding.vue') 11 | }, 12 | { 13 | path: '/update', 14 | component: () => import('src/components/LayoutUpdate.vue') 15 | }, 16 | { 17 | path: '/start', 18 | component: () => import('src/components/LayoutStepper.vue'), 19 | children: [{ path: '', component: () => import('src/components/ScreenStart.vue') }] 20 | }, 21 | { 22 | path: '/authors', 23 | component: () => import('src/components/LayoutStepper.vue'), 24 | children: [{ path: '', component: () => import('src/components/ScreenAuthors.vue') }] 25 | }, 26 | { 27 | path: '/identifiers', 28 | component: () => import('src/components/LayoutStepper.vue'), 29 | children: [{ path: '', component: () => import('src/components/ScreenIdentifiers.vue') }] 30 | }, 31 | { 32 | path: '/related-resources', 33 | component: () => import('src/components/LayoutStepper.vue'), 34 | children: [{ path: '', component: () => import('src/components/ScreenRelatedResources.vue') }] 35 | }, 36 | { 37 | path: '/abstract', 38 | component: () => import('src/components/LayoutStepper.vue'), 39 | children: [{ path: '', component: () => import('src/components/ScreenAbstract.vue') }] 40 | }, 41 | { 42 | path: '/keywords', 43 | component: () => import('src/components/LayoutStepper.vue'), 44 | children: [{ path: '', component: () => import('src/components/ScreenKeywords.vue') }] 45 | }, 46 | { 47 | path: '/license', 48 | component: () => import('src/components/LayoutStepper.vue'), 49 | children: [{ path: '', component: () => import('src/components/ScreenLicense.vue') }] 50 | }, 51 | { 52 | path: '/version-specific', 53 | component: () => import('src/components/LayoutStepper.vue'), 54 | children: [{ path: '', component: () => import('src/components/ScreenVersionSpecific.vue') }] 55 | }, 56 | { 57 | path: '/extra-cff-fields', 58 | component: () => import('src/components/LayoutStepper.vue'), 59 | children: [{ path: '', component: () => import('src/components/ScreenExtraCffFields.vue') }] 60 | }, 61 | { 62 | path: '/finish', 63 | component: () => import('src/components/LayoutStepper.vue'), 64 | children: [{ path: '', component: () => import('src/components/ScreenFinish.vue') }] 65 | }, 66 | { 67 | path: '/404', 68 | component: () => import('src/components/Error404.vue') 69 | }, 70 | { 71 | path: '/:catchAll(.*)*', 72 | component: () => import('src/components/Error404.vue') 73 | } 74 | ] 75 | 76 | export default routes 77 | -------------------------------------------------------------------------------- /src/scroll-to-bottom.ts: -------------------------------------------------------------------------------- 1 | export const scrollToBottom = (targetClass = 'bottom') => { 2 | document.getElementsByClassName(targetClass)[0].scrollIntoView({ 3 | behavior: 'smooth', 4 | block: 'start', 5 | inline: 'nearest' 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | // Mocks all files ending in `.vue` showing them as plain Vue instances 2 | declare module '*.vue' { 3 | import { ComponentOptions } from 'vue' 4 | const component: ComponentOptions 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /src/store/app.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from 'vue' 2 | import { useRouter } from 'vue-router' 3 | 4 | export type StepNameType = 'start' | 'authors' | 'identifiers' | 'related-resources' | 5 | 'abstract' | 'keywords' | 'license' | 'version-specific' | 'extra-cff-fields' | 'finish' 6 | 7 | const stepNames = [ 8 | 'start', 9 | 'authors', 10 | 'identifiers', 11 | 'related-resources', 12 | 'abstract', 13 | 'keywords', 14 | 'license', 15 | 'version-specific', 16 | 'extra-cff-fields', 17 | 'finish' 18 | ] as Array 19 | 20 | const state = ref({ 21 | stepIndex: 0, 22 | screenVisited: Array(stepNames.length).fill(false) as Array 23 | }) 24 | 25 | const firstStepIndex = 0 26 | 27 | const lastStepIndex = computed(() => stepNames.length - 1) 28 | const stepName = computed(() => stepNames[state.value.stepIndex]) 29 | 30 | export const useApp = () => { 31 | const router = useRouter() 32 | const focusFormTitle = () => { 33 | const element = window.document.getElementById('form-title') 34 | if (!element) return 35 | element.focus() 36 | } 37 | const visitScreen = (screenName: StepNameType) => { 38 | for (let i = 0; i <= stepNames.indexOf(screenName); i++) { 39 | state.value.screenVisited[i] = true 40 | } 41 | } 42 | return { 43 | cannotGoBack: computed(() => state.value.stepIndex === firstStepIndex), 44 | cannotGoForward: computed(() => state.value.stepIndex === lastStepIndex.value), 45 | currentStepIndex: computed(() => state.value.stepIndex), 46 | lastStepIndex, 47 | stepName, 48 | stepNames, 49 | navigateDirect: (newStepName: StepNameType) => { 50 | if (![...stepNames, 'finish'].includes(newStepName)) { 51 | return 52 | } 53 | state.value.stepIndex = stepNames.indexOf(newStepName) 54 | visitScreen(newStepName) 55 | }, 56 | navigateNext: async () => { 57 | if (state.value.stepIndex < lastStepIndex.value) { 58 | state.value.stepIndex++ 59 | visitScreen(stepName.value) 60 | await router.push({ path: `/${stepName.value}` }) 61 | focusFormTitle() 62 | } 63 | }, 64 | navigatePrevious: async () => { 65 | if (state.value.stepIndex > firstStepIndex) { 66 | state.value.stepIndex-- 67 | visitScreen(stepName.value) 68 | await router.push({ path: `/${stepName.value}` }) 69 | focusFormTitle() 70 | } 71 | }, 72 | resetState: () => { 73 | state.value.stepIndex = 0 74 | state.value.screenVisited.fill(false) 75 | }, 76 | screenVisited: (screenName: StepNameType) => { 77 | return state.value.screenVisited[stepNames.indexOf(screenName)] 78 | }, 79 | setStepName: async (newStepName: StepNameType) => { 80 | state.value.stepIndex = stepNames.indexOf(newStepName) 81 | visitScreen(stepName.value) 82 | await router.push({ path: `/${stepName.value}` }) 83 | focusFormTitle() 84 | }, 85 | visitScreen 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/store/cffstr.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 2 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 3 | /* eslint-disable @typescript-eslint/no-unused-vars */ 4 | 5 | import * as yaml from 'js-yaml' 6 | import { CffType } from 'src/types' 7 | import { computed } from 'vue' 8 | import deepfilter from 'deep-filter' 9 | import kebabcaseKeys from 'kebabcase-keys' 10 | import { useCff } from 'src/store/cff' 11 | 12 | export const useCffstr = () => { 13 | const { 14 | abstract, 15 | authors, 16 | commit, 17 | cffVersion, 18 | dateReleased, 19 | identifiers, 20 | keywords, 21 | license, 22 | message, 23 | repository, 24 | repositoryArtifact, 25 | repositoryCode, 26 | title, 27 | type, 28 | url, 29 | version, 30 | extraCffFields 31 | } = useCff() 32 | 33 | const notEmpty = (value: unknown, prop: unknown, subject: unknown) => { 34 | return value !== undefined && value !== null 35 | } 36 | 37 | const makeJavascriptObject = () => { 38 | const cff = { 39 | cffVersion: cffVersion.value, 40 | title: title.value, 41 | message: message.value, 42 | type: type.value, 43 | authors: authors.value, 44 | identifiers: identifiers.value, 45 | repositoryCode: repositoryCode.value, 46 | url: url.value, 47 | repository: repository.value, 48 | repositoryArtifact: repositoryArtifact.value, 49 | abstract: abstract.value, 50 | keywords: keywords.value, 51 | license: license.value, 52 | commit: commit.value, 53 | version: version.value, 54 | dateReleased: dateReleased.value 55 | } as CffType 56 | const filtered = deepfilter(cff, notEmpty) 57 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 58 | return kebabcaseKeys(filtered, { deep: true }) 59 | } 60 | 61 | const makeCffstr = () => { 62 | const kebabed = makeJavascriptObject() 63 | const yamlString = yaml.dump(kebabed, { indent: 2, lineWidth: 60 }) 64 | const generatedBy = '# This CITATION.cff file was generated with cffinit.\n# Visit https://bit.ly/cffinit to generate yours today!\n\n' 65 | return generatedBy + yamlString + extraCffFields.value 66 | } 67 | return { 68 | jsObject: computed(makeJavascriptObject), 69 | cffstr: computed(makeCffstr) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/store/stepper-errors.ts: -------------------------------------------------------------------------------- 1 | import { 2 | byError, 3 | instancePathStartsWithMatcher, 4 | screenAuthorQueries, 5 | screenIdentifiersQueries, 6 | screenKeywordsQueries, 7 | screenRelatedResourcesQueries, 8 | screenStartQueries, 9 | screenVersionSpecificQueries 10 | } from 'src/error-filtering' 11 | import { computed } from 'vue' 12 | import { useCff } from 'src/store/cff' 13 | import { useValidation } from 'src/store/validation' 14 | 15 | const { errors } = useValidation() 16 | const { extraCffFields } = useCff() 17 | 18 | const errorStateScreenAuthors = computed(() => { 19 | return screenAuthorQueries 20 | .filter(byError(errors.value, instancePathStartsWithMatcher)) 21 | .length > 0 22 | }) 23 | const errorStateScreenIdentifiers = computed(() => { 24 | return screenIdentifiersQueries 25 | .filter(byError(errors.value, instancePathStartsWithMatcher)) 26 | .length > 0 27 | }) 28 | const errorStateScreenKeywords = computed(() => { 29 | return screenKeywordsQueries 30 | .filter(byError(errors.value, instancePathStartsWithMatcher)) 31 | .length > 0 32 | }) 33 | const errorStateScreenRelatedResources = computed(() => { 34 | return screenRelatedResourcesQueries 35 | .filter(byError(errors.value, instancePathStartsWithMatcher)) 36 | .length > 0 37 | }) 38 | const errorStateScreenStart = computed(() => { 39 | return screenStartQueries 40 | .filter(byError(errors.value)) // One of the possible errors is instancePath == '', so we use a traditional approach here 41 | .length > 0 42 | }) 43 | const errorStateScreenVersionSpecific = computed(() => { 44 | return screenVersionSpecificQueries 45 | .filter(byError(errors.value, instancePathStartsWithMatcher)) 46 | .length > 0 47 | }) 48 | 49 | export const errorPerStep = { 50 | start: errorStateScreenStart, 51 | authors: errorStateScreenAuthors, 52 | identifiers: errorStateScreenIdentifiers, 53 | 'related-resources': errorStateScreenRelatedResources, 54 | abstract: computed(() => false), 55 | keywords: errorStateScreenKeywords, 56 | license: computed(() => false), 57 | 'version-specific': errorStateScreenVersionSpecific, 58 | 'extra-cff-fields': computed(() => { return extraCffFields.value.length > 0 && errors.value.length > 0 }), 59 | finish: computed(() => false) 60 | } 61 | -------------------------------------------------------------------------------- /src/store/store-flag.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 3 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 4 | import "quasar/dist/types/feature-flag"; 5 | 6 | declare module "quasar/dist/types/feature-flag" { 7 | interface QuasarFeatureFlags { 8 | store: true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/store/validation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 2 | import * as yaml from 'js-yaml' 3 | import Ajv, { ErrorObject } from 'ajv' 4 | import addFormats from 'ajv-formats' 5 | import { computed } from 'vue' 6 | import schema from 'src/schemas/1.2.0/schema.json' 7 | import { useCff } from 'src/store/cff' 8 | import { useCffstr } from 'src/store/cffstr' 9 | 10 | const ajv = new Ajv({ allErrors: true }) 11 | addFormats(ajv) 12 | ajv.addSchema(schema) 13 | 14 | type ajvErrorType = ErrorObject, unknown> 15 | const { extraCffFields } = useCff() 16 | const { jsObject } = useCffstr() 17 | 18 | const errors = computed(() => { 19 | try { 20 | if (extraCffFields.value.trim() === '') { 21 | ajv.validate(schema.$id, jsObject.value) 22 | } else { 23 | const extraYaml = yaml.load(extraCffFields.value) as Record 24 | const duplicateKeys = Object.keys(extraYaml).filter(x => x in jsObject.value) 25 | if (duplicateKeys.length > 0) { 26 | throw Error(`Duplicate keys: '${duplicateKeys.join("', '")}'`) 27 | } 28 | ajv.validate(schema.$id, { 29 | ...jsObject.value, 30 | ...extraYaml 31 | }) 32 | } 33 | } catch (error) { 34 | ajv.validate(schema.$id, { 35 | ...jsObject.value 36 | }) 37 | let msg = '' 38 | if (error instanceof yaml.YAMLException) { 39 | msg = 'YAML Error: ' + error.message 40 | } else if (error instanceof Error) { 41 | msg = error.message 42 | } else { 43 | msg = 'Uncaught error. Please report this issue.' 44 | } 45 | if (!(ajv.errors)) { 46 | ajv.errors = [] as ErrorObject[] 47 | } 48 | ajv.errors.push({ 49 | keyword: 'Extra CFF Fields error', 50 | instancePath: '', 51 | schemaPath: '', 52 | params: [], 53 | message: msg 54 | } as ErrorObject) 55 | } 56 | if (ajv.errors) { 57 | return ajv.errors 58 | } else { 59 | return [] as ajvErrorType[] 60 | } 61 | }) 62 | 63 | export const useValidation = () => { 64 | return { 65 | errors: errors 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type PersonType = { 2 | givenNames?: string; 3 | nameParticle?: string; 4 | nameSuffix?: string; 5 | orcid?: string; 6 | familyNames?: string; 7 | affiliation?: string; 8 | email?: string; 9 | } 10 | 11 | export type EntityType = { 12 | address?: string 13 | alias?: string 14 | city?: string 15 | country?: string 16 | dateEnd?: string 17 | dateStart?: string 18 | email?: string 19 | fax?: string 20 | location?: string 21 | name?: string 22 | orcid?: string 23 | postCode?: string 24 | region?: string 25 | tel?: string 26 | website?: string 27 | } 28 | 29 | export type AuthorsType = Array 30 | 31 | export type AuthorKind = 'person' | 'entity' 32 | 33 | export type IdentifierTypeType = 'doi' | 'url' | 'swh' | 'other' 34 | export type IdentifierType = { 35 | type: IdentifierTypeType, 36 | value: string, 37 | description?: string 38 | } 39 | 40 | export type IdentifiersType = Array | undefined 41 | 42 | export type KeywordsType = Array | undefined 43 | 44 | export type TypeType = 'software' | 'dataset' 45 | 46 | export type CffType = { 47 | abstract?: string, 48 | authors: AuthorsType, 49 | authorsKind: Array, 50 | cffVersion: string, 51 | commit?: string, 52 | dateReleased?: string, 53 | keywords?: KeywordsType, 54 | identifiers?: IdentifiersType, 55 | license?: string, 56 | message: string, 57 | repository?: string, 58 | repositoryArtifact?: string, 59 | repositoryCode?: string, 60 | title: string, 61 | type: TypeType, 62 | url?: string, 63 | version?: string 64 | } 65 | -------------------------------------------------------------------------------- /src/updown.ts: -------------------------------------------------------------------------------- 1 | const moveUpdate = (index: number, value: number, array: Type[], updateArray: (a: Type[]) => void): void => { 2 | const newArray: Type[] = [...array] 3 | newArray[index] = newArray.splice(index + value, 1, newArray[index])[0] 4 | updateArray(newArray) 5 | } 6 | 7 | export const moveDown = (index: number, array: Type[], updateArray: (a: Type[]) => void): void => { 8 | if (index === array.length - 1) return 9 | moveUpdate(index, 1, array, updateArray) 10 | } 11 | 12 | export const moveUp = (index: number, array: Type[], updateArray: (a: Type[]) => void): void => { 13 | if (index === 0) return 14 | moveUpdate(index, -1, array, updateArray) 15 | } 16 | -------------------------------------------------------------------------------- /test/jest/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | // Removes 'no-undef' lint errors for Jest global functions (`describe`, `it`, etc), 4 | // add Jest-specific lint rules and Jest plugin 5 | // See https://github.com/jest-community/eslint-plugin-jest#recommended 6 | 'plugin:jest/recommended', 7 | // Uncomment following line to apply style rules 8 | // 'plugin:jest/style', 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /test/jest/.gitignore: -------------------------------------------------------------------------------- 1 | coverage/* -------------------------------------------------------------------------------- /test/jest/__tests__/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citation-file-format/cff-initializer-javascript/48a503ba5f7a45a68d5c72e32a4973d5be0d0551/test/jest/__tests__/.gitkeep -------------------------------------------------------------------------------- /test/jest/__tests__/pages/Start.jest.spec.ts: -------------------------------------------------------------------------------- 1 | import { VueWrapper, shallowMount } from '@vue/test-utils' 2 | import { beforeEach, describe, expect, it } from '@jest/globals' 3 | import LayoutLanding from 'src/components/LayoutLanding.vue' 4 | import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-jest' 5 | 6 | // Specify here Quasar config you'll need to test your component 7 | installQuasarPlugin() 8 | 9 | describe('LayoutLanding', () => { 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | let wrapper: VueWrapper 12 | 13 | beforeEach(() => { 14 | wrapper = shallowMount(LayoutLanding) 15 | }) 16 | 17 | it('should mount without errors', () => { 18 | expect(wrapper).toBeTruthy() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /test/jest/__tests__/store/app.jest.spec.ts: -------------------------------------------------------------------------------- 1 | import { StepNameType, useApp } from 'src/store/app' 2 | import { describe, expect, it, jest } from '@jest/globals' 3 | import { useCff } from 'src/store/cff' 4 | 5 | const routerPushMock = jest.fn() 6 | 7 | jest.mock('vue-router', () => ({ 8 | useRouter: () => ({ 9 | push: routerPushMock 10 | }) 11 | })) 12 | 13 | describe('useApp', () => { 14 | const { 15 | cannotGoBack, 16 | cannotGoForward, 17 | lastStepIndex, 18 | navigateNext, 19 | navigatePrevious, 20 | setStepName, 21 | stepName 22 | } = useApp() 23 | const { reset: resetCffData } = useCff() 24 | const stepNames = [ 25 | 'start', 26 | 'authors', 27 | 'identifiers', 28 | 'related-resources', 29 | 'abstract', 30 | 'keywords', 31 | 'license', 32 | 'version-specific', 33 | 'extra-cff-fields' 34 | ] as Array 35 | 36 | beforeEach(() => { 37 | resetCffData() 38 | void setStepName('start') 39 | }) 40 | 41 | it('should start with state on step 0', () => { 42 | expect(stepName.value).toBe('start') 43 | }) 44 | 45 | describe('navigation', () => { 46 | it(`should have ${stepNames.length} as lastIndex`, () => { 47 | expect(lastStepIndex.value).toBe(stepNames.length) 48 | }) 49 | it('should be navigable with next and previous (except at boundaries)', () => { 50 | expect(cannotGoBack.value).toBe(true) 51 | stepNames.forEach((step) => { 52 | expect(stepName.value).toBe(step) 53 | expect(cannotGoForward.value).toBe(false) 54 | void navigateNext() 55 | expect(cannotGoBack.value).toBe(false) 56 | }) 57 | expect(stepName.value).toBe('finish') 58 | expect(cannotGoForward.value).toBe(true) 59 | Array.from(stepNames).reverse().forEach((step) => { 60 | expect(cannotGoBack.value).toBe(false) 61 | void navigatePrevious() 62 | expect(stepName.value).toBe(step) 63 | expect(cannotGoForward.value).toBe(false) 64 | }) 65 | expect(stepName.value).toBe('start') 66 | expect(cannotGoBack.value).toBe(true) 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/jest/__tests__/store/cffstr.jest.spec.ts: -------------------------------------------------------------------------------- 1 | import * as yaml from 'js-yaml' 2 | import { beforeEach, describe, expect, it } from '@jest/globals' 3 | import { AuthorsType } from 'src/types' 4 | import { useCff } from 'src/store/cff' 5 | import { useCffstr } from 'src/store/cffstr' 6 | 7 | describe('useCffstr', () => { 8 | const cff = useCff() 9 | const { cffstr } = useCffstr() 10 | const parsedCffStr = () => { return yaml.load(cffstr.value) } 11 | const cffMinimumFields = { 12 | 'cff-version': '1.2.0', 13 | type: 'software', 14 | title: '', 15 | message: 'If you use this software, please cite it using the metadata from this file.', 16 | authors: [] 17 | } 18 | const cffstrFields = [ 19 | { field: 'abstract', value: 'Description', cffFunction: cff.setAbstract }, 20 | { field: 'authors', value: [{ 'given-names': 'John', 'family-names': 'Doe', orcid: 'https://1234-1234-1234-123X' }, { name: 'Netherlands eScience Center', alias: 'NLeSC', country: 'NL' }], cffFunction: (authors: AuthorsType) => { cff.setAuthors(authors, ['person', 'entity']) } }, 21 | { field: 'commit', value: '1234567890abcde', cffFunction: cff.setCommit }, 22 | { field: 'date-released', value: '2022-01-01', cffFunction: cff.setDateReleased }, 23 | { field: 'identifiers', value: [{ type: 'doi', value: '10.5281/zenodo.5171937' }], cffFunction: cff.setIdentifiers }, 24 | { field: 'keywords', value: ['kw1', 'kw2'], cffFunction: cff.setKeywords }, 25 | { field: 'license', value: 'Apache-2.0', cffFunction: cff.setLicense }, 26 | { field: 'message', value: 'Cite me!', cffFunction: cff.setMessage }, 27 | { field: 'repository-artifact', value: 'https://a', cffFunction: cff.setRepositoryArtifact }, 28 | { field: 'repository-code', value: 'https://a', cffFunction: cff.setRepositoryCode }, 29 | { field: 'repository', value: 'https://a', cffFunction: cff.setRepository }, 30 | { field: 'title', value: 'Title', cffFunction: cff.setTitle }, 31 | { field: 'type', value: 'dataset', cffFunction: cff.setType }, 32 | { field: 'url', value: 'https://a', cffFunction: cff.setUrl }, 33 | { field: 'version', value: '1.2.3', cffFunction: cff.setVersion } 34 | ] 35 | 36 | beforeEach(() => { 37 | cff.reset() 38 | }) 39 | describe('initial content', () => { 40 | it('should only have fields with defaults', () => { 41 | const expected = cffMinimumFields 42 | expect(parsedCffStr()).toEqual(expected) 43 | }) 44 | }) 45 | 46 | describe('changing only a single field at a time', () => { 47 | for (const fieldData of cffstrFields) { 48 | const { field, value, cffFunction } = fieldData 49 | it(`should work for field '${field}'`, () => { 50 | cffFunction(value as never) 51 | const expected = { ...cffMinimumFields, [field]: value } 52 | expect(parsedCffStr()).toEqual(expected) 53 | }) 54 | } 55 | }) 56 | 57 | describe('adding every field', () => { 58 | it('should work', () => { 59 | let expected = { ...cffMinimumFields } 60 | for (const fieldData of cffstrFields) { 61 | const { field, value, cffFunction } = fieldData 62 | cffFunction(value as never) 63 | expected = { ...expected, [field]: value } 64 | } 65 | expect(parsedCffStr()).toEqual(expected) 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/jest/__tests__/store/validation.jest.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from '@jest/globals' 2 | import { AuthorKind } from 'src/types' 3 | import { useCff } from 'src/store/cff' 4 | import { useValidation } from 'src/store/validation' 5 | 6 | describe('useValidation', () => { 7 | const cff = useCff() 8 | const { errors } = useValidation() 9 | 10 | const anyErrorMatches = (instancePath: string, schemaPath: string) => { 11 | return errors.value.reduce((acc, err) => { 12 | return acc || (err.instancePath === instancePath && err.schemaPath === schemaPath) 13 | }, false) 14 | } 15 | 16 | cff.reset() 17 | it('should start with errors in title and authors', () => { 18 | expect(errors.value.length).toEqual(2) 19 | expect(anyErrorMatches('/authors', '#/properties/authors/minItems')).toBe(true) 20 | expect(anyErrorMatches('/title', '#/properties/title/minLength')).toBe(true) 21 | }) 22 | 23 | it('should have no more errors after title and authors are fixed', () => { 24 | cff.setTitle('Title') 25 | cff.setAuthors([{}], ['person']) 26 | expect(errors.value.length).toEqual(0) 27 | }) 28 | 29 | it('should complain about missing message', () => { 30 | cff.setMessage('') 31 | expect(errors.value.length).toEqual(1) 32 | expect(anyErrorMatches('/message', '#/properties/message/minLength')).toBe(true) 33 | }) 34 | 35 | describe('catches', () => { 36 | const relatedResources = [ 37 | { key: 'repository', foo: cff.setRepository }, 38 | { key: 'repository-artifact', foo: cff.setRepositoryArtifact }, 39 | { key: 'repository-code', foo: cff.setRepositoryCode }, 40 | { key: 'url', foo: cff.setUrl } 41 | ] 42 | 43 | beforeEach(() => { 44 | cff.reset() 45 | cff.setTitle('Title') 46 | cff.setAuthors([{}], ['person']) 47 | }) 48 | 49 | test('missing author', () => { 50 | cff.setAuthors([], []) 51 | expect(anyErrorMatches('/authors', '#/properties/authors/minItems')).toBe(true) 52 | }) 53 | test('bad release date', () => { 54 | cff.setDateReleased('bad') 55 | expect(anyErrorMatches('/date-released', '#/definitions/date/pattern')).toBe(true) 56 | }) 57 | test('duplicate author', () => { 58 | cff.setAuthors([{}, {}], ['person', 'person']) 59 | expect(anyErrorMatches('/authors', '#/properties/authors/uniqueItems')).toBe(true) 60 | }) 61 | test('duplicate identifier', () => { 62 | cff.setIdentifiers([{ type: 'other', value: '1' }, { type: 'other', value: '1' }]) 63 | expect(anyErrorMatches('/identifiers', '#/properties/identifiers/uniqueItems')).toBe(true) 64 | }) 65 | test('duplicate keyword', () => { 66 | cff.setKeywords(['a', 'a']) 67 | expect(anyErrorMatches('/keywords', '#/properties/keywords/uniqueItems')).toBe(true) 68 | }) 69 | for (const authorKind of ['person', 'entity']) { 70 | test('bad e-mail on author', () => { 71 | cff.setAuthors([{ email: 'bad' }], [authorKind as AuthorKind]) 72 | expect(anyErrorMatches('/authors/0/email', '#/definitions/email/pattern')).toBe(true) 73 | }) 74 | test('bad ORCID', () => { 75 | cff.setAuthors([{ orcid: 'bad' }], [authorKind as AuthorKind]) 76 | expect(anyErrorMatches('/authors/0/orcid', '#/definitions/orcid/pattern')).toBe(true) 77 | }) 78 | } 79 | test('bad start date on entity', () => { 80 | cff.setAuthors([{ dateStart: 'bad' }], ['entity']) 81 | expect(anyErrorMatches('/authors/0/date-start', '#/definitions/date/pattern')).toBe(true) 82 | }) 83 | test('bad end date on entity', () => { 84 | cff.setAuthors([{ dateEnd: 'bad' }], ['entity']) 85 | expect(anyErrorMatches('/authors/0/date-end', '#/definitions/date/pattern')).toBe(true) 86 | }) 87 | test('bad website on entity', () => { 88 | cff.setAuthors([{ website: 'bad' }], ['entity']) 89 | expect(anyErrorMatches('/authors/0/website', '#/definitions/url/pattern')).toBe(true) 90 | }) 91 | test('bad DOI identifier', () => { 92 | cff.setIdentifiers([{ type: 'doi', value: 'bad' }]) 93 | expect(anyErrorMatches('/identifiers/0/value', '#/definitions/doi/pattern')).toBe(true) 94 | }) 95 | test('bad URL identifier', () => { 96 | cff.setIdentifiers([{ type: 'url', value: 'bad' }]) 97 | expect(anyErrorMatches('/identifiers/0/value', '#/definitions/url/pattern')).toBe(true) 98 | // Trailing white spaces on URLs are a different kind of error. See #605 99 | cff.setIdentifiers([{ type: 'url', value: 'https:// ' }]) 100 | expect(anyErrorMatches('/identifiers/0/value', '#/definitions/url/format')).toBe(true) 101 | }) 102 | test('bad SWH identifier', () => { 103 | cff.setIdentifiers([{ type: 'swh', value: 'bad' }]) 104 | expect(anyErrorMatches('/identifiers/0/value', '#/definitions/swh-identifier/pattern')).toBe(true) 105 | }) 106 | test('bad other identifier', () => { 107 | cff.setIdentifiers([{ type: 'other', value: '' }]) 108 | expect(anyErrorMatches('/identifiers/0/value', '#/anyOf/3/properties/value/minLength')).toBe(true) 109 | }) 110 | test('bad keyword', () => { 111 | cff.setKeywords(['']) 112 | expect(anyErrorMatches('/keywords/0', '#/properties/keywords/items/minLength')).toBe(true) 113 | }) 114 | for (const relatedResource of relatedResources) { 115 | const { key, foo } = relatedResource 116 | test(`bad ${key}`, () => { 117 | foo('bad') 118 | expect(anyErrorMatches(`/${key}`, '#/definitions/url/pattern')).toBe(true) 119 | // Trailing white spaces on URLs are a different kind of error. See #605 120 | foo('https:// ') 121 | expect(anyErrorMatches(`/${key}`, '#/definitions/url/format')).toBe(true) 122 | }) 123 | } 124 | test('bad YAML in extra CFF', () => { 125 | cff.setExtraCffFields('a: -') 126 | expect(errors.value[0].keyword).toEqual('Extra CFF Fields error') 127 | expect(errors.value[0].message).toContain('end of the stream') 128 | }) 129 | test('wrong extra field in extra CFF', () => { 130 | cff.setExtraCffFields('extra: field') 131 | expect(errors.value[0].keyword).toEqual('additionalProperties') 132 | }) 133 | test('preferred-citation missing title', () => { 134 | cff.setExtraCffFields('preferred-citation:\n authors: [{}]\n type: article') 135 | expect(errors.value.length).toBe(1) 136 | expect(errors.value[0].instancePath).toEqual('/preferred-citation') 137 | expect(errors.value[0].message).toEqual("must have required property 'title'") 138 | }) 139 | }) 140 | }) 141 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@quasar/app/tsconfig-preset", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "jsx": "react-jsx" 6 | } 7 | } 8 | --------------------------------------------------------------------------------