Compare commits

..

3 Commits

Author SHA1 Message Date
kolaente 39a2774d57
Fix release buckets in drone config 2020-08-06 14:52:11 +02:00
kolaente efad37add9
Release preparations 2020-08-06 12:31:10 +02:00
kolaente eb1b021d72
Prevent rendering html in tooltips
(cherry picked from commit 5c9e8b8b0c)
2020-08-06 12:29:45 +02:00
672 changed files with 30745 additions and 72531 deletions

View File

@ -1,173 +1,32 @@
---
kind: pipeline
name: build
name: testing
trigger:
branch:
include:
- main
- master
event:
include:
- push
- pull_request
services:
- name: api
image: vikunja/api:unstable
environment:
VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB
VIKUNJA_LOG_LEVEL: DEBUG
steps:
# Disabled until we figure out why it is so slow
# - name: restore-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# debug: true
# restore: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "pnpm-lock.yaml" }}_{{ arch }}_{{ os }}'
# mount:
# - .cache
- name: dependencies
image: node:18-alpine
- name: build
image: node:13
pull: true
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
CYPRESS_CACHE_FOLDER: .cache/cypress
group: build-static
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
- pnpm install --fetch-timeout 100000
# depends_on:
# - restore-cache
- name: lint
image: node:18-alpine
pull: true
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
- pnpm run lint
depends_on:
- dependencies
- name: build-prod
image: node:18-alpine
pull: true
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
- pnpm run build
depends_on:
- dependencies
- name: test-unit
image: node:18-alpine
pull: true
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
- pnpm run test:unit
depends_on:
- dependencies
- name: typecheck
failure: ignore
image: node:18-alpine
pull: true
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
- pnpm run typecheck
depends_on:
- dependencies
- name: test-frontend
image: cypress/browsers:node16.14.0-chrome99-ff97
pull: true
environment:
CYPRESS_API_URL: http://api:3456/api/v1
CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB
PNPM_CACHE_FOLDER: .cache/pnpm
CYPRESS_CACHE_FOLDER: .cache/cypress
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000
CYPRESS_RECORD_KEY:
from_secret: cypress_project_key
commands:
- sed -i 's/localhost/api/g' dist/index.html
- corepack enable && pnpm config set store-dir .cache/pnpm
- pnpm cypress install
- pnpm run serve:dist & npx wait-on http://localhost:4173
- pnpm run test:frontend --browser chrome --record
depends_on:
- build-prod
# - name: rebuild-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# rebuild: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "pnpm-lock.yaml" }}_{{ arch }}_{{ os }}'
# mount:
# - .cache
# depends_on:
# - dependencies
- name: deploy-preview
image: node:18-alpine
pull: true
environment:
NETLIFY_AUTH_TOKEN:
from_secret: netlify_auth_token
NETLIFY_SITE_ID:
from_secret: netlify_site_id
GITEA_TOKEN:
from_secret: gitea_token
commands:
- cp -r dist dist-preview
# Override the default api url used for preview
- sed -i 's|http://localhost:3456|https://try.vikunja.io|g' dist-preview/index.html
- apk add --no-cache perl-utils
- shasum -a 384 -c ./scripts/deploy-preview-netlify.js.sha384
- node ./scripts/deploy-preview-netlify.js
depends_on:
- build-prod
when:
event:
include:
- pull_request
- yarn --frozen-lockfile --network-timeout 100000
- yarn run lint
- yarn run build
---
kind: pipeline
name: release-latest
depends_on:
- build
trigger:
branch:
- main
- master
event:
- push
@ -177,51 +36,28 @@ steps:
commands:
- git fetch --tags
# - name: restore-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# restore: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "pnpm-lock.yaml" }}_{{ arch }}_{{ os }}'
# mount:
# - .cache
- name: build
image: node:18-alpine
image: node:13
pull: true
group: build-static
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
commands:
- corepack enable && pnpm config set store-dir .cache/.pnp
- pnpm install --fetch-timeout 100000
- pnpm run lint
- yarn --frozen-lockfile --network-timeout 100000
- yarn run lint
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
- pnpm run build
- yarn run build
- sed -i 's/http\:\\/\\/localhost\\:3456\\/api\\/v1/\\/api\\/v1/g' dist/index.html # Override the default api url used for developing
# depends_on:
# - restore-cache
- name: static
image: kolaente/zip
pull: true
commands:
- cd dist
- zip -r ../vikunja-frontend-unstable.zip *
- zip -r ../vikunja-frontend-master.zip *
- cd ..
depends_on: [ build ]
- name: release
image: plugins/s3
image: plugins/s3:1
pull: true
settings:
bucket: vikunja-releases
@ -232,7 +68,7 @@ steps:
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
source: vikunja-frontend-unstable.zip
source: vikunja-frontend-master.zip
target: /frontend/
depends_on: [ static ]
@ -240,9 +76,6 @@ steps:
kind: pipeline
name: release-version
depends_on:
- build
trigger:
event:
- tag
@ -253,39 +86,16 @@ steps:
commands:
- git fetch --tags
# - name: restore-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# restore: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "pnpm-lock.yaml" }}_{{ arch }}_{{ os }}'
# mount:
# - .cache
- name: build
image: node:18-alpine
image: node:13
pull: true
group: build-static
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
- pnpm install --fetch-timeout 100000
- pnpm run lint
- yarn --frozen-lockfile --network-timeout 100000
- yarn run lint
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
- pnpm run build
- yarn run build
- sed -i 's/http\:\\/\\/localhost\\:3456\\/api\\/v1/\\/api\\/v1/g' dist/index.html # Override the default api url used for developing
# depends_on:
# - restore-cache
- name: static
image: kolaente/zip
@ -297,7 +107,7 @@ steps:
depends_on: [ build ]
- name: release
image: plugins/s3
image: plugins/s3:1
pull: true
settings:
bucket: vikunja-releases
@ -312,71 +122,26 @@ steps:
target: /frontend/
depends_on: [ static ]
---
kind: pipeline
name: trigger-desktop-update
trigger:
branch:
- main
event:
- push
depends_on:
- release-latest
steps:
- name: trigger
image: plugins/downstream
settings:
server: https://drone.kolaente.de
token:
from_secret: drone_token
repositories:
- vikunja/desktop@main
---
kind: pipeline
type: docker
name: docker-arm-release
depends_on:
- release-latest
- release-version
platform:
os: linux
arch: arm64
arch: arm
trigger:
ref:
- refs/heads/main
- refs/heads/master
- "refs/tags/**"
event:
exclude:
- cron
steps:
- name: docker-unstable
image: plugins/docker:linux-arm
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/frontend
tags: unstable-linux-arm
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
when:
ref:
- refs/heads/main
depends_on:
- clone
- name: docker-version
- name: fetch-tags
image: docker:git
commands:
- git fetch --tags
- name: docker
image: plugins/docker:linux-arm
pull: true
settings:
@ -387,53 +152,7 @@ steps:
repo: vikunja/frontend
auto_tag: true
auto_tag_suffix: linux-arm
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=${DRONE_TAG##v}
when:
ref:
- "refs/tags/**"
depends_on:
- clone
- name: docker-unstable-arm64
image: plugins/docker:linux-arm64
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/frontend
tags: unstable-linux-arm64
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
when:
ref:
- refs/heads/main
depends_on:
- clone
- name: docker-version-arm64
image: plugins/docker:linux-arm64
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/frontend
auto_tag: true
auto_tag_suffix: linux-arm64
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=${DRONE_TAG##v}
when:
ref:
- "refs/tags/**"
depends_on:
- clone
depends_on: [ fetch-tags ]
---
kind: pipeline
@ -444,37 +163,17 @@ platform:
os: linux
arch: amd64
depends_on:
- release-latest
- release-version
trigger:
ref:
- refs/heads/main
- refs/heads/master
- "refs/tags/**"
event:
exclude:
- cron
steps:
- name: docker-unstable
image: plugins/docker:linux-amd64
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/frontend
tags: unstable-linux-amd64
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
when:
ref:
- refs/heads/main
- name: docker-version
- name: fetch-tags
image: docker:git
commands:
- git fetch --tags
- name: docker
image: plugins/docker:linux-amd64
pull: true
settings:
@ -485,12 +184,7 @@ steps:
repo: vikunja/frontend
auto_tag: true
auto_tag_suffix: linux-amd64
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=${DRONE_TAG##v}
when:
ref:
- "refs/tags/**"
depends_on: [ fetch-tags ]
---
kind: pipeline
@ -499,32 +193,15 @@ name: docker-manifest
trigger:
ref:
- refs/heads/main
- refs/heads/master
- "refs/tags/**"
event:
exclude:
- cron
depends_on:
- docker-amd64-release
- docker-arm-release
steps:
- name: manifest-unstable
pull: always
image: plugins/manifest
settings:
tags: unstable
spec: docker-manifest-unstable.tmpl
password:
from_secret: docker_password
username:
from_secret: docker_username
when:
ref:
- refs/heads/main
- name: manifest-release
- name: manifest
pull: always
image: plugins/manifest
settings:
@ -535,26 +212,6 @@ steps:
from_secret: docker_password
username:
from_secret: docker_username
when:
ref:
- "refs/tags/**"
- name: manifest-release-latest
pull: always
image: plugins/manifest
depends_on:
- clone
settings:
tags: latest
ignore_missing: true
spec: docker-manifest.tmpl
password:
from_secret: docker_password
username:
from_secret: docker_username
when:
ref:
- "refs/tags/**"
---
kind: pipeline
@ -563,102 +220,27 @@ name: notify
trigger:
ref:
- refs/heads/main
- refs/heads/master
- "refs/tags/**"
event:
exclude:
- cron
depends_on:
- build
- release-version
- release-latest
- trigger-desktop-update
- docker-arm-release
- docker-amd64-release
- docker-manifest
steps:
- name: notify
image: plugins/matrix
- name: telegram
image: appleboy/drone-telegram:1-linux-amd64
settings:
homeserver: https://matrix.org
roomid: WqBDCxzghKcNflkErL:matrix.org
username:
from_secret: matrix_username
password:
from_secret: matrix_password
token:
from_secret: TELEGRAM_TOKEN
to:
from_secret: TELEGRAM_TO
message: >
{{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}}
{{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}`
Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}.
when:
status:
- success
- failure
---
kind: pipeline
type: docker
name: update-translations
depends_on:
- build
trigger:
branch:
- main
event:
- cron
cron:
- update_translations
steps:
- name: download
pull: always
image: jonasfranz/crowdin
settings:
download: true
export_dir: src/i18n/lang/
ignore_branch: true
project_identifier: vikunja
environment:
CROWDIN_KEY:
from_secret: crowdin_key
- name: move-files
pull: always
image: bash
depends_on:
- download
commands:
- mv src/i18n/lang/*/*.json src/i18n/lang
- name: push
pull: always
image: appleboy/drone-git-push
depends_on:
- move-files
settings:
author_email: "frederik@vikunja.io"
author_name: Frederick [Bot]
branch: main
commit: true
commit_message: "[skip ci] Updated translations via Crowdin"
remote: "ssh://git@kolaente.dev:9022/vikunja/frontend.git"
ssh_key:
from_secret: translation_git_push_ssh_key
- name: upload
pull: always
image: jonasfranz/crowdin
depends_on:
- clone
settings:
files:
en.json: src/i18n/lang/en.json
ignore_branch: true
project_identifier: vikunja
environment:
CROWDIN_KEY:
from_secret: crowdin_key
---
kind: signature
hmac: c885a0e50db729842402494aa645dd3ac662828b691108550f6bf302158295ba
...

View File

@ -1,29 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
[*.vue]
indent_style = tab
[*.{yaml,yml}]
indent_style = space
indent_size = 2
[*.json]
indent_style = space
indent_size = 2
[*.{scss,css}]
indent_style = space
indent_size = 2
[.nvmrc]
insert_final_newline = false

1
.envrc
View File

@ -1 +0,0 @@
use flake

View File

@ -1,54 +0,0 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")
module.exports = {
'root': true,
'env': {
'browser': true,
'es2021': true,
'node': true,
'vue/setup-compiler-macros': true,
},
'extends': [
'eslint:recommended',
'plugin:vue/vue3-essential',
'@vue/eslint-config-typescript/recommended',
],
'rules': {
'vue/html-quotes': [
'error',
'double',
],
'quotes': [
'error',
'single',
],
'comma-dangle': [
'error',
'always-multiline',
],
'semi': [
'error',
'never',
],
// see https://segmentfault.com/q/1010000040813116/a-1020000041134455 (original in chinese)
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }],
'vue/multi-word-component-names': 0,
},
'parser': 'vue-eslint-parser',
'parserOptions': {
'parser': '@typescript-eslint/parser',
'ecmaVersion': 2022,
'sourceType': 'module',
},
'ignorePatterns': [
'*.test.*',
'cypress/*',
],
'globals': {
'defineProps': 'readonly',
},
}

View File

@ -1,44 +0,0 @@
<!--
Please fill out this issue template to report a bug.
If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
-->
**Version information:**
Frontend Version:
API Version:
Browser and OS Version:
**Steps to reproduce:**
<!--
Add clear steps to reproduce the bug. Provide screenshots where applicable.
-->
1.
2.
...
**Expected behavior:**
<!--
Describe what happened.
-->
**Actual behavior:**
<!--
Describe what happened instead.
-->
**Checklist:**
* [ ] I have provided all required information
* [ ] I am using the latest release or the latest unstable build
* [ ] I was able to reproduce the bug on [try](https://try.vikunja.io)

2
.github/FUNDING.yml vendored
View File

@ -1,2 +0,0 @@
github: kolaente
custom: https://www.buymeacoffee.com/kolaente

View File

@ -1,58 +0,0 @@
name: Bug Report
description: Found something you weren't expecting? Report it here!
labels: kind/bug
body:
- type: markdown
attributes:
value: |
NOTE: If your issue is a security concern, please send an email to security@vikunja.io instead of opening a public issue.
- type: markdown
attributes:
value: |
Please fill out this issue template to report a bug.
1. If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
3. Make sure you are using the latest release and
take a moment to check that your issue hasn't been reported before.
4. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report and closed.
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below).
- type: input
id: frontend-version
attributes:
label: Vikunja Frontend Version
description: Vikunja frontend version (or commit reference) of your instance
validations:
required: true
- type: input
id: api-version
attributes:
label: Vikunja API Version
description: Vikunja API version (or commit reference) of your instance
validations:
required: true
- type: input
id: browser-version
attributes:
label: Browser and version
description: If your issue is related to a frontend problem, please provide the browser and version you used to reproduce it.
- type: dropdown
id: can-reproduce
attributes:
label: Can you reproduce the bug on the Vikunja demo site?
options:
- "Yes"
- "No"
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots

View File

@ -1,17 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: API issues
url: https://code.vikunja.io/api/issues
about: This is the frontend repo. Please open api-related bug reports and discussions in the api 0repo. Not sure if your issue is frontend or api? Ask in Matrix or the forum first.
- name: Forum
url: https://community.vikunja.io/
about: Feature Requests, Questions, configuration or deployment problems should be discussed in the forum.
- name: Security-related issues
url: https://vikunja.io/contact/#security
about: For security concerns, please send a mail to security@vikunja.io instead of opening a public issue.
- name: Chat on Matrix
url: https://matrix.to/#/#vikunja:matrix.org
about: Please ask any quick questions here.
- name: Translations
url: https://crowdin.com/project/vikunja
about: Any problems or requests for new languages about translations should be handled in crowdin.

16
.gitignore vendored
View File

@ -1,22 +1,16 @@
.DS_Store
node_modules
/dist*
/dist
*.zip
.direnv/
# local env files
.env.local
.env.*.local
# Log files
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
stats.html
pnpm-debug.log*
lerna-debug.log*
# Editor directories and files
.idea
@ -26,11 +20,3 @@ lerna-debug.log*
*.njsproj
*.sln
*.sw*
!rollup.sw.js
# Test files
cypress/screenshots
cypress/videos
# Local Netlify folder
.netlify

2
.npmrc
View File

@ -1,2 +0,0 @@
auto-install-peers=true
fetch-timeout=100000

1
.nvmrc
View File

@ -1 +0,0 @@
v18

View File

@ -1,13 +0,0 @@
{
"recommendations": [
"codezombiech.gitignore",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"vue.volar",
"vue.vscode-typescript-vue-plugin",
"lokalise.i18n-ally",
"mgmcdermott.vscode-language-babel",
"mikestead.dotenv",
"Syler.sass-indented"
]
}

View File

@ -1,28 +0,0 @@
{
"eslint.packageManager": "pnpm",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
// https://eslint.vuejs.org/user-guide/#editor-integrations
"eslint.validate": [
"javascript",
"javascriptreact",
"vue"
],
// i18n ally
"i18n-ally.localesPaths": [
"src/i18n/lang"
],
"i18n-ally.sortKeys": true,
"i18n-ally.keepFulfilled": true,
"i18n-ally.keystyle": "nested"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,24 @@
# Stage 1: Build application
FROM node:18-alpine AS compile-image
FROM node:13.14.0 AS compile-image
WORKDIR /build
ARG USE_RELEASE=false
ARG RELEASE_VERSION=main
ENV PNPM_CACHE_FOLDER .cache/pnpm/
ADD . ./
COPY . ./
RUN \
if [ $USE_RELEASE = true ]; then \
wget https://dl.vikunja.io/frontend/vikunja-frontend-$RELEASE_VERSION.zip -O frontend-release.zip && \
unzip frontend-release.zip -d dist/ && \
exit 0; \
fi && \
# https://pnpm.io/installation#using-corepack
corepack enable && \
# we don't use corepack prepare here by intend since
# we have renovate to keep our dependencies up to date
# Build the frontend
pnpm install && \
apk add --no-cache git && \
yarn install --frozen-lockfile --network-timeout 100000 && \
echo '{"VERSION": "'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'"}' > src/version.json && \
pnpm run build
yarn run build
# Stage 2: copy
FROM nginx:alpine
FROM nginx
RUN apt-get update && apt-get install -y apt-utils openssl && \
mkdir -p /etc/nginx/ssl && \
openssl genrsa -out /etc/nginx/ssl/dummy.key 2048 && \
openssl req -new -key /etc/nginx/ssl/dummy.key -out /etc/nginx/ssl/dummy.csr -subj "/C=DE/L=Berlin/O=Vikunja/CN=Vikunja Snakeoil" && \
openssl x509 -req -days 3650 -in /etc/nginx/ssl/dummy.csr -signkey /etc/nginx/ssl/dummy.key -out /etc/nginx/ssl/dummy.crt
COPY nginx.conf /etc/nginx/nginx.conf
COPY run.sh /run.sh
@ -40,10 +32,4 @@ ENV PGID 1000
LABEL maintainer="maintainers@vikunja.io"
RUN apk add --no-cache \
# for sh file
bash \
# installs usermod and groupmod
shadow
CMD "/run.sh"
CMD "/run.sh"

818
LICENSE
View File

@ -1,661 +1,165 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -3,46 +3,33 @@
> The todo app to organize your life.
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.19.1-brightgreen.svg)](https://dl.vikunja.io)
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.14.1-brightgreen.svg)](https://dl.vikunja.io)
This is the web frontend for Vikunja, written in Vue.js.
Take a look at [our roadmap](https://my.vikunja.cloud/share/UrdhKPqumxDXUbYpEGJLSIyNTwAnbBzVlwdDpRbv/auth) (hosted on Vikunja!) for a list of things we're currently working on!
## Security Reports
If you find any security-related issues you don't want to disclose publicly, please use [the contact information on our website](https://vikunja.io/contact/#security).
## Docker
There is a [docker image available](https://hub.docker.com/r/vikunja/api) with support for http/2 and aggressive caching enabled.
## Project setup
```shell
pnpm install
```
yarn install
```
### Compiles and hot-reloads for development
```shell
pnpm run serve
```
yarn run serve
```
### Compiles and minifies for production
```shell
pnpm run build
```
yarn run build
```
### Lints and fixes files
```shell
pnpm run lint
```
## Sponsors
[![Relm](https://vikunja.io/images/sponsors/relm.png)](https://relm.us)
yarn run lint
```

5
babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/app'
]
}

View File

@ -1,59 +0,0 @@
[changelog]
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits
| filter(attribute="scope")
| sort(attribute="scope") %}
* *({{commit.scope}})* {{ commit.message | upper_first }}
{%- if commit.breaking %}
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
{%- endif -%}
{%- endfor -%}
{%- for commit in commits %}
{%- if commit.scope -%}
{% else -%}
* {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))
{% if commit.breaking -%}
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
{% endif -%}
{% endif -%}
{% endfor -%}
{% raw %}\n{% endraw %}\
{% endfor %}\n
"""
#{% for group, commits in commits | group_by(attribute="group") %}
# ### {{ group | upper_first }}
# {% for commit in commits %}\
# - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))
# {% endfor %}\
#{% endfor %}\n
# remove the leading and trailing whitespace from the template
trim = true
[git]
conventional_commits = true
filter_unconventional = false
commit_parsers = [
{ message = ".*(deps).*", group = "Dependencies"},
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^doc", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore\\(release\\): prepare for", skip = true},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
{ message = ".*", group = "Other", default_scope = "other"}, # Everything that's not a conventional commit goes into the "Other" category
]

View File

View File

@ -1,25 +0,0 @@
import {defineConfig} from 'cypress'
export default defineConfig({
env: {
API_URL: 'http://localhost:3456/api/v1',
TEST_SECRET: 'averyLongSecretToSe33dtheDB',
},
video: false,
retries: {
runMode: 2,
},
projectId: '181c7x',
e2e: {
baseUrl: 'http://localhost:4173',
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
},
component: {
devServer: {
framework: 'vue',
bundler: 'vite',
},
},
viewportWidth: 1600,
viewportHeight: 900,
})

View File

@ -1,48 +0,0 @@
# Frontend Testing With Cypress
## Setup
* Enable the [seeder api endpoint](https://vikunja.io/docs/config-options/#testingtoken). You'll then need to add the testingtoken in `cypress.json` or set the `CYPRESS_TEST_SECRET` environment variable.
* Basic configuration happens in the `cypress.json` file
* Overridable with [env](https://docs.cypress.io/guides/guides/environment-variables.html#Option-3-CYPRESS)
* Override base url with `CYPRESS_BASE_URL`
## Fixtures
We're using the [test endpoint](https://vikunja.io/docs/config-options/#testingtoken) of the vikunja api to
seed the database with test data before running the tests.
This ensures better reproducability of tests.
## Running The Tests Locally
### Using Docker
The easiest way to run all frontend tests locally is by using the `docker-compose` file in this repository.
It uses the same configuration as the CI.
To use it, run
```shell
docker-compose up -d
```
Then, once all containers are started, run
```shell
docker-compose run cypress bash
```
to get a shell inside the cypress container.
In that shell you can then execute the tests with
```shell
pnpm run test:frontend
```
### Using The Cypress Dashboard
To open the Cypress Dashboard and run tests from there, run
```shell
pnpm run cypress:open
```

View File

@ -1,20 +0,0 @@
version: '3'
services:
api:
image: vikunja/api:unstable
environment:
VIKUNJA_LOG_LEVEL: DEBUG
VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB
ports:
- 3456:3456
cypress:
image: cypress/browsers:node16.14.0-chrome99-ff97
volumes:
- ..:/project
- $HOME/.cache:/home/node/.cache/
user: node
working_dir: /project
environment:
CYPRESS_API_URL: http://api:3456/api/v1
CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB

View File

@ -1,56 +0,0 @@
import {ListFactory} from '../../factories/list'
import '../../support/authenticateUser'
import {prepareLists} from './prepareLists'
describe('List History', () => {
prepareLists()
it('should show a list history on the home page', () => {
cy.intercept(Cypress.env('API_URL') + '/namespaces*').as('loadNamespaces')
cy.intercept(Cypress.env('API_URL') + '/lists/*').as('loadList')
const lists = ListFactory.create(6)
cy.visit('/')
cy.wait('@loadNamespaces')
cy.get('body')
.should('not.contain', 'Last viewed')
cy.visit(`/lists/${lists[0].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[1].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[2].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[3].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[4].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[5].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
// cy.visit('/')
// cy.wait('@loadNamespaces')
// Not using cy.visit here to work around the redirect issue fixed in #1337
cy.get('nav.menu.top-menu a')
.contains('Overview')
.click()
cy.get('body')
.should('contain', 'Last viewed')
cy.get('.list-cards-wrapper-2-rows')
.should('not.contain', lists[0].title)
.should('contain', lists[1].title)
.should('contain', lists[2].title)
.should('contain', lists[3].title)
.should('contain', lists[4].title)
.should('contain', lists[5].title)
})
})

View File

@ -1,78 +0,0 @@
import {formatISO, format} from 'date-fns'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import '../../support/authenticateUser'
describe('List View Gantt', () => {
prepareLists()
it('Hides tasks with no dates', () => {
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .tasks')
.should('not.contain', tasks[0].title)
})
it('Shows tasks from the current and next month', () => {
const now = Date.UTC(2022, 8, 25)
cy.clock(now, ['Date'])
const nextMonth = new Date(now)
nextMonth.setDate(1)
nextMonth.setMonth(9)
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .months')
.should('contain', format(now, 'MMMM'))
.should('contain', format(nextMonth, 'MMMM'))
})
it('Shows tasks with dates', () => {
const now = new Date()
const tasks = TaskFactory.create(1, {
start_date: formatISO(now),
end_date: formatISO(now.setDate(now.getDate() + 4)),
})
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .tasks')
.should('not.be.empty')
cy.get('.gantt-chart .tasks')
.should('contain', tasks[0].title)
})
it('Shows tasks with no dates after enabling them', () => {
TaskFactory.create(1, {
start_date: null,
end_date: null,
})
cy.visit('/lists/1/gantt')
cy.get('.gantt-options .fancycheckbox')
.contains('Show tasks which don\'t have dates set')
.click()
cy.get('.gantt-chart .tasks')
.should('not.be.empty')
cy.get('.gantt-chart .tasks .task.nodate')
.should('exist')
})
it('Drags a task around', () => {
const now = new Date()
TaskFactory.create(1, {
start_date: formatISO(now),
end_date: formatISO(now.setDate(now.getDate() + 4)),
})
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .tasks .task')
.first()
.trigger('mousedown', {which: 1})
.trigger('mousemove', {clientX: 500, clientY: 0})
.trigger('mouseup', {force: true})
})
})

View File

@ -1,196 +0,0 @@
import {BucketFactory} from '../../factories/bucket'
import {ListFactory} from '../../factories/list'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import '../../support/authenticateUser'
describe('List View Kanban', () => {
let buckets
prepareLists()
beforeEach(() => {
buckets = BucketFactory.create(2)
})
it('Shows all buckets with their tasks', () => {
const data = TaskFactory.create(10, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
.should('exist')
cy.get('.kanban .bucket .title')
.contains(buckets[1].title)
.should('exist')
cy.get('.kanban .bucket')
.first()
.should('contain', data[0].title)
})
it('Can add a new task to a bucket', () => {
TaskFactory.create(2, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket')
.contains(buckets[0].title)
.get('.bucket-footer .button')
.contains('Add another task')
.click()
cy.get('.kanban .bucket')
.contains(buckets[0].title)
.get('.bucket-footer .field .control input.input')
.type('New Task{enter}')
cy.get('.kanban .bucket')
.first()
.should('contain', 'New Task')
})
it('Can create a new bucket', () => {
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket.new-bucket .button')
.click()
cy.get('.kanban .bucket.new-bucket input.input')
.type('New Bucket{enter}')
cy.wait(1000) // Wait for the request to finish
cy.get('.kanban .bucket .title')
.contains('New Bucket')
.should('exist')
})
it('Can set a bucket limit', () => {
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Limit: Not Set')
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
.first()
.type(3)
cy.get('[data-cy="setBucketLimit"]')
.first()
.click()
cy.get('.kanban .bucket .bucket-header span.limit')
.contains('0/3')
.should('exist')
})
it('Can rename a bucket', () => {
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .bucket-header .title')
.first()
.type('{selectall}New Bucket Title{enter}')
cy.get('.kanban .bucket .bucket-header .title')
.first()
.should('contain', 'New Bucket Title')
})
it('Can delete a bucket', () => {
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Delete')
.click()
cy.get('.modal-mask .modal-container .modal-content .header')
.should('contain', 'Delete the bucket')
cy.get('.modal-mask .modal-container .modal-content .actions .button')
.contains('Do it!')
.click()
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
.should('not.exist')
cy.get('.kanban .bucket .title')
.contains(buckets[1].title)
.should('exist')
})
it('Can drag tasks around', () => {
const tasks = TaskFactory.create(2, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.first()
.drag('.kanban .bucket:nth-child(2) .tasks')
cy.get('.kanban .bucket:nth-child(2) .tasks')
.should('contain', tasks[0].title)
cy.get('.kanban .bucket:nth-child(1) .tasks')
.should('not.contain', tasks[0].title)
})
it('Should navigate to the task when the task card is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.should('be.visible')
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`, { timeout: 1000 })
})
it('Should remove a task from the kanban board when moving it to another list', () => {
const lists = ListFactory.create(2)
BucketFactory.create(2, {
list_id: '{increment}',
})
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
bucket_id: 1,
})
const task = tasks[0]
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .tasks .task')
.contains(task.title)
.should('be.visible')
.click()
cy.get('.task-view .action-buttons .button', { timeout: 3000 })
.contains('Move')
.click()
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
.type(`${lists[1].title}{enter}`)
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
// presses enter and we can't simulate pressing on enter to select the item.
cy.get('.task-view .content.details .field .multiselect.control .search-results')
.children()
.first()
.click()
cy.get('.global-notification', { timeout: 1000 })
.should('contain', 'Success')
cy.go('back')
cy.get('.kanban .bucket')
.should('not.contain', task.title)
})
})

View File

@ -1,109 +0,0 @@
import {UserListFactory} from '../../factories/users_list'
import {TaskFactory} from '../../factories/task'
import {UserFactory} from '../../factories/user'
import {ListFactory} from '../../factories/list'
import {prepareLists} from './prepareLists'
import '../../support/authenticateUser'
describe('List View List', () => {
prepareLists()
it('Should be an empty list', () => {
cy.visit('/lists/1')
cy.url()
.should('contain', '/lists/1/list')
cy.get('.list-title h1')
.should('contain', 'First List')
cy.get('.list-title .dropdown')
.should('exist')
cy.get('p')
.contains('This list is currently empty.')
.should('exist')
})
it('Should create a new task', () => {
const newTaskTitle = 'New task'
cy.visit('/lists/1')
cy.get('.task-add textarea')
.type(newTaskTitle+'{enter}')
cy.get('.tasks')
.should('contain.text', newTaskTitle)
})
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
})
cy.visit('/lists/1/list')
cy.get('.tasks .task .tasktext')
.contains(tasks[0].title)
.first()
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`)
})
it('Should not see any elements for a list which is shared read only', () => {
UserFactory.create(2)
UserListFactory.create(1, {
list_id: 2,
user_id: 1,
right: 0,
})
const lists = ListFactory.create(2, {
owner_id: '{increment}',
namespace_id: '{increment}',
})
cy.visit(`/lists/${lists[1].id}/`)
cy.get('.list-title .icon')
.should('not.exist')
cy.get('input.input[placeholder="Add a new task..."')
.should('not.exist')
})
it('Should only show the color of a list in the navigation and not in the list view', () => {
const lists = ListFactory.create(1, {
hex_color: '00db60',
})
TaskFactory.create(10, {
list_id: lists[0].id,
})
cy.visit(`/lists/${lists[0].id}/`)
cy.get('.menu-list li .list-menu-link .color-bubble')
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
cy.get('.tasks-container .tasks .color-bubble')
.should('not.exist')
})
it('Should paginate for > 50 tasks', () => {
const tasks = TaskFactory.create(100, {
id: '{increment}',
title: i => `task${i}`,
list_id: 1,
})
cy.visit('/lists/1/list')
cy.get('.tasks-container .tasks')
.should('contain', tasks[1].title)
cy.get('.tasks-container .tasks')
.should('not.contain', tasks[99].title)
cy.get('.card-content .pagination .pagination-link')
.contains('2')
.click()
cy.url()
.should('contain', '?page=2')
cy.get('.tasks-container .tasks')
.should('contain', tasks[99].title)
cy.get('.tasks-container .tasks')
.should('not.contain', tasks[1].title)
})
})

View File

@ -1,52 +0,0 @@
import {TaskFactory} from '../../factories/task'
import '../../support/authenticateUser'
describe('List View Table', () => {
it('Should show a table with tasks', () => {
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/table')
cy.get('.list-table table.table')
.should('exist')
cy.get('.list-table table.table')
.should('contain', tasks[0].title)
})
it('Should have working column switches', () => {
TaskFactory.create(1)
cy.visit('/lists/1/table')
cy.get('.list-table .filter-container .items .button')
.contains('Columns')
.click()
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
.contains('Priority')
.click()
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
.contains('Done')
.click()
cy.get('.list-table table.table th')
.contains('Priority')
.should('exist')
cy.get('.list-table table.table th')
.contains('Done')
.should('not.exist')
})
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
})
cy.visit('/lists/1/table')
cy.get('.list-table table.table')
.contains(tasks[0].title)
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`)
})
})

View File

@ -1,120 +0,0 @@
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import '../../support/authenticateUser'
describe('Lists', () => {
let lists
prepareLists((newLists) => (lists = newLists))
it('Should create a new list', () => {
cy.visit('/')
cy.get('.namespace-title .dropdown-trigger')
.click()
cy.get('.namespace-title .dropdown .dropdown-item')
.contains('New list')
.click()
cy.url()
.should('contain', '/lists/new/1')
cy.get('.card-header-title')
.contains('New list')
cy.get('input.input')
.type('New List')
cy.get('.button')
.contains('Create')
.click()
cy.get('.global-notification', { timeout: 1000 }) // Waiting until the request to create the new list is done
.should('contain', 'Success')
cy.url()
.should('contain', '/lists/')
cy.get('.list-title h1')
.should('contain', 'New List')
})
it('Should redirect to a specific list view after visited', () => {
cy.visit('/lists/1/kanban')
cy.url()
.should('contain', '/lists/1/kanban')
cy.visit('/lists/1')
cy.url()
.should('contain', '/lists/1/kanban')
})
it('Should rename the list in all places', () => {
TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
})
const newListName = 'New list name'
cy.visit('/lists/1')
cy.get('.list-title h1')
.should('contain', 'First List')
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-trigger')
.click()
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-content')
.contains('Edit')
.click()
cy.get('#title')
.type(`{selectall}${newListName}`)
cy.get('footer.card-footer .button')
.contains('Save')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.list-title h1')
.should('contain', newListName)
.should('not.contain', lists[0].title)
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child')
.should('contain', newListName)
.should('not.contain', lists[0].title)
cy.visit('/')
cy.get('.card-content')
.should('contain', newListName)
.should('not.contain', lists[0].title)
})
it('Should remove a list', () => {
cy.visit(`/lists/${lists[0].id}`)
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-trigger')
.click()
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-content')
.contains('Delete')
.click()
cy.url()
.should('contain', '/settings/delete')
cy.get('[data-cy="modalPrimary"]')
.contains('Do it')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.namespace-container .menu.namespaces-lists .menu-list')
.should('not.contain', lists[0].title)
cy.location('pathname')
.should('equal', '/')
})
it('Should archive a list', () => {
cy.visit(`/lists/${lists[0].id}`)
cy.get('.list-title .dropdown')
.click()
cy.get('.list-title .dropdown .dropdown-menu .dropdown-item')
.contains('Archive')
.click()
cy.get('.modal-content')
.should('contain.text', 'Archive this list')
cy.get('.modal-content [data-cy=modalPrimary]')
.click()
cy.get('.namespace-container .menu.namespaces-lists .menu-list')
.should('not.contain', lists[0].title)
cy.get('main.app-content')
.should('contain.text', 'This list is archived. It is not possible to create new or edit tasks for it.')
})
})

View File

@ -1,145 +0,0 @@
import {UserFactory} from '../../factories/user'
import '../../support/authenticateUser'
import {ListFactory} from '../../factories/list'
import {NamespaceFactory} from '../../factories/namespace'
describe('Namepaces', () => {
let namespaces
beforeEach(() => {
UserFactory.create(1)
namespaces = NamespaceFactory.create(1)
ListFactory.create(1)
})
it('Should be all there', () => {
cy.visit('/namespaces')
cy.get('[data-cy="namespace-title"]')
.should('contain', namespaces[0].title)
})
it('Should create a new Namespace', () => {
const newNamespaceTitle = 'New Namespace'
cy.visit('/namespaces')
cy.get('[data-cy="new-namespace"]')
.should('contain', 'New namespace')
.click()
cy.url()
.should('contain', '/namespaces/new')
cy.get('.card-header-title')
.should('contain', 'New namespace')
cy.get('input.input')
.type(newNamespaceTitle)
cy.get('.button')
.contains('Create')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.namespace-container')
.should('contain', newNamespaceTitle)
cy.url()
.should('contain', '/namespaces')
})
it('Should rename the namespace all places', () => {
const newNamespaces = NamespaceFactory.create(5)
const newNamespaceName = 'New namespace name'
cy.visit('/namespaces')
cy.get(`.namespace-container .menu.namespaces-lists .namespace-title:contains(${newNamespaces[0].title}) .dropdown .dropdown-trigger`)
.click()
cy.get('.namespace-container .menu.namespaces-lists .namespace-title .dropdown .dropdown-content')
.contains('Edit')
.click()
cy.url()
.should('contain', '/settings/edit')
cy.get('#namespacetext')
.invoke('val')
.should('equal', newNamespaces[0].title) // wait until the namespace data is loaded
cy.get('#namespacetext')
.type(`{selectall}${newNamespaceName}`)
cy.get('footer.card-footer .button')
.contains('Save')
.click()
cy.get('.global-notification', { timeout: 1000 })
.should('contain', 'Success')
cy.get('.namespace-container .menu.namespaces-lists')
.should('contain', newNamespaceName)
.should('not.contain', newNamespaces[0].title)
cy.get('[data-cy="namespaces-list"]')
.should('contain', newNamespaceName)
.should('not.contain', newNamespaces[0].title)
})
it('Should remove a namespace when deleting it', () => {
const newNamespaces = NamespaceFactory.create(5)
cy.visit('/')
cy.get(`.namespace-container .menu.namespaces-lists .namespace-title:contains(${newNamespaces[0].title}) .dropdown .dropdown-trigger`)
.click()
cy.get('.namespace-container .menu.namespaces-lists .namespace-title .dropdown .dropdown-content')
.contains('Delete')
.click()
cy.url()
.should('contain', '/settings/delete')
cy.get('[data-cy="modalPrimary"]')
.contains('Do it')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.namespace-container .menu.namespaces-lists')
.should('not.contain', newNamespaces[0].title)
})
it('Should not show archived lists & namespaces if the filter is not checked', () => {
const n = NamespaceFactory.create(1, {
id: 2,
is_archived: true,
}, false)
ListFactory.create(1, {
id: 2,
namespace_id: n[0].id,
}, false)
ListFactory.create(1, {
id: 3,
is_archived: true,
}, false)
// Initial
cy.visit('/namespaces')
cy.get('.namespace')
.should('not.contain', 'Archived')
// Show archived
cy.get('[data-cy="show-archived-check"] label.check span')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
.should('be.checked')
cy.get('.namespace')
.should('contain', 'Archived')
// Don't show archived
cy.get('[data-cy="show-archived-check"] label.check span')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
// Second time visiting after unchecking
cy.visit('/namespaces')
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
cy.get('.namespace')
.should('not.contain', 'Archived')
})
})

View File

@ -1,21 +0,0 @@
import {ListFactory} from '../../factories/list'
import {UserFactory} from '../../factories/user'
import {NamespaceFactory} from '../../factories/namespace'
import {TaskFactory} from '../../factories/task'
export function createLists() {
UserFactory.create(1)
NamespaceFactory.create(1)
const lists = ListFactory.create(1, {
title: 'First List'
})
TaskFactory.truncate()
return lists
}
export function prepareLists(setLists = () => {}) {
beforeEach(() => {
const lists = createLists()
setLists(lists)
})
}

View File

@ -1,37 +0,0 @@
import {TaskFactory} from '../../factories/task'
import {ListFactory} from '../../factories/list'
import {NamespaceFactory} from '../../factories/namespace'
import {UserListFactory} from '../../factories/users_list'
import '../../support/authenticateUser'
describe('Editor', () => {
beforeEach(() => {
NamespaceFactory.create(1)
const lists = ListFactory.create(1)
TaskFactory.truncate()
UserListFactory.truncate()
})
it('Has a preview with checkable checkboxes', () => {
const tasks = TaskFactory.create(1, {
description: `# Test Heading
* Bullet 1
* Bullet 2
* [ ] Checklist
* [x] Checklist checked
`,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('input[type=checkbox][data-checkbox-num=0]')
.click()
cy.get('.task-view .details.content.description h3 span.is-small.has-text-success')
.contains('Saved!')
.should('exist')
cy.get('.preview.content')
.should('contain', 'Test Heading')
})
})

View File

@ -1,29 +0,0 @@
import '../../support/authenticateUser'
describe('The Menu', () => {
it('Is visible by default on desktop', () => {
cy.get('.namespace-container')
.should('have.class', 'is-active')
})
it('Can be hidden on desktop', () => {
cy.get('button.menu-show-button:visible')
.click()
cy.get('.namespace-container')
.should('not.have.class', 'is-active')
})
it('Is hidden by default on mobile', () => {
cy.viewport('iphone-8')
cy.get('.namespace-container')
.should('not.have.class', 'is-active')
})
it('Is can be shown on mobile', () => {
cy.viewport('iphone-8')
cy.get('button.menu-show-button:visible')
.click()
cy.get('.namespace-container')
.should('have.class', 'is-active')
})
})

View File

@ -1,25 +0,0 @@
import {LinkShareFactory} from '../../factories/link_sharing'
import {ListFactory} from '../../factories/list'
import {TaskFactory} from '../../factories/task'
describe('Link shares', () => {
it('Can view a link share', () => {
const lists = ListFactory.create(1)
const tasks = TaskFactory.create(10, {
list_id: lists[0].id
})
const linkShares = LinkShareFactory.create(1, {
list_id: lists[0].id,
right: 0,
})
cy.visit(`/share/${linkShares[0].hash}/auth`)
cy.get('h1.title')
.should('contain', lists[0].title)
cy.get('input.input[placeholder="Add a new task..."')
.should('not.exist')
cy.get('.tasks')
.should('contain', tasks[0].title)
})
})

View File

@ -1,128 +0,0 @@
import {TeamFactory} from '../../factories/team'
import {TeamMemberFactory} from '../../factories/team_member'
import {UserFactory} from '../../factories/user'
import '../../support/authenticateUser'
describe('Team', () => {
it('Creates a new team', () => {
TeamFactory.truncate()
cy.visit('/teams')
const newTeamName = 'New Team'
cy.get('a.button')
.contains('Create a new team')
.click()
cy.url()
.should('contain', '/teams/new')
cy.get('.card-header-title')
.contains('Create a new team')
cy.get('input.input')
.type(newTeamName)
cy.get('.button')
.contains('Create')
.click()
cy.url()
.should('contain', '/edit')
cy.get('input#teamtext')
.should('have.value', newTeamName)
})
it('Shows all teams', () => {
TeamMemberFactory.create(10, {
team_id: '{increment}',
})
const teams = TeamFactory.create(10, {
id: '{increment}',
})
cy.visit('/teams')
cy.get('.teams.box')
.should('not.be.empty')
teams.forEach(t => {
cy.get('.teams.box')
.should('contain', t.name)
})
})
it('Allows an admin to edit the team', () => {
TeamMemberFactory.create(1, {
team_id: 1,
admin: true,
})
const teams = TeamFactory.create(1, {
id: 1,
})
cy.visit('/teams/1/edit')
cy.get('.card input.input')
.first()
.type('{selectall}New Team Name')
cy.get('.card .button')
.contains('Save')
.click()
cy.get('table.table td')
.contains('Admin')
.should('exist')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Does not allow a normal user to edit the team', () => {
TeamMemberFactory.create(1, {
team_id: 1,
admin: false,
})
const teams = TeamFactory.create(1, {
id: 1,
})
cy.visit('/teams/1/edit')
cy.get('.card input.input')
.should('not.exist')
cy.get('table.table td')
.contains('Member')
.should('exist')
})
it('Allows an admin to add members to the team', () => {
TeamMemberFactory.create(1, {
team_id: 1,
admin: true,
})
TeamFactory.create(1, {
id: 1,
})
const users = UserFactory.create(5)
cy.visit('/teams/1/edit')
cy.get('.card')
.contains('Team Members')
.get('.card-content .multiselect .input-wrapper input')
.type(users[1].username)
cy.get('.card')
.contains('Team Members')
.get('.card-content .multiselect .search-results')
.children()
.first()
.click()
cy.get('.card')
.contains('Team Members')
.get('.card-content .button')
.contains('Add to team')
.click()
cy.get('table.table td')
.contains('Admin')
.should('exist')
cy.get('table.table tr')
.should('contain', users[1].username)
.should('contain', 'Member')
cy.get('.global-notification')
.should('contain', 'Success')
})
})

View File

@ -1,131 +0,0 @@
import {ListFactory} from '../../factories/list'
import {seed} from '../../support/seed'
import {TaskFactory} from '../../factories/task'
import {formatISO} from 'date-fns'
import {UserFactory} from '../../factories/user'
import {NamespaceFactory} from '../../factories/namespace'
import {BucketFactory} from '../../factories/bucket'
import {updateUserSettings} from '../../support/updateUserSettings'
import '../../support/authenticateUser'
function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
UserFactory.create(1)
NamespaceFactory.create(1)
const list = ListFactory.create()[0]
BucketFactory.create(1, {
list_id: list.id,
})
const tasks = []
let dueDate = startDueDate
for (let i = 0; i < numberOfTasks; i++) {
const now = new Date()
dueDate = (new Date(dueDate.valueOf())).setDate((new Date(dueDate.valueOf())).getDate() + 2)
tasks.push({
id: i + 1,
list_id: list.id,
done: false,
created_by_id: 1,
title: 'Test Task ' + i,
index: i + 1,
due_date: formatISO(dueDate),
created: formatISO(now),
updated: formatISO(now),
})
}
seed(TaskFactory.table, tasks)
return {tasks, list}
}
describe('Home Page Task Overview', () => {
it('Should show tasks with a near due date first on the home page overview', () => {
const {tasks} = seedTasks()
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.each(([task], index) => {
expect(task.innerText).to.contain(tasks[index].title)
})
})
it('Should show overdue tasks first, then show other tasks', () => {
const oldDate = (new Date()).setDate((new Date()).getDate() - 14)
const {tasks} = seedTasks(100, oldDate)
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.each(([task], index) => {
expect(task.innerText).to.contain(tasks[index].title)
})
})
it('Should show a new task with a very soon due date at the top', () => {
const {tasks} = seedTasks()
const newTaskTitle = 'New Task'
cy.visit('/')
TaskFactory.create(1, {
id: 999,
title: newTaskTitle,
due_date: formatISO(new Date()),
}, false)
cy.visit(`/lists/${tasks[0].list_id}/list`)
cy.get('.tasks .task')
.first()
.should('contain.text', newTaskTitle)
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.first()
.should('contain.text', newTaskTitle)
})
it('Should not show a new task without a date at the bottom when there are > 50 tasks', () => {
// We're not using the api here to create the task in order to verify the flow
const {tasks} = seedTasks()
const newTaskTitle = 'New Task'
cy.visit('/')
cy.visit(`/lists/${tasks[0].list_id}/list`)
cy.get('.task-add textarea')
.type(newTaskTitle+'{enter}')
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.last()
.should('not.contain.text', newTaskTitle)
})
it('Should show a new task without a date at the bottom when there are < 50 tasks', () => {
seedTasks(40)
const newTaskTitle = 'New Task'
TaskFactory.create(1, {
id: 999,
title: newTaskTitle,
}, false)
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.last()
.should('contain.text', newTaskTitle)
})
it('Should show a task without a due date added via default list at the bottom', () => {
const {list} = seedTasks(40)
updateUserSettings({
default_list_id: list.id,
overdue_tasks_reminders_time: '9:00',
})
const newTaskTitle = 'New Task'
cy.visit('/')
cy.get('.add-task-textarea')
.type(`${newTaskTitle}{enter}`)
cy.get('[data-cy="showTasks"] .card .task')
.last()
.should('contain.text', newTaskTitle)
})
})

View File

@ -1,550 +0,0 @@
import {formatISO} from 'date-fns'
import {TaskFactory} from '../../factories/task'
import {ListFactory} from '../../factories/list'
import {TaskCommentFactory} from '../../factories/task_comment'
import {UserFactory} from '../../factories/user'
import {NamespaceFactory} from '../../factories/namespace'
import {UserListFactory} from '../../factories/users_list'
import {TaskAssigneeFactory} from '../../factories/task_assignee'
import {LabelFactory} from '../../factories/labels'
import {LabelTaskFactory} from '../../factories/label_task'
import {BucketFactory} from '../../factories/bucket'
import '../../support/authenticateUser'
import {TaskAttachmentFactory} from '../../factories/task_attachments'
function addLabelToTaskAndVerify(labelTitle: string) {
cy.get('.task-view .action-buttons .button')
.contains('Add Labels')
.click()
cy.get('.task-view .details.labels-list .multiselect input')
.type(labelTitle)
cy.get('.task-view .details.labels-list .multiselect .search-results')
.children()
.first()
.click()
cy.get('.global-notification', { timeout: 4000 })
.should('contain', 'Success')
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
.should('exist')
.should('contain', labelTitle)
}
function uploadAttachmentAndVerify(taskId: number) {
cy.intercept(`${Cypress.env('API_URL')}/tasks/${taskId}/attachments`).as('uploadAttachment')
cy.get('.task-view .action-buttons .button')
.contains('Add Attachments')
.click()
cy.get('input[type=file]', {timeout: 1000})
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
cy.wait('@uploadAttachment')
cy.get('.attachments .attachments .files a.attachment')
.should('exist')
}
describe('Task', () => {
let namespaces
let lists
let buckets
beforeEach(() => {
UserFactory.create(1)
namespaces = NamespaceFactory.create(1)
lists = ListFactory.create(1)
buckets = BucketFactory.create(1, {
list_id: lists[0].id,
})
TaskFactory.truncate()
UserListFactory.truncate()
})
it('Should be created new', () => {
cy.visit('/lists/1/list')
cy.get('.input[placeholder="Add a new task…"')
.type('New Task')
cy.get('.button')
.contains('Add')
.click()
cy.get('.tasks .task .tasktext')
.first()
.should('contain', 'New Task')
})
it('Inserts new tasks at the top of the list', () => {
TaskFactory.create(1)
cy.visit('/lists/1/list')
cy.get('.list-is-empty-notice')
.should('not.exist')
cy.get('.input[placeholder="Add a new task…"')
.type('New Task')
cy.get('.button')
.contains('Add')
.click()
cy.wait(1000) // Wait for the request
cy.get('.tasks .task .tasktext')
.first()
.should('contain', 'New Task')
})
it('Marks a task as done', () => {
TaskFactory.create(1)
cy.visit('/lists/1/list')
cy.get('.tasks .task .fancycheckbox label.check')
.first()
.click()
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can add a task to favorites', () => {
TaskFactory.create(1)
cy.visit('/lists/1/list')
cy.get('.tasks .task .favorite')
.first()
.click()
cy.get('.menu.namespaces-lists')
.should('contain', 'Favorites')
})
describe('Task Detail View', () => {
beforeEach(() => {
TaskCommentFactory.truncate()
LabelTaskFactory.truncate()
})
it('Shows all task details', () => {
const tasks = TaskFactory.create(1, {
id: 1,
index: 1,
description: 'Lorem ipsum dolor sit amet.'
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view h1.title.input')
.should('contain', tasks[0].title)
cy.get('.task-view h1.title.task-id')
.should('contain', '#1')
cy.get('.task-view h6.subtitle')
.should('contain', namespaces[0].title)
.should('contain', lists[0].title)
cy.get('.task-view .details.content.description')
.should('contain', tasks[0].description)
cy.get('.task-view .action-buttons p.created')
.should('contain', 'Created')
})
it('Shows a done label for done tasks', () => {
const tasks = TaskFactory.create(1, {
id: 1,
index: 1,
done: true,
done_at: formatISO(new Date())
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .heading .is-done')
.should('be.visible')
.should('contain', 'Done')
cy.get('.task-view .action-buttons p.created')
.scrollIntoView()
.should('be.visible')
.should('contain', 'Done')
})
it('Can mark a task as done', () => {
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Mark task done!')
.click()
cy.get('.task-view .heading .is-done')
.should('exist')
.should('contain', 'Done')
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.task-view .action-buttons .button')
.should('contain', 'Mark as undone')
})
it('Shows a task identifier since the list has one', () => {
const lists = ListFactory.create(1, {
id: 1,
identifier: 'TEST',
})
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
index: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view h1.title.task-id')
.should('contain', `${lists[0].identifier}-${tasks[0].index}`)
})
it('Can edit the description', () => {
const tasks = TaskFactory.create(1, {
id: 1,
description: 'Lorem ipsum dolor sit amet.'
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .details.content.description .editor button')
.click()
cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
.type('{selectall}New Description')
cy.get('[data-cy="saveEditor"]')
.contains('Save')
.click()
cy.get('.task-view .details.content.description h3 span.is-small.has-text-success')
.contains('Saved!')
.should('exist')
})
it('Can add a new comment', () => {
const tasks = TaskFactory.create(1, {
id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .comments .media.comment .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
.should('be.visible')
.type('{selectall}New Comment')
cy.get('.task-view .comments .media.comment .button:not([disabled])')
.contains('Comment')
.should('be.visible')
.click()
cy.get('.task-view .comments .media.comment .editor')
.should('contain', 'New Comment')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can move a task to another list', () => {
const lists = ListFactory.create(2)
BucketFactory.create(2, {
list_id: '{increment}'
})
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Move')
.click()
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
.type(`${lists[1].title}{enter}`)
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
// presses enter and we can't simulate pressing on enter to select the item.
cy.get('.task-view .content.details .field .multiselect.control .search-results')
.children()
.first()
.click()
cy.get('.task-view h6.subtitle')
.should('contain', namespaces[0].title)
.should('contain', lists[1].title)
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can delete a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.should('be.visible')
.contains('Delete')
.click()
cy.get('.modal-mask .modal-container .modal-content .header')
.should('contain', 'Delete this task')
cy.get('.modal-mask .modal-container .modal-content .actions .button')
.contains('Do it!')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.url()
.should('contain', `/lists/${tasks[0].list_id}/`)
})
it('Can add an assignee to a task', () => {
const users = UserFactory.create(5)
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
})
UserListFactory.create(5, {
list_id: 1,
user_id: '{increment}',
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('[data-cy="taskDetail.assign"]')
.click()
cy.get('.task-view .column.assignees .multiselect input')
.type(users[1].username)
cy.get('.task-view .column.assignees .multiselect .search-results')
.children()
.first()
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.task-view .column.assignees .multiselect .input-wrapper span.assignee')
.should('exist')
})
it('Can remove an assignee from a task', () => {
const users = UserFactory.create(2)
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
})
UserListFactory.create(5, {
list_id: 1,
user_id: '{increment}',
})
TaskAssigneeFactory.create(1, {
task_id: tasks[0].id,
user_id: users[1].id,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .column.assignees .multiselect .input-wrapper span.assignee')
.get('.remove-assignee')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.task-view .column.assignees .multiselect .input-wrapper span.assignee')
.should('not.exist')
})
it('Can add a new label to a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
})
LabelFactory.truncate()
const newLabelText = 'some new label'
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Add Labels')
.should('be.visible')
.click()
cy.get('.task-view .details.labels-list .multiselect input')
.type(newLabelText)
cy.get('.task-view .details.labels-list .multiselect .search-results')
.children()
.first()
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
.should('exist')
.should('contain', newLabelText)
})
it('Can add an existing label to a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/tasks/${tasks[0].id}`)
addLabelToTaskAndVerify(labels[0].title)
})
it('Can add a label to a task and it shows up on the kanban board afterwards', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
bucket_id: buckets[0].id,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/lists/${lists[0].id}/kanban`)
cy.get('.bucket .task')
.contains(tasks[0].title)
.click()
addLabelToTaskAndVerify(labels[0].title)
cy.get('.modal-content .close')
.click()
cy.get('.bucket .task')
.should('contain.text', labels[0].title)
})
it('Can remove a label from a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.create(1, {
task_id: tasks[0].id,
label_id: labels[0].id,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
.should('be.visible')
.should('contain', labels[0].title)
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
.children()
.first()
.get('[data-cy="taskDetail.removeLabel"]')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
.should('not.contain', labels[0].title)
})
it('Can set a due date for a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Due Date')
.click()
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker .show')
.click()
cy.get('.datepicker .datepicker-popup button')
.contains('Tomorrow')
.click()
cy.get('[data-cy="closeDatepicker"]')
.contains('Confirm')
.click()
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker-popup')
.should('not.exist')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can set a priority for a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Priority')
.click()
cy.get('.task-view .columns.details .column')
.contains('Priority')
.get('.select select')
.select('Urgent')
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.task-view .columns.details .column')
.contains('Priority')
.get('.select select')
.should('have.value', '4')
})
it('Can set the progress for a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Progress')
.click()
cy.get('.task-view .columns.details .column')
.contains('Progress')
.get('.select select')
.select('50%')
cy.get('.global-notification')
.should('contain', 'Success')
cy.wait(200)
cy.get('.task-view .columns.details .column')
.contains('Progress')
.get('.select select')
.should('be.visible')
.should('have.value', '0.5')
})
it('Can add an attachment to a task', () => {
TaskAttachmentFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
uploadAttachmentAndVerify(tasks[0].id)
})
it('Can add an attachment to a task and see it appearing on kanban', () => {
TaskAttachmentFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
bucket_id: buckets[0].id,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/lists/${lists[0].id}/kanban`)
cy.get('.bucket .task')
.contains(tasks[0].title)
.click()
uploadAttachmentAndVerify(tasks[0].id)
cy.get('.modal-content .close')
.click()
cy.get('.bucket .task .footer .icon svg.fa-paperclip')
.should('exist')
})
})
})

