Compare commits
No commits in common. "main" and "main" have entirely different histories.
28
.drone.yml
28
.drone.yml
|
@ -139,7 +139,7 @@ steps:
|
|||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: api-lint
|
||||
image: golangci/golangci-lint:v1.57.2
|
||||
image: golangci/golangci-lint:v1.56.2
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
|
@ -364,7 +364,7 @@ steps:
|
|||
- api-build
|
||||
|
||||
- name: frontend-dependencies
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.11.1-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -378,7 +378,7 @@ steps:
|
|||
# - restore-cache
|
||||
|
||||
- name: frontend-lint
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.11.1-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -390,7 +390,7 @@ steps:
|
|||
- frontend-dependencies
|
||||
|
||||
- name: frontend-build-prod
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.11.1-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -402,7 +402,7 @@ steps:
|
|||
- frontend-dependencies
|
||||
|
||||
- name: frontend-test-unit
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.11.1-alpine
|
||||
pull: always
|
||||
commands:
|
||||
- cd frontend
|
||||
|
@ -413,7 +413,7 @@ steps:
|
|||
|
||||
- name: frontend-typecheck
|
||||
failure: ignore
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.11.1-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -544,7 +544,7 @@ steps:
|
|||
- git fetch --tags
|
||||
|
||||
- name: frontend-dependencies
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.11.1-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -556,7 +556,7 @@ steps:
|
|||
- pnpm install --fetch-timeout 100000
|
||||
|
||||
- name: frontend-build
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.11.1-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -716,7 +716,7 @@ steps:
|
|||
|
||||
# Build os packages and push it to our bucket
|
||||
- name: build-os-packages-unstable
|
||||
image: goreleaser/nfpm:v2.36.1
|
||||
image: goreleaser/nfpm:v2.35.3
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
|
@ -732,7 +732,7 @@ steps:
|
|||
depends_on: [ after-build-compress ]
|
||||
|
||||
- name: build-os-packages-version
|
||||
image: goreleaser/nfpm:v2.36.1
|
||||
image: goreleaser/nfpm:v2.35.3
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
|
@ -901,7 +901,7 @@ steps:
|
|||
- git fetch --tags
|
||||
|
||||
- name: build
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.11.1-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -962,7 +962,7 @@ steps:
|
|||
- git fetch --tags
|
||||
|
||||
- name: build
|
||||
image: node:20.14.0-alpine
|
||||
image: node:20.11.1-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -1120,7 +1120,7 @@ steps:
|
|||
- sed -i 's/\\/api\\/v1//g' frontend/index.html
|
||||
- ./bumpp.sh
|
||||
- yarn install
|
||||
- yarn dist --linux zip
|
||||
- yarn dist --linux --windows
|
||||
|
||||
# - name: rebuild-cache
|
||||
# image: meltwater/drone-cache:dev
|
||||
|
@ -1400,6 +1400,6 @@ steps:
|
|||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: da7295a4503b9ce865c2cb2ffd1f38fec53cc999f11c842172c475b93bd89798
|
||||
hmac: c312afe632177a2d45f47c429bf6c7528af3c51a097430956558532ccdcc42b9
|
||||
|
||||
...
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -28,4 +28,3 @@ vendor/
|
|||
os-packages/
|
||||
mage_output_file.go
|
||||
mage-static
|
||||
.direnv/
|
||||
|
|
|
@ -18,7 +18,6 @@ linters:
|
|||
- scopelint # Obsolete, using exportloopref instead
|
||||
- durationcheck
|
||||
- goconst
|
||||
- musttag
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
|
@ -104,7 +103,3 @@ issues:
|
|||
text: "parameter 'tx' seems to be unused, consider removing or renaming it as"
|
||||
linters:
|
||||
- revive
|
||||
- path: pkg/models/typesense.go
|
||||
text: 'structtag: struct field Position repeats json tag "position" also at'
|
||||
linters:
|
||||
- govet
|
||||
|
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -4,10 +4,11 @@
|
|||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"vue.volar",
|
||||
"vue.vscode-typescript-vue-plugin",
|
||||
"lokalise.i18n-ally",
|
||||
"mgmcdermott.vscode-language-babel",
|
||||
"mikestead.dotenv",
|
||||
"Syler.sass-indented",
|
||||
"vitest.explorer"
|
||||
"zixuanchen.vitest-explorer"
|
||||
]
|
||||
}
|
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
|
@ -2,9 +2,10 @@
|
|||
"go.testEnvVars": {
|
||||
"VIKUNJA_SERVICE_ROOTPATH": "${workspaceRoot}"
|
||||
},
|
||||
"eslint.packageManager": "pnpm",
|
||||
"editor.formatOnSave": false,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit"
|
||||
"source.fixAll": true
|
||||
},
|
||||
"eslint.format.enable": true,
|
||||
"[javascript]": {
|
||||
|
@ -21,6 +22,11 @@
|
|||
"vue"
|
||||
],
|
||||
|
||||
"volar.completion.preferredTagNameCase": "pascal",
|
||||
|
||||
// disable vetur in case it is installed
|
||||
"vetur.validation.template": false,
|
||||
|
||||
// i18n ally
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/i18n/lang"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
FROM --platform=$BUILDPLATFORM node:20.14.0-alpine AS frontendbuilder
|
||||
FROM --platform=$BUILDPLATFORM node:20.11.1-alpine AS frontendbuilder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ If you find any security-related issues you don't want to disclose publicly, ple
|
|||
|
||||
## Features
|
||||
|
||||
See [the features page](https://vikunja.io/features/) on our website for a more exhaustive list or
|
||||
See [the features page](https://vikunja.io/features/) on our website for a more exaustive list or
|
||||
try it on [try.vikunja.io](https://try.vikunja.io)!
|
||||
|
||||
## Docs
|
||||
|
|
|
@ -156,7 +156,7 @@ mailer:
|
|||
username: "user"
|
||||
# SMTP password
|
||||
password: ""
|
||||
# Whether to skip verification of the tls certificate on the server
|
||||
# Wether to skip verification of the tls certificate on the server
|
||||
skiptlsverify: false
|
||||
# The default from address when sending emails
|
||||
fromemail: "mail@vikunja"
|
||||
|
@ -306,7 +306,7 @@ auth:
|
|||
# The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
|
||||
# **Note:** Some openid providers (like Gitlab) only make the email of the user available through OpenID if they have set it to be publicly visible.
|
||||
# If the email is not public in those cases, authenticating will fail.
|
||||
# +**Note 2:** The frontend expects the third party to redirect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
|
||||
# **Note 2:** The frontend expects the third party to rediect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
|
||||
# The frontend will automatically provide the API with the redirect url, composed from the current url where it's hosted.
|
||||
# If you want to use the desktop client with OpenID, make sure to allow redirects to `127.0.0.1`.
|
||||
# Take a look at the [default config file](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
|
||||
|
@ -368,8 +368,8 @@ defaultsettings:
|
|||
webhooks:
|
||||
# Whether to enable support for webhooks
|
||||
enabled: true
|
||||
# The timeout in seconds until a webhook request fails when no response has been received.
|
||||
timeoutseconds: 30
|
||||
# The timout in seconds until a webhook request fails when no response has been received.
|
||||
timoutseconds: 30
|
||||
# The URL of [a mole instance](https://github.com/frain-dev/mole) to use to proxy outgoing webhook requests. You should use this and configure appropriately if you're not the only one using your Vikunja instance. More info about why: https://webhooks.fyi/best-practices/webhook-providers#implement-security-on-egress-communication. Must be used in combination with `webhooks.password` (see below).
|
||||
proxyurl:
|
||||
# The proxy password to use when authenticating against the proxy.
|
||||
|
|
9
desktop/default.nix
Normal file
9
desktop/default.nix
Normal file
|
@ -0,0 +1,9 @@
|
|||
{ pkgs ? import <nixpkgs> {}
|
||||
}:
|
||||
pkgs.mkShell {
|
||||
name="electron-dev";
|
||||
buildInputs = [
|
||||
pkgs.electron
|
||||
];
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "29.4.2",
|
||||
"electron": "29.1.4",
|
||||
"electron-builder": "24.13.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
2028
desktop/yarn.lock
Normal file
2028
desktop/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -24,7 +24,7 @@ In other packages, use the `db.NewSession()` method to get a new database sessio
|
|||
|
||||
To add a new table to the database, create the struct and [add a migration for it]({{< ref "db-migrations.md" >}}).
|
||||
|
||||
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentation](https://xorm.io/docs/).
|
||||
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](https://xorm.io/docs/).
|
||||
|
||||
In most cases you will also need to implement the `TableName() string` method on the new struct to make sure the table name matches the rest of the tables - plural.
|
||||
|
||||
|
|
|
@ -26,15 +26,6 @@ If you plan to do a bigger change, it is better to open an issue for discussion
|
|||
|
||||
The main repo is [`vikunja/vikunja`](https://kolaente.dev/vikunja/vikunja), it contains all code for the api, frontend and desktop applications.
|
||||
|
||||
## Where to file issues
|
||||
|
||||
You can file issues on [the Gitea repo](https://kolaente.dev/vikunja/vikunja) or [on the GitHub mirror](https://github.com/go-vikunja/vikunja), when you don't want to create an account on the Gitea instance.
|
||||
|
||||
Please note that due to a spam problem, we need to manually enable accounts on Gitea after you've registered there.
|
||||
To get that started, please reach out on another channel with your username.
|
||||
|
||||
Another option is [the community forum](https://community.vikunja.io), especially if you want to discuss a feature in more detail.
|
||||
|
||||
## API
|
||||
|
||||
You'll need at least Go 1.21 to build Vikunja's api.
|
||||
|
|
|
@ -101,7 +101,7 @@ You should also document the routes with [swagger annotations]({{< ref "swagger-
|
|||
|
||||
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
|
||||
This means you start by adding a project, then add projects inside that project, then tasks in the lists and so on.
|
||||
In general, it is recommended to have one root project with all projects of the other service as child projects.
|
||||
In general, it is reccommended to have one root project with all projects of the other service as child projects.
|
||||
|
||||
The root structure must be present as `[]*models.ProjectWithTasksAndBuckets`. It allows to represent all of Vikunja's hierarchy as a single data structure.
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ menu:
|
|||
|
||||
The following parts are about the kinds of tests in the API package and how to run them.
|
||||
|
||||
### Prerequisites
|
||||
### Prerequesites
|
||||
|
||||
To run any kind of test, you need to specify Vikunja's [root path](https://vikunja.io/docs/config-options/#rootpath).
|
||||
This is required to make sure all test fixtures are correctly loaded.
|
||||
|
@ -39,7 +39,7 @@ This definition is a bit blurry, but we haven't found a better one yet.
|
|||
All integration tests live in `pkg/integrations`.
|
||||
You can run them by executing `mage test:integration`.
|
||||
|
||||
The integration tests use the same config and fixtures as the unit tests and therefore have the same options available,
|
||||
The integration tests use the same config and fixtures as the unit tests and therefor have the same options available,
|
||||
see at the beginning of this document.
|
||||
|
||||
To run integration tests, use `mage test:integration`.
|
||||
|
|
|
@ -18,7 +18,6 @@ To fully build Vikunja from source files, you need to build the api and frontend
|
|||
|
||||
1. Make sure you have git installed
|
||||
2. Clone the repo with `git clone https://code.vikunja.io/vikunja` and switch into the directory.
|
||||
3. Check out the version you want to build with `git checkout VERSION` - replace `VERSION` with the version want to use. If you don't do this, you'll build the [latest unstable build]({{< ref "versions.md">}}), which might contain bugs.
|
||||
|
||||
## Frontend
|
||||
|
||||
|
@ -36,7 +35,7 @@ That means compiling it boils down to these steps:
|
|||
|
||||
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.21`.
|
||||
2. Make sure [Mage](https://magefile.org) is properly installed on your system.
|
||||
3. If you did not build the frontend in the steps before, you need to either do that or create a dummy index file with `mkdir -p frontend/dist && touch frontend/dist/index.html`.
|
||||
3. If you did not build the frontend in the steps before, you need to either do that or create a dummy index file with `mkdir -p frontend/dist && touch index.html`.
|
||||
4. Run `mage build` in the source of the main repo. This will build a binary in the root of the repo which will be able to run on your system.
|
||||
|
||||
### Build for different architectures
|
||||
|
|
|
@ -22,6 +22,7 @@ export VIKUNJA_FIRST_CHILD=true
|
|||
|
||||
is the same as defining it in a `config.yml` like so:
|
||||
|
||||
```yaml
|
||||
```yaml
|
||||
first:
|
||||
child: true
|
||||
|
@ -347,7 +348,6 @@ Environment path: `VIKUNJA_SERVICE_CUSTOMLOGOURL`
|
|||
|
||||
### enablepublicteams
|
||||
|
||||
Enables the public team feature. If enabled, it is possible to configure teams to be public, which makes them
|
||||
discoverable when sharing a project, therefore not only showing teams the user is member of.
|
||||
|
||||
Default: `false`
|
||||
|
@ -779,7 +779,7 @@ Environment path: `VIKUNJA_MAILER_PASSWORD`
|
|||
|
||||
### skiptlsverify
|
||||
|
||||
Whether to skip verification of the tls certificate on the server
|
||||
Wether to skip verification of the tls certificate on the server
|
||||
|
||||
Default: `false`
|
||||
|
||||
|
@ -1222,7 +1222,7 @@ OpenID configuration will allow users to authenticate through a third-party Open
|
|||
The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
|
||||
**Note:** Some openid providers (like Gitlab) only make the email of the user available through OpenID if they have set it to be publicly visible.
|
||||
If the email is not public in those cases, authenticating will fail.
|
||||
+**Note 2:** The frontend expects the third party to redirect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
|
||||
**Note 2:** The frontend expects the third party to rediect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
|
||||
The frontend will automatically provide the API with the redirect url, composed from the current url where it's hosted.
|
||||
If you want to use the desktop client with OpenID, make sure to allow redirects to `127.0.0.1`.
|
||||
Take a look at the [default config file](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
|
||||
|
@ -1421,15 +1421,15 @@ Full path: `webhooks.enabled`
|
|||
Environment path: `VIKUNJA_WEBHOOKS_ENABLED`
|
||||
|
||||
|
||||
### timeoutseconds
|
||||
### timoutseconds
|
||||
|
||||
The timeout in seconds until a webhook request fails when no response has been received.
|
||||
The timout in seconds until a webhook request fails when no response has been received.
|
||||
|
||||
Default: `30`
|
||||
|
||||
Full path: `webhooks.timeoutseconds`
|
||||
Full path: `webhooks.timoutseconds`
|
||||
|
||||
Environment path: `VIKUNJA_WEBHOOKS_TIMEOUTSECONDS`
|
||||
Environment path: `VIKUNJA_WEBHOOKS_TIMOUTSECONDS`
|
||||
|
||||
|
||||
### proxyurl
|
||||
|
|
|
@ -40,7 +40,7 @@ chown 1000 $PWD/files
|
|||
|
||||
You'll need to do this before running any of the examples on this page.
|
||||
|
||||
Vikunja will not try to acquire ownership of the files folder, as that would mean it had to run as root.
|
||||
Vikunja will not try to aquire ownership of the files folder, as that would mean it had to run as root.
|
||||
|
||||
## PostgreSQL
|
||||
|
||||
|
@ -312,7 +312,7 @@ To do that, you can
|
|||
|
||||
* Either activate SSH and paste the adapted compose file in a terminal (using Putty or similar)
|
||||
* Without activating SSH as a "custom script" (go to Control Panel / Task Scheduler / Create / Scheduled Task / User-defined script)
|
||||
* Without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for example):
|
||||
* Without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for exmple):
|
||||
1. Go to **Dashboard / Stacks** click the button **"Add Stack"**
|
||||
2. Give it the name Vikunja and paste the adapted docker compose file
|
||||
3. Deploy the Stack with the "Deploy Stack" button:
|
||||
|
|
|
@ -37,7 +37,7 @@ This document provides an overview and instructions for the different methods:
|
|||
* [FreeBSD](#freebsd--freenas)
|
||||
* [Kubernetes]({{< ref "k8s.md" >}})
|
||||
|
||||
And after you installed Vikunja, you may want to check out these other resources:
|
||||
And after you installed Vikunja, you may want to check out these other ressources:
|
||||
|
||||
* [Configuration]({{< ref "config.md">}})
|
||||
* [UTF-8 Settings]({{< ref "utf-8.md">}})
|
||||
|
@ -225,13 +225,10 @@ go install github.com/magefile/mage
|
|||
```
|
||||
mkdir /mnt/GO/code.vikunja.io
|
||||
cd /mnt/GO/code.vikunja.io
|
||||
git clone https://code.vikunja.io/vikunja
|
||||
cd vikunja
|
||||
git clone https://code.vikunja.io/api
|
||||
cd /mnt/GO/code.vikunja.io/api
|
||||
```
|
||||
|
||||
**Note:** Check out the version you want to build with `git checkout VERSION` - replace `VERSION` with the version want to use.
|
||||
If you don't do this, you'll build the [latest unstable build]({{< ref "versions.md">}}), which might contain bugs.
|
||||
|
||||
### Compile binaries
|
||||
|
||||
```
|
||||
|
|
|
@ -17,7 +17,7 @@ Vikunja allows for authentication with an external identity source such as Authe
|
|||
|
||||
## OpenID Connect Overview
|
||||
|
||||
OpenID Connect is a standardized identity layer built on top of the more generic OAuth 2.0 specification, simplifying interaction between the involved parties significantly.
|
||||
OpenID Connect is a standardized identity layer built on top of the more generic OAuth 2.0 specification, simplying interaction between the involved parties significantly.
|
||||
While the [OpenID specification](https://openid.net/specs/openid-connect-core-1_0.html#Overview) is worth a read, we summarize the most important basics here.
|
||||
|
||||
The involved parties are:
|
||||
|
@ -103,7 +103,7 @@ auth:
|
|||
|
||||
## Automatically assign users to teams
|
||||
|
||||
Starting with version 0.24.0, Vikunja is capable of automatically adding users to a team based on OIDC claims added by the identity provider.
|
||||
Vikunja is capable of automatically adding users to a team based on OIDC claims added by the identity provider.
|
||||
If configured, Vikunja will sync teams, automatically create new ones and make sure the members are part of the configured teams.
|
||||
Teams which exist only because they were created from oidc attributes are not editable in Vikunja.
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ However, you can still run it in a subdirectory but need to build the frontend y
|
|||
First, make sure you're able to build the frontend from source.
|
||||
Check [the guide about building from source]({{< ref "build-from-source.md">}}#frontend) about that.
|
||||
|
||||
### Dynamically set with build command
|
||||
### Dynamicly set with build command
|
||||
|
||||
Run the build with the `VIKUNJA_FRONTEND_BASE` variable specified.
|
||||
|
||||
|
|
|
@ -10,13 +10,13 @@ menu:
|
|||
|
||||
# Vikunja Versions
|
||||
|
||||
Vikunja api is available in two different release flavors.
|
||||
The Vikunja api and frontend are available in two different release flavors.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Stable
|
||||
|
||||
Stable releases have a fixed version number like `0.20.2` and are published at irregular intervals whenever a new version is ready.
|
||||
Stable releases have a fixed version number like `0.18.2` and are published at irregular intervals whenever a new version is ready.
|
||||
They receive few bugfixes and security patches.
|
||||
|
||||
We use [Semantic Versioning](https://semver.org) for these releases.
|
||||
|
@ -28,10 +28,7 @@ As such, they contain the current development code and are more likely to have b
|
|||
There might be multiple new such builds a day.
|
||||
|
||||
Versions contain the last stable version, the number of commits since then and the commit the currently running binary was built from.
|
||||
They look like this: `v0.20.2-75-6049427322`
|
||||
|
||||
Since a release is also cut from the main branch at some point, features from unstable will eventually become available in stable releases.
|
||||
At the point in time of a new version release, the unstable build is the same exact thing.
|
||||
They look like this: `v0.18.1+269-5cc4927b9e`
|
||||
|
||||
The demo instance at [try.vikunja.io](https://try.vikunja.io) automatically updates and always runs the last unstable build.
|
||||
|
||||
|
|
|
@ -72,7 +72,6 @@ Vikunja **currently does not** support these properties:
|
|||
* [Evolution](https://wiki.gnome.org/Apps/Evolution/)
|
||||
* [OpenTasks](https://opentasks.app/) & [DAVx⁵](https://www.davx5.com/)
|
||||
* [Tasks (Android)](https://tasks.org/)
|
||||
* [Korganizer](https://apps.kde.org/korganizer/)
|
||||
|
||||
### Not working
|
||||
|
||||
|
|
|
@ -10,9 +10,17 @@ menu:
|
|||
|
||||
# Command line interface
|
||||
|
||||
You can interact with Vikunja using its `cli` interface.
|
||||
You can interact with Vikunja using its `cli` interface.<br />
|
||||
The following commands are available:
|
||||
|
||||
{{< table_of_contents >}}
|
||||
* [dump](#dump)
|
||||
* [help](#help)
|
||||
* [migrate](#migrate)
|
||||
* [restore](#restore)
|
||||
* [testmail](#testmail)
|
||||
* [user](#user)
|
||||
* [version](#version)
|
||||
* [web](#web)
|
||||
|
||||
If you don't specify a command, the [`web`](#web) command will be executed.
|
||||
|
||||
|
@ -20,22 +28,14 @@ All commands use the same standard [config file]({{< ref "../setup/config.md">}}
|
|||
|
||||
## Using the cli in docker
|
||||
|
||||
When running Vikunja in docker, you'll need to execute all commands in the `vikunja` container.
|
||||
When running Vikunja in docker, you'll need to execute all commands in the `api` container.
|
||||
Instead of running the `vikunja` binary directly, run it like this:
|
||||
|
||||
```sh
|
||||
docker exec <name of the vikunja container> /app/vikunja/vikunja <subcommand>
|
||||
docker exec <name of the vikunja api container> /app/vikunja/vikunja <subcommand>
|
||||
```
|
||||
|
||||
If you need to run a bunch of Vikunja commands, you can also create a shell alias for it:
|
||||
|
||||
```sh
|
||||
alias vikunja-docker='docker exec <name of the vikunja container> /app/vikunja/vikunja'
|
||||
```
|
||||
|
||||
Then use it as `vikunja-docker <subcommand>`.
|
||||
|
||||
## `dump`
|
||||
### `dump`
|
||||
|
||||
Creates a zip file with all vikunja-related files.
|
||||
This includes config, version, all files and the full database.
|
||||
|
@ -45,21 +45,7 @@ Usage:
|
|||
$ vikunja dump
|
||||
```
|
||||
|
||||
## `index`
|
||||
|
||||
Perform a full reindex of all tasks into Typesense. This will clear all tasks already present in the index unless the `--partial` flag is provided, see below.
|
||||
|
||||
The command will only work if Typesense is enabled.
|
||||
|
||||
Flags:
|
||||
* `-p`, `--partial`: If provided, Vikunja will only index tasks which are not present in the index yet.
|
||||
|
||||
Usage:
|
||||
```
|
||||
$ vikunja index [flags]
|
||||
```
|
||||
|
||||
## `help`
|
||||
### `help`
|
||||
|
||||
Shows more detailed help about any command.
|
||||
|
||||
|
@ -69,7 +55,7 @@ Usage:
|
|||
$ vikunja help [command]
|
||||
```
|
||||
|
||||
## `migrate`
|
||||
### `migrate`
|
||||
|
||||
Run all database migrations which didn't already run.
|
||||
|
||||
|
@ -79,7 +65,7 @@ $ vikunja migrate [flags]
|
|||
$ vikunja migrate [command]
|
||||
```
|
||||
|
||||
### `migrate list`
|
||||
#### `migrate list`
|
||||
|
||||
Shows a list with all database migrations.
|
||||
|
||||
|
@ -88,7 +74,7 @@ Usage:
|
|||
$ vikunja migrate list
|
||||
```
|
||||
|
||||
### `migrate rollback`
|
||||
#### `migrate rollback`
|
||||
|
||||
Roll migrations back until a certain point.
|
||||
|
||||
|
@ -100,42 +86,42 @@ $ vikunja migrate rollback [flags]
|
|||
Flags:
|
||||
* `-n`, `--name` string: The id of the migration you want to roll back until.
|
||||
|
||||
## `restore`
|
||||
### `restore`
|
||||
|
||||
Restores a previously created dump from a zip file, see `dump`.
|
||||
|
||||
Usage:
|
||||
```
|
||||
$ vikunja restore [path to dump zip file]
|
||||
$ vikunja restore <path to dump zip file>
|
||||
```
|
||||
|
||||
## `testmail`
|
||||
### `testmail`
|
||||
|
||||
Sends a test mail using the configured smtp connection.
|
||||
|
||||
Usage:
|
||||
```
|
||||
$ vikunja testmail [email to send the test mail to]
|
||||
$ vikunja testmail <email to send the test mail to>
|
||||
```
|
||||
|
||||
## `user`
|
||||
### `user`
|
||||
|
||||
Bundles a few commands to manage users.
|
||||
|
||||
### `user change-status`
|
||||
#### `user change-status`
|
||||
|
||||
Enable or disable a user. Will toggle the current status if no flag (`--enable` or `--disable`) is provided.
|
||||
|
||||
Usage:
|
||||
```
|
||||
$ vikunja user change-status [user id] [flags]
|
||||
$ vikunja user change-status <user id> <flags>
|
||||
```
|
||||
|
||||
Flags:
|
||||
* `-d`, `--disable`: Disable the user.
|
||||
* `-e`, `--enable`: Enable the user.
|
||||
|
||||
### `user create`
|
||||
#### `user create`
|
||||
|
||||
Create a new user.
|
||||
|
||||
|
@ -150,22 +136,22 @@ Flags:
|
|||
* `-p`, `--password`: The password of the new user. You will be asked to enter it if not provided through the flag.
|
||||
* `-u`, `--username`: The username of the new user.
|
||||
|
||||
### `user delete`
|
||||
#### `user delete`
|
||||
|
||||
Start the user deletion process.
|
||||
If called without the `--now` flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process (this is the same behavior as if the user requested their deletion via the web interface).
|
||||
If called without the `--now` flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process (this is the same behavoir as if the user requested their deletion via the web interface).
|
||||
With the flag the user is deleted **immediately**.
|
||||
|
||||
**USE WITH CAUTION.**
|
||||
|
||||
```
|
||||
$ vikunja user delete [id] [flags]
|
||||
$ vikunja user delete <id> <flags>
|
||||
```
|
||||
|
||||
Flags:
|
||||
* `-n`, `--now` If provided, deletes the user immediately instead of emailing them first.
|
||||
|
||||
### `user list`
|
||||
#### `user list`
|
||||
|
||||
Shows a list of all users.
|
||||
|
||||
|
@ -174,26 +160,26 @@ Usage:
|
|||
$ vikunja user list
|
||||
```
|
||||
|
||||
### `user reset-password`
|
||||
#### `user reset-password`
|
||||
|
||||
Reset a users password, either through mailing them a reset link or directly.
|
||||
|
||||
Usage:
|
||||
```
|
||||
$ vikunja user reset-password [flags]
|
||||
$ vikunja user reset-password <flags>
|
||||
```
|
||||
|
||||
Flags:
|
||||
* `-d`, `--direct`: If provided, reset the password directly instead of sending the user a reset mail.
|
||||
* `-p`, `--password`: The new password of the user. Only used in combination with --direct. You will be asked to enter it if not provided through the flag.
|
||||
|
||||
### `user update`
|
||||
#### `user update`
|
||||
|
||||
Update an existing user.
|
||||
|
||||
Usage:
|
||||
```
|
||||
$ vikunja user update [user id]
|
||||
$ vikunja user update <user id>
|
||||
```
|
||||
|
||||
Flags:
|
||||
|
@ -201,19 +187,19 @@ Flags:
|
|||
* `-e`, `--email`: The new email address of the user.
|
||||
* `-u`, `--username`: The new username of the user.
|
||||
|
||||
## `version`
|
||||
### `version`
|
||||
|
||||
Prints the version of Vikunja.
|
||||
This is either the semantic version (something like `0.24.0`) or version + git commit hash.
|
||||
This is either the semantic version (something like `0.7`) or version + git commit hash.
|
||||
|
||||
Usage:
|
||||
```
|
||||
$ vikunja version
|
||||
```
|
||||
|
||||
## `web`
|
||||
### `web`
|
||||
|
||||
Starts Vikunja's web server, serving the api and frontend.
|
||||
Starts Vikunja's REST api server.
|
||||
|
||||
Usage:
|
||||
```
|
||||
|
|
|
@ -51,7 +51,7 @@ This document describes the different errors Vikunja can return.
|
|||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 2001 | 400 | ID cannot be empty or 0. |
|
||||
| 2002 | 400 | Some of the request data was invalid. The response contains an additional array with all invalid fields. |
|
||||
| 2002 | 400 | Some of the request data was invalid. The response contains an aditional array with all invalid fields. |
|
||||
|
||||
## Project
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ Here are some examples of filter queries:
|
|||
* `priority = 4`: Matches tasks with priority level 4
|
||||
* `dueDate < now`: Matches tasks with a due date in the past
|
||||
* `done = false && priority >= 3`: Matches undone tasks with priority level 3 or higher
|
||||
* `assignees in user1, user2`: Matches tasks assigned to either "user1" or "user2
|
||||
* `assignees in [user1, user2]`: Matches tasks assigned to either "user1" or "user2
|
||||
* `(priority = 1 || priority = 2) && dueDate <= now`: Matches tasks with priority level 1 or 2 and a due date in the past
|
||||
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ Starting with version 0.22.0, Vikunja allows you to define webhooks to notify ot
|
|||
|
||||
To create a webhook, in the project options select "Webhooks". The form will allow you to create and modify webhooks.
|
||||
|
||||
Check out [the api docs](https://try.vikunja.io/api/v1/docs#tag/webhooks) for information about how to create webhooks programmatically.
|
||||
Check out [the api docs](https://try.vikunja.io/api/v1/docs#tag/webhooks) for information about how to create webhooks programatically.
|
||||
|
||||
## Available events and their payload
|
||||
|
||||
|
|
20
flake.nix
20
flake.nix
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
description = "Vikunja dev environment";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||
in {
|
||||
defaultPackage.x86_64-linux =
|
||||
pkgs.mkShell { buildInputs = with pkgs; [
|
||||
# General tools
|
||||
git-cliff
|
||||
# Frontend tools
|
||||
pnpm cypress
|
||||
# API tools
|
||||
go golangci-lint mage
|
||||
# Desktop
|
||||
electron
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -46,8 +46,7 @@ module.exports = {
|
|||
'vue/html-indent': ['error', 'tab'],
|
||||
|
||||
// vue3
|
||||
'vue/no-ref-object-reactivity-loss': 'error',
|
||||
'vue/no-setup-props-reactivity-loss': 'warn', // TODO: switch to error after vite `propsDestructure` is removed
|
||||
'vue/no-ref-object-destructure': 'error',
|
||||
},
|
||||
'parser': 'vue-eslint-parser',
|
||||
'parserOptions': {
|
||||
|
|
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
|
@ -13,6 +13,7 @@ node_modules
|
|||
/dist*
|
||||
coverage
|
||||
*.zip
|
||||
.direnv/
|
||||
|
||||
# Test files
|
||||
cypress/screenshots
|
||||
|
|
14
frontend/.npmrc
Normal file
14
frontend/.npmrc
Normal file
|
@ -0,0 +1,14 @@
|
|||
fetch-timeout=100000
|
||||
|
||||
# pnpm settings
|
||||
# The following settings prepare for the new default value of pnpm 8
|
||||
# they can be removed directly after having moved to pnpm 8
|
||||
auto-install-peers=true
|
||||
dedupe-peer-dependents=true
|
||||
resolve-peers-from-workspace-root=true
|
||||
save-workspace-protocol=rolling
|
||||
resolution-mode=lowest-direct
|
||||
publishConfig.linkDirectory=true
|
||||
|
||||
# remove some time after having moved to pnpm 8
|
||||
use-lockfile-v6=true
|
|
@ -1 +1 @@
|
|||
20.14.0
|
||||
20.11.1
|
|
@ -12,7 +12,7 @@ describe('Project History', () => {
|
|||
cy.intercept(Cypress.env('API_URL') + '/projects*').as('loadProjectArray')
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/*').as('loadProject')
|
||||
|
||||
const projects = ProjectFactory.create(7)
|
||||
const projects = ProjectFactory.create(6)
|
||||
ProjectViewFactory.truncate()
|
||||
projects.forEach(p => ProjectViewFactory.create(1, {
|
||||
id: p.id,
|
||||
|
@ -36,8 +36,6 @@ describe('Project History', () => {
|
|||
cy.wait('@loadProject')
|
||||
cy.visit(`/projects/${projects[5].id}/${projects[5].id}`)
|
||||
cy.wait('@loadProject')
|
||||
cy.visit(`/projects/${projects[6].id}/${projects[6].id}`)
|
||||
cy.wait('@loadProject')
|
||||
|
||||
// cy.visit('/')
|
||||
// Not using cy.visit here to work around the redirect issue fixed in #1337
|
||||
|
@ -54,6 +52,5 @@ describe('Project History', () => {
|
|||
.should('contain', projects[3].title)
|
||||
.should('contain', projects[4].title)
|
||||
.should('contain', projects[5].title)
|
||||
.should('contain', projects[6].title)
|
||||
})
|
||||
})
|
|
@ -52,7 +52,7 @@ describe('Project View Gantt', () => {
|
|||
})
|
||||
cy.visit('/projects/1/2')
|
||||
|
||||
cy.get('.gantt-options .fancy-checkbox')
|
||||
cy.get('.gantt-options .fancycheckbox')
|
||||
.contains('Show tasks which don\'t have dates set')
|
||||
.click()
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ describe('Project View List', () => {
|
|||
|
||||
cy.get('.project-title-wrapper .icon')
|
||||
.should('not.exist')
|
||||
cy.get('input.input[placeholder="Add a task..."')
|
||||
cy.get('input.input[placeholder="Add a new task..."')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
|
|
|
@ -24,10 +24,10 @@ describe('Project View Table', () => {
|
|||
cy.get('.project-table .filter-container .button')
|
||||
.contains('Columns')
|
||||
.click()
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancy-checkbox')
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
|
||||
.contains('Priority')
|
||||
.click()
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancy-checkbox')
|
||||
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
|
||||
.contains('Done')
|
||||
.click()
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ describe('Link shares', () => {
|
|||
|
||||
cy.get('h1.title')
|
||||
.should('contain', project.title)
|
||||
cy.get('input.input[placeholder="Add a task..."')
|
||||
cy.get('input.input[placeholder="Add a new task..."')
|
||||
.should('not.exist')
|
||||
cy.get('.tasks')
|
||||
.should('contain', tasks[0].title)
|
||||
|
@ -42,7 +42,7 @@ describe('Link shares', () => {
|
|||
|
||||
cy.get('h1.title')
|
||||
.should('contain', project.title)
|
||||
cy.get('input.input[placeholder="Add a task..."')
|
||||
cy.get('input.input[placeholder="Add a new task..."')
|
||||
.should('not.exist')
|
||||
cy.get('.tasks')
|
||||
.should('contain', tasks[0].title)
|
||||
|
|
|
@ -14,12 +14,12 @@ describe('Team', () => {
|
|||
const newTeamName = 'New Team'
|
||||
|
||||
cy.get('a.button')
|
||||
.contains('Create a team')
|
||||
.contains('Create a new team')
|
||||
.click()
|
||||
cy.url()
|
||||
.should('contain', '/teams/new')
|
||||
cy.get('.card-header-title')
|
||||
.contains('Create a team')
|
||||
.contains('Create a new team')
|
||||
cy.get('input.input')
|
||||
.type(newTeamName)
|
||||
cy.get('.button')
|
||||
|
|
|
@ -13,7 +13,6 @@ import {BucketFactory} from '../../factories/bucket'
|
|||
import {TaskAttachmentFactory} from '../../factories/task_attachments'
|
||||
import {TaskReminderFactory} from '../../factories/task_reminders'
|
||||
import {createDefaultViews} from "../project/prepareProjects";
|
||||
import { TaskBucketFactory } from '../../factories/task_buckets'
|
||||
|
||||
function addLabelToTaskAndVerify(labelTitle: string) {
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
|
@ -42,14 +41,14 @@ function uploadAttachmentAndVerify(taskId: number) {
|
|||
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
|
||||
cy.wait('@uploadAttachment')
|
||||
|
||||
cy.get('.attachments .attachments .files button.attachment')
|
||||
cy.get('.attachments .attachments .files a.attachment')
|
||||
.should('exist')
|
||||
}
|
||||
|
||||
describe('Task', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
let projects: {}[]
|
||||
let projects
|
||||
let buckets
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -65,7 +64,7 @@ describe('Task', () => {
|
|||
|
||||
it('Should be created new', () => {
|
||||
cy.visit('/projects/1/1')
|
||||
cy.get('.input[placeholder="Add a task…"')
|
||||
cy.get('.input[placeholder="Add a new task…"')
|
||||
.type('New Task')
|
||||
cy.get('.button')
|
||||
.contains('Add')
|
||||
|
@ -81,7 +80,7 @@ describe('Task', () => {
|
|||
cy.visit('/projects/1/1')
|
||||
cy.get('.project-is-empty-notice')
|
||||
.should('not.exist')
|
||||
cy.get('.input[placeholder="Add a task…"')
|
||||
cy.get('.input[placeholder="Add a new task…"')
|
||||
.type('New Task')
|
||||
cy.get('.button')
|
||||
.contains('Add')
|
||||
|
@ -97,7 +96,7 @@ describe('Task', () => {
|
|||
TaskFactory.create(1)
|
||||
|
||||
cy.visit('/projects/1/1')
|
||||
cy.get('.tasks .task .fancy-checkbox')
|
||||
cy.get('.tasks .task .fancycheckbox')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.global-notification')
|
||||
|
@ -471,10 +470,6 @@ describe('Task', () => {
|
|||
})
|
||||
const labels = LabelFactory.create(1)
|
||||
LabelTaskFactory.truncate()
|
||||
TaskBucketFactory.create(1, {
|
||||
task_id: tasks[0].id,
|
||||
bucket_id: buckets[0].id,
|
||||
})
|
||||
|
||||
cy.visit(`/projects/${projects[0].id}/4`)
|
||||
|
||||
|
@ -640,7 +635,7 @@ describe('Task', () => {
|
|||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a reminder')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.contains('Tomorrow')
|
||||
|
@ -665,7 +660,7 @@ describe('Task', () => {
|
|||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a reminder')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.should('not.exist')
|
||||
|
@ -694,7 +689,7 @@ describe('Task', () => {
|
|||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a reminder')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.should('not.exist')
|
||||
|
@ -723,7 +718,7 @@ describe('Task', () => {
|
|||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a reminder')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.should('not.exist')
|
||||
|
@ -759,7 +754,7 @@ describe('Task', () => {
|
|||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a reminder')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.should('not.exist')
|
||||
|
|
10
frontend/env.d.ts
vendored
10
frontend/env.d.ts
vendored
|
@ -3,6 +3,16 @@
|
|||
/// <reference types="cypress" />
|
||||
/// <reference types="@histoire/plugin-vue/components" />
|
||||
|
||||
declare module 'postcss-focus-within/browser' {
|
||||
import focusWithinInit from 'postcss-focus-within/browser'
|
||||
export default focusWithinInit
|
||||
}
|
||||
|
||||
declare module 'css-has-pseudo/browser' {
|
||||
import cssHasPseudo from 'css-has-pseudo/browser'
|
||||
export default cssHasPseudo
|
||||
}
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VIKUNJA_API_URL?: string
|
||||
readonly VIKUNJA_HTTP_PORT?: number
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1718149104,
|
||||
"narHash": "sha256-Ds1QpobBX2yoUDx9ZruqVGJ/uQPgcXoYuobBguyKEh8=",
|
||||
"lastModified": 1701336116,
|
||||
"narHash": "sha256-kEmpezCR/FpITc6yMbAh4WrOCiT2zg5pSjnKrq51h5Y=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e913ae340076bbb73d9f4d3d065c2bca7caafb16",
|
||||
"rev": "f5c27c6136db4d76c30e533c20517df6864c46ee",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
10
frontend/flake.nix
Normal file
10
frontend/flake.nix
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
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 pkgs.git-cliff ]; };
|
||||
};
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"homepage": "https://vikunja.io/",
|
||||
"funding": "https://opencollective.com/vikunja",
|
||||
"packageManager": "pnpm@9.3.0",
|
||||
"packageManager": "pnpm@8.15.5",
|
||||
"keywords": [
|
||||
"todo",
|
||||
"productivity",
|
||||
|
@ -50,60 +50,60 @@
|
|||
"story:preview": "histoire preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.2",
|
||||
"@fortawesome/free-regular-svg-icons": "6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.2",
|
||||
"@fortawesome/vue-fontawesome": "3.0.8",
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.5.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.1",
|
||||
"@fortawesome/vue-fontawesome": "3.0.6",
|
||||
"@github/hotkey": "3.1.0",
|
||||
"@infectoone/vue-ganttastic": "2.3.2",
|
||||
"@intlify/unplugin-vue-i18n": "4.0.0",
|
||||
"@infectoone/vue-ganttastic": "2.3.1",
|
||||
"@intlify/unplugin-vue-i18n": "3.0.1",
|
||||
"@kyvg/vue3-notification": "3.2.1",
|
||||
"@sentry/tracing": "7.114.0",
|
||||
"@sentry/vue": "8.9.2",
|
||||
"@tiptap/core": "2.4.0",
|
||||
"@tiptap/extension-blockquote": "2.4.0",
|
||||
"@tiptap/extension-bold": "2.4.0",
|
||||
"@tiptap/extension-bullet-list": "2.4.0",
|
||||
"@tiptap/extension-code": "2.4.0",
|
||||
"@tiptap/extension-code-block-lowlight": "2.4.0",
|
||||
"@tiptap/extension-document": "2.4.0",
|
||||
"@tiptap/extension-dropcursor": "2.4.0",
|
||||
"@tiptap/extension-gapcursor": "2.4.0",
|
||||
"@tiptap/extension-hard-break": "2.4.0",
|
||||
"@tiptap/extension-heading": "2.4.0",
|
||||
"@tiptap/extension-history": "2.4.0",
|
||||
"@tiptap/extension-horizontal-rule": "2.4.0",
|
||||
"@tiptap/extension-image": "2.4.0",
|
||||
"@tiptap/extension-italic": "2.4.0",
|
||||
"@tiptap/extension-link": "2.4.0",
|
||||
"@tiptap/extension-list-item": "2.4.0",
|
||||
"@tiptap/extension-ordered-list": "2.4.0",
|
||||
"@tiptap/extension-paragraph": "2.4.0",
|
||||
"@tiptap/extension-placeholder": "2.4.0",
|
||||
"@tiptap/extension-strike": "2.4.0",
|
||||
"@tiptap/extension-table": "2.4.0",
|
||||
"@tiptap/extension-table-cell": "2.4.0",
|
||||
"@tiptap/extension-table-header": "2.4.0",
|
||||
"@tiptap/extension-table-row": "2.4.0",
|
||||
"@tiptap/extension-task-item": "2.4.0",
|
||||
"@tiptap/extension-task-list": "2.4.0",
|
||||
"@tiptap/extension-text": "2.4.0",
|
||||
"@tiptap/extension-typography": "2.4.0",
|
||||
"@tiptap/extension-underline": "2.4.0",
|
||||
"@tiptap/pm": "2.4.0",
|
||||
"@tiptap/suggestion": "2.4.0",
|
||||
"@tiptap/vue-3": "2.4.0",
|
||||
"@sentry/tracing": "7.109.0",
|
||||
"@sentry/vue": "7.109.0",
|
||||
"@tiptap/core": "2.2.4",
|
||||
"@tiptap/extension-blockquote": "2.2.4",
|
||||
"@tiptap/extension-bold": "2.2.4",
|
||||
"@tiptap/extension-bullet-list": "2.2.4",
|
||||
"@tiptap/extension-code": "2.2.4",
|
||||
"@tiptap/extension-code-block-lowlight": "2.2.4",
|
||||
"@tiptap/extension-document": "2.2.4",
|
||||
"@tiptap/extension-dropcursor": "2.2.4",
|
||||
"@tiptap/extension-gapcursor": "2.2.4",
|
||||
"@tiptap/extension-hard-break": "2.2.4",
|
||||
"@tiptap/extension-heading": "2.2.4",
|
||||
"@tiptap/extension-history": "2.2.4",
|
||||
"@tiptap/extension-horizontal-rule": "2.2.4",
|
||||
"@tiptap/extension-image": "2.2.4",
|
||||
"@tiptap/extension-italic": "2.2.4",
|
||||
"@tiptap/extension-link": "2.2.4",
|
||||
"@tiptap/extension-list-item": "2.2.4",
|
||||
"@tiptap/extension-ordered-list": "2.2.4",
|
||||
"@tiptap/extension-paragraph": "2.2.4",
|
||||
"@tiptap/extension-placeholder": "2.2.4",
|
||||
"@tiptap/extension-strike": "2.2.4",
|
||||
"@tiptap/extension-table": "2.2.4",
|
||||
"@tiptap/extension-table-cell": "2.2.4",
|
||||
"@tiptap/extension-table-header": "2.2.4",
|
||||
"@tiptap/extension-table-row": "2.2.4",
|
||||
"@tiptap/extension-task-item": "2.2.4",
|
||||
"@tiptap/extension-task-list": "2.2.4",
|
||||
"@tiptap/extension-text": "2.2.4",
|
||||
"@tiptap/extension-typography": "2.2.4",
|
||||
"@tiptap/extension-underline": "2.2.4",
|
||||
"@tiptap/pm": "2.2.4",
|
||||
"@tiptap/suggestion": "2.2.4",
|
||||
"@tiptap/vue-3": "2.2.4",
|
||||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.clonedeep": "4.5.9",
|
||||
"@vueuse/core": "10.11.0",
|
||||
"@vueuse/router": "10.11.0",
|
||||
"axios": "1.7.2",
|
||||
"@vueuse/core": "10.9.0",
|
||||
"@vueuse/router": "10.9.0",
|
||||
"axios": "1.6.8",
|
||||
"blurhash": "2.0.5",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
"camel-case": "5.0.0",
|
||||
"camel-case": "4.1.2",
|
||||
"date-fns": "3.6.0",
|
||||
"dayjs": "1.11.11",
|
||||
"dompurify": "3.1.5",
|
||||
"dayjs": "1.11.10",
|
||||
"dompurify": "3.0.11",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"flatpickr": "4.6.13",
|
||||
"flexsearch": "0.7.31",
|
||||
|
@ -114,70 +114,74 @@
|
|||
"lowlight": "2.9.0",
|
||||
"pinia": "2.1.7",
|
||||
"register-service-worker": "1.7.2",
|
||||
"snake-case": "4.0.0",
|
||||
"snake-case": "3.0.4",
|
||||
"sortablejs": "1.15.2",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.5.3",
|
||||
"vue": "3.4.29",
|
||||
"vue-advanced-cropper": "2.8.9",
|
||||
"vue": "3.4.21",
|
||||
"vue-advanced-cropper": "2.8.8",
|
||||
"vue-flatpickr-component": "11.0.5",
|
||||
"vue-i18n": "9.13.1",
|
||||
"vue-router": "4.3.3",
|
||||
"vue-i18n": "9.10.2",
|
||||
"vue-router": "4.3.0",
|
||||
"vuemoji-picker": "0.2.1",
|
||||
"workbox-precaching": "7.1.0",
|
||||
"workbox-precaching": "7.0.0",
|
||||
"zhyswan-vuedraggable": "4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@4tw/cypress-drag-drop": "2.2.5",
|
||||
"@cypress/vite-dev-server": "5.1.1",
|
||||
"@cypress/vue": "6.0.1",
|
||||
"@cypress/vite-dev-server": "5.0.7",
|
||||
"@cypress/vue": "6.0.0",
|
||||
"@faker-js/faker": "8.4.1",
|
||||
"@histoire/plugin-screenshot": "0.17.17",
|
||||
"@histoire/plugin-vue": "0.17.17",
|
||||
"@rushstack/eslint-patch": "1.10.3",
|
||||
"@tsconfig/node20": "20.1.4",
|
||||
"@histoire/plugin-screenshot": "0.17.14",
|
||||
"@histoire/plugin-vue": "0.17.14",
|
||||
"@rushstack/eslint-patch": "1.8.0",
|
||||
"@tsconfig/node18": "18.2.2",
|
||||
"@types/codemirror": "5.60.15",
|
||||
"@types/dompurify": "3.0.5",
|
||||
"@types/flexsearch": "0.7.6",
|
||||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.debounce": "4.0.9",
|
||||
"@types/node": "20.14.2",
|
||||
"@types/marked": "5.0.2",
|
||||
"@types/node": "20.11.30",
|
||||
"@types/postcss-preset-env": "7.7.0",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.13.0",
|
||||
"@typescript-eslint/parser": "7.13.0",
|
||||
"@vitejs/plugin-legacy": "5.4.1",
|
||||
"@vitejs/plugin-vue": "5.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "7.3.1",
|
||||
"@typescript-eslint/parser": "7.3.1",
|
||||
"@vitejs/plugin-legacy": "5.3.2",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"@vue/eslint-config-typescript": "13.0.0",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"@vue/test-utils": "2.4.5",
|
||||
"@vue/tsconfig": "0.5.1",
|
||||
"autoprefixer": "10.4.19",
|
||||
"browserslist": "4.23.1",
|
||||
"caniuse-lite": "1.0.30001634",
|
||||
"autoprefixer": "10.4.18",
|
||||
"browserslist": "4.23.0",
|
||||
"caniuse-lite": "1.0.30001599",
|
||||
"css-has-pseudo": "6.0.2",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.11.0",
|
||||
"esbuild": "0.21.5",
|
||||
"cypress": "13.7.0",
|
||||
"esbuild": "0.20.2",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-vue": "9.26.0",
|
||||
"happy-dom": "14.12.0",
|
||||
"histoire": "0.17.17",
|
||||
"postcss": "8.4.38",
|
||||
"eslint-plugin-vue": "9.23.0",
|
||||
"happy-dom": "14.0.0",
|
||||
"histoire": "0.17.14",
|
||||
"postcss": "8.4.37",
|
||||
"postcss-easing-gradients": "3.0.1",
|
||||
"postcss-easings": "4.0.0",
|
||||
"postcss-preset-env": "9.5.14",
|
||||
"rollup": "4.18.0",
|
||||
"postcss-focus-within": "8.0.1",
|
||||
"postcss-preset-env": "9.5.2",
|
||||
"rollup": "4.13.0",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"sass": "1.77.5",
|
||||
"start-server-and-test": "2.0.4",
|
||||
"typescript": "5.4.5",
|
||||
"unplugin-inject-preload": "2.0.4",
|
||||
"vite": "5.3.0",
|
||||
"vite-plugin-pwa": "0.20.0",
|
||||
"sass": "1.72.0",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"typescript": "5.4.2",
|
||||
"vite": "5.1.6",
|
||||
"vite-plugin-inject-preload": "1.3.3",
|
||||
"vite-plugin-pwa": "0.19.5",
|
||||
"vite-plugin-sentry": "1.4.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "1.6.0",
|
||||
"vue-tsc": "2.0.21",
|
||||
"vitest": "1.4.0",
|
||||
"vue-tsc": "2.0.6",
|
||||
"wait-on": "7.2.0",
|
||||
"workbox-cli": "7.1.0"
|
||||
"workbox-cli": "7.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
|
|
16258
frontend/pnpm-lock.yaml
16258
frontend/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -26,15 +26,15 @@ import {useRoute, useRouter} from 'vue-router'
|
|||
import {useI18n} from 'vue-i18n'
|
||||
import isTouchDevice from 'is-touch-device'
|
||||
|
||||
import Notification from '@/components/misc/Notification.vue'
|
||||
import Notification from '@/components/misc/notification.vue'
|
||||
import UpdateNotification from '@/components/home/UpdateNotification.vue'
|
||||
import KeyboardShortcuts from '@/components/misc/keyboard-shortcuts/index.vue'
|
||||
|
||||
import TheNavigation from '@/components/home/TheNavigation.vue'
|
||||
import ContentAuth from '@/components/home/ContentAuth.vue'
|
||||
import ContentLinkShare from '@/components/home/ContentLinkShare.vue'
|
||||
import NoAuthWrapper from '@/components/misc/NoAuthWrapper.vue'
|
||||
import Ready from '@/components/misc/Ready.vue'
|
||||
import ContentAuth from '@/components/home/contentAuth.vue'
|
||||
import ContentLinkShare from '@/components/home/contentLinkShare.vue'
|
||||
import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
|
||||
import Ready from '@/components/misc/ready.vue'
|
||||
|
||||
import {setLanguage} from '@/i18n'
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 313 KiB |
|
@ -73,16 +73,14 @@ export interface BaseButtonProps extends /* @vue-ignore */ HTMLAttributes {
|
|||
}
|
||||
|
||||
export interface BaseButtonEmits {
|
||||
click: [payload: MouseEvent]
|
||||
(e: 'click', payload: MouseEvent): void
|
||||
}
|
||||
|
||||
withDefaults(defineProps<BaseButtonProps>(), {
|
||||
type: BASE_BUTTON_TYPES_MAP.BUTTON,
|
||||
disabled: false,
|
||||
to: undefined,
|
||||
href: undefined,
|
||||
openExternalInNewTab: true,
|
||||
})
|
||||
const {
|
||||
type = BASE_BUTTON_TYPES_MAP.BUTTON,
|
||||
disabled = false,
|
||||
openExternalInNewTab = true,
|
||||
} = defineProps<BaseButtonProps>()
|
||||
|
||||
const emit = defineEmits<BaseButtonEmits>()
|
||||
|
||||
|
@ -98,7 +96,7 @@ defineExpose({
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
// NOTE: we do not use scoped styles to reduce specificity and make it easy to overwrite
|
||||
// NOTE: we do not use scoped styles to reduce specifity and make it easy to overwrite
|
||||
|
||||
// We reset the default styles of a button element to enable easier styling
|
||||
:where(.base-button--type-button) {
|
||||
|
|
|
@ -45,7 +45,7 @@ const emit = defineEmits<{
|
|||
(event: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
|
||||
const checkboxId = ref(`checkbox_${createRandomID()}`)
|
||||
const checkboxId = ref(`fancycheckbox_${createRandomID()}`)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -28,24 +28,28 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// the logic of this component is loosely based on this article
|
||||
// the logic of this component is loosly based on this article
|
||||
// https://gomakethings.com/how-to-add-transition-animations-to-vanilla-javascript-show-and-hide-methods/#putting-it-all-together
|
||||
|
||||
import {computed, ref} from 'vue'
|
||||
import {getInheritedBackgroundColor} from '@/helpers/getInheritedBackgroundColor'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
const props = defineProps({
|
||||
/** Whether the Expandable is open or not */
|
||||
open?: boolean,
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** If there is too much content, content will be cut of here. */
|
||||
initialHeight?: number
|
||||
initialHeight: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
/** The hidden content is indicated by a gradient. This is the color that the gradient fades to.
|
||||
* Makes only sense if `initialHeight` is set. */
|
||||
backgroundColor?: string
|
||||
}>(), {
|
||||
open: false,
|
||||
initialHeight: undefined,
|
||||
backgroundColor: undefined,
|
||||
* Makes only sense if `initialHeight` is set. */
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
},
|
||||
})
|
||||
|
||||
const wrapper = ref<HTMLElement | null>(null)
|
||||
|
@ -78,7 +82,7 @@ function forceLayout(el: HTMLElement) {
|
|||
|
||||
/* ######################################################################
|
||||
# The following functions are called by the js hooks of the transitions.
|
||||
# They follow the original hook order of the vue transition component
|
||||
# They follow the orignal hook order of the vue transition component
|
||||
# see: https://vuejs.org/guide/built-ins/transition.html#javascript-hooks
|
||||
###################################################################### */
|
||||
|
||||
|
@ -113,8 +117,8 @@ function beforeLeave(el: HTMLElement) {
|
|||
function leave(el: HTMLElement) {
|
||||
// Set the height back to 0
|
||||
el.style.height = '0'
|
||||
el.style.willChange = ''
|
||||
el.style.backfaceVisibility = ''
|
||||
el.style.willChange = ''
|
||||
el.style.backfaceVisibility = ''
|
||||
}
|
||||
|
||||
function afterLeave(el: HTMLElement) {
|
||||
|
|
|
@ -18,7 +18,7 @@ export const DATE_RANGES = {
|
|||
|
||||
'thisYear': ['now/y', 'now/y+1y'],
|
||||
'restOfThisYear': ['now', 'now/y+1y'],
|
||||
} as const
|
||||
}
|
||||
|
||||
export const DATE_VALUES = {
|
||||
'now': 'now',
|
||||
|
@ -43,4 +43,4 @@ export const DATE_VALUES = {
|
|||
|
||||
'startOfThisYear': 'now/y',
|
||||
'endOfThisYear': 'now/y+1y',
|
||||
} as const
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import DatemathHelp from './DatemathHelp.vue'
|
||||
import datemathHelp from './datemathHelp.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story>
|
||||
<Variant title="Default">
|
||||
<DatemathHelp />
|
||||
<datemathHelp />
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
|
@ -108,10 +108,10 @@ import flatPickr from 'vue-flatpickr-component'
|
|||
import 'flatpickr/dist/flatpickr.css'
|
||||
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
|
||||
|
||||
import Popup from '@/components/misc/Popup.vue'
|
||||
import Popup from '@/components/misc/popup.vue'
|
||||
import {DATE_RANGES} from '@/components/date/dateRanges'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import DatemathHelp from '@/components/date/DatemathHelp.vue'
|
||||
import DatemathHelp from '@/components/date/datemathHelp.vue'
|
||||
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
|
||||
|
||||
const props = defineProps({
|
|
@ -85,10 +85,10 @@ import flatPickr from 'vue-flatpickr-component'
|
|||
import 'flatpickr/dist/flatpickr.css'
|
||||
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
|
||||
|
||||
import Popup from '@/components/misc/Popup.vue'
|
||||
import Popup from '@/components/misc/popup.vue'
|
||||
import {DATE_VALUES} from '@/components/date/dateRanges'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import DatemathHelp from '@/components/date/DatemathHelp.vue'
|
||||
import DatemathHelp from '@/components/date/datemathHelp.vue'
|
||||
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
|
||||
|
||||
const props = defineProps({
|
|
@ -1,107 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'has-background': background,
|
||||
'link-share-is-fullwidth': isFullWidth,
|
||||
}"
|
||||
:style="{'background-image': `url(${background})`}"
|
||||
class="link-share-container"
|
||||
>
|
||||
<div class="has-text-centered link-share-view">
|
||||
<Logo
|
||||
v-if="logoVisible"
|
||||
class="logo"
|
||||
/>
|
||||
<h1
|
||||
:class="{'m-0': !logoVisible}"
|
||||
:style="{ 'opacity': currentProject?.title === '' ? '0': '1' }"
|
||||
class="title"
|
||||
>
|
||||
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
|
||||
</h1>
|
||||
<div class="box has-text-left view">
|
||||
<router-view />
|
||||
<PoweredByLink />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import PoweredByLink from './PoweredByLink.vue'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import {useLabelStore} from '@/stores/labels'
|
||||
import {PROJECT_VIEW_KINDS} from '@/modelTypes/IProjectView'
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const currentProject = computed(() => baseStore.currentProject)
|
||||
const background = computed(() => baseStore.background)
|
||||
const logoVisible = computed(() => baseStore.logoVisible)
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
projectStore.loadAllProjects()
|
||||
|
||||
const labelStore = useLabelStore()
|
||||
labelStore.loadAllLabels()
|
||||
|
||||
const route = useRoute()
|
||||
const isFullWidth = computed(() => {
|
||||
const viewId = route.params?.viewId ?? null
|
||||
const projectId = route.params?.projectId ?? null
|
||||
if (!viewId || !projectId) {
|
||||
return false
|
||||
}
|
||||
|
||||
const view = projectStore.projects[Number(projectId)]?.views.find(v => v.id === Number(viewId))
|
||||
|
||||
return view?.viewKind === PROJECT_VIEW_KINDS.KANBAN ||
|
||||
view?.viewKind === PROJECT_VIEW_KINDS.GANTT
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.link-share-container.has-background .view {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 300px;
|
||||
width: 90%;
|
||||
margin: 1rem auto 2rem;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-shadow: 0 0 1rem var(--white);
|
||||
}
|
||||
|
||||
.link-share-view {
|
||||
width: 100%;
|
||||
max-width: $desktop;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.link-share-container.link-share-is-fullwidth {
|
||||
.link-share-view {
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
|
||||
.link-share-container:not(.has-background) {
|
||||
:deep(.loader-container, .gantt-chart-container > .card) {
|
||||
box-shadow: none !important;
|
||||
border: none;
|
||||
|
||||
.task-add {
|
||||
padding: 1rem 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -9,7 +9,7 @@ import {MILLISECONDS_A_HOUR} from '@/constants/date'
|
|||
const now = useNow({
|
||||
interval: MILLISECONDS_A_HOUR,
|
||||
})
|
||||
const Logo = computed(() => window.ALLOW_ICON_CHANGES && now.value.getMonth() === 5 ? LogoFullPride : LogoFull)
|
||||
const Logo = computed(() => window.ALLOW_ICON_CHANGES && now.value.getMonth() === 6 ? LogoFullPride : LogoFull)
|
||||
const CustomLogo = computed(() => window.CUSTOM_LOGO_URL)
|
||||
</script>
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
:project="project"
|
||||
:is-loading="projectUpdating[project.id]"
|
||||
:can-collapse="canCollapse"
|
||||
:level="level"
|
||||
:data-project-id="project.id"
|
||||
/>
|
||||
</template>
|
||||
|
@ -48,6 +49,7 @@ const props = defineProps<{
|
|||
modelValue?: IProject[],
|
||||
canEditOrder: boolean,
|
||||
canCollapse?: boolean,
|
||||
level?: number,
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', projects: IProject[]): void
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
<ProjectSettingsDropdown
|
||||
class="menu-list-dropdown"
|
||||
:project="project"
|
||||
:level="level"
|
||||
>
|
||||
<template #trigger="{toggleOpen}">
|
||||
<BaseButton
|
||||
|
@ -77,6 +78,7 @@
|
|||
:model-value="childProjects"
|
||||
:can-edit-order="true"
|
||||
:can-collapse="canCollapse"
|
||||
:level="level + 1"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
|
@ -90,19 +92,21 @@ import {useStorage} from '@vueuse/core'
|
|||
import type {IProject} from '@/modelTypes/IProject'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import ProjectSettingsDropdown from '@/components/project/ProjectSettingsDropdown.vue'
|
||||
import ProjectSettingsDropdown from '@/components/project/project-settings-dropdown.vue'
|
||||
import {getProjectTitle} from '@/helpers/getProjectTitle'
|
||||
import ColorBubble from '@/components/misc/ColorBubble.vue'
|
||||
import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||
import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue'
|
||||
|
||||
const {
|
||||
project,
|
||||
isLoading,
|
||||
canCollapse,
|
||||
level = 0,
|
||||
} = defineProps<{
|
||||
project: IProject,
|
||||
isLoading?: boolean,
|
||||
canCollapse?: boolean,
|
||||
level?: number,
|
||||
}>()
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
|
|
|
@ -114,10 +114,10 @@ import { computed } from 'vue'
|
|||
|
||||
import { RIGHTS as Rights } from '@/constants/rights'
|
||||
|
||||
import ProjectSettingsDropdown from '@/components/project/ProjectSettingsDropdown.vue'
|
||||
import Dropdown from '@/components/misc/Dropdown.vue'
|
||||
import DropdownItem from '@/components/misc/DropdownItem.vue'
|
||||
import Notifications from '@/components/notifications/Notifications.vue'
|
||||
import ProjectSettingsDropdown from '@/components/project/project-settings-dropdown.vue'
|
||||
import Dropdown from '@/components/misc/dropdown.vue'
|
||||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
import Notifications from '@/components/notifications/notifications.vue'
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import MenuButton from '@/components/home/MenuButton.vue'
|
||||
|
|
|
@ -67,8 +67,8 @@
|
|||
import {watch, computed} from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
import Navigation from '@/components/home/Navigation.vue'
|
||||
import QuickActions from '@/components/quick-actions/QuickActions.vue'
|
||||
import Navigation from '@/components/home/navigation.vue'
|
||||
import QuickActions from '@/components/quick-actions/quick-actions.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
|
@ -162,8 +162,8 @@ projectStore.loadAllProjects()
|
|||
.app-content {
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
padding: 1.5rem 0.5rem 0;
|
||||
// TODO refactor: DRY `transition-timing-function` with `./Navigation.vue`.
|
||||
padding: 1.5rem 0.5rem 1rem;
|
||||
// TODO refactor: DRY `transition-timing-function` with `./navigation.vue`.
|
||||
transition: margin-left $transition-duration;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
|
@ -172,7 +172,7 @@ projectStore.loadAllProjects()
|
|||
}
|
||||
|
||||
@media screen and (min-width: $tablet) {
|
||||
padding: $navbar-height + 1.5rem 1.5rem 0 1.5rem;
|
||||
padding: $navbar-height + 1.5rem 1.5rem 1rem 1.5rem;
|
||||
}
|
||||
|
||||
&.is-menu-enabled {
|
72
frontend/src/components/home/contentLinkShare.vue
Normal file
72
frontend/src/components/home/contentLinkShare.vue
Normal file
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<div
|
||||
:class="[background ? 'has-background' : '', $route.name as string +'-view']"
|
||||
:style="{'background-image': `url(${background})`}"
|
||||
class="link-share-container"
|
||||
>
|
||||
<div class="container has-text-centered link-share-view">
|
||||
<div class="column is-10 is-offset-1">
|
||||
<Logo
|
||||
v-if="logoVisible"
|
||||
class="logo"
|
||||
/>
|
||||
<h1
|
||||
:class="{'m-0': !logoVisible}"
|
||||
:style="{ 'opacity': currentProject?.title === '' ? '0': '1' }"
|
||||
class="title"
|
||||
>
|
||||
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
|
||||
</h1>
|
||||
<div class="box has-text-left view">
|
||||
<router-view />
|
||||
<PoweredByLink />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import PoweredByLink from './PoweredByLink.vue'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const currentProject = computed(() => baseStore.currentProject)
|
||||
const background = computed(() => baseStore.background)
|
||||
const logoVisible = computed(() => baseStore.logoVisible)
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
projectStore.loadAllProjects()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.link-share-container.has-background .view {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 300px;
|
||||
width: 90%;
|
||||
margin: 2rem 0 1.5rem;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.column {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-shadow: 0 0 1rem var(--white);
|
||||
}
|
||||
|
||||
// FIXME: this should be defined somewhere deep
|
||||
.link-share-view .card {
|
||||
background-color: var(--white);
|
||||
}
|
||||
</style>
|
|
@ -118,7 +118,7 @@ import {computed} from 'vue'
|
|||
|
||||
import PoweredByLink from '@/components/home/PoweredByLink.vue'
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import Loading from '@/components/misc/Loading.vue'
|
||||
import Loading from '@/components/misc/loading.vue'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
@ -155,8 +155,8 @@ const savedFilterProjects = computed(() => projectStore.savedFilterProjects)
|
|||
bottom: 0;
|
||||
left: 0;
|
||||
transform: translateX(-100%);
|
||||
overflow-x: auto;
|
||||
width: $navbar-width;
|
||||
overflow-y: auto;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
top: 0;
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import {logEvent} from 'histoire/client'
|
||||
import XButton from './Button.vue'
|
||||
import XButton from './button.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, ref, watch} from 'vue'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
import XButton from '@/components/input/Button.vue'
|
||||
import XButton from '@/components/input/button.vue'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, ref, shallowReactive, watchEffect, type PropType} from 'vue'
|
||||
|
||||
import Multiselect from '@/components/input/Multiselect.vue'
|
||||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, ref, shallowReactive, watchEffect, type PropType} from 'vue'
|
||||
|
||||
import Multiselect from '@/components/input/Multiselect.vue'
|
||||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
|
||||
import type {IUser} from '@/modelTypes/IUser'
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
import {ref, onMounted, onBeforeUnmount, toRef, watch, type PropType} from 'vue'
|
||||
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import DatepickerInline from '@/components/input/DatepickerInline.vue'
|
||||
import DatepickerInline from '@/components/input/datepickerInline.vue'
|
||||
import SimpleButton from '@/components/input/SimpleButton.vue'
|
||||
|
||||
import {formatDateShort} from '@/helpers/time/formatDate'
|
|
@ -67,7 +67,6 @@
|
|||
class="tiptap__editor"
|
||||
:class="{'tiptap__editor-is-edit-enabled': isEditing}"
|
||||
:editor="editor"
|
||||
@dblclick="setEditIfApplicable()"
|
||||
@click="focusIfEditing()"
|
||||
/>
|
||||
|
||||
|
@ -172,7 +171,7 @@ import {OrderedList} from '@tiptap/extension-ordered-list'
|
|||
import {Paragraph} from '@tiptap/extension-paragraph'
|
||||
import {Strike} from '@tiptap/extension-strike'
|
||||
import {Text} from '@tiptap/extension-text'
|
||||
import {BubbleMenu, EditorContent, type Extensions, useEditor} from '@tiptap/vue-3'
|
||||
import {BubbleMenu, EditorContent, useEditor} from '@tiptap/vue-3'
|
||||
import {Node} from '@tiptap/pm/model'
|
||||
|
||||
import Commands from './commands'
|
||||
|
@ -187,10 +186,10 @@ import AttachmentModel from '@/models/attachment'
|
|||
import AttachmentService from '@/services/attachment'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import XButton from '@/components/input/Button.vue'
|
||||
import XButton from '@/components/input/button.vue'
|
||||
import {Placeholder} from '@tiptap/extension-placeholder'
|
||||
import {eventToHotkeyString} from '@github/hotkey'
|
||||
import {Extension, mergeAttributes} from '@tiptap/core'
|
||||
import {mergeAttributes} from '@tiptap/core'
|
||||
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||
import inputPrompt from '@/helpers/inputPrompt'
|
||||
import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
|
||||
|
@ -203,7 +202,6 @@ const {
|
|||
showSave = false,
|
||||
placeholder = '',
|
||||
editShortcut = '',
|
||||
enableDiscardShortcut = false,
|
||||
} = defineProps<{
|
||||
modelValue: string,
|
||||
uploadCallback?: UploadCallback,
|
||||
|
@ -212,7 +210,6 @@ const {
|
|||
showSave?: boolean,
|
||||
placeholder?: string,
|
||||
editShortcut?: string,
|
||||
enableDiscardShortcut?: boolean,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'save'])
|
||||
|
@ -314,8 +311,6 @@ const internalMode = ref<Mode>('preview')
|
|||
const isEditing = computed(() => internalMode.value === 'edit' && isEditEnabled)
|
||||
const contentHasChanged = ref<boolean>(false)
|
||||
|
||||
let lastSavedState = modelValue
|
||||
|
||||
watch(
|
||||
() => internalMode.value,
|
||||
mode => {
|
||||
|
@ -325,127 +320,107 @@ watch(
|
|||
},
|
||||
)
|
||||
|
||||
const extensions : Extensions = [
|
||||
// Starterkit:
|
||||
Blockquote,
|
||||
Bold,
|
||||
BulletList,
|
||||
Code,
|
||||
CodeBlockLowlight.configure({
|
||||
lowlight,
|
||||
}),
|
||||
Document,
|
||||
Dropcursor,
|
||||
Gapcursor,
|
||||
HardBreak.extend({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
'Shift-Enter': () => this.editor.commands.setHardBreak(),
|
||||
'Mod-Enter': () => {
|
||||
if (contentHasChanged.value) {
|
||||
const editor = useEditor({
|
||||
// eslint-disable-next-line vue/no-ref-object-destructure
|
||||
editable: isEditing.value,
|
||||
extensions: [
|
||||
// Starterkit:
|
||||
Blockquote,
|
||||
Bold,
|
||||
BulletList,
|
||||
Code,
|
||||
CodeBlockLowlight.configure({
|
||||
lowlight,
|
||||
}),
|
||||
Document,
|
||||
Dropcursor,
|
||||
Gapcursor,
|
||||
HardBreak.extend({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
'Mod-Enter': () => {
|
||||
if (contentHasChanged.value) {
|
||||
bubbleSave()
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
Heading,
|
||||
History,
|
||||
HorizontalRule,
|
||||
Italic,
|
||||
ListItem,
|
||||
OrderedList,
|
||||
Paragraph,
|
||||
Strike,
|
||||
Text,
|
||||
|
||||
Placeholder.configure({
|
||||
placeholder: ({editor}) => {
|
||||
if (!isEditing.value) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (editor.getText() !== '' && !editor.isFocused) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return placeholder !== ''
|
||||
? placeholder
|
||||
: t('input.editor.placeholder')
|
||||
},
|
||||
}),
|
||||
Typography,
|
||||
Underline,
|
||||
Link.configure({
|
||||
openOnClick: false,
|
||||
validate: (href: string) => /^https?:\/\//.test(href),
|
||||
}),
|
||||
Table.configure({
|
||||
resizable: true,
|
||||
}),
|
||||
TableRow,
|
||||
TableHeader,
|
||||
// Custom TableCell with backgroundColor attribute
|
||||
CustomTableCell,
|
||||
|
||||
CustomImage,
|
||||
|
||||
TaskList,
|
||||
TaskItem.configure({
|
||||
nested: true,
|
||||
onReadOnlyChecked: (node: Node, checked: boolean): boolean => {
|
||||
if (!isEditEnabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
// The following is a workaround for this bug:
|
||||
// https://github.com/ueberdosis/tiptap/issues/4521
|
||||
// https://github.com/ueberdosis/tiptap/issues/3676
|
||||
|
||||
editor.value!.state.doc.descendants((subnode, pos) => {
|
||||
if (node.eq(subnode)) {
|
||||
const {tr} = editor.value!.state
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
...node.attrs,
|
||||
checked,
|
||||
})
|
||||
editor.value!.view.dispatch(tr)
|
||||
bubbleSave()
|
||||
}
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
Heading,
|
||||
History,
|
||||
HorizontalRule,
|
||||
Italic,
|
||||
ListItem,
|
||||
OrderedList,
|
||||
Paragraph,
|
||||
Strike,
|
||||
Text,
|
||||
|
||||
Placeholder.configure({
|
||||
placeholder: ({editor}) => {
|
||||
if (!isEditing.value) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (editor.getText() !== '' && !editor.isFocused) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return placeholder !== ''
|
||||
? placeholder
|
||||
: t('input.editor.placeholder')
|
||||
},
|
||||
}),
|
||||
Typography,
|
||||
Underline,
|
||||
Link.configure({
|
||||
openOnClick: false,
|
||||
validate: (href: string) => /^https?:\/\//.test(href),
|
||||
}),
|
||||
Table.configure({
|
||||
resizable: true,
|
||||
}),
|
||||
TableRow,
|
||||
TableHeader,
|
||||
// Custom TableCell with backgroundColor attribute
|
||||
CustomTableCell,
|
||||
|
||||
CustomImage,
|
||||
|
||||
TaskList,
|
||||
TaskItem.configure({
|
||||
nested: true,
|
||||
onReadOnlyChecked: (node: Node, checked: boolean): boolean => {
|
||||
if (!isEditEnabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
// The following is a workaround for this bug:
|
||||
// https://github.com/ueberdosis/tiptap/issues/4521
|
||||
// https://github.com/ueberdosis/tiptap/issues/3676
|
||||
|
||||
editor.value!.state.doc.descendants((subnode, pos) => {
|
||||
if (node.eq(subnode)) {
|
||||
const {tr} = editor.value!.state
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
...node.attrs,
|
||||
checked,
|
||||
})
|
||||
editor.value!.view.dispatch(tr)
|
||||
bubbleSave()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
return true
|
||||
},
|
||||
}),
|
||||
return true
|
||||
},
|
||||
}),
|
||||
|
||||
Commands.configure({
|
||||
suggestion: suggestionSetup(t),
|
||||
}),
|
||||
BubbleMenu,
|
||||
]
|
||||
|
||||
// Add a custom extension for the Escape key
|
||||
if (enableDiscardShortcut) {
|
||||
extensions.push(Extension.create({
|
||||
name: 'escapeKey',
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
'Escape': () => {
|
||||
exitEditMode()
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
const editor = useEditor({
|
||||
// eslint-disable-next-line vue/no-ref-object-reactivity-loss
|
||||
editable: isEditing.value,
|
||||
extensions: extensions,
|
||||
Commands.configure({
|
||||
suggestion: suggestionSetup(t),
|
||||
}),
|
||||
BubbleMenu,
|
||||
],
|
||||
onUpdate: () => {
|
||||
bubbleNow()
|
||||
},
|
||||
|
@ -484,27 +459,12 @@ function bubbleNow() {
|
|||
|
||||
function bubbleSave() {
|
||||
bubbleNow()
|
||||
lastSavedState = editor.value?.getHTML() ?? ''
|
||||
emit('save', lastSavedState)
|
||||
emit('save', editor.value?.getHTML())
|
||||
if (isEditing.value) {
|
||||
internalMode.value = 'preview'
|
||||
}
|
||||
}
|
||||
|
||||
function exitEditMode() {
|
||||
editor.value?.commands.setContent(lastSavedState, false)
|
||||
if (isEditing.value) {
|
||||
internalMode.value = 'preview'
|
||||
}
|
||||
}
|
||||
|
||||
function setEditIfApplicable() {
|
||||
if (!isEditEnabled) return
|
||||
if (isEditing.value) return
|
||||
|
||||
setEdit()
|
||||
}
|
||||
|
||||
function setEdit(focus: boolean = true) {
|
||||
internalMode.value = 'edit'
|
||||
if (focus) {
|
||||
|
@ -856,7 +816,7 @@ watch(
|
|||
td,
|
||||
th {
|
||||
min-width: 1em;
|
||||
border: 2px solid var(--grey-300) !important;
|
||||
border: 2px solid #ced4da;
|
||||
padding: 3px 5px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
|
@ -870,7 +830,7 @@ watch(
|
|||
th {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
background-color: var(--grey-200);
|
||||
background-color: #f1f3f5;
|
||||
}
|
||||
|
||||
.selectedCell:after {
|
||||
|
@ -931,14 +891,8 @@ ul[data-type='taskList'] {
|
|||
padding: 0;
|
||||
margin-left: 0;
|
||||
|
||||
li[data-checked='true'] {
|
||||
color: var(--grey-500);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
margin-top: 0.25rem;
|
||||
|
||||
> label {
|
||||
flex: 0 0 auto;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import {ref} from 'vue'
|
||||
import {logEvent} from 'histoire/client'
|
||||
import FancyCheckbox from './FancyCheckbox.vue'
|
||||
import FancyCheckbox from './fancycheckbox.vue'
|
||||
|
||||
const isDisabled = ref<boolean | undefined>()
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<BaseCheckbox
|
||||
class="fancy-checkbox"
|
||||
class="fancycheckbox"
|
||||
:class="{
|
||||
'is-disabled': disabled,
|
||||
'is-block': isBlock,
|
||||
|
@ -9,10 +9,10 @@
|
|||
:model-value="modelValue"
|
||||
@update:modelValue="value => emit('update:modelValue', value)"
|
||||
>
|
||||
<CheckboxIcon class="fancy-checkbox__icon" />
|
||||
<CheckboxIcon class="fancycheckbox__icon" />
|
||||
<span
|
||||
v-if="$slots.default"
|
||||
class="fancy-checkbox__content"
|
||||
class="fancycheckbox__content"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
|
@ -44,7 +44,7 @@ const emit = defineEmits<{
|
|||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fancy-checkbox {
|
||||
.fancycheckbox {
|
||||
display: inline-block;
|
||||
padding-right: 5px;
|
||||
padding-top: 3px;
|
||||
|
@ -55,13 +55,13 @@ const emit = defineEmits<{
|
|||
}
|
||||
}
|
||||
|
||||
.fancy-checkbox__content {
|
||||
.fancycheckbox__content {
|
||||
font-size: 0.8rem;
|
||||
vertical-align: top;
|
||||
padding-left: .5rem;
|
||||
}
|
||||
|
||||
.fancy-checkbox__icon:deep() {
|
||||
.fancycheckbox__icon:deep() {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
stroke: var(--stroke-color, #c8ccd4);
|
||||
|
@ -74,8 +74,8 @@ const emit = defineEmits<{
|
|||
}
|
||||
}
|
||||
|
||||
.fancy-checkbox:not(:has(input:disabled)):hover .fancy-checkbox__icon,
|
||||
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
|
||||
.fancycheckbox:not(:has(input:disabled)):hover .fancycheckbox__icon,
|
||||
.fancycheckbox:has(input:checked) .fancycheckbox__icon {
|
||||
--stroke-color: var(--primary);
|
||||
}
|
||||
</style>
|
||||
|
@ -84,13 +84,13 @@ const emit = defineEmits<{
|
|||
// Since css-has-pseudo doesn't work with deep classes,
|
||||
// the following rules can't be scoped
|
||||
|
||||
.fancy-checkbox:has(:not(input:checked)) .fancy-checkbox__icon {
|
||||
.fancycheckbox:has(:not(input:checked)) .fancycheckbox__icon {
|
||||
path {
|
||||
transition-delay: 0.05s;
|
||||
}
|
||||
}
|
||||
|
||||
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
|
||||
.fancycheckbox:has(input:checked) .fancycheckbox__icon {
|
||||
path {
|
||||
stroke-dashoffset: 60;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import Card from './Card.vue'
|
||||
import Card from './card.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -24,15 +24,11 @@ import {
|
|||
faCocktail,
|
||||
faCoffee,
|
||||
faCog,
|
||||
faCopy,
|
||||
faDownload,
|
||||
faEllipsisH,
|
||||
faEllipsisV,
|
||||
faExclamation,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faFile,
|
||||
faFileImage,
|
||||
faFillDrip,
|
||||
faFilter,
|
||||
faForward,
|
||||
|
@ -85,6 +81,7 @@ import {
|
|||
faCheckSquare,
|
||||
faClock,
|
||||
faComments,
|
||||
faFileImage,
|
||||
faSave,
|
||||
faSquareCheck,
|
||||
faStar,
|
||||
|
@ -105,7 +102,6 @@ library.add(faUnlink)
|
|||
library.add(faParagraph)
|
||||
library.add(faSquareCheck)
|
||||
library.add(faTable)
|
||||
library.add(faFile)
|
||||
library.add(faFileImage)
|
||||
library.add(faCheckSquare)
|
||||
library.add(faStrikethrough)
|
||||
|
@ -134,8 +130,6 @@ library.add(faCocktail)
|
|||
library.add(faCoffee)
|
||||
library.add(faCog)
|
||||
library.add(faComments)
|
||||
library.add(faCopy)
|
||||
library.add(faDownload)
|
||||
library.add(faEllipsisH)
|
||||
library.add(faEllipsisV)
|
||||
library.add(faExclamation)
|
||||
|
|
|
@ -68,7 +68,7 @@ import {parseURL} from 'ufo'
|
|||
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
||||
import {success} from '@/message'
|
||||
|
||||
import Message from '@/components/misc/Message.vue'
|
||||
import Message from '@/components/misc/message.vue'
|
||||
import ButtonLink from '@/components/misc/ButtonLink.vue'
|
||||
|
||||
const props = defineProps({
|
|
@ -14,13 +14,15 @@
|
|||
</Message>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Message from '@/components/misc/Message.vue'
|
||||
import ButtonLink from '@/components/misc/ButtonLink.vue'
|
||||
|
||||
defineOptions({
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Message from '@/components/misc/message.vue'
|
||||
import ButtonLink from '@/components/misc/ButtonLink.vue'
|
||||
|
||||
function reload() {
|
||||
window.location.reload()
|
|
@ -50,8 +50,8 @@
|
|||
<script lang="ts" setup>
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
|
||||
import Shortcut from '@/components/misc/Shortcut.vue'
|
||||
import Message from '@/components/misc/Message.vue'
|
||||
import Shortcut from '@/components/misc/shortcut.vue'
|
||||
import Message from '@/components/misc/message.vue'
|
||||
|
||||
import {KEYBOARD_SHORTCUTS as shortcuts} from './shortcuts'
|
||||
|
||||
|
|
|
@ -4,13 +4,13 @@ import {isAppleDevice} from '@/helpers/isAppleDevice'
|
|||
|
||||
const ctrl = isAppleDevice() ? '⌘' : 'ctrl'
|
||||
|
||||
export interface Shortcut {
|
||||
interface Shortcut {
|
||||
title: string
|
||||
keys: string[]
|
||||
combination?: 'then'
|
||||
}
|
||||
|
||||
export interface ShortcutGroup {
|
||||
interface ShortcutGroup {
|
||||
title: string
|
||||
available?: (route: RouteLocation) => boolean
|
||||
shortcuts: Shortcut[]
|
||||
|
@ -158,4 +158,4 @@ export const KEYBOARD_SHORTCUTS : ShortcutGroup[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
] as const
|
||||
]
|
|
@ -66,39 +66,41 @@
|
|||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import {ref, useAttrs, watchEffect} from 'vue'
|
||||
import {useScrollLock} from '@vueuse/core'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
const {
|
||||
enabled = true,
|
||||
overflow,
|
||||
wide,
|
||||
transitionName = 'modal',
|
||||
variant = 'default',
|
||||
} = defineProps<{
|
||||
enabled?: boolean,
|
||||
overflow?: boolean,
|
||||
wide?: boolean,
|
||||
transitionName?: 'modal' | 'fade',
|
||||
variant?: 'default' | 'hint-modal' | 'scrolling',
|
||||
}>(), {
|
||||
enabled: true,
|
||||
overflow: false,
|
||||
wide: false,
|
||||
transitionName: 'modal',
|
||||
variant: 'default',
|
||||
})
|
||||
}>()
|
||||
|
||||
defineEmits(['close', 'submit'])
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
const modal = ref<HTMLElement | null>(null)
|
||||
const scrollLock = useScrollLock(modal)
|
||||
|
||||
watchEffect(() => {
|
||||
scrollLock.value = props.enabled
|
||||
scrollLock.value = enabled
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -186,6 +188,12 @@ $modal-width: 1024px;
|
|||
.info {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,9 +46,9 @@ import {useRoute} from 'vue-router'
|
|||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import Message from '@/components/misc/Message.vue'
|
||||
import Legal from '@/components/misc/Legal.vue'
|
||||
import ApiConfig from '@/components/misc/ApiConfig.vue'
|
||||
import Message from '@/components/misc/message.vue'
|
||||
import Legal from '@/components/misc/legal.vue'
|
||||
import ApiConfig from '@/components/misc/api-config.vue'
|
||||
|
||||
import {useTitle} from '@/composables/useTitle'
|
||||
import {useConfigStore} from '@/stores/config'
|
|
@ -10,7 +10,7 @@
|
|||
class="popup"
|
||||
:class="{
|
||||
'is-open': openValue,
|
||||
'has-overflow': hasOverflow && openValue
|
||||
'has-overflow': props.hasOverflow && openValue
|
||||
}"
|
||||
>
|
||||
<slot
|
||||
|
@ -23,23 +23,31 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, watchEffect} from 'vue'
|
||||
import {ref, watch} from 'vue'
|
||||
import {onClickOutside} from '@vueuse/core'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
hasOverflow?: boolean
|
||||
open?: boolean
|
||||
}>(), {
|
||||
hasOverflow: false,
|
||||
open: false,
|
||||
const props = defineProps({
|
||||
hasOverflow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const openValue = ref(props.open)
|
||||
watchEffect(() => {
|
||||
openValue.value = props.open
|
||||
})
|
||||
watch(
|
||||
() => props.open,
|
||||
nowOpen => {
|
||||
openValue.value = nowOpen
|
||||
},
|
||||
)
|
||||
|
||||
const openValue = ref(false)
|
||||
const popup = ref<HTMLElement | null>(null)
|
||||
|
||||
function close() {
|
||||
openValue.value = false
|
||||
|
@ -50,8 +58,6 @@ function toggle() {
|
|||
openValue.value = !openValue.value
|
||||
}
|
||||
|
||||
const popup = ref<HTMLElement | null>(null)
|
||||
|
||||
onClickOutside(popup, () => {
|
||||
if (!openValue.value) {
|
||||
return
|
|
@ -61,10 +61,10 @@ import {ref, computed} from 'vue'
|
|||
import {useRouter, useRoute} from 'vue-router'
|
||||
|
||||
import Logo from '@/assets/logo.svg?component'
|
||||
import ApiConfig from '@/components/misc/ApiConfig.vue'
|
||||
import Message from '@/components/misc/Message.vue'
|
||||
import ApiConfig from '@/components/misc/api-config.vue'
|
||||
import Message from '@/components/misc/message.vue'
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import NoAuthWrapper from '@/components/misc/NoAuthWrapper.vue'
|
||||
import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
|
||||
|
||||
import {ERROR_NO_API_URL, InvalidApiUrlProvidedError, NoApiUrlProvidedError} from '@/helpers/checkAndSetApiUrl'
|
||||
import {useOnline} from '@/composables/useOnline'
|
|
@ -14,13 +14,19 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
withDefaults(defineProps<{
|
||||
keys: string[],
|
||||
combination?: string,
|
||||
is?: string
|
||||
}>(), {
|
||||
combination: '+',
|
||||
is: 'div',
|
||||
defineProps({
|
||||
keys: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
combination: {
|
||||
type: String,
|
||||
default: '+',
|
||||
},
|
||||
is: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -33,11 +33,11 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, shallowReactive} from 'vue'
|
||||
import {computed, shallowRef, type PropType} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import DropdownItem from '@/components/misc/DropdownItem.vue'
|
||||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
|
||||
import SubscriptionService from '@/services/subscription'
|
||||
import SubscriptionModel from '@/models/subscription'
|
||||
|
@ -46,23 +46,28 @@ import type {ISubscription} from '@/modelTypes/ISubscription'
|
|||
import {success} from '@/message'
|
||||
import type { IconProp } from '@fortawesome/fontawesome-svg-core'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
entity: ISubscription['entity'],
|
||||
entityId: number,
|
||||
isButton?: boolean,
|
||||
modelValue?: ISubscription,
|
||||
type?: 'button' | 'dropdown',
|
||||
}>(), {
|
||||
isButton: true,
|
||||
modelValue: null,
|
||||
type: 'button',
|
||||
const props = defineProps({
|
||||
entity: String as ISubscription['entity'],
|
||||
entityId: Number,
|
||||
isButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: Object as PropType<ISubscription>,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'button' | 'dropdown' | 'null'>,
|
||||
default: 'button',
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const subscriptionEntity = computed<string | null>(() => props.modelValue?.entity ?? null)
|
||||
|
||||
const subscriptionService = shallowReactive(new SubscriptionService())
|
||||
const subscriptionService = shallowRef(new SubscriptionService())
|
||||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
|
@ -72,8 +77,8 @@ const tooltipText = computed(() => {
|
|||
return t('task.subscription.subscribedTaskThroughParentProject')
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
switch (props.entity) {
|
||||
case 'project':
|
||||
|
@ -110,7 +115,7 @@ async function subscribe() {
|
|||
entity: props.entity,
|
||||
entityId: props.entityId,
|
||||
})
|
||||
await subscriptionService.create(subscription)
|
||||
await subscriptionService.value.create(subscription)
|
||||
emit('update:modelValue', subscription)
|
||||
|
||||
let message = ''
|
||||
|
@ -130,7 +135,7 @@ async function unsubscribe() {
|
|||
entity: props.entity,
|
||||
entityId: props.entityId,
|
||||
})
|
||||
await subscriptionService.delete(subscription)
|
||||
await subscriptionService.value.delete(subscription)
|
||||
emit('update:modelValue', null)
|
||||
|
||||
let message = ''
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user