View File

@ -1,58 +0,0 @@
import {UserFactory} from '../../factories/user'
const testAndAssertFailed = fixture => {
cy.visit('/login')
cy.get('input[id=username]').type(fixture.username)
cy.get('input[id=password]').type(fixture.password)
cy.get('.button').contains('Login').click()
cy.wait(5000) // It can take waaaayy too long to log the user in
cy.url().should('include', '/')
cy.get('div.message.danger').contains('Wrong username or password.')
}
context('Login', () => {
beforeEach(() => {
UserFactory.create(1, {
username: 'test',
})
cy.visit('/', {
onBeforeLoad(win) {
win.localStorage.removeItem('token')
},
})
})
it('Should log in with the right credentials', () => {
const fixture = {
username: 'test',
password: '1234',
}
cy.visit('/login')
cy.get('input[id=username]').type(fixture.username)
cy.get('input[id=password]').type(fixture.password)
cy.get('.button').contains('Login').click()
cy.url().should('include', '/')
cy.clock(1625656161057) // 13:00
cy.get('h2').should('contain', `Hi ${fixture.username}!`)
})
it('Should fail with a bad password', () => {
const fixture = {
username: 'test',
password: '123456',
}
testAndAssertFailed(fixture)
})
it('Should fail with a bad username', () => {
const fixture = {
username: 'loremipsum',
password: '1234',
}
testAndAssertFailed(fixture)
})
})

View File

@ -1,44 +0,0 @@
import '../../support/authenticateUser'
import {createLists} from '../list/prepareLists'
function logout() {
cy.get('.navbar .user .username')
.click()
cy.get('.navbar .user .dropdown-menu .dropdown-item')
.contains('Logout')
.click()
}
describe('Log out', () => {
it('Logs the user out', () => {
cy.visit('/')
expect(localStorage.getItem('token')).to.not.eq(null)
logout()
cy.url()
.should('contain', '/login')
.then(() => {
expect(localStorage.getItem('token')).to.eq(null)
})
})
it.skip('Should clear the list history after logging the user out', () => {
const lists = createLists()
cy.visit(`/lists/${lists[0].id}`)
.then(() => {
expect(localStorage.getItem('listHistory')).to.not.eq(null)
})
logout()
cy.wait(1000) // This makes re-loading of the list and associated entities (and the resulting error) visible
cy.url()
.should('contain', '/login')
.then(() => {
expect(localStorage.getItem('listHistory')).to.eq(null)
})
})
})

View File

@ -1,48 +0,0 @@
// This test assumes no mailer is set up and all users are activated immediately.
import {UserFactory} from '../../factories/user'
context('Registration', () => {
beforeEach(() => {
UserFactory.create(1, {
username: 'test',
})
cy.visit('/', {
onBeforeLoad(win) {
win.localStorage.removeItem('token')
},
})
})
it('Should work without issues', () => {
const fixture = {
username: 'testuser',
password: '123456',
email: 'testuser@example.com',
}
cy.visit('/register')
cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email)
cy.get('#password').type(fixture.password)
cy.get('#register-submit').click()
cy.url().should('include', '/')
cy.clock(1625656161057) // 13:00
cy.get('h2').should('contain', `Hi ${fixture.username}!`)
})
it.only('Should fail', () => {
const fixture = {
username: 'test',
password: '123456',
email: 'testuser@example.com',
}
cy.visit('/register')
cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email)
cy.get('#password').type(fixture.password)
cy.get('#register-submit').click()
cy.get('div.message.danger').contains('A user with this username already exists.')
})
})

View File

@ -1,47 +0,0 @@
import {UserFactory} from '../../factories/user'
import '../../support/authenticateUser'
describe('User Settings', () => {
beforeEach(() => {
UserFactory.create(1)
})
it('Changes the user avatar', () => {
cy.intercept(`${Cypress.env('API_URL')}/user/settings/avatar/upload`).as('uploadAvatar')
cy.visit('/user/settings/avatar')
cy.get('input[name=avatarProvider][value=upload]')
.click()
cy.get('input[type=file]', {timeout: 1000})
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
cy.get('.vue-handler-wrapper.vue-handler-wrapper--south .vue-simple-handler.vue-simple-handler--south')
.trigger('mousedown', {which: 1})
.trigger('mousemove', {clientY: 100})
.trigger('mouseup')
cy.get('[data-cy="uploadAvatar"]')
.contains('Upload Avatar')
.click()
cy.wait('@uploadAvatar')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Updates the name', () => {
cy.visit('/user/settings/general')
cy.get('.general-settings .control input.input')
.first()
.type('Lorem Ipsum')
cy.get('[data-cy="saveGeneralSettings"]')
.contains('Save')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.navbar .user .username')
.should('contain', 'Lorem Ipsum')
})
})

View File

@ -1,20 +0,0 @@
import {faker} from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class BucketFactory extends Factory {
static table = 'buckets'
static factory() {
const now = new Date()
return {
id: '{increment}',
title: faker.lorem.words(3),
list_id: 1,
created_by_id: 1,
created: formatISO(now),
updated: formatISO(now)
}
}
}

View File

@ -1,17 +0,0 @@
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class LabelTaskFactory extends Factory {
static table = 'label_tasks'
static factory() {
const now = new Date()
return {
id: '{increment}',
task_id: 1,
label_id: 1,
created: formatISO(now),
}
}
}

View File

@ -1,22 +0,0 @@
import {faker} from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class LabelFactory extends Factory {
static table = 'labels'
static factory() {
const now = new Date()
return {
id: '{increment}',
title: faker.lorem.words(2),
description: faker.lorem.text(10),
hex_color: (Math.random()*0xFFFFFF<<0).toString(16), // random 6-digit hex number
created_by_id: 1,
created: formatISO(now),
updated: formatISO(now),
}
}
}

View File

@ -1,22 +0,0 @@
import {Factory} from '../support/factory'
import {formatISO} from "date-fns"
import {faker} from '@faker-js/faker'
export class LinkShareFactory extends Factory {
static table = 'link_shares'
static factory() {
const now = new Date()
return {
id: '{increment}',
hash: faker.random.word(32),
list_id: 1,
right: 0,
sharing_type: 0,
shared_by_id: 1,
created: formatISO(now),
updated: formatISO(now)
}
}
}

View File

@ -1,20 +0,0 @@
import {Factory} from '../support/factory'
import {formatISO} from "date-fns"
import {faker} from '@faker-js/faker'
export class ListFactory extends Factory {
static table = 'lists'
static factory() {
const now = new Date()
return {
id: '{increment}',
title: faker.lorem.words(3),
owner_id: 1,
namespace_id: 1,
created: formatISO(now),
updated: formatISO(now)
}
}
}

View File

@ -1,19 +0,0 @@
import {faker} from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class NamespaceFactory extends Factory {
static table = 'namespaces'
static factory() {
const now = new Date()
return {
id: '{increment}',
title: faker.lorem.words(3),
owner_id: 1,
created: formatISO(now),
updated: formatISO(now)
}
}
}

View File

@ -1,23 +0,0 @@
import {faker} from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class TaskFactory extends Factory {
static table = 'tasks'
static factory() {
const now = new Date()
return {
id: '{increment}',
title: faker.lorem.words(3),
done: false,
list_id: 1,
created_by_id: 1,
index: '{increment}',
position: '{increment}',
created: formatISO(now),
updated: formatISO(now)
}
}
}

View File

@ -1,17 +0,0 @@
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class TaskAssigneeFactory extends Factory {
static table = 'task_assignees'
static factory() {
const now = new Date()
return {
id: '{increment}',
task_id: 1,
user_id: 1,
created: formatISO(now),
}
}
}

View File

@ -1,17 +0,0 @@
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class TaskAttachmentFactory extends Factory {
static table = 'task_attachments'
static factory() {
const now = new Date()
return {
id: '{increment}',
task_id: 1,
file_id: 1,
created: formatISO(now),
}
}
}

View File

@ -1,21 +0,0 @@
import {faker} from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from "date-fns"
export class TaskCommentFactory extends Factory {
static table = 'task_comments'
static factory() {
const now = new Date()
return {
id: '{increment}',
comment: faker.lorem.text(3),
author_id: 1,
task_id: 1,
created: formatISO(now),
updated: formatISO(now)
}
}
}

View File

@ -1,18 +0,0 @@
import {faker} from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class TeamFactory extends Factory {
static table = 'teams'
static factory() {
const now = new Date()
return {
name: faker.lorem.words(3),
created_by_id: 1,
created: formatISO(now),
updated: formatISO(now)
}
}
}

View File

@ -1,15 +0,0 @@
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class TeamMemberFactory extends Factory {
static table = 'team_members'
static factory() {
return {
team_id: 1,
user_id: 1,
admin: false,
created: formatISO(new Date()),
}
}
}

View File

@ -1,22 +0,0 @@
import {faker} from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from "date-fns"
export class UserFactory extends Factory {
static table = 'users'
static factory() {
const now = new Date()
return {
id: '{increment}',
username: faker.lorem.word(10) + faker.datatype.uuid(),
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.', // 1234
status: 0,
issuer: 'local',
created: formatISO(now),
updated: formatISO(now)
}
}
}

View File

@ -1,19 +0,0 @@
import {Factory} from '../support/factory'
import {formatISO} from "date-fns"
export class UserListFactory extends Factory {
static table = 'users_lists'
static factory() {
const now = new Date()
return {
id: '{increment}',
list_id: 1,
user_id: 1,
right: 0,
created: formatISO(now),
updated: formatISO(now)
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 872 KiB

View File

@ -1,29 +0,0 @@
// This authenticates a user and puts the token in local storage which allows us to perform authenticated requests.
// Built after https://github.com/cypress-io/cypress-example-recipes/tree/bd2d6ffb33214884cab343d38e7f9e6ebffb323f/examples/logging-in__jwt
import {UserFactory} from '../factories/user'
let token
before(() => {
const users = UserFactory.create(1)
cy.request('POST', `${Cypress.env('API_URL')}/login`, {
username: users[0].username,
password: '1234',
})
.its('body')
.then(r => {
token = r.token
})
})
beforeEach(() => {
cy.log(`Using token ${token} to make authenticated requests`)
cy.visit('/', {
onBeforeLoad(win) {
win.localStorage.setItem('token', token)
},
})
})

View File

@ -1,71 +0,0 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
/**
* Recursively gets an element, returning only after it's determined to be attached to the DOM for good.
*
* Source: https://github.com/cypress-io/cypress/issues/7306#issuecomment-850621378
*/
Cypress.Commands.add('getSettled', (selector, opts = {}) => {
const retries = opts.retries || 3
const delay = opts.delay || 100
const isAttached = (resolve, count = 0) => {
const el = Cypress.$(selector)
// is element attached to the DOM?
count = Cypress.dom.isAttached(el) ? count + 1 : 0
// hit our base case, return the element
if (count >= retries) {
return resolve(el)
}
// retry after a bit of a delay
setTimeout(() => isAttached(resolve, count), delay)
}
// wrap, so we can chain cypress commands off the result
return cy.wrap(null).then(() => {
return new Cypress.Promise((resolve) => {
return isAttached(resolve, 0)
}).then((el) => {
return cy.wrap(el)
})
})
})

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@ -1,29 +0,0 @@
// ***********************************************************
// This example support/component.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
import { mount } from 'cypress/vue'
// Ensure global styles are loaded
import '../../src/styles/global.scss';
Cypress.Commands.add('mount', mount)
// Example use:
// cy.mount(MyComponent)

View File

@ -1,10 +0,0 @@
import './commands'
import '@4tw/cypress-drag-drop'
// see https://github.com/cypress-io/cypress/issues/702#issuecomment-587127275
Cypress.on('window:before:load', (win) => {
// disable service workers
// @ts-ignore
delete win.navigator.__proto__.ServiceWorker
})

View File

@ -1,52 +0,0 @@
import {seed} from './seed'
/**
* A factory makes it easy to seed the database with data.
*/
export class Factory {
static table = null
static factory() {
return {}
}
/**
* Seeds a bunch of fake data into the database.
*
* Takes an override object as its single argument which will override the data from the factory.
* If the value of one of the override fields is `{increment}` that value will be replaced with an incrementing
* number through all created entities.
*
* @param override
* @returns {[]}
*/
static create(count = 1, override = {}, truncate = true) {
const data = []
for (let i = 1; i <= count; i++) {
const entry = {
...this.factory(),
...override,
}
for (const e in entry) {
if(typeof entry[e] === 'function') {
entry[e] = entry[e](i)
continue
}
if (entry[e] === '{increment}') {
entry[e] = i
}
}
data.push(entry)
}
seed(this.table, data, truncate)
return data
}
static truncate() {
seed(this.table, null)
}
}

View File

@ -1,24 +0,0 @@
/**
* Seeds a db table with data. If a data object is provided as the second argument, it will load the fixtures
* file for the table and merge the data from it with the passed data. This allows you to override specific
* fields of the fixtures without having to redeclare the whole fixture.
*
* Passing null as the second argument empties the table.
*
* @param table
* @param data
*/
export function seed(table, data = {}, truncate = true) {
if (data === null) {
data = []
}
cy.request({
method: 'PATCH',
url: `${Cypress.env('API_URL')}/test/${table}?truncate=${truncate ? 'true' : 'false'}`,
headers: {
'Authorization': Cypress.env('TEST_SECRET'),
},
body: data,
})
}

View File

@ -1,26 +0,0 @@
export function updateUserSettings(settings) {
const token = `Bearer ${window.localStorage.getItem('token')}`
return cy.request({
method: 'GET',
url: `${Cypress.env('API_URL')}/user`,
headers: {
'Authorization': token,
},
})
.its('body')
.then(oldSettings => {
return cy.request({
method: 'POST',
url: `${Cypress.env('API_URL')}/user/settings/general`,
headers: {
'Authorization': token,
},
body: {
...oldSettings,
...settings,
},
})
})
}

View File

@ -1,10 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["./integration/**/*", "./support/**/*"],
"compilerOptions": {
"isolatedModules": false,
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
}
}

View File

@ -1,17 +0,0 @@
image: vikunja/frontend:unstable
manifests:
-
image: vikunja/frontend:unstable-linux-amd64
platform:
architecture: amd64
os: linux
-
image: vikunja/frontend:unstable-linux-arm64
platform:
architecture: arm64
os: linux
-
image: vikunja/frontend:unstable-linux-arm
platform:
architecture: arm
os: linux

View File

@ -11,11 +11,6 @@ manifests:
platform:
architecture: amd64
os: linux
-
image: vikunja/frontend:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform:
architecture: arm64
os: linux
-
image: vikunja/frontend:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
platform:

3
env.d.ts vendored
View File

@ -1,3 +0,0 @@
/// <reference types="vite/client" />
/// <reference types="vite-svg-loader" />
/// <reference types="cypress" />

View File

@ -1,25 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1664753041,
"narHash": "sha256-0ogaD8PaGHluARFeupofvk1Nq9gpVeZdlFM0Kcwguys=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62844b302507c7531ad68a86cb7aa54704c9cb4",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,10 +0,0 @@
{
description = "Vikunja frontend dev environment";
outputs = { self, nixpkgs }:
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
in {
defaultPackage.x86_64-linux =
pkgs.mkShell { buildInputs = [ pkgs.nodePackages.pnpm pkgs.cypress ]; };
};
}

View File

@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Vikunja</title>
<meta name="description" content="Vikunja (/vɪˈkuːnjə/) - The to-do app to organize your life.">
<meta name="theme-color" content="#1973ff"/>
<link rel="icon" href="/favicon.ico">
<link rel="apple-touch-icon" href="/images/icons/apple-touch-icon-180x180.png"/>
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-700italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-500.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-700.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-regular.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-700.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-regular.woff2" as="font">
</head>
<body>
<noscript>
<strong>We're sorry but Vikunja doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script>
//
// This variable points the frontend to the api.
// It has to be the full url, including the last /api/v1 part and port.
// You can change this if your api is not reachable on the same port as the frontend.
window.API_URL = 'http://localhost:3456/api/v1'
// Enable error tracking with sentry. If this is set to true, will send anonymized data to
// our sentry instance to notify us of potential problems.
window.SENTRY_ENABLED = false
window.SENTRY_DSN = 'https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480'
</script>
</body>
</html>

View File

@ -1,15 +0,0 @@
[build]
command = "pnpm run build"
publish = "dist-preview"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Robots-Tag = "noindex"

View File

@ -6,110 +6,67 @@ pid /var/run/nginx.pid;
events {
worker_connections 1024;
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/mime.types;
default_type application/octet-stream;
types {
application/manifest+json webmanifest;
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
keepalive_timeout 65;
gzip on;
gzip_disable "msie6";
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml font/woff2 image/x-icon;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
text/plain
text/css
application/json
application/x-javascript
application/javascript
text/xml
application/xml
application/xml+rss
text/javascript
application/vnd.ms-fontobject
application/x-font-ttf
font/opentype
image/svg+xml
image/x-icon
audio/wav;
# Expires map
map $sent_http_content_type $expires {
default off;
text/html epoch; # We don't cache the html for the browser to get the content
text/css max;
application/javascript max;
~image/ max;
~font/ max;
}
map_hash_max_size 128;
map_hash_bucket_size 128;
server {
listen 80;
listen 81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
listen 443 default_server ssl http2;
# Expires map
map $sent_http_content_type $expires {
default off;
text/css max;
application/javascript max;
text/javascript max;
application/vnd.ms-fontobject max;
application/x-font-ttf max;
font/opentype max;
font/woff2 max;
image/svg+xml max;
image/x-icon max;
audio/wav max;
~images/ max;
~font/ max;
}
server_name _;
server {
listen 80;
listen 81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
expires $expires;
server_name _;
ssl_certificate /etc/nginx/ssl/dummy.crt;
ssl_certificate_key /etc/nginx/ssl/dummy.key;
expires $expires;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /;
index index.html index.htm;
}
root /usr/share/nginx/html;
# all assets contain hash in filename, cache forever
location ^~ /assets/ {
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
try_files $uri =404;
}
# all workbox scripts are compiled with hash in filename, cache forever3
location ^~ /workbox- {
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
try_files $uri =404;
}
# assume that everything else is handled by the application router, by injecting the index.html.
location / {
autoindex off;
expires off;
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
try_files $uri /index.html =404;
}
location ~* .(txt|webmanifest|css|js|mjs|map|svg|jpg|jpeg|png|ico|ttf|woff|woff2|wav)$ {
try_files $uri $uri/ =404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,149 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,16.000000) scale(0.000320,-0.000320)"
fill="#000000" stroke="none">
<path d="M18 46618 c45 -75 122 -207 122 -211 0 -2 25 -45 55 -95 30 -50 55
-96 55 -102 0 -5 5 -10 10 -10 6 0 10 -4 10 -9 0 -5 73 -135 161 -288 89 -153
173 -298 187 -323 14 -25 32 -57 41 -72 88 -149 187 -324 189 -335 2 -7 8 -13
13 -13 5 0 9 -4 9 -10 0 -5 46 -89 103 -187 175 -302 490 -846 507 -876 8 -16
20 -36 25 -45 28 -46 290 -498 339 -585 13 -23 74 -129 136 -236 61 -107 123
-215 137 -240 14 -25 29 -50 33 -56 5 -5 23 -37 40 -70 18 -33 38 -67 44 -75
11 -16 21 -33 63 -109 14 -25 29 -50 33 -56 4 -5 21 -35 38 -65 55 -100 261
-455 269 -465 4 -5 14 -21 20 -35 15 -29 41 -75 103 -180 24 -41 52 -88 60
-105 9 -16 57 -100 107 -185 112 -193 362 -626 380 -660 8 -14 23 -38 33 -55
11 -16 23 -37 27 -45 4 -8 26 -46 48 -85 23 -38 53 -90 67 -115 46 -81 64
-113 178 -310 62 -107 121 -210 132 -227 37 -67 56 -99 85 -148 16 -27 32 -57
36 -65 4 -8 15 -27 25 -42 9 -15 53 -89 96 -165 44 -76 177 -307 296 -513 120
-206 268 -463 330 -570 131 -227 117 -203 200 -348 36 -62 73 -125 82 -140 10
-15 21 -34 25 -42 4 -8 20 -37 36 -65 17 -27 38 -65 48 -82 49 -85 64 -111 87
-153 13 -25 28 -49 32 -55 4 -5 78 -134 165 -285 87 -151 166 -288 176 -305
10 -16 26 -43 35 -59 9 -17 125 -217 257 -445 132 -229 253 -441 270 -471 17
-30 45 -79 64 -108 18 -29 33 -54 33 -57 0 -2 20 -37 44 -77 24 -40 123 -212
221 -383 97 -170 190 -330 205 -355 16 -25 39 -65 53 -90 13 -25 81 -144 152
-265 70 -121 137 -238 150 -260 12 -22 37 -65 55 -95 18 -30 43 -73 55 -95 12
-22 48 -85 80 -140 77 -132 163 -280 190 -330 13 -22 71 -123 130 -225 59
-102 116 -199 126 -217 10 -17 29 -50 43 -72 15 -22 26 -43 26 -45 0 -2 27
-50 60 -106 33 -56 60 -103 60 -105 0 -2 55 -98 90 -155 8 -14 182 -316 239
-414 13 -22 45 -79 72 -124 27 -46 49 -86 49 -89 0 -2 14 -24 30 -48 16 -24
30 -46 30 -49 0 -5 74 -135 100 -176 5 -8 24 -42 43 -75 50 -88 58 -101 262
-455 104 -179 199 -345 213 -370 14 -25 28 -49 32 -55 4 -5 17 -26 28 -45 10
-19 62 -109 114 -200 114 -197 133 -230 170 -295 16 -27 33 -57 38 -65 17 -28
96 -165 103 -180 4 -8 16 -28 26 -45 10 -16 77 -131 148 -255 72 -124 181
-313 243 -420 62 -107 121 -209 131 -227 35 -62 323 -560 392 -678 38 -66 83
-145 100 -175 16 -30 33 -59 37 -65 4 -5 17 -27 29 -47 34 -61 56 -100 90
-156 17 -29 31 -55 31 -57 0 -2 17 -32 39 -67 21 -35 134 -229 251 -433 117
-203 235 -407 261 -451 27 -45 49 -85 49 -88 0 -4 8 -19 19 -34 15 -21 200
-341 309 -533 10 -19 33 -58 51 -87 17 -29 31 -54 31 -56 0 -2 25 -44 55 -94
30 -50 55 -95 55 -98 0 -4 6 -15 14 -23 7 -9 27 -41 43 -71 17 -30 170 -297
342 -594 171 -296 311 -542 311 -547 0 -5 5 -9 10 -9 6 0 10 -4 10 -10 0 -5
22 -47 49 -92 27 -46 58 -99 68 -118 24 -43 81 -140 93 -160 5 -8 66 -114 135
-235 69 -121 130 -227 135 -235 12 -21 259 -447 283 -490 10 -19 28 -47 38
-62 11 -14 19 -29 19 -32 0 -3 37 -69 83 -148 99 -170 305 -526 337 -583 13
-22 31 -53 41 -70 11 -16 22 -37 26 -45 7 -14 82 -146 103 -180 14 -24 181
-311 205 -355 13 -22 46 -80 75 -130 29 -49 64 -110 78 -135 14 -25 51 -88 82
-140 31 -52 59 -102 63 -110 4 -8 18 -33 31 -55 205 -353 284 -489 309 -535
17 -30 45 -78 62 -106 18 -28 36 -60 39 -72 4 -12 12 -22 17 -22 5 0 9 -4 9
-10 0 -5 109 -197 241 -427 133 -230 250 -431 259 -448 51 -90 222 -385 280
-485 37 -63 78 -135 92 -160 14 -25 67 -117 118 -205 51 -88 101 -175 111
-193 34 -58 55 -95 149 -257 51 -88 101 -173 110 -190 9 -16 76 -131 147 -255
72 -124 140 -241 151 -260 61 -108 281 -489 355 -615 38 -66 77 -133 87 -150
35 -63 91 -161 100 -175 14 -23 99 -169 128 -220 54 -97 135 -235 142 -245 4
-5 20 -32 35 -60 26 -48 238 -416 276 -480 10 -16 26 -46 37 -65 30 -53 382
-661 403 -695 10 -16 22 -37 26 -45 4 -8 26 -48 50 -88 24 -41 43 -75 43 -77
0 -2 22 -40 50 -85 27 -45 50 -84 50 -86 0 -3 38 -69 83 -147 84 -142 302
-520 340 -587 10 -19 34 -60 52 -90 18 -30 44 -75 57 -100 14 -25 45 -79 70
-120 25 -41 56 -96 70 -121 14 -25 77 -133 138 -240 62 -107 122 -210 132
-229 25 -43 310 -535 337 -581 11 -19 26 -45 34 -59 17 -32 238 -414 266 -460
11 -19 24 -41 28 -49 3 -7 75 -133 160 -278 84 -146 153 -269 153 -274 0 -5 5
-9 10 -9 6 0 10 -4 10 -10 0 -5 82 -150 181 -322 182 -314 201 -346 240 -415
12 -21 80 -139 152 -263 71 -124 141 -245 155 -270 14 -25 28 -49 32 -55 6 -8
145 -248 220 -380 37 -66 209 -362 229 -395 11 -19 24 -42 28 -49 4 -8 67
-118 140 -243 73 -125 133 -230 133 -233 0 -2 15 -28 33 -57 19 -29 47 -78 64
-108 17 -30 53 -93 79 -139 53 -90 82 -141 157 -272 82 -142 115 -199 381
-659 142 -245 268 -463 281 -485 12 -22 71 -125 132 -230 60 -104 172 -298
248 -430 76 -132 146 -253 156 -270 11 -16 22 -36 26 -44 3 -8 30 -54 60 -103
29 -49 53 -91 53 -93 0 -3 18 -34 40 -70 22 -36 40 -67 40 -69 0 -2 37 -66 81
-142 45 -77 98 -168 119 -204 20 -36 47 -81 58 -100 12 -19 27 -47 33 -62 6
-16 15 -28 20 -28 5 0 9 -4 9 -9 0 -6 63 -118 140 -251 77 -133 140 -243 140
-245 0 -2 18 -33 41 -70 22 -37 49 -83 60 -101 10 -19 29 -51 40 -71 25 -45
109 -189 126 -218 7 -11 17 -29 22 -40 6 -11 22 -38 35 -60 14 -22 37 -62 52
-90 14 -27 35 -62 45 -77 11 -14 19 -29 19 -32 0 -3 18 -35 40 -71 22 -36 40
-67 40 -69 0 -2 19 -35 42 -72 23 -38 55 -94 72 -124 26 -47 139 -244 171
-298 6 -9 21 -36 34 -60 28 -48 37 -51 51 -19 6 12 19 36 29 52 10 17 27 46
38 65 11 19 104 181 208 360 103 179 199 345 213 370 14 25 42 74 64 109 21
34 38 65 38 67 0 2 18 33 40 69 22 36 40 67 40 69 0 3 177 310 199 346 16 26
136 234 140 244 2 5 25 44 52 88 27 44 49 81 49 84 0 2 18 34 40 70 22 36 40
67 40 69 0 2 20 36 43 77 35 58 169 289 297 513 9 17 50 86 90 155 40 69 86
150 103 180 16 30 35 62 41 70 6 8 16 24 22 35 35 64 72 129 167 293 59 100
116 199 127 220 11 20 30 53 41 72 43 72 1070 1850 1121 1940 14 25 65 113
113 195 48 83 96 166 107 185 10 19 28 50 38 68 11 18 73 124 137 235 64 111
175 303 246 427 71 124 173 299 225 390 52 91 116 202 143 248 27 45 49 85 49
89 0 4 6 14 14 22 7 9 28 43 46 76 26 47 251 436 378 655 11 19 29 51 40 70
11 19 101 176 201 348 99 172 181 317 181 323 0 5 5 9 10 9 6 0 10 5 10 11 0
6 8 23 18 37 11 15 32 52 49 82 16 30 130 228 253 440 122 212 234 405 248
430 13 25 39 70 57 100 39 65 69 117 130 225 25 44 50 87 55 95 12 19 78 134
220 380 61 107 129 224 150 260 161 277 222 382 246 425 15 28 47 83 71 123
24 41 43 78 43 83 0 5 4 9 8 9 4 0 13 12 19 28 7 15 23 45 36 67 66 110 277
478 277 483 0 3 6 13 14 21 7 9 27 41 43 71 17 30 45 80 63 110 34 57 375 649
394 685 6 11 16 27 22 35 6 8 26 42 44 75 18 33 41 74 51 90 10 17 24 41 32
55 54 97 72 128 88 152 11 14 19 28 19 30 0 3 79 141 175 308 96 167 175 305
175 308 0 3 6 13 14 21 7 9 26 39 41 66 33 60 276 483 338 587 24 40 46 80 50
88 4 8 13 24 20 35 14 23 95 163 125 215 11 19 52 91 92 160 40 69 80 139 90
155 9 17 103 179 207 360 105 182 200 346 211 365 103 181 463 802 489 845 7
11 15 27 19 35 4 8 29 51 55 95 64 110 828 1433 848 1470 9 17 24 41 33 55 9
14 29 48 45 77 15 28 52 93 82 145 30 51 62 107 71 123 17 30 231 398 400 690
51 88 103 179 115 202 12 23 26 48 32 55 6 7 24 38 40 68 17 30 61 107 98 170
37 63 84 144 103 180 19 36 41 72 48 81 8 8 14 18 14 21 0 4 27 51 59 106 32
55 72 124 89 154 16 29 71 125 122 213 51 88 104 180 118 205 13 25 28 50 32
55 4 6 17 26 28 45 11 19 45 80 77 135 31 55 66 116 77 135 11 19 88 152 171
295 401 694 620 1072 650 1125 11 19 87 152 170 295 83 143 158 273 166 288 9
16 21 36 26 45 6 9 31 52 55 96 25 43 54 94 66 115 11 20 95 164 186 321 91
157 173 299 182 315 9 17 26 46 37 65 12 19 66 114 121 210 56 96 108 186 117
200 8 14 24 40 34 59 24 45 383 664 412 713 5 9 17 29 26 45 15 28 120 210
241 419 36 61 68 117 72 125 4 8 12 23 19 34 35 57 245 420 262 453 11 20 35
61 53 90 17 29 32 54 32 56 0 3 28 51 62 108 33 57 70 119 80 138 10 19 23 42
28 50 5 8 32 53 59 100 27 47 149 258 271 470 122 212 234 405 248 430 30 53
62 108 80 135 6 11 15 27 19 35 4 8 85 150 181 315 96 165 187 323 202 350 31
56 116 202 130 225 5 8 25 42 43 75 19 33 92 159 162 280 149 257 157 271 202
350 19 33 38 67 43 75 9 14 228 392 275 475 12 22 55 96 95 165 40 69 80 139
90 155 24 42 202 350 221 383 9 15 27 47 41 72 14 25 75 131 136 236 61 106
121 210 134 232 99 172 271 470 279 482 5 8 23 40 40 70 18 30 81 141 142 245
60 105 121 210 135 235 14 25 71 124 127 220 56 96 143 247 194 335 51 88 96
167 102 175 14 24 180 311 204 355 23 43 340 590 356 615 5 8 50 87 101 175
171 301 517 898 582 1008 25 43 46 81 46 83 0 2 12 23 27 47 14 23 40 67 56
97 16 30 35 62 42 70 7 8 15 22 18 30 4 8 20 38 37 65 16 28 33 57 37 65 6 12
111 196 143 250 5 8 55 95 112 193 57 98 113 195 126 215 12 20 27 46 32 57 6
11 14 27 20 35 5 8 76 130 156 270 80 140 165 287 187 325 23 39 52 90 66 115
13 25 30 52 37 61 8 8 14 18 14 21 0 4 41 77 92 165 50 87 175 302 276 478
101 176 208 360 236 408 28 49 67 117 86 152 19 35 41 70 48 77 6 6 12 15 12
19 0 7 124 224 167 291 12 21 23 40 23 42 0 2 21 40 46 83 26 43 55 92 64 109
54 95 327 568 354 614 19 30 45 75 59 100 71 128 82 145 89 148 4 2 8 8 8 13
0 5 42 82 94 172 311 538 496 858 518 897 14 25 40 70 58 100 18 30 42 71 53
90 10 19 79 139 152 265 73 127 142 246 153 265 10 19 43 76 72 125 29 50 63
108 75 130 65 116 80 140 87 143 4 2 8 8 8 12 0 8 114 212 140 250 6 8 14 24
20 35 5 11 54 97 108 190 l100 170 -9611 3 c-5286 1 -9614 -1 -9618 -5 -5 -6
-419 -719 -619 -1068 -89 -155 -267 -463 -323 -560 -38 -66 -81 -140 -95 -165
-31 -56 -263 -457 -526 -910 -110 -190 -224 -388 -254 -440 -29 -52 -61 -109
-71 -125 -23 -39 -243 -420 -268 -465 -11 -19 -204 -352 -428 -740 -224 -388
-477 -826 -563 -975 -85 -148 -185 -322 -222 -385 -37 -63 -120 -207 -185
-320 -65 -113 -177 -306 -248 -430 -72 -124 -172 -297 -222 -385 -51 -88 -142
-245 -202 -350 -131 -226 -247 -427 -408 -705 -65 -113 -249 -432 -410 -710
-160 -278 -388 -673 -506 -877 -118 -205 -216 -373 -219 -373 -3 0 -52 82
-109 183 -58 100 -144 250 -192 332 -95 164 -402 696 -647 1120 -85 149 -228
396 -317 550 -212 365 -982 1700 -1008 1745 -10 19 -43 76 -72 125 -29 50 -64
110 -77 135 -14 25 -63 110 -110 190 -47 80 -96 165 -110 190 -14 25 -99 171
-188 325 -89 154 -174 300 -188 325 -13 25 -64 113 -112 195 -48 83 -140 242
-205 355 -65 113 -183 317 -263 454 -79 137 -152 264 -163 282 -50 89 -335
583 -354 614 -12 19 -34 58 -50 85 -15 28 -129 226 -253 440 -124 215 -235
408 -247 430 -12 22 -69 121 -127 220 -58 99 -226 389 -373 645 -148 256 -324
561 -392 678 -67 117 -134 232 -147 255 -13 23 -33 59 -46 80 l-22 37 -9615 0
-9615 0 20 -32z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1007.922 821.827"><defs><style>.a{fill:#fff;}.b{fill:url(#a);}.c{mask:url(#b);}.d{fill:url(#c);}.e{mask:url(#d);}.f{fill:url(#e);}.g{fill:url(#f);}.h{mask:url(#g);}.i{fill:url(#h);}.j{fill:#195abd;}.k{fill:url(#i);}</style><linearGradient id="a" x1="700.766" y1="597.024" x2="749.765" y2="597.024" gradientTransform="translate(86.603 -142.296) scale(0.867 1.307)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-opacity="0.13"/><stop offset="0.994" stop-opacity="0"/></linearGradient><mask id="b" x="317.137" y="651.827" width="170" height="205.208" maskUnits="userSpaceOnUse"><g transform="translate(-8.064 -116.521)"><rect class="a" x="367.701" y="870.953" width="85" height="85" transform="translate(766.054 -22.512) rotate(45)"/></g></mask><radialGradient id="c" cx="410.201" cy="853.349" r="85" gradientTransform="translate(715.49 -156.637) rotate(45)" gradientUnits="userSpaceOnUse"><stop offset="0.5" stop-opacity="0.13"/><stop offset="0.994" stop-opacity="0"/></radialGradient><mask id="d" x="837.922" y="95.835" width="205.208" height="205.208" maskUnits="userSpaceOnUse"><g transform="translate(-8.064 -116.521)"><rect class="a" x="876.038" y="260.012" width="170" height="85" transform="translate(1426.685 1195.977) rotate(-135)"/></g></mask><radialGradient id="e" cx="1051.126" cy="1265.852" r="85" gradientTransform="translate(771.087 1854.394) rotate(-135)" xlink:href="#c"/><linearGradient id="f" x1="1880.8" y1="34.286" x2="1929.799" y2="34.286" gradientTransform="matrix(0.867, 0, 0, -0.796, -1446.031, 767.147)" xlink:href="#a"/><mask id="g" x="-35.208" y="299.482" width="205.208" height="205.208" maskUnits="userSpaceOnUse"><g transform="translate(-8.064 -116.521)"><rect class="a" x="-21.988" y="463.659" width="170" height="85" transform="translate(-339.453 192.807) rotate(-45)"/></g></mask><radialGradient id="h" cx="27.608" cy="2001.37" r="85" gradientTransform="matrix(0.707, -0.707, -0.707, -0.707, 1480.66, 1854.394)" xlink:href="#c"/><linearGradient id="i" x1="308.378" y1="811.629" x2="919.318" y2="200.689" gradientTransform="translate(-178.117 582.307) rotate(-45)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2987e6"/><stop offset="0.994" stop-color="#58c1f5"/></linearGradient></defs><title>Todo</title><rect class="b" x="694.422" y="269.785" width="42.5" height="736.5" transform="translate(652.71 -435.701) rotate(45)"/><g class="c"><circle class="d" cx="402.137" cy="736.827" r="85"/></g><g class="e"><circle class="f" cx="922.922" cy="216.043" r="85"/></g><rect class="g" x="185.305" y="515.608" width="42.5" height="448.5" transform="translate(867.705 1000.439) rotate(135)"/><g class="h"><circle class="i" cx="85" cy="419.69" r="85"/></g><rect class="j" x="164.378" y="319.982" width="288" height="576" rx="42.5" transform="translate(-347.651 279.609) rotate(-45)"/><rect class="k" x="469.848" y="74.159" width="288" height="864" rx="42.5" transform="translate(529.636 -402.327) rotate(45)"/></svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M224.001997,0 L31.9980026,0 C14.3579381,0.0394964443 0.0614809418,14.336846 0,32 L0,224 C0,241.6 14.3971038,256 31.9980026,256 L224.001997,256 C241.602896,256 256,241.6 256,224 L256,32 C256,14.4 241.602896,0 224.001997,0" fill="#E44332"></path>
<path d="M54.132778,120.802491 C58.5960224,118.196275 154.476075,62.477451 156.667847,61.1862981 C158.859619,59.9110855 158.97917,55.9898065 156.508446,54.5711324 C154.053661,53.1604284 149.391165,50.4824817 147.661658,49.4543415 C145.192242,48.0957707 142.191169,48.132074 139.755339,49.5499825 C138.527947,50.2672896 56.6035026,97.8486625 53.8697654,99.4107981 C50.5781227,101.291737 46.5372925,101.323617 43.2695601,99.4107981 L0,74.0181257 L0,95.6011002 C10.5205046,101.801822 36.7181549,117.200015 43.062338,120.826401 C46.8481256,122.978322 50.4745117,122.930502 54.1407481,120.802491" fill="#FFFFFF"></path>
<path d="M54.132778,161.609296 C58.5960224,159.00308 154.476075,103.284257 156.667847,101.993104 C158.859619,100.717891 158.97917,96.7966121 156.508446,95.377938 C154.053661,93.9672339 149.391165,91.2892873 147.661658,90.2611471 C145.192242,88.9025763 142.191169,88.9388796 139.755339,90.3567881 C138.527947,91.0740952 56.6035026,138.655468 53.8697654,140.217604 C50.5781227,142.098542 46.5372925,142.130423 43.2695601,140.217604 L0,114.824931 L0,136.407906 C10.5205046,142.608627 36.7181549,158.00682 43.062338,161.633206 C46.8481256,163.785128 50.4745117,163.737307 54.1407481,161.609296" fill="#FFFFFF"></path>
<path d="M54.132778,204.966527 C58.5960224,202.360311 154.476075,146.641487 156.667847,145.350335 C158.859619,144.075122 158.97917,140.153843 156.508446,138.735169 C154.053661,137.324465 149.391165,134.646518 147.661658,133.618378 C145.192242,132.259807 142.191169,132.29611 139.755339,133.714019 C138.527947,134.431326 56.6035026,182.012699 53.8697654,183.574835 C50.5781227,185.455773 46.5372925,185.487654 43.2695601,183.574835 L0,158.182162 L0,179.765137 C10.5205046,185.965858 36.7181549,201.364051 43.062338,204.990437 C46.8481256,207.142359 50.4745117,207.094538 54.1407481,204.966527" fill="#FFFFFF"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#0091E6" offset="0%"></stop>
<stop stop-color="#0079BF" offset="100%"></stop>
</linearGradient>
</defs>
<g>
<g>
<rect fill="url(#linearGradient-1)" x="0" y="0" width="256" height="256" rx="25"></rect>
<rect fill="#FFFFFF" x="144.64" y="33.28" width="78.08" height="112" rx="12"></rect>
<rect fill="#FFFFFF" x="33.28" y="33.28" width="78.08" height="176" rx="12"></rect>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

View File

@ -3,108 +3,72 @@
"version": "0.10.0",
"private": true,
"scripts": {
"serve": "vite",
"serve:dist-dev": "node scripts/serve-dist.js",
"serve:dist": "vite preview --port 4173",
"build": "vite build && workbox copyLibraries dist/",
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
"build:dev": "vite build -m development --outDir dist-dev/",
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
"cypress:open": "cypress open",
"test:unit": "vitest --run",
"test:unit-watch": "vitest watch",
"test:frontend": "cypress run",
"typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"browserslist:update": "npx browserslist@latest --update-db"
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.2.0",
"@fortawesome/free-regular-svg-icons": "6.2.0",
"@fortawesome/free-solid-svg-icons": "6.2.0",
"@fortawesome/vue-fontawesome": "3.0.1",
"@github/hotkey": "2.0.1",
"@kyvg/vue3-notification": "2.4.1",
"@sentry/tracing": "7.16.0",
"@sentry/vue": "7.16.0",
"@types/is-touch-device": "1.0.0",
"@types/lodash.clonedeep": "4.5.7",
"@types/sortablejs": "1.15.0",
"@vueuse/core": "9.3.1",
"@vueuse/router": "9.3.1",
"axios": "0.27.2",
"blurhash": "2.0.3",
"bulma-css-variables": "0.9.33",
"camel-case": "4.1.2",
"codemirror": "5.65.9",
"date-fns": "2.29.3",
"dompurify": "2.4.0",
"easymde": "2.18.0",
"flatpickr": "4.6.13",
"flexsearch": "0.7.21",
"floating-vue": "2.0.0-beta.20",
"highlight.js": "11.6.0",
"is-touch-device": "1.0.1",
"lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8",
"marked": "4.1.1",
"minimist": "1.2.7",
"pinia": "2.0.23",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",
"sortablejs": "1.15.0",
"ufo": "0.8.6",
"vue": "3.2.41",
"vue-advanced-cropper": "2.8.6",
"vue-drag-resize": "2.0.3",
"vue-flatpickr-component": "9.0.8",
"vue-i18n": "9.2.2",
"vue-router": "4.1.5",
"workbox-precaching": "6.5.4",
"zhyswan-vuedraggable": "4.1.3"
"bulma": "0.9.0",
"camel-case": "4.1.1",
"copy-to-clipboard": "3.3.1",
"date-fns": "2.14.0",
"lodash": "4.17.15",
"register-service-worker": "1.7.1",
"snake-case": "3.0.3",
"v-tooltip": "2.0.3",
"verte": "0.0.12",
"vue": "2.6.11",
"vue-drag-resize": "1.3.2",
"vue-easymde": "1.2.1",
"vue-smooth-dnd": "0.8.1",
"vuex": "3.5.1"
},
"devDependencies": {
"@4tw/cypress-drag-drop": "2.2.1",
"@cypress/vite-dev-server": "3.3.1",
"@cypress/vue": "4.2.1",
"@faker-js/faker": "7.6.0",
"@rushstack/eslint-patch": "1.2.0",
"@types/dompurify": "2.3.4",
"@types/flexsearch": "0.7.3",
"@types/lodash.debounce": "4.0.7",
"@types/marked": "4.0.7",
"@types/node": "16.11.68",
"@types/postcss-preset-env": "7.7.0",
"@typescript-eslint/eslint-plugin": "5.40.1",
"@typescript-eslint/parser": "5.40.1",
"@vitejs/plugin-legacy": "2.2.0",
"@vitejs/plugin-vue": "3.1.2",
"@vue/eslint-config-typescript": "11.0.2",
"@vue/test-utils": "2.1.0",
"@vue/tsconfig": "0.1.3",
"autoprefixer": "10.4.12",
"browserslist": "4.21.4",
"caniuse-lite": "1.0.30001420",
"cypress": "10.10.0",
"esbuild": "0.15.11",
"eslint": "8.25.0",
"eslint-plugin-vue": "9.6.0",
"express": "4.18.2",
"happy-dom": "7.5.12",
"netlify-cli": "12.0.9",
"postcss": "8.4.18",
"postcss-preset-env": "7.8.2",
"rollup": "3.2.3",
"rollup-plugin-visualizer": "5.8.3",
"sass": "1.55.0",
"typescript": "4.8.4",
"vite": "3.1.8",
"vite-plugin-pwa": "0.13.1",
"vite-svg-loader": "3.6.0",
"vitest": "0.24.3",
"vue-tsc": "1.0.8",
"wait-on": "6.0.1",
"workbox-cli": "6.5.4"
"@fortawesome/fontawesome-svg-core": "1.2.29",
"@fortawesome/free-regular-svg-icons": "5.13.1",
"@fortawesome/free-solid-svg-icons": "5.13.1",
"@fortawesome/vue-fontawesome": "0.1.10",
"@vue/cli": "4.4.6",
"@vue/cli-plugin-babel": "4.4.6",
"@vue/cli-plugin-eslint": "4.4.6",
"@vue/cli-plugin-pwa": "4.4.6",
"@vue/cli-service": "4.4.6",
"axios": "0.19.2",
"babel-eslint": "10.1.0",
"core-js": "3.6.5",
"eslint": "7.3.1",
"eslint-plugin-vue": "6.2.2",
"node-sass": "4.14.1",
"sass-loader": "8.0.2",
"vue-flatpickr-component": "8.1.5",
"vue-multiselect": "2.1.6",
"vue-notification": "1.3.20",
"vue-router": "3.3.4",
"vue-template-compiler": "2.6.11"
},
"license": "AGPL-3.0-or-later",
"packageManager": "pnpm@7.13.5"
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"license": "LGPL-3.0-or-later"
}

File diff suppressed because it is too large Load Diff

119
public/fonts/fonts.css Normal file
View File

@ -0,0 +1,119 @@
/* quicksand-300 - latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url('quicksand-v7-latin-300.eot'); /* IE9 Compat Modes */
src: local('Quicksand Light'), local('Quicksand-Light'),
url('quicksand-v7-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('quicksand-v7-latin-300.woff2') format('woff2'), /* Super Modern Browsers */
url('quicksand-v7-latin-300.woff') format('woff'), /* Modern Browsers */
url('quicksand-v7-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */
url('quicksand-v7-latin-300.svg#Quicksand') format('svg'); /* Legacy iOS */
}
/* quicksand-regular - latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('quicksand-v7-latin-regular.eot'); /* IE9 Compat Modes */
src: local('Quicksand Regular'), local('Quicksand-Regular'),
url('quicksand-v7-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('quicksand-v7-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('quicksand-v7-latin-regular.woff') format('woff'), /* Modern Browsers */
url('quicksand-v7-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('quicksand-v7-latin-regular.svg#Quicksand') format('svg'); /* Legacy iOS */
}
/* quicksand-500 - latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('quicksand-v7-latin-500.eot'); /* IE9 Compat Modes */
src: local('Quicksand Medium'), local('Quicksand-Medium'),
url('quicksand-v7-latin-500.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('quicksand-v7-latin-500.woff2') format('woff2'), /* Super Modern Browsers */
url('quicksand-v7-latin-500.woff') format('woff'), /* Modern Browsers */
url('quicksand-v7-latin-500.ttf') format('truetype'), /* Safari, Android, iOS */
url('quicksand-v7-latin-500.svg#Quicksand') format('svg'); /* Legacy iOS */
}
/* quicksand-700 - latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('quicksand-v7-latin-700.eot'); /* IE9 Compat Modes */
src: local('Quicksand Bold'), local('Quicksand-Bold'),
url('quicksand-v7-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('quicksand-v7-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
url('quicksand-v7-latin-700.woff') format('woff'), /* Modern Browsers */
url('quicksand-v7-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
url('quicksand-v7-latin-700.svg#Quicksand') format('svg'); /* Legacy iOS */
}
/* open-sans-regular - latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('open-sans-v15-latin-regular.eot'); /* IE9 Compat Modes */
src: local('Open Sans Regular'), local('OpenSans-Regular'),
url('open-sans-v15-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('open-sans-v15-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('open-sans-v15-latin-regular.woff') format('woff'), /* Modern Browsers */
url('open-sans-v15-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('open-sans-v15-latin-regular.svg#OpenSans') format('svg'); /* Legacy iOS */
}
/* open-sans-italic - latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url('open-sans-v15-latin-italic.eot'); /* IE9 Compat Modes */
src: local('Open Sans Italic'), local('OpenSans-Italic'),
url('open-sans-v15-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('open-sans-v15-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */
url('open-sans-v15-latin-italic.woff') format('woff'), /* Modern Browsers */
url('open-sans-v15-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('open-sans-v15-latin-italic.svg#OpenSans') format('svg'); /* Legacy iOS */
}
/* open-sans-700 - latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('open-sans-v15-latin-700.eot'); /* IE9 Compat Modes */
src: local('Open Sans Bold'), local('OpenSans-Bold'),
url('open-sans-v15-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('open-sans-v15-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
url('open-sans-v15-latin-700.woff') format('woff'), /* Modern Browsers */
url('open-sans-v15-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
url('open-sans-v15-latin-700.svg#OpenSans') format('svg'); /* Legacy iOS */
}
/* open-sans-700italic - latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url('open-sans-v15-latin-700italic.eot'); /* IE9 Compat Modes */
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'),
url('open-sans-v15-latin-700italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('open-sans-v15-latin-700italic.woff2') format('woff2'), /* Super Modern Browsers */
url('open-sans-v15-latin-700italic.woff') format('woff'), /* Modern Browsers */
url('open-sans-v15-latin-700italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('open-sans-v15-latin-700italic.svg#OpenSans') format('svg'); /* Legacy iOS */
}

Binary file not shown.

View File

@ -0,0 +1,334 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<defs >
<font id="OpenSans" horiz-adv-x="1295" ><font-face
font-family="Open Sans"
units-per-em="2048"
panose-1="2 11 8 6 3 5 4 2 2 4"
ascent="2189"
descent="-600"
alphabetic="0" />
<glyph unicode=" " glyph-name="space" horiz-adv-x="532" />
<glyph unicode="!" glyph-name="exclam" horiz-adv-x="586" d="M416 485H172L121 1462H467L416 485ZM117 143Q117 227 162 270T293 313Q376 313 421 269T467 143Q467 64 421 19T293 -27Q209 -27 163 17T117 143Z" />
<glyph unicode="&quot;" glyph-name="quotedbl" horiz-adv-x="967" d="M412 1462L371 934H174L133 1462H412ZM834 1462L793 934H596L555 1462H834Z" />
<glyph unicode="#" glyph-name="numbersign" horiz-adv-x="1323" d="M999 844L952 612H1210V406H913L836 0H616L694 406H500L424 0H209L283 406H45V612H322L369 844H117V1053H406L483 1460H702L625 1053H823L901 1460H1116L1038 1053H1278V844H999ZM539 612H735L782
844H586L539 612Z" />
<glyph unicode="$" glyph-name="dollar" horiz-adv-x="1169" d="M1092 457Q1092 298 977 202T655 86V-119H518V82Q274 87 90 168V432Q177 389 299 356T518 317V627L451 653Q253 731 171 822T88 1049Q88 1194 201 1287T518 1401V1554H655V1405Q884 1395 1069 1313L975
1079Q819 1143 655 1157V862Q850 787 932 732T1053 611T1092 457ZM791 442Q791 484 757 513T655 573V324Q791 347 791 442ZM389 1049Q389 1005 419 977T518 918V1153Q389 1134 389 1049Z" />
<glyph unicode="%" glyph-name="percent" horiz-adv-x="1845" d="M315 1024Q315 897 337 835T410 772Q506 772 506 1024Q506 1274 410 1274Q360 1274 338 1213T315 1024ZM758 1026Q758 796 669 681T408 565Q243 565 153 683T63 1026Q63 1483 408 1483Q577 1483
667 1365T758 1026ZM1446 1462L635 0H395L1206 1462H1446ZM1339 440Q1339 313 1361 251T1434 188Q1530 188 1530 440Q1530 690 1434 690Q1384 690 1362 629T1339 440ZM1782 442Q1782 213 1693 98T1432 -18Q1267 -18 1177 100T1087 442Q1087 899 1432 899Q1601 899
1691 781T1782 442Z" />
<glyph unicode="&amp;" glyph-name="ampersand" horiz-adv-x="1536" d="M1536 0H1159L1044 113Q853 -20 612 -20Q368 -20 225 92T82 395Q82 532 142 628T350 809Q275 895 241 973T207 1145Q207 1297 323 1390T635 1483Q821 1483 932 1397T1044 1165Q1044 1046
975 948T752 760L1036 483Q1107 600 1159 784H1477Q1441 649 1378 521T1235 293L1536 0ZM403 424Q403 338 467 287T633 236Q759 236 860 297L528 627Q470 583 437 535T403 424ZM762 1133Q762 1186 726 1216T633 1247Q566 1247 528 1215T489 1124Q489 1036 584 930Q670
978 716 1024T762 1133Z" />
<glyph unicode="&apos;" glyph-name="quotesingle" horiz-adv-x="545" d="M412 1462L371 934H174L133 1462H412Z" />
<glyph unicode="(" glyph-name="parenleft" horiz-adv-x="694" d="M82 561Q82 826 159 1057T383 1462H633Q492 1269 420 1038T348 563Q348 318 421 90T631 -324H383Q236 -154 159 73T82 561Z" />
<glyph unicode=")" glyph-name="parenright" horiz-adv-x="694" d="M612 561Q612 298 535 71T311 -324H63Q198 -140 272 88T346 563Q346 807 274 1038T61 1462H311Q458 1287 535 1056T612 561Z" />
<glyph unicode="*" glyph-name="asterisk" horiz-adv-x="1116" d="M688 1556L647 1188L1020 1292L1053 1040L713 1016L936 719L709 598L553 911L416 600L180 719L401 1016L63 1042L102 1292L467 1188L426 1556H688Z" />
<glyph unicode="+" glyph-name="plus" horiz-adv-x="1169" d="M475 612H88V831H475V1221H694V831H1081V612H694V227H475V612Z" />
<glyph unicode="," glyph-name="comma" horiz-adv-x="594" d="M459 215Q407 13 283 -264H63Q128 2 164 238H444L459 215Z" />
<glyph unicode="-" glyph-name="hyphen" horiz-adv-x="659" d="M61 424V674H598V424H61Z" />
<glyph unicode="." glyph-name="period" horiz-adv-x="584" d="M117 143Q117 227 162 270T293 313Q376 313 421 269T467 143Q467 64 421 19T293 -27Q209 -27 163 17T117 143Z" />
<glyph unicode="/" glyph-name="slash" horiz-adv-x="846" d="M836 1462L291 0H14L559 1462H836Z" />
<glyph unicode="0" glyph-name="zero" horiz-adv-x="1169" d="M1096 731Q1096 348 971 164T584 -20Q331 -20 203 170T74 731Q74 1118 199 1301T584 1485Q837 1485 966 1293T1096 731ZM381 731Q381 462 427 346T584 229Q692 229 740 347T788 731Q788 1000 740 1117T584
1235Q475 1235 428 1118T381 731Z" />
<glyph unicode="1" glyph-name="one" horiz-adv-x="1169" d="M846 0H537V846L540 985L545 1137Q468 1060 438 1036L270 901L121 1087L592 1462H846V0Z" />
<glyph unicode="2" glyph-name="two" horiz-adv-x="1169" d="M1104 0H82V215L449 586Q612 753 662 817T734 937T756 1051Q756 1139 708 1182T578 1225Q493 1225 413 1186T246 1075L78 1274Q186 1366 257 1404T412 1462T600 1483Q737 1483 842 1433T1005 1293T1063
1087Q1063 986 1028 898T918 716T655 451L467 274V260H1104V0Z" />
<glyph unicode="3" glyph-name="three" horiz-adv-x="1169" d="M1047 1135Q1047 998 964 902T731 770V764Q908 742 999 657T1090 426Q1090 215 937 98T500 -20Q262 -20 78 59V322Q163 279 265 252T467 225Q620 225 693 277T766 444Q766 547 682 590T414 633H303V870H416Q586
870 664 914T743 1067Q743 1233 535 1233Q463 1233 389 1209T223 1126L80 1339Q280 1483 557 1483Q784 1483 915 1391T1047 1135Z" />
<glyph unicode="4" glyph-name="four" horiz-adv-x="1169" d="M1137 303H961V0H659V303H35V518L676 1462H961V543H1137V303ZM659 543V791Q659 853 664 971T672 1108H664Q627 1026 575 948L307 543H659Z" />
<glyph unicode="5" glyph-name="five" horiz-adv-x="1169" d="M614 934Q826 934 951 815T1077 489Q1077 244 926 112T494 -20Q250 -20 100 59V326Q179 284 284 258T483 231Q766 231 766 463Q766 684 473 684Q420 684 356 674T252 651L129 717L184 1462H977V1200H455L428
913L463 920Q524 934 614 934Z" />
<glyph unicode="6" glyph-name="six" horiz-adv-x="1169" d="M72 621Q72 1055 255 1267T805 1479Q930 1479 1001 1464V1217Q912 1237 825 1237Q666 1237 566 1189T415 1047T356 780H369Q468 950 686 950Q882 950 993 827T1104 487Q1104 253 972 117T606 -20Q444
-20 324 55T138 274T72 621ZM600 227Q699 227 752 293T805 483Q805 590 756 651T606 713Q512 713 446 652T379 510Q379 391 441 309T600 227Z" />
<glyph unicode="7" glyph-name="seven" horiz-adv-x="1169" d="M227 0L776 1200H55V1460H1104V1266L551 0H227Z" />
<glyph unicode="8" glyph-name="eight" horiz-adv-x="1169" d="M586 1481Q796 1481 924 1386T1053 1128Q1053 1016 991 929T791 772Q955 684 1026 589T1098 379Q1098 199 957 90T586 -20Q346 -20 209 82T72 371Q72 496 138 593T352 764Q227 843 172 933T117 1130Q117
1287 247 1384T586 1481ZM358 389Q358 303 418 255T582 207Q697 207 754 256T811 387Q811 454 755 512T571 637Q358 539 358 389ZM584 1255Q505 1255 457 1215T408 1106Q408 1046 446 999T586 901Q684 947 723 995T762 1106Q762 1175 712 1215T584 1255Z" />
<glyph unicode="9" glyph-name="nine" horiz-adv-x="1169" d="M1098 838Q1098 406 916 193T365 -20Q235 -20 168 -6V242Q252 221 344 221Q499 221 599 266T752 409T813 678H801Q743 584 667 546T477 508Q286 508 176 630T66 971Q66 1206 199 1342T563 1479Q725
1479 846 1403T1033 1183T1098 838ZM569 1231Q473 1231 419 1165T365 975Q365 869 414 807T563 745Q657 745 724 806T791 948Q791 1067 729 1149T569 1231Z" />
<glyph unicode=":" glyph-name="colon" horiz-adv-x="584" d="M117 143Q117 227 162 270T293 313Q376 313 421 269T467 143Q467 64 421 19T293 -27Q209 -27 163 17T117 143ZM117 969Q117 1053 162 1096T293 1139Q376 1139 421 1095T467 969Q467 888 421 844T293
799Q209 799 163 843T117 969Z" />
<glyph unicode=";" glyph-name="semicolon" horiz-adv-x="594" d="M444 238L459 215Q407 13 283 -264H63Q128 2 164 238H444ZM117 969Q117 1053 162 1096T293 1139Q376 1139 421 1095T467 969Q467 888 421 844T293 799Q209 799 163 843T117 969Z" />
<glyph unicode="&lt;" glyph-name="less" horiz-adv-x="1169" d="M1081 203L88 641V784L1081 1280V1040L397 723L1081 442V203Z" />
<glyph unicode="=" glyph-name="equal" horiz-adv-x="1169" d="M88 805V1024H1081V805H88ZM88 418V637H1081V418H88Z" />
<glyph unicode="&gt;" glyph-name="greater" horiz-adv-x="1169" d="M88 442L772 723L88 1040V1280L1081 784V641L88 203V442Z" />
<glyph unicode="?" glyph-name="question" horiz-adv-x="977" d="M276 485V559Q276 655 317 726T467 877Q572 952 605 999T639 1104Q639 1169 591 1203T457 1237Q307 1237 115 1139L6 1358Q229 1483 479 1483Q685 1483 806 1384T928 1120Q928 1010 878 930T688
750Q592 679 567 642T541 545V485H276ZM244 143Q244 227 289 270T420 313Q503 313 548 269T594 143Q594 64 548 19T420 -27Q336 -27 290 17T244 143Z" />
<glyph unicode="@" glyph-name="at" horiz-adv-x="1837" d="M1735 752Q1735 608 1689 489T1559 301T1364 233Q1290 233 1233 268T1151 362H1135Q1027 233 860 233Q683 233 581 339T479 631Q479 842 613 971T963 1100Q1049 1100 1152 1084T1323 1044L1300 555Q1300
416 1376 416Q1440 416 1478 509T1516 754Q1516 915 1449 1038T1261 1227T983 1292Q781 1292 632 1209T404 970T324 608Q324 332 471 185T899 37Q1005 37 1132 60T1382 129V-63Q1168 -154 907 -154Q527 -154 315 46T102 602Q102 849 210 1050T519 1366T981 1481Q1201
1481 1374 1391T1641 1135T1735 752ZM711 627Q711 416 883 416Q973 416 1020 479T1077 686L1090 907Q1039 918 975 918Q850 918 781 840T711 627Z" />
<glyph unicode="A" glyph-name="A" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899Z" />
<glyph unicode="B" glyph-name="B" horiz-adv-x="1376" d="M184 1462H639Q950 1462 1090 1374T1231 1092Q1231 961 1170 877T1006 776V766Q1145 735 1206 650T1268 424Q1268 224 1124 112T731 0H184V1462ZM494 883H674Q800 883 856 922T913 1051Q913 1135 852
1171T657 1208H494V883ZM494 637V256H696Q824 256 885 305T946 455Q946 637 686 637H494Z" />
<glyph unicode="C" glyph-name="C" horiz-adv-x="1305" d="M805 1225Q630 1225 534 1094T438 727Q438 238 805 238Q959 238 1178 315V55Q998 -20 776 -20Q457 -20 288 173T119 729Q119 957 202 1128T440 1391T805 1483Q1018 1483 1233 1380L1133 1128Q1051 1167
968 1196T805 1225Z" />
<glyph unicode="D" glyph-name="D" horiz-adv-x="1516" d="M1397 745Q1397 384 1192 192T598 0H184V1462H643Q1001 1462 1199 1273T1397 745ZM1075 737Q1075 1208 659 1208H494V256H627Q1075 256 1075 737Z" />
<glyph unicode="E" glyph-name="E" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0Z" />
<glyph unicode="F" glyph-name="F" horiz-adv-x="1124" d="M489 0H184V1462H1022V1208H489V831H985V578H489V0Z" />
<glyph unicode="G" glyph-name="G" horiz-adv-x="1483" d="M739 821H1319V63Q1178 17 1054 -1T799 -20Q468 -20 294 174T119 733Q119 1087 321 1285T883 1483Q1108 1483 1317 1393L1214 1145Q1054 1225 881 1225Q680 1225 559 1090T438 727Q438 489 535 364T819
238Q916 238 1016 258V563H739V821Z" />
<glyph unicode="H" glyph-name="H" horiz-adv-x="1567" d="M1382 0H1073V631H494V0H184V1462H494V889H1073V1462H1382V0Z" />
<glyph unicode="I" glyph-name="I" horiz-adv-x="678" d="M184 0V1462H494V0H184Z" />
<glyph unicode="J" glyph-name="J" horiz-adv-x="678" d="M31 -430Q-74 -430 -152 -408V-150Q-72 -170 -6 -170Q96 -170 140 -107T184 92V1462H494V94Q494 -162 377 -296T31 -430Z" />
<glyph unicode="K" glyph-name="K" horiz-adv-x="1360" d="M1360 0H1008L625 616L494 522V0H184V1462H494V793L616 965L1012 1462H1356L846 815L1360 0Z" />
<glyph unicode="L" glyph-name="L" horiz-adv-x="1157" d="M184 0V1462H494V256H1087V0H184Z" />
<glyph unicode="M" glyph-name="M" horiz-adv-x="1931" d="M803 0L451 1147H442Q461 797 461 680V0H184V1462H606L952 344H958L1325 1462H1747V0H1458V692Q1458 741 1459 805T1473 1145H1464L1087 0H803Z" />
<glyph unicode="N" glyph-name="N" horiz-adv-x="1665" d="M1481 0H1087L451 1106H442Q461 813 461 688V0H184V1462H575L1210 367H1217Q1202 652 1202 770V1462H1481V0Z" />
<glyph unicode="O" glyph-name="O" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227 817 1227Q632
1227 538 1103T444 733Z" />
<glyph unicode="P" glyph-name="P" horiz-adv-x="1286" d="M494 774H596Q739 774 810 830T881 995Q881 1104 822 1156T635 1208H494V774ZM1194 1006Q1194 770 1047 645T627 520H494V0H184V1462H651Q917 1462 1055 1348T1194 1006Z" />
<glyph unicode="Q" glyph-name="Q" horiz-adv-x="1630" d="M1511 733Q1511 475 1420 301T1151 45L1503 -348H1106L838 -20H815Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240
1186 733Q1186 1227 817 1227Q632 1227 538 1103T444 733Z" />
<glyph unicode="R" glyph-name="R" horiz-adv-x="1352" d="M494 813H594Q741 813 811 862T881 1016Q881 1120 810 1164T588 1208H494V813ZM494 561V0H184V1462H610Q908 1462 1051 1354T1194 1024Q1194 895 1123 795T922 637Q1252 144 1352 0H1008L659 561H494Z" />
<glyph unicode="S" glyph-name="S" horiz-adv-x="1128" d="M1047 406Q1047 208 905 94T508 -20Q274 -20 94 68V356Q242 290 344 263T532 236Q634 236 688 275T743 391Q743 434 719 467T649 532T459 631Q325 694 258 752T151 887T111 1067Q111 1261 242 1372T606
1483Q720 1483 823 1456T1040 1380L940 1139Q823 1187 747 1206T596 1225Q508 1225 461 1184T414 1077Q414 1036 433 1006T493 947T690 844Q895 746 971 648T1047 406Z" />
<glyph unicode="T" glyph-name="T" horiz-adv-x="1186" d="M748 0H438V1204H41V1462H1145V1204H748V0Z" />
<glyph unicode="U" glyph-name="U" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374Z" />
<glyph unicode="V" glyph-name="V" horiz-adv-x="1331" d="M1018 1462H1331L834 0H496L0 1462H313L588 592Q611 515 635 413T666 270Q677 362 741 592L1018 1462Z" />
<glyph unicode="W" glyph-name="W" horiz-adv-x="1980" d="M1608 0H1255L1057 768Q1046 809 1020 937T989 1110Q983 1056 959 937T922 766L725 0H373L0 1462H305L492 664Q541 443 563 281Q569 338 590 457T631 643L844 1462H1137L1350 643Q1364 588 1385 475T1417
281Q1427 359 1449 475T1489 664L1675 1462H1980L1608 0Z" />
<glyph unicode="X" glyph-name="X" horiz-adv-x="1366" d="M1366 0H1012L672 553L332 0H0L485 754L31 1462H373L688 936L997 1462H1331L872 737L1366 0Z" />
<glyph unicode="Y" glyph-name="Y" horiz-adv-x="1278" d="M639 860L944 1462H1278L793 569V0H485V559L0 1462H336L639 860Z" />
<glyph unicode="Z" glyph-name="Z" horiz-adv-x="1186" d="M1137 0H49V201L750 1206H68V1462H1118V1262L418 256H1137V0Z" />
<glyph unicode="[" glyph-name="bracketleft" horiz-adv-x="678" d="M627 -324H143V1462H627V1251H403V-113H627V-324Z" />
<glyph unicode="\" glyph-name="backslash" horiz-adv-x="846" d="M289 1462L834 0H557L12 1462H289Z" />
<glyph unicode="]" glyph-name="bracketright" horiz-adv-x="678" d="M51 -113H274V1251H51V1462H535V-324H51V-113Z" />
<glyph unicode="^" glyph-name="asciicircum" horiz-adv-x="1090" d="M8 520L446 1470H590L1085 520H846L524 1163L244 520H8Z" />
<glyph unicode="_" glyph-name="underscore" horiz-adv-x="842" d="M846 -324H-4V-184H846V-324Z" />
<glyph unicode="`" glyph-name="grave" horiz-adv-x="1243" d="M707 1241Q644 1285 522 1383T332 1548V1569H674Q737 1468 909 1268V1241H707Z" />
<glyph unicode="a" glyph-name="a" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083 745V0H870ZM780
518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518Z" />
<glyph unicode="b" glyph-name="b" horiz-adv-x="1296" d="M782 1139Q980 1139 1092 985T1204 561Q1204 284 1089 132T774 -20Q577 -20 465 123H444L393 0H160V1556H465V1194Q465 1125 453 973H465Q572 1139 782 1139ZM684 895Q571 895 519 826T465 596V563Q465
383 518 305T688 227Q782 227 837 313T893 565Q893 730 837 812T684 895Z" />
<glyph unicode="c" glyph-name="c" horiz-adv-x="1053" d="M614 -20Q92 -20 92 553Q92 838 234 988T641 1139Q835 1139 989 1063L899 827Q827 856 765 874T641 893Q403 893 403 555Q403 227 641 227Q729 227 804 250T954 324V63Q880 16 805 -2T614 -20Z" />
<glyph unicode="d" glyph-name="d" horiz-adv-x="1296" d="M514 -20Q317 -20 205 133T92 557Q92 832 206 985T522 1139Q733 1139 844 975H854Q831 1100 831 1198V1556H1137V0H903L844 145H831Q727 -20 514 -20ZM621 223Q738 223 792 291T852 522V555Q852 735 797
813T616 891Q514 891 458 805T401 553Q401 388 458 306T621 223Z" />
<glyph unicode="e" glyph-name="e" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680 207Q781 207 871
228T1059 295V59Q979 19 888 0T666 -20Z" />
<glyph unicode="f" glyph-name="f" horiz-adv-x="793" d="M778 889H514V0H209V889H41V1036L209 1118V1200Q209 1391 303 1479T604 1567Q762 1567 885 1520L807 1296Q715 1325 637 1325Q572 1325 543 1287T514 1188V1118H778V889Z" />
<glyph unicode="g" glyph-name="g" horiz-adv-x="1157" d="M1133 1118V963L958 918Q1006 843 1006 750Q1006 570 881 470T532 369L477 372L432 377Q385 341 385 297Q385 231 553 231H743Q927 231 1023 152T1120 -80Q1120 -276 957 -384T487 -492Q253 -492 130
-411T6 -182Q6 -81 69 -13T254 84Q207 104 172 149T137 246Q137 310 174 352T281 436Q193 474 142 558T90 756Q90 939 209 1039T549 1139Q596 1139 660 1131T743 1118H1133ZM270 -158Q270 -221 330 -257T500 -293Q664 -293 757 -248T850 -125Q850 -62 795 -38T625
-14H467Q383 -14 327 -53T270 -158ZM381 752Q381 661 422 608T549 555Q635 555 675 608T715 752Q715 954 549 954Q381 954 381 752Z" />
<glyph unicode="h" glyph-name="h" horiz-adv-x="1346" d="M1192 0H887V653Q887 895 707 895Q579 895 522 808T465 526V0H160V1556H465V1239Q465 1202 458 1065L451 975H467Q569 1139 791 1139Q988 1139 1090 1033T1192 729V0Z" />
<glyph unicode="i" glyph-name="i" horiz-adv-x="625" d="M147 1407Q147 1556 313 1556Q479 1556 479 1407Q479 1336 438 1297T313 1257Q147 1257 147 1407ZM465 0H160V1118H465V0Z" />
<glyph unicode="j" glyph-name="j" horiz-adv-x="625" d="M70 -492Q-47 -492 -131 -467V-227Q-61 -246 12 -246Q89 -246 124 -203T160 -76V1118H465V-121Q465 -299 362 -395T70 -492ZM147 1407Q147 1556 313 1556Q479 1556 479 1407Q479 1336 438 1297T313 1257Q147
1257 147 1407Z" />
<glyph unicode="k" glyph-name="k" horiz-adv-x="1270" d="M453 608L586 778L899 1118H1243L799 633L1270 0H918L596 453L465 348V0H160V1556H465V862L449 608H453Z" />
<glyph unicode="l" glyph-name="l" horiz-adv-x="625" d="M465 0H160V1556H465V0Z" />
<glyph unicode="m" glyph-name="m" horiz-adv-x="2011" d="M1161 0H856V653Q856 774 816 834T688 895Q571 895 518 809T465 526V0H160V1118H393L434 975H451Q496 1052 581 1095T776 1139Q1027 1139 1116 975H1143Q1188 1053 1275 1096T1473 1139Q1663 1139 1760
1042T1858 729V0H1552V653Q1552 774 1512 834T1384 895Q1272 895 1217 815T1161 561V0Z" />
<glyph unicode="n" glyph-name="n" horiz-adv-x="1346" d="M1192 0H887V653Q887 774 844 834T707 895Q579 895 522 810T465 526V0H160V1118H393L434 975H451Q502 1056 591 1097T795 1139Q990 1139 1091 1034T1192 729V0Z" />
<glyph unicode="o" glyph-name="o" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835 235 987T637
1139Q798 1139 921 1069T1110 868T1176 561Z" />
<glyph unicode="p" glyph-name="p" horiz-adv-x="1296" d="M774 -20Q577 -20 465 123H449Q465 -17 465 -39V-492H160V1118H408L451 973H465Q572 1139 782 1139Q980 1139 1092 986T1204 561Q1204 382 1152 250T1002 49T774 -20ZM684 895Q571 895 519 826T465 596V563Q465
383 518 305T688 227Q893 227 893 565Q893 730 843 812T684 895Z" />
<glyph unicode="q" glyph-name="q" horiz-adv-x="1296" d="M623 219Q739 219 793 285T852 518V555Q852 735 797 813T618 891Q403 891 403 553Q403 385 456 302T623 219ZM514 -20Q316 -20 204 132T92 557Q92 831 206 985T520 1139Q626 1139 705 1099T844 975H852L879
1118H1137V-492H831V-23Q831 38 844 145H831Q782 64 701 22T514 -20Z" />
<glyph unicode="r" glyph-name="r" horiz-adv-x="930" d="M784 1139Q846 1139 887 1130L864 844Q827 854 774 854Q628 854 547 779T465 569V0H160V1118H391L436 930H451Q503 1024 591 1081T784 1139Z" />
<glyph unicode="s" glyph-name="s" horiz-adv-x="1018" d="M940 332Q940 160 821 70T463 -20Q341 -20 255 -4T94 45V297Q179 257 285 230T473 203Q639 203 639 299Q639 335 617 357T541 408T397 475Q268 529 208 575T120 680T92 827Q92 976 207 1057T535 1139Q737
1139 928 1051L836 831Q752 867 679 890T530 913Q395 913 395 840Q395 799 438 769T629 680Q760 627 821 581T911 475T940 332Z" />
<glyph unicode="t" glyph-name="t" horiz-adv-x="889" d="M631 223Q711 223 823 258V31Q709 -20 543 -20Q360 -20 277 72T193 350V889H47V1018L215 1120L303 1356H498V1118H811V889H498V350Q498 285 534 254T631 223Z" />
<glyph unicode="u" glyph-name="u" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952Z" />
<glyph unicode="v" glyph-name="v" horiz-adv-x="1165" d="M426 0L0 1118H319L535 481Q571 360 580 252H586Q591 348 631 481L846 1118H1165L739 0H426Z" />
<glyph unicode="w" glyph-name="w" horiz-adv-x="1753" d="M1079 0L993 391L877 885H870L666 0H338L20 1118H324L453 623Q484 490 516 256H522Q526 332 557 497L573 582L711 1118H1047L1178 582Q1182 560 1190 517T1207 426T1221 331T1229 256H1235Q1244 328 1267
453T1300 623L1434 1118H1733L1411 0H1079Z" />
<glyph unicode="x" glyph-name="x" horiz-adv-x="1184" d="M389 571L29 1118H375L592 762L811 1118H1157L793 571L1174 0H827L592 383L356 0H10L389 571Z" />
<glyph unicode="y" glyph-name="y" horiz-adv-x="1165" d="M0 1118H334L545 489Q572 407 582 295H588Q599 398 631 489L838 1118H1165L692 -143Q627 -318 507 -405T225 -492Q146 -492 70 -475V-233Q125 -246 190 -246Q271 -246 331 -197T426 -47L444 8L0 1118Z" />
<glyph unicode="z" glyph-name="z" horiz-adv-x="999" d="M938 0H55V180L573 885H86V1118H920V920L416 233H938V0Z" />
<glyph unicode="{" glyph-name="braceleft" horiz-adv-x="807" d="M287 270Q287 357 222 403T31 449V688Q157 688 222 732T287 858V866V1184Q287 1337 384 1399T725 1462V1237Q626 1234 589 1199T551 1096V797Q545 609 317 575V563Q551 528 551 351V342V43Q551
-25 588 -60T725 -98V-324Q481 -324 384 -262T287 -45V270Z" />
<glyph unicode="|" glyph-name="bar" horiz-adv-x="1128" d="M455 1550H674V-465H455V1550Z" />
<glyph unicode="}" glyph-name="braceright" horiz-adv-x="807" d="M520 -45Q520 -157 479 -214T344 -297T82 -324V-98Q181 -96 218 -62T256 43V342V353Q256 439 315 492T489 563V575Q262 609 256 797V1096Q256 1166 219 1200T82 1237V1462Q249 1462 344 1436T479
1352T520 1184V866V856Q520 772 581 730T776 688V449Q651 449 586 408T520 270V-45Z" />
<glyph unicode="~" glyph-name="asciitilde" horiz-adv-x="1169" d="M322 672Q267 672 205 639T88 551V782Q191 891 344 891Q417 891 481 875T621 827Q750 772 848 772Q901 772 964 804T1081 893V662Q980 553 825 553Q759 553 699 566T549 616Q418 672 322 672Z" />
<glyph unicode="&#xa0;" glyph-name="nbspace" horiz-adv-x="532" />
<glyph unicode="&#xa1;" glyph-name="exclamdown" horiz-adv-x="586" d="M168 606H412L463 -369H117L168 606ZM467 948Q467 864 422 821T291 778Q208 778 163 822T117 948Q117 1029 163 1073T291 1118Q375 1118 421 1074T467 948Z" />
<glyph unicode="&#xa2;" glyph-name="cent" horiz-adv-x="1169" d="M563 176Q143 235 143 741Q143 1002 247 1144T563 1317V1483H741V1325Q907 1316 1040 1251L950 1016Q878 1045 816 1063T692 1081Q571 1081 513 998T455 743Q455 416 692 416Q774 416 840 431T1006
492V238Q879 177 741 168V-20H563V176Z" />
<glyph unicode="&#xa3;" glyph-name="sterling" horiz-adv-x="1169" d="M700 1483Q895 1483 1090 1401L997 1171Q840 1235 725 1235Q647 1235 605 1191T563 1063V870H938V651H563V508Q563 338 412 260H1130V0H82V248Q185 292 223 349T262 506V651H84V870H262V1065Q262
1266 376 1374T700 1483Z" />
<glyph unicode="&#xa4;" glyph-name="currency" horiz-adv-x="1169" d="M188 723Q188 825 242 920L113 1047L260 1194L387 1067Q478 1120 584 1120Q689 1120 780 1065L907 1194L1057 1051L928 922Q981 833 981 723Q981 616 928 524L1053 399L907 254L780 379Q685
328 584 328Q469 328 385 379L260 256L115 401L242 526Q188 619 188 723ZM395 723Q395 646 449 591T584 535Q665 535 720 590T776 723Q776 803 720 858T584 913Q506 913 451 857T395 723Z" />
<glyph unicode="&#xa5;" glyph-name="yen" horiz-adv-x="1169" d="M584 860L848 1462H1161L778 715H973V537H727V399H973V221H727V0H440V221H193V399H440V537H193V715H383L6 1462H322L584 860Z" />
<glyph unicode="&#xa6;" glyph-name="brokenbar" horiz-adv-x="1128" d="M455 1550H674V735H455V1550ZM455 350H674V-465H455V350Z" />
<glyph unicode="&#xa7;" glyph-name="section" horiz-adv-x="995" d="M121 805Q121 884 157 949T254 1055Q121 1139 121 1288Q121 1419 232 1498T526 1577Q696 1577 889 1493L807 1303Q739 1335 669 1360T520 1386Q439 1386 402 1363T365 1292Q365 1243 414 1206T578
1124Q741 1060 818 976T895 782Q895 605 770 522Q832 482 863 430T895 303Q895 155 776 68T455 -20Q252 -20 106 59V266Q187 225 286 197T455 168Q649 168 649 285Q649 324 631 348T567 397T442 457Q259 531 190 609T121 805ZM344 827Q344 760 409 708T590 610Q668
667 668 756Q668 824 618 871T434 967Q397 953 371 914T344 827Z" />
<glyph unicode="&#xa8;" glyph-name="dieresis" horiz-adv-x="1243" d="M279 1405Q279 1470 316 1505T418 1540Q484 1540 521 1503T559 1405Q559 1345 521 1309T418 1272Q354 1272 317 1307T279 1405ZM682 1405Q682 1475 722 1507T823 1540Q888 1540 926 1504T965
1405Q965 1344 926 1308T823 1272Q763 1272 723 1304T682 1405Z" />
<glyph unicode="&#xa9;" glyph-name="copyright" horiz-adv-x="1704" d="M895 1010Q798 1010 745 936T692 731Q692 451 895 451Q952 451 1018 466T1141 510V319Q1021 262 889 262Q685 262 573 387T461 733Q461 953 571 1075T881 1198Q1030 1198 1186 1120L1112
952Q999 1010 895 1010ZM100 731Q100 931 200 1106T475 1382T852 1483Q1052 1483 1227 1383T1503 1108T1604 731Q1604 534 1507 361T1235 84T852 -20Q645 -20 470 83T198 360T100 731ZM242 731Q242 567 324 426T548 203T852 121Q1016 121 1157 203T1380 427T1462
731Q1462 895 1380 1036T1156 1259T852 1341Q688 1341 547 1259T324 1035T242 731Z" />
<glyph unicode="&#xaa;" glyph-name="ordfeminine" horiz-adv-x="784" d="M561 764L530 874Q487 816 425 784T289 752Q172 752 110 810T47 975Q47 1084 129 1138T397 1202L496 1206Q496 1323 369 1323Q288 1323 152 1262L86 1397Q152 1429 231 1454T410 1479Q547
1479 621 1408T696 1206V764H561ZM252 977Q252 939 275 921T330 903Q407 903 451 944T496 1051V1087L397 1081Q252 1071 252 977Z" />
<glyph unicode="&#xab;" glyph-name="guillemotleft" horiz-adv-x="1260" d="M82 573L453 1028L672 909L393 561L672 213L453 94L82 547V573ZM588 573L958 1028L1178 909L899 561L1178 213L958 94L588 547V573Z" />
<glyph unicode="&#xac;" glyph-name="logicalnot" horiz-adv-x="1169" d="M1081 248H862V612H88V831H1081V248Z" />
<glyph unicode="&#xad;" glyph-name="uni00AD" horiz-adv-x="659" d="M61 424V674H598V424H61Z" />
<glyph unicode="&#xae;" glyph-name="registered" horiz-adv-x="1704" d="M1157 905Q1157 735 1014 672L1251 272H997L819 610H772V272H543V1188H807Q988 1188 1072 1118T1157 905ZM772 778H803Q869 778 897 806T926 901Q926 966 898 993T801 1020H772V778ZM100
731Q100 931 200 1106T475 1382T852 1483Q1052 1483 1227 1383T1503 1108T1604 731Q1604 534 1507 361T1235 84T852 -20Q645 -20 470 83T198 360T100 731ZM242 731Q242 567 324 426T548 203T852 121Q1016 121 1157 203T1380 427T1462 731Q1462 895 1380 1036T1156
1259T852 1341Q688 1341 547 1259T324 1035T242 731Z" />
<glyph unicode="&#xaf;" glyph-name="overscore" horiz-adv-x="1024" d="M1030 1556H-6V1757H1030V1556Z" />
<glyph unicode="&#xb0;" glyph-name="degree" horiz-adv-x="877" d="M92 1137Q92 1229 138 1309T264 1436T438 1483Q530 1483 610 1437T737 1310T784 1137Q784 1044 738 964T611 838T438 793Q293 793 193 892T92 1137ZM283 1137Q283 1073 327 1028T438 983Q504
983 549 1029T594 1137Q594 1200 549 1247T438 1294Q374 1294 329 1248T283 1137Z" />
<glyph unicode="&#xb1;" glyph-name="plusminus" horiz-adv-x="1169" d="M475 674H88V893H475V1282H694V893H1081V674H694V289H475V674ZM88 0V219H1081V0H88Z" />
<glyph unicode="&#xb2;" glyph-name="twosuperior" horiz-adv-x="776" d="M702 586H55V754L279 973Q381 1073 409 1117T438 1212Q438 1250 414 1270T350 1290Q269 1290 170 1202L47 1354Q194 1483 383 1483Q520 1483 599 1417T678 1233Q678 1148 631 1073T455
881L350 786H702V586Z" />
<glyph unicode="&#xb3;" glyph-name="threesuperior" horiz-adv-x="776" d="M666 1249Q666 1106 496 1051V1038Q590 1018 642 963T694 829Q694 708 606 639T332 569Q189 569 59 639V829Q207 739 330 739Q473 739 473 846Q473 899 429 925T307 952H195V1112H287Q370
1112 410 1138T451 1221Q451 1259 426 1284T350 1309Q303 1309 261 1290T162 1231L61 1372Q123 1419 198 1450T377 1481Q504 1481 585 1417T666 1249Z" />
<glyph unicode="&#xb4;" glyph-name="acute" horiz-adv-x="1243" d="M332 1241V1268Q504 1468 567 1569H909V1548Q857 1496 732 1394T535 1241H332Z" />
<glyph unicode="&#xb5;" glyph-name="mu" horiz-adv-x="1352" d="M465 465Q465 344 509 284T647 223Q773 223 830 309T887 592V1118H1192V0H961L918 150H903Q861 65 801 23T653 -20Q591 -20 539 3T455 70L460 -15L465 -172V-492H160V1118H465V465Z" />
<glyph unicode="&#xb6;" glyph-name="paragraph" horiz-adv-x="1341" d="M1167 -260H1006V1356H840V-260H678V559Q616 541 532 541Q316 541 215 666T113 1042Q113 1302 222 1429T563 1556H1167V-260Z" />
<glyph unicode="&#xb7;" glyph-name="middot" horiz-adv-x="584" d="M117 723Q117 807 162 850T293 893Q376 893 421 849T467 723Q467 642 421 598T293 553Q209 553 163 597T117 723Z" />
<glyph unicode="&#xb8;" glyph-name="cedilla" horiz-adv-x="420" d="M418 -250Q418 -378 343 -435T109 -492Q31 -492 -37 -471V-303Q-10 -310 35 -317T106 -324Q178 -324 178 -262Q178 -179 12 -154L90 0H283L256 -61Q330 -85 374 -135T418 -250Z" />
<glyph unicode="&#xb9;" glyph-name="onesuperior" horiz-adv-x="776" d="M584 586H346V1032L349 1144L354 1239Q327 1203 279 1161L201 1100L92 1227L393 1462H584V586Z" />
<glyph unicode="&#xba;" glyph-name="ordmasculine" horiz-adv-x="795" d="M737 1116Q737 945 646 849T395 752Q242 752 150 850T57 1116Q57 1285 146 1382T399 1479Q551 1479 644 1381T737 1116ZM260 1116Q260 1016 292 966T397 915Q469 915 500 965T532 1116Q532
1216 501 1265T397 1315Q325 1315 293 1266T260 1116Z" />
<glyph unicode="&#xbb;" glyph-name="guillemotright" horiz-adv-x="1260" d="M1178 547L807 94L588 213L866 561L588 909L807 1028L1178 573V547ZM672 547L301 94L82 213L360 561L82 909L301 1028L672 573V547Z" />
<glyph unicode="&#xbc;" glyph-name="onequarter" horiz-adv-x="1804" d="M794 586H556V1032L559 1144L564 1239Q537 1203 489 1161L411 1100L302 1227L603 1462H794V586ZM1370 1462L559 0H320L1131 1462H1370ZM1682 152H1557V1H1319V152H936V306L1321 883H1557V320H1682V152ZM1319
320V484Q1319 570 1325 668Q1316 642 1290 588T1248 511L1121 320H1319Z" />
<glyph unicode="&#xbd;" glyph-name="onehalf" horiz-adv-x="1804" d="M794 586H556V1032L559 1144L564 1239Q537 1203 489 1161L411 1100L302 1227L603 1462H794V586ZM1370 1462L559 0H320L1131 1462H1370ZM1716 1H1069V169L1293 388Q1395 488 1423 532T1452
627Q1452 665 1428 685T1364 705Q1283 705 1184 617L1061 769Q1208 898 1397 898Q1534 898 1613 832T1692 648Q1692 563 1645 488T1469 296L1364 201H1716V1Z" />
<glyph unicode="&#xbe;" glyph-name="threequarters" horiz-adv-x="1804" d="M697 1249Q697 1106 527 1051V1038Q621 1018 673 963T725 829Q725 708 637 639T363 569Q220 569 90 639V829Q238 739 361 739Q504 739 504 846Q504 899 460 925T338 952H226V1112H318Q401
1112 441 1138T482 1221Q482 1259 457 1284T381 1309Q334 1309 292 1290T193 1231L92 1372Q154 1419 229 1450T408 1481Q535 1481 616 1417T697 1249ZM1441 1462L630 0H391L1202 1462H1441ZM1712 152H1587V1H1349V152H966V306L1351 883H1587V320H1712V152ZM1349
320V484Q1349 570 1355 668Q1346 642 1320 588T1278 511L1151 320H1349Z" />
<glyph unicode="&#xbf;" glyph-name="questiondown" horiz-adv-x="977" d="M713 606V532Q713 434 669 363T516 215Q407 137 379 93T350 -14Q350 -71 393 -108T526 -145Q605 -145 695 -116T881 -45L983 -266Q885 -322 762 -356T532 -391Q312 -391 187 -295T61 -29Q61
79 109 158T301 342Q396 412 422 449T449 547V606H713ZM745 948Q745 864 700 821T569 778Q486 778 441 822T395 948Q395 1029 441 1073T569 1118Q653 1118 699 1074T745 948Z" />
<glyph unicode="&#xc0;" glyph-name="Agrave" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM713 1579Q650 1623 528 1721T338 1886V1907H680Q743 1806 915 1606V1579H713Z" />
<glyph unicode="&#xc1;" glyph-name="Aacute" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM541 1579V1606Q713 1806 776 1907H1118V1886Q1066 1834 941 1732T744 1579H541Z" />
<glyph unicode="&#xc2;" glyph-name="Acircumflex" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM938 1579Q781 1672 704 1755Q626 1674 475 1579H272V1606Q461 1795
528 1907H885Q916 1855 992 1766T1141 1606V1579H938Z" />
<glyph unicode="&#xc3;" glyph-name="Atilde" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM543 1684Q512 1684 484 1658T442 1577H293Q304 1722 375 1804T565 1886Q606
1886 645 1870T723 1834T799 1798T872 1782Q903 1782 931 1808T973 1888H1122Q1111 1743 1039 1661T850 1579Q809 1579 770 1595T692 1631T616 1667T543 1684Z" />
<glyph unicode="&#xc4;" glyph-name="Adieresis" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM365 1743Q365 1808 402 1843T504 1878Q570 1878 607 1841T645 1743Q645
1683 607 1647T504 1610Q440 1610 403 1645T365 1743ZM768 1743Q768 1813 808 1845T909 1878Q974 1878 1012 1842T1051 1743Q1051 1682 1012 1646T909 1610Q849 1610 809 1642T768 1743Z" />
<glyph unicode="&#xc5;" glyph-name="Aring" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM959 1567Q959 1459 888 1393T705 1327Q593 1327 525 1391T457 1565Q457
1673 524 1737T705 1802Q815 1802 887 1736T959 1567ZM801 1565Q801 1610 774 1635T705 1661Q663 1661 636 1636T609 1565Q609 1520 633 1494T705 1468Q747 1468 774 1494T801 1565Z" />
<glyph unicode="&#xc6;" glyph-name="AE" horiz-adv-x="1950" d="M1829 0H956V348H465L315 0H0L655 1462H1829V1208H1266V887H1792V633H1266V256H1829V0ZM578 608H956V1198H829L578 608Z" />
<glyph unicode="&#xc7;" glyph-name="Ccedilla" horiz-adv-x="1305" d="M805 1225Q630 1225 534 1094T438 727Q438 238 805 238Q959 238 1178 315V55Q998 -20 776 -20Q457 -20 288 173T119 729Q119 957 202 1128T440 1391T805 1483Q1018 1483 1233 1380L1133 1128Q1051
1167 968 1196T805 1225ZM959 -250Q959 -378 884 -435T650 -492Q572 -492 504 -471V-303Q531 -310 576 -317T647 -324Q719 -324 719 -262Q719 -179 553 -154L631 0H824L797 -61Q871 -85 915 -135T959 -250Z" />
<glyph unicode="&#xc8;" glyph-name="Egrave" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0ZM634 1579Q571 1623 449 1721T259 1886V1907H601Q664 1806 836 1606V1579H634Z" />
<glyph unicode="&#xc9;" glyph-name="Eacute" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0ZM424 1579V1606Q596 1806 659 1907H1001V1886Q949 1834 824 1732T627 1579H424Z" />
<glyph unicode="&#xca;" glyph-name="Ecircumflex" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0ZM841 1579Q684 1672 607 1755Q529 1674 378 1579H175V1606Q364 1795 431 1907H788Q819 1855 895 1766T1044 1606V1579H841Z" />
<glyph unicode="&#xcb;" glyph-name="Edieresis" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0ZM272 1743Q272 1808 309 1843T411 1878Q477 1878 514 1841T552 1743Q552 1683 514 1647T411 1610Q347 1610 310 1645T272 1743ZM675
1743Q675 1813 715 1845T816 1878Q881 1878 919 1842T958 1743Q958 1682 919 1646T816 1610Q756 1610 716 1642T675 1743Z" />
<glyph unicode="&#xcc;" glyph-name="Igrave" horiz-adv-x="678" d="M184 0V1462H494V0H184ZM317 1579Q254 1623 132 1721T-58 1886V1907H284Q347 1806 519 1606V1579H317Z" />
<glyph unicode="&#xcd;" glyph-name="Iacute" horiz-adv-x="678" d="M184 0V1462H494V0H184ZM167 1579V1606Q339 1806 402 1907H744V1886Q692 1834 567 1732T370 1579H167Z" />
<glyph unicode="&#xce;" glyph-name="Icircumflex" horiz-adv-x="678" d="M184 0V1462H494V0H184ZM570 1579Q413 1672 336 1755Q258 1674 107 1579H-96V1606Q93 1795 160 1907H517Q548 1855 624 1766T773 1606V1579H570Z" />
<glyph unicode="&#xcf;" glyph-name="Idieresis" horiz-adv-x="678" d="M184 0V1462H494V0H184ZM-3 1743Q-3 1808 34 1843T136 1878Q202 1878 239 1841T277 1743Q277 1683 239 1647T136 1610Q72 1610 35 1645T-3 1743ZM400 1743Q400 1813 440 1845T541 1878Q606
1878 644 1842T683 1743Q683 1682 644 1646T541 1610Q481 1610 441 1642T400 1743Z" />
<glyph unicode="&#xd0;" glyph-name="Eth" horiz-adv-x="1516" d="M47 850H184V1462H643Q1001 1462 1199 1273T1397 745Q1397 384 1192 192T598 0H184V596H47V850ZM1075 737Q1075 969 971 1088T657 1208H494V850H731V596H494V256H625Q1075 256 1075 737Z" />
<glyph unicode="&#xd1;" glyph-name="Ntilde" horiz-adv-x="1665" d="M1481 0H1087L451 1106H442Q461 813 461 688V0H184V1462H575L1210 367H1217Q1202 652 1202 770V1462H1481V0ZM668 1684Q637 1684 609 1658T567 1577H418Q429 1722 500 1804T690 1886Q731 1886
770 1870T848 1834T924 1798T997 1782Q1028 1782 1056 1808T1098 1888H1247Q1236 1743 1164 1661T975 1579Q934 1579 895 1595T817 1631T741 1667T668 1684Z" />
<glyph unicode="&#xd2;" glyph-name="Ograve" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227
817 1227Q632 1227 538 1103T444 733ZM824 1579Q761 1623 639 1721T449 1886V1907H791Q854 1806 1026 1606V1579H824Z" />
<glyph unicode="&#xd3;" glyph-name="Oacute" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227
817 1227Q632 1227 538 1103T444 733ZM658 1579V1606Q830 1806 893 1907H1235V1886Q1183 1834 1058 1732T861 1579H658Z" />
<glyph unicode="&#xd4;" glyph-name="Ocircumflex" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186
1227 817 1227Q632 1227 538 1103T444 733ZM1047 1579Q890 1672 813 1755Q735 1674 584 1579H381V1606Q570 1795 637 1907H994Q1025 1855 1101 1766T1250 1606V1579H1047Z" />
<glyph unicode="&#xd5;" glyph-name="Otilde" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227
817 1227Q632 1227 538 1103T444 733ZM652 1684Q621 1684 593 1658T551 1577H402Q413 1722 484 1804T674 1886Q715 1886 754 1870T832 1834T908 1798T981 1782Q1012 1782 1040 1808T1082 1888H1231Q1220 1743 1148 1661T959 1579Q918 1579 879 1595T801 1631T725
1667T652 1684Z" />
<glyph unicode="&#xd6;" glyph-name="Odieresis" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186
1227 817 1227Q632 1227 538 1103T444 733ZM474 1743Q474 1808 511 1843T613 1878Q679 1878 716 1841T754 1743Q754 1683 716 1647T613 1610Q549 1610 512 1645T474 1743ZM877 1743Q877 1813 917 1845T1018 1878Q1083 1878 1121 1842T1160 1743Q1160 1682 1121
1646T1018 1610Q958 1610 918 1642T877 1743Z" />
<glyph unicode="&#xd7;" glyph-name="multiply" horiz-adv-x="1169" d="M428 723L129 1024L281 1178L582 879L887 1178L1040 1028L735 723L1036 420L887 268L582 569L281 270L131 422L428 723Z" />
<glyph unicode="&#xd8;" glyph-name="Oslash" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q618 -20 479 45L389 -90L227 18L317 154Q119 348 119 735Q119 1100 299 1292T817 1485Q1015 1485 1161 1415L1245 1540L1405 1436L1317 1305Q1511 1111
1511 733ZM444 733Q444 542 500 426L1006 1182Q922 1227 817 1227Q632 1227 538 1103T444 733ZM1186 733Q1186 913 1135 1030L635 279Q711 240 815 240Q1186 240 1186 733Z" />
<glyph unicode="&#xd9;" glyph-name="Ugrave" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374ZM750 1579Q687 1623 565 1721T375
1886V1907H717Q780 1806 952 1606V1579H750Z" />
<glyph unicode="&#xda;" glyph-name="Uacute" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374ZM602 1579V1606Q774 1806 837 1907H1179V1886Q1127
1834 1002 1732T805 1579H602Z" />
<glyph unicode="&#xdb;" glyph-name="Ucircumflex" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374ZM1006 1579Q849 1672 772 1755Q694
1674 543 1579H340V1606Q529 1795 596 1907H953Q984 1855 1060 1766T1209 1606V1579H1006Z" />
<glyph unicode="&#xdc;" glyph-name="Udieresis" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374ZM433 1743Q433 1808 470 1843T572
1878Q638 1878 675 1841T713 1743Q713 1683 675 1647T572 1610Q508 1610 471 1645T433 1743ZM836 1743Q836 1813 876 1845T977 1878Q1042 1878 1080 1842T1119 1743Q1119 1682 1080 1646T977 1610Q917 1610 877 1642T836 1743Z" />
<glyph unicode="&#xdd;" glyph-name="Yacute" horiz-adv-x="1278" d="M639 860L944 1462H1278L793 569V0H485V559L0 1462H336L639 860ZM461 1579V1606Q633 1806 696 1907H1038V1886Q986 1834 861 1732T664 1579H461Z" />
<glyph unicode="&#xde;" glyph-name="Thorn" horiz-adv-x="1286" d="M1194 770Q1194 541 1052 417T647 293H494V0H184V1462H494V1233H672Q926 1233 1060 1114T1194 770ZM494 543H594Q739 543 810 595T881 770Q881 877 818 929T618 981H494V543Z" />
<glyph unicode="&#xdf;" glyph-name="germandbls" horiz-adv-x="1456" d="M1249 1241Q1249 1177 1228 1129T1175 1042T1106 975T1037 922T984 877T963 834Q963 807 989 781T1082 715Q1228 624 1280 575T1358 465T1384 326Q1384 154 1268 67T924 -20Q825 -20 753
-6T621 43V285Q674 249 756 224T903 199Q1071 199 1071 322Q1071 363 1055 388T998 444T883 516Q757 588 708 647T659 788Q659 852 694 905T799 1007Q876 1062 907 1102T938 1188Q938 1248 875 1288T711 1329Q595 1329 530 1277T465 1128V0H160V1139Q160 1340 306
1453T711 1567Q955 1567 1102 1479T1249 1241Z" />
<glyph unicode="&#xe0;" glyph-name="agrave" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083
745V0H870ZM780 518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM870 1241Q807 1285 685 1383T495 1548V1569H837Q900 1468 1072 1268V1241H870Z" />
<glyph unicode="&#xe1;" glyph-name="aacute" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083
745V0H870ZM780 518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM441 1241V1268Q613 1468 676 1569H1018V1548Q966 1496 841 1394T644 1241H441Z" />
<glyph unicode="&#xe2;" glyph-name="acircumflex" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963
1043T1083 745V0H870ZM780 518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM1099 1496Q942 1589 865 1672Q787 1591 636 1496H433V1523Q622 1712 689 1824H1046Q1077 1772 1153 1683T1302 1523V1496H1099Z" />
<glyph unicode="&#xe3;" glyph-name="atilde" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083
745V0H870ZM780 518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM467 1346Q436 1346 408 1320T366 1239H217Q228 1384 299 1466T489 1548Q530 1548 569 1532T647 1496T723 1460T796 1444Q827 1444 855 1470T897 1550H1046Q1035
1405 963 1323T774 1241Q733 1241 694 1257T616 1293T540 1329T467 1346Z" />
<glyph unicode="&#xe4;" glyph-name="adieresis" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083
745V0H870ZM780 518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM285 1405Q285 1470 322 1505T424 1540Q490 1540 527 1503T565 1405Q565 1345 527 1309T424 1272Q360 1272 323 1307T285 1405ZM688 1405Q688 1475 728 1507T829
1540Q894 1540 932 1504T971 1405Q971 1344 932 1308T829 1272Q769 1272 729 1304T688 1405Z" />
<glyph unicode="&#xe5;" glyph-name="aring" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083
745V0H870ZM780 518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM883 1479Q883 1371 812 1305T629 1239Q517 1239 449 1303T381 1477Q381 1585 448 1649T629 1714Q739 1714 811 1648T883 1479ZM725 1477Q725 1522 698 1547T629
1573Q587 1573 560 1548T533 1477Q533 1432 557 1406T629 1380Q671 1380 698 1406T725 1477Z" />
<glyph unicode="&#xe6;" glyph-name="ae" horiz-adv-x="1878" d="M1329 -20Q1192 -20 1080 30T895 186Q797 69 699 25T442 -20Q281 -20 184 74T86 334Q86 512 207 596T569 690L760 696V780Q760 849 716 882T594 915Q454 915 289 838L190 1040Q379 1141 612 1141Q839
1141 954 1010Q1020 1074 1106 1106T1313 1139Q1534 1139 1662 1002T1790 631V483H1067Q1072 353 1144 280T1346 207Q1542 207 1726 295V59Q1647 20 1555 0T1329 -20ZM760 518L647 514Q523 510 461 467T399 332Q399 203 539 203Q640 203 700 264T760 426V518ZM1307
922Q1090 922 1073 686H1503Q1501 798 1448 860T1307 922Z" />
<glyph unicode="&#xe7;" glyph-name="ccedilla" horiz-adv-x="1053" d="M614 -20Q92 -20 92 553Q92 838 234 988T641 1139Q835 1139 989 1063L899 827Q827 856 765 874T641 893Q403 893 403 555Q403 227 641 227Q729 227 804 250T954 324V63Q880 16 805 -2T614
-20ZM805 -250Q805 -378 730 -435T496 -492Q418 -492 350 -471V-303Q377 -310 422 -317T493 -324Q565 -324 565 -262Q565 -179 399 -154L477 0H670L643 -61Q717 -85 761 -135T805 -250Z" />
<glyph unicode="&#xe8;" glyph-name="egrave" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680 207Q781
207 871 228T1059 295V59Q979 19 888 0T666 -20ZM876 1241Q813 1285 691 1383T501 1548V1569H843Q906 1468 1078 1268V1241H876Z" />
<glyph unicode="&#xe9;" glyph-name="eacute" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680 207Q781
207 871 228T1059 295V59Q979 19 888 0T666 -20ZM447 1241V1268Q619 1468 682 1569H1024V1548Q972 1496 847 1394T650 1241H447Z" />
<glyph unicode="&#xea;" glyph-name="ecircumflex" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680
207Q781 207 871 228T1059 295V59Q979 19 888 0T666 -20ZM860 1241Q703 1334 626 1417Q548 1336 397 1241H194V1268Q383 1457 450 1569H807Q838 1517 914 1428T1063 1268V1241H860Z" />
<glyph unicode="&#xeb;" glyph-name="edieresis" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680
207Q781 207 871 228T1059 295V59Q979 19 888 0T666 -20ZM297 1405Q297 1470 334 1505T436 1540Q502 1540 539 1503T577 1405Q577 1345 539 1309T436 1272Q372 1272 335 1307T297 1405ZM700 1405Q700 1475 740 1507T841 1540Q906 1540 944 1504T983 1405Q983 1344
944 1308T841 1272Q781 1272 741 1304T700 1405Z" />
<glyph unicode="&#xec;" glyph-name="igrave" horiz-adv-x="625" d="M465 0H160V1118H465V0ZM274 1241Q211 1285 89 1383T-101 1548V1569H241Q304 1468 476 1268V1241H274Z" />
<glyph unicode="&#xed;" glyph-name="iacute" horiz-adv-x="625" d="M465 0H160V1118H465V0ZM145 1241V1268Q317 1468 380 1569H722V1548Q670 1496 545 1394T348 1241H145Z" />
<glyph unicode="&#xee;" glyph-name="icircumflex" horiz-adv-x="625" d="M465 0H160V1118H465V0ZM544 1241Q387 1334 310 1417Q232 1336 81 1241H-122V1268Q67 1457 134 1569H491Q522 1517 598 1428T747 1268V1241H544Z" />
<glyph unicode="&#xef;" glyph-name="idieresis" horiz-adv-x="625" d="M465 0H160V1118H465V0ZM-29 1405Q-29 1470 8 1505T110 1540Q176 1540 213 1503T251 1405Q251 1345 213 1309T110 1272Q46 1272 9 1307T-29 1405ZM374 1405Q374 1475 414 1507T515 1540Q580
1540 618 1504T657 1405Q657 1344 618 1308T515 1272Q455 1272 415 1304T374 1405Z" />
<glyph unicode="&#xf0;" glyph-name="eth" horiz-adv-x="1268" d="M510 1303Q430 1356 358 1395L459 1571Q603 1506 717 1430L942 1569L1042 1415L872 1311Q1028 1168 1102 987T1176 573Q1176 293 1031 137T631 -20Q386 -20 239 117T92 489Q92 722 222 858T573
995Q778 995 848 897L856 901Q789 1063 664 1182L434 1040L334 1196L510 1303ZM864 532Q864 640 803 705T635 770Q514 770 459 702T403 487Q403 347 463 276T635 205Q758 205 811 287T864 532Z" />
<glyph unicode="&#xf1;" glyph-name="ntilde" horiz-adv-x="1346" d="M1192 0H887V653Q887 774 844 834T707 895Q579 895 522 810T465 526V0H160V1118H393L434 975H451Q502 1056 591 1097T795 1139Q990 1139 1091 1034T1192 729V0ZM508 1346Q477 1346 449 1320T407
1239H258Q269 1384 340 1466T530 1548Q571 1548 610 1532T688 1496T764 1460T837 1444Q868 1444 896 1470T938 1550H1087Q1076 1405 1004 1323T815 1241Q774 1241 735 1257T657 1293T581 1329T508 1346Z" />
<glyph unicode="&#xf2;" glyph-name="ograve" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835
235 987T637 1139Q798 1139 921 1069T1110 868T1176 561ZM868 1241Q805 1285 683 1383T493 1548V1569H835Q898 1468 1070 1268V1241H868Z" />
<glyph unicode="&#xf3;" glyph-name="oacute" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835
235 987T637 1139Q798 1139 921 1069T1110 868T1176 561ZM467 1241V1268Q639 1468 702 1569H1044V1548Q992 1496 867 1394T670 1241H467Z" />
<glyph unicode="&#xf4;" glyph-name="ocircumflex" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92
835 235 987T637 1139Q798 1139 921 1069T1110 868T1176 561ZM864 1241Q707 1334 630 1417Q552 1336 401 1241H198V1268Q387 1457 454 1569H811Q842 1517 918 1428T1067 1268V1241H864Z" />
<glyph unicode="&#xf5;" glyph-name="otilde" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835
235 987T637 1139Q798 1139 921 1069T1110 868T1176 561ZM469 1346Q438 1346 410 1320T368 1239H219Q230 1384 301 1466T491 1548Q532 1548 571 1532T649 1496T725 1460T798 1444Q829 1444 857 1470T899 1550H1048Q1037 1405 965 1323T776 1241Q735 1241 696 1257T618
1293T542 1329T469 1346Z" />
<glyph unicode="&#xf6;" glyph-name="odieresis" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92
835 235 987T637 1139Q798 1139 921 1069T1110 868T1176 561ZM291 1405Q291 1470 328 1505T430 1540Q496 1540 533 1503T571 1405Q571 1345 533 1309T430 1272Q366 1272 329 1307T291 1405ZM694 1405Q694 1475 734 1507T835 1540Q900 1540 938 1504T977 1405Q977
1344 938 1308T835 1272Q775 1272 735 1304T694 1405Z" />
<glyph unicode="&#xf7;" glyph-name="divide" horiz-adv-x="1169" d="M88 612V831H1081V612H88ZM444 373Q444 449 481 486T584 524Q650 524 686 485T723 373Q723 303 686 262T584 221Q519 221 482 260T444 373ZM444 1071Q444 1146 481 1184T584 1223Q651 1223
687 1183T723 1071Q723 1001 686 961T584 920Q519 920 482 959T444 1071Z" />
<glyph unicode="&#xf8;" glyph-name="oslash" horiz-adv-x="1268" d="M1176 561Q1176 288 1032 134T631 -20Q505 -20 397 25L330 -76L176 29L244 129Q92 285 92 561Q92 835 235 987T637 1139Q769 1139 885 1087L940 1169L1092 1061L1034 977Q1176 822 1176 561ZM403
561Q403 467 422 395L739 870Q696 893 633 893Q511 893 457 811T403 561ZM864 561Q864 642 852 702L543 240Q581 225 635 225Q757 225 810 309T864 561Z" />
<glyph unicode="&#xf9;" glyph-name="ugrave" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952ZM876 1241Q813 1285 691 1383T501 1548V1569H843Q906
1468 1078 1268V1241H876Z" />
<glyph unicode="&#xfa;" glyph-name="uacute" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952ZM498 1241V1268Q670 1468 733 1569H1075V1548Q1023
1496 898 1394T701 1241H498Z" />
<glyph unicode="&#xfb;" glyph-name="ucircumflex" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952ZM901 1241Q744 1334 667 1417Q589
1336 438 1241H235V1268Q424 1457 491 1569H848Q879 1517 955 1428T1104 1268V1241H901Z" />
<glyph unicode="&#xfc;" glyph-name="udieresis" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952ZM326 1405Q326 1470 363 1505T465
1540Q531 1540 568 1503T606 1405Q606 1345 568 1309T465 1272Q401 1272 364 1307T326 1405ZM729 1405Q729 1475 769 1507T870 1540Q935 1540 973 1504T1012 1405Q1012 1344 973 1308T870 1272Q810 1272 770 1304T729 1405Z" />
<glyph unicode="&#xfd;" glyph-name="yacute" horiz-adv-x="1165" d="M0 1118H334L545 489Q572 407 582 295H588Q599 398 631 489L838 1118H1165L692 -143Q627 -318 507 -405T225 -492Q146 -492 70 -475V-233Q125 -246 190 -246Q271 -246 331 -197T426 -47L444
8L0 1118ZM393 1241V1268Q565 1468 628 1569H970V1548Q918 1496 793 1394T596 1241H393Z" />
<glyph unicode="&#xfe;" glyph-name="thorn" horiz-adv-x="1296" d="M465 973Q515 1054 596 1096T782 1139Q980 1139 1092 985T1204 561Q1204 288 1093 134T782 -20Q569 -20 465 117H451L458 55L465 -39V-492H160V1556H465V1165L458 1045L451 973H465ZM684 895Q571
895 519 826T465 596V563Q465 383 518 305T688 227Q893 227 893 565Q893 730 843 812T684 895Z" />
<glyph unicode="&#xff;" glyph-name="ydieresis" horiz-adv-x="1165" d="M0 1118H334L545 489Q572 407 582 295H588Q599 398 631 489L838 1118H1165L692 -143Q627 -318 507 -405T225 -492Q146 -492 70 -475V-233Q125 -246 190 -246Q271 -246 331 -197T426 -47L444
8L0 1118ZM499 1405Q499 1470 536 1505T638 1540Q704 1540 741 1503T779 1405Q779 1345 741 1309T638 1272Q574 1272 537 1307T499 1405ZM902 1405Q902 1475 942 1507T1043 1540Q1108 1540 1146 1504T1185 1405Q1185 1344 1146 1308T1043 1272Q983 1272 943 1304T902
1405Z" />
<glyph unicode="&#x2013;" glyph-name="endash" horiz-adv-x="1024" d="M82 436V666H942V436H82Z" />
<glyph unicode="&#x2014;" glyph-name="emdash" horiz-adv-x="2048" d="M82 436V666H1966V436H82Z" />
<glyph unicode="&#x2018;" glyph-name="quoteleft" horiz-adv-x="444" d="M39 961L25 983Q47 1074 97 1211T201 1462H420Q354 1195 319 961H39Z" />
<glyph unicode="&#x2019;" glyph-name="quoteright" horiz-adv-x="444" d="M406 1462L420 1440Q370 1243 244 961H25Q94 1257 125 1462H406Z" />
<glyph unicode="&#x201a;" glyph-name="quotesinglbase" horiz-adv-x="596" d="M459 215Q407 13 283 -264H63Q128 2 164 238H444L459 215Z" />
<glyph unicode="&#x201c;" glyph-name="quotedblleft" horiz-adv-x="911" d="M492 983Q514 1074 564 1211T668 1462H887Q821 1195 786 961H506L492 983ZM25 983Q47 1074 97 1211T201 1462H420Q354 1195 319 961H39L25 983Z" />
<glyph unicode="&#x201d;" glyph-name="quotedblright" horiz-adv-x="911" d="M420 1440Q370 1243 244 961H25Q94 1257 125 1462H406L420 1440ZM887 1440Q837 1243 711 961H492Q561 1257 592 1462H872L887 1440Z" />
<glyph unicode="&#x201e;" glyph-name="quotedblbase" horiz-adv-x="1061" d="M459 215Q407 13 283 -264H63Q128 2 164 238H444L459 215ZM926 215Q874 13 750 -264H530Q595 2 631 238H911L926 215Z" />
<glyph unicode="&#x2022;" glyph-name="bullet" horiz-adv-x="770" d="M98 748Q98 902 172 983T385 1065Q522 1065 597 983T672 748Q672 596 597 513T385 430Q247 430 173 513T98 748Z" />
<glyph unicode="&#x2039;" glyph-name="guilsinglleft" horiz-adv-x="754" d="M82 573L453 1028L672 909L393 561L672 213L453 94L82 547V573Z" />
<glyph unicode="&#x203a;" glyph-name="guilsinglright" horiz-adv-x="754" d="M672 547L301 94L82 213L360 561L82 909L301 1028L672 573V547Z" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More