Compare commits

...

580 Commits

Author SHA1 Message Date
renovate[bot]
f865bd8555 fix(deps): update dependency express to v5.2.1 (#1932)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [express](https://expressjs.com/)
([source](https://redirect.github.com/expressjs/express)) | [`5.2.0` ->
`5.2.1`](https://renovatebot.com/diffs/npm/express/5.2.0/5.2.1) |
![age](https://developer.mend.io/api/mc/badges/age/npm/express/5.2.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/express/5.2.0/5.2.1?slim=true)
|

---

### Release Notes

<details>
<summary>expressjs/express (express)</summary>

###
[`v5.2.1`](https://redirect.github.com/expressjs/express/blob/HEAD/History.md#521--2025-12-01)

[Compare
Source](https://redirect.github.com/expressjs/express/compare/v5.2.0...v5.2.1)

\=======================

- Revert security fix for
[CVE-2024-51999](https://www.cve.org/CVERecord?id=CVE-2024-51999)
([GHSA-pj86-cfqh-vqx6](https://redirect.github.com/expressjs/express/security/advisories/GHSA-pj86-cfqh-vqx6))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zMi4yIiwidXBkYXRlZEluVmVyIjoiNDIuMzIuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 09:04:57 +01:00
renovate[bot]
5a17c56735 chore(deps): update node.js to 682368d (#1931)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [node](https://redirect.github.com/nodejs/node)
([changelog](https://redirect.github.com/nodejs/node/compare/sha256:2867d550cf9d8bb50059a0fff528741f11a84d985c732e60e19e8e75c7239c43..sha256:682368d8253e0c3364b803956085c456a612d738bd635926d73fa24db3ce53d7))
| stage | digest | `2867d55` -> `682368d` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zMi4yIiwidXBkYXRlZEluVmVyIjoiNDIuMzIuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 09:04:37 +01:00
kolaente
112df4a752 fix(caldav): init logger in tests 2025-12-04 11:10:19 +01:00
kolaente
da0822c3f4 feat(caldav): add more error logging 2025-12-04 10:54:31 +01:00
Copilot
30104fb749 fix: escape backticks and special chars in commit message for GitHub Action (#1928)
The `issue-closed-comment` workflow fails when commit messages contain
backticks because they're interpolated directly into JS template
strings, breaking syntax.

### Changes
- Escape backslashes, backticks, and `${` sequences before setting the
commit message output
- Order matters: backslashes first to avoid interfering with subsequent
escaping

```javascript
// Before: raw message breaks template string if it contains backticks
core.setOutput('commit_message', commit.message);

// After: properly escaped for safe interpolation
const escapedMessage = commit.message.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
core.setOutput('commit_message', escapedMessage);
```

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> the github action which comments on issue closure fails when the
commit message contains ` since these are js strings. Make sure to
escape them.


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

 Let Copilot coding agent [set things up for
you](https://github.com/go-vikunja/vikunja/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-12-04 09:38:24 +00:00
Copilot
7cf2a6886e fix: clear error when duplicating project with uploaded background (#1926)
Resolves https://github.com/go-vikunja/vikunja/issues/1745

- [x] Understand the issue from GitHub issue #1745
- [x] Analyze the codebase to locate the bug in
`duplicateProjectBackground` function
- [x] Fix the bug: return nil explicitly at the end of
duplicateProjectBackground
- [x] Add test for duplicating a project with an uploaded background (as
subtest)
- [x] Run tests and verify the fix
- [x] Run code review and address any feedback
- [x] Run CodeQL security scan

## Summary of Changes

### Problem
When duplicating a project with an uploaded (non-Unsplash) background
image, users encounter an internal server error (HTTP 500). The backend
logs show: `file was not downloaded from unsplash [FileID: X]`

### Root Cause
The `duplicateProjectBackground` function in
`pkg/models/project_duplicate.go` uses named returns. When
`GetUnsplashPhotoByFileID` returns `ErrFileIsNotUnsplashFile` for an
uploaded background, the error was intentionally ignored (to proceed
with copying the file) but not cleared from the named return variable.
This caused the error to be returned at the end of the function via the
bare `return` statement, triggering a 500 response.

### Solution
Changed the bare `return` at the end of `duplicateProjectBackground` to
`return nil` explicitly.

### Changes
1. **`pkg/models/project_duplicate.go`**: Changed bare `return` to
`return nil` at the end of `duplicateProjectBackground`
2. **`pkg/models/project_duplicate_test.go`**: Added subtest "duplicate
project with uploaded background" to `TestProjectDuplicate`

### Testing
- All existing tests pass
- Added subtest to `TestProjectDuplicate` for uploaded background
scenario (project 35 with non-Unsplash background)

### Security Summary
- No security vulnerabilities found by CodeQL
- Code review passed

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> # Duplicate project with uploaded background - Implementation Plan
> 
> ## Overview
> Users encounter an internal server error when duplicating a project
that uses an uploaded background image (non-Unsplash). The b
> ackend attempt to copy the background leaves a non-Unsplash error
(`ErrFileIsNotUnsplashFile`) in a named return value, causing
> the duplication API call to fail even though the error should be
ignored. We need to adjust the duplication flow to allow upload
> ed backgrounds and add regression tests.
> 
> ## Current State Analysis
> - Project duplication calls `duplicateProjectBackground` to copy the
background file. The helper tries to copy a downloaded Unsp
> lash image and returns `ErrFileIsNotUnsplashFile` for uploaded files.
> - In the duplication code, the error variable is not cleared after
intentionally ignoring this specific error, so the function s
> till returns the error and triggers a 500 response.
> - There are no automated regression tests covering project duplication
with uploaded backgrounds.
> 
> ### Key Discoveries
> - The duplication logic treats Unsplash and uploaded backgrounds
differently and only clears the Unsplash download error, leavin
> g the non-Unsplash error set.
> - The API currently works for Unsplash backgrounds but fails for
uploaded backgrounds due to the lingering error value.
> 
> ## Desired End State
> - Duplicating a project succeeds for both Unsplash and uploaded
backgrounds.
> - Uploaded background files (and their metadata) are copied correctly
to the new project when possible, or gracefully skipped wi
> thout failing duplication.
> - Regression tests cover duplication with both background types to
prevent future regressions.
> 
> ## What We're NOT Doing
> - No changes to the background upload endpoints or UI selection
workflow.
> - No changes to Unsplash download behavior or quota handling.
> - No new migration or database schema changes.
> 
> ## Implementation Approach
> 1. Fix backend duplication error handling so uploaded backgrounds do
not cause a fatal error.
> 2. Add backend tests to cover duplication with uploaded backgrounds
and Unsplash backgrounds (success paths) and verify duplicat
> ion works without returning 500 errors.
> 3. Ensure tests document the expected behavior and guard against
regressions.
> 
> ## Phase 1: Fix duplication error handling
> ### Overview
> Make project duplication tolerate uploaded backgrounds by clearing or
not propagating `ErrFileIsNotUnsplashFile` once it has bee
> n intentionally ignored.
> 
> ### Changes Required
> - **File:** `pkg/models/projects.go` (or relevant duplication helper)
> - Adjust `duplicateProjectBackground` (or the calling logic) to reset
the named return error after handling `ErrFileIsNotUnspl
> ashFile`, ensuring the function returns `nil` when no real error
occurs.
> - Keep existing behavior for other errors and for Unsplash downloads.
> 
> ### Success Criteria
> - Uploaded background duplication no longer returns an internal server
error.
> - Unsplash background duplication remains functional and still
surfaces real errors.
> 
> ## Phase 2: Add regression tests
> ### Overview
> Add automated tests verifying project duplication works for both
uploaded and Unsplash backgrounds.
> 
> ### Changes Required
> - **File:** `pkg/models/projects_test.go` (or closest existing test
file for project duplication)
> - Add a test that sets up a project with an uploaded background file,
duplicates the project, and asserts duplication succeeds
>  and the duplicated project has an appropriate background reference.
> - Add/adjust test coverage for Unsplash background duplication to
confirm unchanged behavior.
> - Use existing fixtures or temporary files as needed for uploaded
background setup.
> 
> ### Success Criteria
> - Tests fail on current main branch but pass after the fix.
> - Tests validate that duplication completes without 500 errors for
both background types.
> 
> ## Testing Strategy
> - Automated Go tests via `mage test:filter` targeting the new
duplication tests.
> - Optionally run the broader suite (`mage test:feature`) if time
permits to ensure no regressions.
> 
> ## Manual Verification
> 1. Create a project and upload a background via the UI; duplicate it;
observe duplication succeeds and background is present or
> gracefully handled.
> 2. Create a project with an Unsplash background; duplicate it; verify
duplication succeeds.
> 3. Check API responses for duplication calls to ensure no internal
server errors.


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-12-04 10:16:16 +01:00
renovate[bot]
5bb53eaefa fix(deps): update module github.com/spf13/cobra to v1.10.2 (#1927)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [github.com/spf13/cobra](https://redirect.github.com/spf13/cobra) |
`v1.10.1` -> `v1.10.2` |
![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fspf13%2fcobra/v1.10.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fspf13%2fcobra/v1.10.1/v1.10.2?slim=true)
|

---

### Release Notes

<details>
<summary>spf13/cobra (github.com/spf13/cobra)</summary>

###
[`v1.10.2`](https://redirect.github.com/spf13/cobra/releases/tag/v1.10.2)

[Compare
Source](https://redirect.github.com/spf13/cobra/compare/v1.10.1...v1.10.2)

#### 🔧 Dependencies

- chore: Migrate from `gopkg.in/yaml.v3` to `go.yaml.in/yaml/v3` by
[@&#8203;dims](https://redirect.github.com/dims) in
[#&#8203;2336](https://redirect.github.com/spf13/cobra/pull/2336) - the
`gopkg.in/yaml.v3` package has been deprecated for some time: this
should significantly cleanup dependency/supply-chains for consumers of
`spf13/cobra`

#### 📈 CI/CD

- Fix linter and allow CI to pass by
[@&#8203;marckhouzam](https://redirect.github.com/marckhouzam) in
[#&#8203;2327](https://redirect.github.com/spf13/cobra/pull/2327)
- fix: actions/setup-go v6 by
[@&#8203;jpmcb](https://redirect.github.com/jpmcb) in
[#&#8203;2337](https://redirect.github.com/spf13/cobra/pull/2337)

#### 🔥✍🏼 Docs

- Add documentation for repeated flags functionality by
[@&#8203;rvergis](https://redirect.github.com/rvergis) in
[#&#8203;2316](https://redirect.github.com/spf13/cobra/pull/2316)

#### 🍂 Refactors

- refactor: replace several vars with consts by
[@&#8203;htoyoda18](https://redirect.github.com/htoyoda18) in
[#&#8203;2328](https://redirect.github.com/spf13/cobra/pull/2328)
- refactor: change minUsagePadding from var to const by
[@&#8203;ssam18](https://redirect.github.com/ssam18) in
[#&#8203;2325](https://redirect.github.com/spf13/cobra/pull/2325)

#### 🤗 New Contributors

- [@&#8203;rvergis](https://redirect.github.com/rvergis) made their
first contribution in
[#&#8203;2316](https://redirect.github.com/spf13/cobra/pull/2316)
- [@&#8203;htoyoda18](https://redirect.github.com/htoyoda18) made their
first contribution in
[#&#8203;2328](https://redirect.github.com/spf13/cobra/pull/2328)
- [@&#8203;ssam18](https://redirect.github.com/ssam18) made their first
contribution in
[#&#8203;2325](https://redirect.github.com/spf13/cobra/pull/2325)
- [@&#8203;dims](https://redirect.github.com/dims) made their first
contribution in
[#&#8203;2336](https://redirect.github.com/spf13/cobra/pull/2336)

**Full Changelog**:
<https://github.com/spf13/cobra/compare/v1.10.1...v1.10.2>

Thank you to our amazing contributors!!!!! 🐍 🚀

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zMi4yIiwidXBkYXRlZEluVmVyIjoiNDIuMzIuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 09:07:54 +01:00
Frederick [Bot]
cec8daba59 chore(i18n): update translations via Crowdin 2025-12-04 00:57:27 +00:00
renovate[bot]
96acdb1692 chore(deps): update actions/checkout digest to 8e8c483 (#1922)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://redirect.github.com/actions/checkout) |
action | digest | `1af3b93` -> `8e8c483` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 11:23:29 +01:00
renovate[bot]
780c5b3b6f chore(deps): update cypress/browsers:latest docker digest to ff79e75 (#1923)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| cypress/browsers | container | digest | `7331c59` -> `ff79e75` |
| cypress/browsers |  | digest | `7331c59` -> `ff79e75` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 11:23:11 +01:00
renovate[bot]
38eae2ae9e chore(deps): update golangci/golangci-lint-action digest to 1e7e51e (#1924)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[golangci/golangci-lint-action](https://redirect.github.com/golangci/golangci-lint-action)
| action | digest | `e7fa5ac` -> `1e7e51e` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 11:22:57 +01:00
renovate[bot]
5a36351f44 chore(deps): update actions/setup-node digest to 395ad32 (#1925)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-node](https://redirect.github.com/actions/setup-node) |
action | digest | `2028fbc` -> `395ad32` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 11:21:57 +01:00
renovate[bot]
90f97a07c2 chore(deps): update dependency go to v1.25.5 (#1921)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/)
([source](https://redirect.github.com/golang/go)) | toolchain | patch |
`1.25.4` -> `1.25.5` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 17:46:01 +01:00
Mithilesh Gupta
a0a8111acb feat: Added background brightness setting (#1915)
Closes https://github.com/go-vikunja/vikunja/pull/1142

---------

Co-authored-by: Mithilesh Gupta <guptamithilesh@protonmail.com>
Co-authored-by: kolaente <k@knt.li>
2025-12-02 16:27:00 +00:00
renovate[bot]
a54c3473b2 chore(deps): update crowdin/github-action digest to 60debf3 (#1920)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[crowdin/github-action](https://redirect.github.com/crowdin/github-action)
| action | digest | `08713f0` -> `60debf3` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 16:28:52 +01:00
kolaente
2b98cf643f chore(deps): update js-yaml 2025-12-02 10:39:36 +01:00
kolaente
478d7b253d chore(deps): update mdast-util-to-hast 2025-12-02 10:37:36 +01:00
kolaente
a5376d7dd3 chore(deps): update glob 2025-12-02 10:35:42 +01:00
renovate[bot]
35c0ecc72e fix(deps): update dependency vue-i18n to v11.2.2 (#1908)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[vue-i18n](https://redirect.github.com/intlify/vue-i18n/tree/master/packages/vue-i18n#readme)
([source](https://redirect.github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n))
| [`11.2.1` ->
`11.2.2`](https://renovatebot.com/diffs/npm/vue-i18n/11.2.1/11.2.2) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vue-i18n/11.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue-i18n/11.2.1/11.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>intlify/vue-i18n (vue-i18n)</summary>

###
[`v11.2.2`](https://redirect.github.com/intlify/vue-i18n/releases/tag/v11.2.2)

[Compare
Source](https://redirect.github.com/intlify/vue-i18n/compare/v11.2.1...v11.2.2)

<!-- Release notes generated using configuration in .github/release.yml
at v11.2.2 -->

#### What's Changed

##### 🐛 Bug Fixes

- fix: avoid bundler static analysis for namespace import by
[@&#8203;kazupon](https://redirect.github.com/kazupon) in
[#&#8203;2326](https://redirect.github.com/intlify/vue-i18n/pull/2326)

**Full Changelog**:
<https://github.com/intlify/vue-i18n/compare/v11.2.1...v11.2.2>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 22:56:38 +01:00
renovate[bot]
4d3f72f656 chore(deps): update softprops/action-gh-release digest to a06a81a (#1914)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[softprops/action-gh-release](https://redirect.github.com/softprops/action-gh-release)
| action | digest | `5be0e66` -> `a06a81a` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 22:56:27 +01:00
dependabot[bot]
8d27120298 chore(deps): bump express from 5.1.0 to 5.2.0 in /desktop (#1919)
Bumps [express](https://github.com/expressjs/express) from 5.1.0 to
5.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/express/releases">express's
releases</a>.</em></p>
<blockquote>
<h2>v5.2.0</h2>
<h2>Important: Security</h2>
<ul>
<li>Security fix for <a
href="https://www.cve.org/CVERecord?id=CVE-2024-51999">CVE-2024-51999</a>
(<a
href="https://github.com/expressjs/express/security/advisories/GHSA-pj86-cfqh-vqx6">GHSA-pj86-cfqh-vqx6</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>build(deps): bump github/codeql-action from 3.28.11 to 3.28.13 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6429">expressjs/express#6429</a></li>
<li>Refactor: simplify <code>acceptsLanguages</code> implementation
using spread operator by <a
href="https://github.com/Ayoub-Mabrouk"><code>@​Ayoub-Mabrouk</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/6137">expressjs/express#6137</a></li>
<li>increased code coverage of utils.js file by <a
href="https://github.com/ashish3011"><code>@​ashish3011</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6386">expressjs/express#6386</a></li>
<li>chore: remove duplicate word by <a
href="https://github.com/dufucun"><code>@​dufucun</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6456">expressjs/express#6456</a></li>
<li>build(deps): bump github/codeql-action from 3.28.13 to 3.28.16 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6498">expressjs/express#6498</a></li>
<li>build(deps): bump actions/setup-node from 4.3.0 to 4.4.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6497">expressjs/express#6497</a></li>
<li>build(deps): bump actions/download-artifact from 4.2.1 to 4.3.0 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6496">expressjs/express#6496</a></li>
<li>ci: add node.js 24 to test matrix by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/6504">expressjs/express#6504</a></li>
<li>ci: update codeql config by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/6488">expressjs/express#6488</a></li>
<li>chore: wider range for query test skip by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6512">expressjs/express#6512</a></li>
<li>chore: fix typos in test by <a
href="https://github.com/noritaka1166"><code>@​noritaka1166</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/6535">expressjs/express#6535</a></li>
<li>ci: disable credential persistence for checkout actions by <a
href="https://github.com/mertssmnoglu"><code>@​mertssmnoglu</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/6522">expressjs/express#6522</a></li>
<li>ci: allow manual triggering of workflow by <a
href="https://github.com/shivarm"><code>@​shivarm</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6515">expressjs/express#6515</a></li>
<li>test: add coverage for app.listen() variants by <a
href="https://github.com/kgarg1"><code>@​kgarg1</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6476">expressjs/express#6476</a></li>
<li>docs: move documentation and charters to the discussions and .github
… by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/6427">expressjs/express#6427</a></li>
<li>build(deps): bump github/codeql-action from 3.28.16 to 3.28.18 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6549">expressjs/express#6549</a></li>
<li>build(deps): bump ossf/scorecard-action from 2.4.1 to 2.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6548">expressjs/express#6548</a></li>
<li>chore: enforce explicit <code>Buffer</code> import and add lint rule
by <a href="https://github.com/shivarm"><code>@​shivarm</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6525">expressjs/express#6525</a></li>
<li>chore: use node protocol for querystring by <a
href="https://github.com/shivarm"><code>@​shivarm</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6520">expressjs/express#6520</a></li>
<li>chore: fix typo by <a
href="https://github.com/mountdisk"><code>@​mountdisk</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6609">expressjs/express#6609</a></li>
<li>build(deps): bump github/codeql-action from 3.28.18 to 3.29.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6618">expressjs/express#6618</a></li>
<li>add deprecation warnings for redirect arguments undefined by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/6405">expressjs/express#6405</a></li>
<li>ci: run CI when the markdown changes by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/6632">expressjs/express#6632</a></li>
<li>doc: fix CONTRIBUTING link by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6653">expressjs/express#6653</a></li>
<li>doc: update contributing guidelines and code of conduct links by <a
href="https://github.com/ShubhamOulkar"><code>@​ShubhamOulkar</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/6601">expressjs/express#6601</a></li>
<li>build(deps-dev): bump morgan from 1.10.0 to 1.10.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6679">expressjs/express#6679</a></li>
<li>build(deps-dev): bump cookie-session from 2.1.0 to 2.1.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6678">expressjs/express#6678</a></li>
<li>lint: add --fix flag to automatic fix linting issue by <a
href="https://github.com/shivarm"><code>@​shivarm</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6644">expressjs/express#6644</a></li>
<li>chore: ignore yarn.lock file and update example by <a
href="https://github.com/shivarm"><code>@​shivarm</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6588">expressjs/express#6588</a></li>
<li>lib: use req.socket over deprecated req.connection by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/6705">expressjs/express#6705</a></li>
<li>doc: update express app example by <a
href="https://github.com/shivarm"><code>@​shivarm</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6718">expressjs/express#6718</a></li>
<li>build(deps): bump github/codeql-action from 3.29.2 to 3.29.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6675">expressjs/express#6675</a></li>
<li>Remove history.md from being packaged on publish by <a
href="https://github.com/sheplu"><code>@​sheplu</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/6780">expressjs/express#6780</a></li>
<li>build(deps): bump actions/checkout from 4.2.2 to 5.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6797">expressjs/express#6797</a></li>
<li>build(deps): bump github/codeql-action from 3.29.7 to 3.30.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6796">expressjs/express#6796</a></li>
<li>build(deps): bump ossf/scorecard-action from 2.4.2 to 2.4.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6795">expressjs/express#6795</a></li>
<li>build(deps): bump actions/setup-node from 4.4.0 to 5.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6794">expressjs/express#6794</a></li>
<li>build(deps): bump actions/download-artifact from 4.3.0 to 5.0.0 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6793">expressjs/express#6793</a></li>
<li>ci: add node.js 25 to test matrix by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/6843">expressjs/express#6843</a></li>
<li>build(deps): bump actions/download-artifact from 5.0.0 to 6.0.0 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6871">expressjs/express#6871</a></li>
<li>build(deps): bump actions/setup-node from 5.0.0 to 6.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6870">expressjs/express#6870</a></li>
<li>build(deps): bump github/codeql-action from 3.30.5 to 4.31.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6869">expressjs/express#6869</a></li>
<li>build(deps): bump actions/upload-artifact from 4.6.2 to 5.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/express/pull/6868">expressjs/express#6868</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/express/blob/master/History.md">express's
changelog</a>.</em></p>
<blockquote>
<h1>5.2.0 / 2025-12-01</h1>
<ul>
<li>Security fix for <a
href="https://www.cve.org/CVERecord?id=CVE-2024-51999">CVE-2024-51999</a>
(<a
href="https://github.com/expressjs/express/security/advisories/GHSA-pj86-cfqh-vqx6">GHSA-pj86-cfqh-vqx6</a>)</li>
<li>deps: <code>body-parser@^2.2.1</code></li>
<li>A deprecation warning was added when using <code>res.redirect</code>
with undefined arguments, Express now emits a warning to help detect
calls that pass undefined as the status or URL and make them easier to
fix.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="4007ad103b"><code>4007ad1</code></a>
Release: 5.2.0 (<a
href="https://redirect.github.com/expressjs/express/issues/6920">#6920</a>)</li>
<li><a
href="2f64f68c37"><code>2f64f68</code></a>
sec: security patch for CVE-2024-51999</li>
<li><a
href="ed0ba3f1dc"><code>ed0ba3f</code></a>
build(deps): bump actions/checkout from 5.0.0 to 6.0.0 (<a
href="https://redirect.github.com/expressjs/express/issues/6928">#6928</a>)</li>
<li><a
href="8eace4603c"><code>8eace46</code></a>
build(deps): bump github/codeql-action from 4.31.2 to 4.31.6 (<a
href="https://redirect.github.com/expressjs/express/issues/6929">#6929</a>)</li>
<li><a
href="30bae81027"><code>30bae81</code></a>
build(deps): bump coverallsapp/github-action from 2.3.6 to 2.3.7 (<a
href="https://redirect.github.com/expressjs/express/issues/6930">#6930</a>)</li>
<li><a
href="758d4355d4"><code>758d435</code></a>
deps: body-parser@^2.2.1 (<a
href="https://redirect.github.com/expressjs/express/issues/6922">#6922</a>)</li>
<li><a
href="77bcd5274a"><code>77bcd52</code></a>
docs: update emeritus triagers (<a
href="https://redirect.github.com/expressjs/express/issues/6890">#6890</a>)</li>
<li><a
href="f33caf1f89"><code>f33caf1</code></a>
Nominate to <a
href="https://github.com/efekrskl"><code>@​efekrskl</code></a> for
triage team (<a
href="https://redirect.github.com/expressjs/express/issues/6888">#6888</a>)</li>
<li><a
href="54af593b73"><code>54af593</code></a>
refactor: use cached slice in app.listen (<a
href="https://redirect.github.com/expressjs/express/issues/6897">#6897</a>)</li>
<li><a
href="2551a7d8af"><code>2551a7d</code></a>
docs: switch badges from badgen.net to shields.io (<a
href="https://redirect.github.com/expressjs/express/issues/6900">#6900</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/expressjs/express/compare/v5.1.0...v5.2.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=express&package-manager=npm_and_yarn&previous-version=5.1.0&new-version=5.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/go-vikunja/vikunja/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 22:56:03 +01:00
renovate[bot]
035a85d9ae fix(deps): update module github.com/redis/go-redis/v9 to v9.17.2 (#1917)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[github.com/redis/go-redis/v9](https://redirect.github.com/redis/go-redis)
| `v9.17.1` -> `v9.17.2` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fredis%2fgo-redis%2fv9/v9.17.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fredis%2fgo-redis%2fv9/v9.17.1/v9.17.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>redis/go-redis (github.com/redis/go-redis/v9)</summary>

###
[`v9.17.2`](https://redirect.github.com/redis/go-redis/releases/tag/v9.17.2):
9.17.2

[Compare
Source](https://redirect.github.com/redis/go-redis/compare/v9.17.1...v9.17.2)

#### 🐛 Bug Fixes

- **Connection Pool**: Fixed critical race condition in turn management
that could cause connection leaks when dial goroutines complete after
request timeout
([#&#8203;3626](https://redirect.github.com/redis/go-redis/pull/3626))
by [@&#8203;cyningsun](https://redirect.github.com/cyningsun)
- **Context Timeout**: Improved context timeout calculation to use
minimum of remaining time and DialTimeout, preventing goroutines from
waiting longer than necessary
([#&#8203;3626](https://redirect.github.com/redis/go-redis/pull/3626))
by [@&#8203;cyningsun](https://redirect.github.com/cyningsun)

#### 🧰 Maintenance

- chore(deps): bump rojopolis/spellcheck-github-actions from 0.54.0 to
0.55.0
([#&#8203;3627](https://redirect.github.com/redis/go-redis/pull/3627))

#### Contributors

We'd like to thank all the contributors who worked on this release!

[@&#8203;cyningsun](https://redirect.github.com/cyningsun) and
[@&#8203;ndyakov](https://redirect.github.com/ndyakov)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 20:08:05 +00:00
kolaente
e48a8d1717 fix(test): replace project creation in history test with for loop
This fixes a race condition when the project_views table was locked.

For example https://github.com/go-vikunja/vikunja/actions/runs/19826720209/job/56802103014
2025-12-01 16:01:22 +01:00
kolaente
dbb4046d51 fix(reaction): use the actual button element to compute rect, not the vue component
Resolves https://github.com/go-vikunja/vikunja/issues/1913
2025-12-01 15:48:59 +01:00
renovate[bot]
5fc9b74f50 chore(deps): update dev-dependencies (#1909)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[@vueuse/shared](https://redirect.github.com/vueuse/vueuse/tree/main/packages/shared#readme)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/shared))
| [`14.0.0` ->
`14.1.0`](https://renovatebot.com/diffs/npm/@vueuse%2fshared/14.0.0/14.1.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fshared/14.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fshared/14.0.0/14.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [electron](https://redirect.github.com/electron/electron) | [`37.10.2`
->
`37.10.3`](https://renovatebot.com/diffs/npm/electron/37.10.2/37.10.3) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/electron/37.10.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/electron/37.10.2/37.10.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [eslint-plugin-vue](https://eslint.vuejs.org)
([source](https://redirect.github.com/vuejs/eslint-plugin-vue)) |
[`10.6.0` ->
`10.6.2`](https://renovatebot.com/diffs/npm/eslint-plugin-vue/10.6.0/10.6.2)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-vue/10.6.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-vue/10.6.0/10.6.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [happy-dom](https://redirect.github.com/capricorn86/happy-dom) |
[`20.0.10` ->
`20.0.11`](https://renovatebot.com/diffs/npm/happy-dom/20.0.10/20.0.11)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/happy-dom/20.0.11?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/happy-dom/20.0.10/20.0.11?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [stylelint](https://stylelint.io)
([source](https://redirect.github.com/stylelint/stylelint)) | [`16.26.0`
->
`16.26.1`](https://renovatebot.com/diffs/npm/stylelint/16.26.0/16.26.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/stylelint/16.26.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/stylelint/16.26.0/16.26.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[vite-plugin-pwa](https://redirect.github.com/vite-pwa/vite-plugin-pwa)
| [`1.1.0` ->
`1.2.0`](https://renovatebot.com/diffs/npm/vite-plugin-pwa/1.1.0/1.2.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/vite-plugin-pwa/1.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite-plugin-pwa/1.1.0/1.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>vueuse/vueuse (@&#8203;vueuse/shared)</summary>

###
[`v14.1.0`](https://redirect.github.com/vueuse/vueuse/releases/tag/v14.1.0)

[Compare
Source](https://redirect.github.com/vueuse/vueuse/compare/v14.0.0...v14.1.0)

#####    🚀 Features

- **useDropZone**: Add checkValidity function  -  by
[@&#8203;kolaente](https://redirect.github.com/kolaente) in
[#&#8203;5169](https://redirect.github.com/vueuse/vueuse/issues/5169)
[<samp>(aee84)</samp>](https://redirect.github.com/vueuse/vueuse/commit/aee846cb)
- **useElementVisibility**: Add `initialValue` option  -  by
[@&#8203;kricsleo](https://redirect.github.com/kricsleo) and
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5159](https://redirect.github.com/vueuse/vueuse/issues/5159)
[<samp>(13f36)</samp>](https://redirect.github.com/vueuse/vueuse/commit/13f361fa)
- **useMouseInElement**: Add support for tracking inline-level elements
 -  by [@&#8203;siavava](https://redirect.github.com/siavava) and
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5049](https://redirect.github.com/vueuse/vueuse/issues/5049)
[<samp>(62dfb)</samp>](https://redirect.github.com/vueuse/vueuse/commit/62dfb80a)
- **useTimeAgoIntl**: Custom units  -  by
[@&#8203;Menci](https://redirect.github.com/Menci) in
[#&#8203;5188](https://redirect.github.com/vueuse/vueuse/issues/5188)
[<samp>(c7d09)</samp>](https://redirect.github.com/vueuse/vueuse/commit/c7d09ef4)
- **useWebSocket**: `autoConnect.delay` support function  -  by
[@&#8203;YuchenWell](https://redirect.github.com/YuchenWell), **Anthony
Fu** and [@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5089](https://redirect.github.com/vueuse/vueuse/issues/5089)
[<samp>(176f2)</samp>](https://redirect.github.com/vueuse/vueuse/commit/176f2515)

#####    🐞 Bug Fixes

- Typescript type of `isIOS` constant  -  by
[@&#8203;toofishes](https://redirect.github.com/toofishes) in
[#&#8203;5163](https://redirect.github.com/vueuse/vueuse/issues/5163)
[<samp>(60888)</samp>](https://redirect.github.com/vueuse/vueuse/commit/60888d43)
- **computedWithControl**: Allow different types in watch sources array
 -  by [@&#8203;kricsleo](https://redirect.github.com/kricsleo) in
[#&#8203;5184](https://redirect.github.com/vueuse/vueuse/issues/5184)
[<samp>(bc4ac)</samp>](https://redirect.github.com/vueuse/vueuse/commit/bc4aca90)
- **types**: Allow async functions in useDebounceFn and useThrottleFn
 -  by
[@&#8203;xiaoxiaohuayu](https://redirect.github.com/xiaoxiaohuayu) in
[#&#8203;5131](https://redirect.github.com/vueuse/vueuse/issues/5131)
[<samp>(7fb7a)</samp>](https://redirect.github.com/vueuse/vueuse/commit/7fb7a05a)
- **types**: Deprecate embeded `ResizeObserverSize` types  -  by
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5127](https://redirect.github.com/vueuse/vueuse/issues/5127)
[<samp>(d7a07)</samp>](https://redirect.github.com/vueuse/vueuse/commit/d7a07010)
- **useArrayReduce**: Export `UseArrayReduceReturn` type  -  by
[@&#8203;michaelcozzolino](https://redirect.github.com/michaelcozzolino)
in [#&#8203;5177](https://redirect.github.com/vueuse/vueuse/issues/5177)
[<samp>(e1204)</samp>](https://redirect.github.com/vueuse/vueuse/commit/e1204722)
- **useAsyncQueue**: Trigger onFinished when the last task is rejected
 -  by
[@&#8203;keeplearning66](https://redirect.github.com/keeplearning66) and
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5144](https://redirect.github.com/vueuse/vueuse/issues/5144)
[<samp>(c4a46)</samp>](https://redirect.github.com/vueuse/vueuse/commit/c4a46025)
- **useClipboard**: Add readonly attribute to textarea fallback to
support Safari 15  -  by
[@&#8203;huajianjiu](https://redirect.github.com/huajianjiu) in
[#&#8203;5179](https://redirect.github.com/vueuse/vueuse/issues/5179)
[<samp>(ef0c4)</samp>](https://redirect.github.com/vueuse/vueuse/commit/ef0c4f82)
- **useInfiniteScroll**: Make canLoadMore reactive  -  by
[@&#8203;nhquyss](https://redirect.github.com/nhquyss) in
[#&#8203;5110](https://redirect.github.com/vueuse/vueuse/issues/5110)
[<samp>(3dc2d)</samp>](https://redirect.github.com/vueuse/vueuse/commit/3dc2d831)
- **useMagicKeys**: Handle empty key events to prevent errors  -  by
[@&#8203;babu-ch](https://redirect.github.com/babu-ch) and
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5149](https://redirect.github.com/vueuse/vueuse/issues/5149)
[<samp>(f8aec)</samp>](https://redirect.github.com/vueuse/vueuse/commit/f8aecd82)
- **useScroll**: Use configurable window's `getComputedStyle`  -  by
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5150](https://redirect.github.com/vueuse/vueuse/issues/5150)
[<samp>(f74a6)</samp>](https://redirect.github.com/vueuse/vueuse/commit/f74a68d4)
- **useSpeechRecognition**: Catch the error while calling method start
 -  by [@&#8203;ben-lau](https://redirect.github.com/ben-lau),
**liubaobin** and [@&#8203;9romise](https://redirect.github.com/9romise)
in [#&#8203;5142](https://redirect.github.com/vueuse/vueuse/issues/5142)
[<samp>(94f1e)</samp>](https://redirect.github.com/vueuse/vueuse/commit/94f1e9e7)
- **useTimeout**: Fix type typo  -  by
[@&#8203;keeplearning66](https://redirect.github.com/keeplearning66),
**Robin** and **Anthony Fu** in
[#&#8203;5147](https://redirect.github.com/vueuse/vueuse/issues/5147)
[<samp>(31e5c)</samp>](https://redirect.github.com/vueuse/vueuse/commit/31e5cb0c)

#####     [View changes on
GitHub](https://redirect.github.com/vueuse/vueuse/compare/v14.0.0...v14.1.0)

</details>

<details>
<summary>electron/electron (electron)</summary>

###
[`v37.10.3`](https://redirect.github.com/electron/electron/releases/tag/v37.10.3):
electron v37.10.3

[Compare
Source](https://redirect.github.com/electron/electron/compare/v37.10.2...v37.10.3)

### Release Notes for v37.10.3

#### Fixes

- Fixed an issue where `systemPreferences.getAccentColor` inverted the
color.
[#&#8203;49067](https://redirect.github.com/electron/electron/pull/49067)
<span style="font-size:small;">(Also in
[39](https://redirect.github.com/electron/electron/pull/48624))</span>

</details>

<details>
<summary>vuejs/eslint-plugin-vue (eslint-plugin-vue)</summary>

###
[`v10.6.2`](https://redirect.github.com/vuejs/eslint-plugin-vue/blob/HEAD/CHANGELOG.md#1062)

[Compare
Source](https://redirect.github.com/vuejs/eslint-plugin-vue/compare/v10.6.1...v10.6.2)

##### Patch Changes

- Fixed false positives in non-intersecting conditions in
[`vue/no-duplicate-class-names`](https://eslint.vuejs.org/rules/no-duplicate-class-names.html)
and correctly detect duplicates in combining expressions
([#&#8203;2980](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/2980))
- Fixed false positives for `TSImportType` in
[`vue/script-indent`](https://eslint.vuejs.org/rules/script-indent.html)
rule
([#&#8203;2969](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/2969))
- Improved performance and type safety in
[`vue/prefer-use-template-ref`](https://eslint.vuejs.org/rules/prefer-use-template-ref.html)
([#&#8203;2982](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/2982))

###
[`v10.6.1`](https://redirect.github.com/vuejs/eslint-plugin-vue/blob/HEAD/CHANGELOG.md#1061)

[Compare
Source](https://redirect.github.com/vuejs/eslint-plugin-vue/compare/v10.6.0...v10.6.1)

##### Patch Changes

- Fixed false positives for comments outside `<template>` in
[vue/no-multiple-template-root](https://eslint.vuejs.org/rules/no-multiple-template-root.html)
rule
([#&#8203;2964](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/2964))

</details>

<details>
<summary>capricorn86/happy-dom (happy-dom)</summary>

###
[`v20.0.11`](https://redirect.github.com/capricorn86/happy-dom/compare/v20.0.10...b435ce751aa2a105398c4c27cc6b086f93d7f7bd)

[Compare
Source](https://redirect.github.com/capricorn86/happy-dom/compare/v20.0.10...v20.0.11)

</details>

<details>
<summary>stylelint/stylelint (stylelint)</summary>

###
[`v16.26.1`](https://redirect.github.com/stylelint/stylelint/blob/HEAD/CHANGELOG.md#16261---2025-11-28)

[Compare
Source](https://redirect.github.com/stylelint/stylelint/compare/16.26.0...16.26.1)

It fixes numerous false positive bugs, including many in the
`declaration-property-value-no-unknown` rule for the latest CSS
specifications.

- Fixed: `*-no-unknown` false positives for latest specs by integrating
`@csstools/css-syntax-patches-for-csstree`
([#&#8203;8850](https://redirect.github.com/stylelint/stylelint/pull/8850))
([@&#8203;romainmenke](https://redirect.github.com/romainmenke)).
- Fixed: `at-rule-no-unknown` false positives for `@function`
([#&#8203;8851](https://redirect.github.com/stylelint/stylelint/pull/8851))
([@&#8203;jeddy3](https://redirect.github.com/jeddy3)).
- Fixed: `declaration-property-value-no-unknown` false positives for
`attr()`, `if()` and custom functions
([#&#8203;8853](https://redirect.github.com/stylelint/stylelint/pull/8853))
([@&#8203;jeddy3](https://redirect.github.com/jeddy3)).
- Fixed: `function-url-quotes` false positives when URLs require quoting
([#&#8203;8804](https://redirect.github.com/stylelint/stylelint/pull/8804))
([@&#8203;taearls](https://redirect.github.com/taearls)).
- Fixed: `selector-pseudo-element-no-unknown` false positives for
`::scroll-button()`
([#&#8203;8856](https://redirect.github.com/stylelint/stylelint/pull/8856))
([@&#8203;Mouvedia](https://redirect.github.com/Mouvedia)).

</details>

<details>
<summary>vite-pwa/vite-plugin-pwa (vite-plugin-pwa)</summary>

###
[`v1.2.0`](https://redirect.github.com/vite-pwa/vite-plugin-pwa/releases/tag/v1.2.0)

[Compare
Source](https://redirect.github.com/vite-pwa/vite-plugin-pwa/compare/v1.1.0...v1.2.0)

*No significant changes*

#####     [View changes on
GitHub](https://redirect.github.com/vite-pwa/vite-plugin-pwa/compare/v1.1.0...v1.2.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM ( * 0-3
* * * ) (UTC), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 11:15:09 +01:00
renovate[bot]
2bcecac91d fix(deps): update vueuse to v14.1.0 (#1910)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [@vueuse/core](https://redirect.github.com/vueuse/vueuse)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/core))
| [`14.0.0` ->
`14.1.0`](https://renovatebot.com/diffs/npm/@vueuse%2fcore/14.0.0/14.1.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fcore/14.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fcore/14.0.0/14.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[@vueuse/router](https://redirect.github.com/vueuse/vueuse/tree/main/packages/router#readme)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/router))
| [`14.0.0` ->
`14.1.0`](https://renovatebot.com/diffs/npm/@vueuse%2frouter/14.0.0/14.1.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2frouter/14.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2frouter/14.0.0/14.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>vueuse/vueuse (@&#8203;vueuse/core)</summary>

###
[`v14.1.0`](https://redirect.github.com/vueuse/vueuse/releases/tag/v14.1.0)

[Compare
Source](https://redirect.github.com/vueuse/vueuse/compare/v14.0.0...v14.1.0)

#####    🚀 Features

- **useDropZone**: Add checkValidity function  -  by
[@&#8203;kolaente](https://redirect.github.com/kolaente) in
[#&#8203;5169](https://redirect.github.com/vueuse/vueuse/issues/5169)
[<samp>(aee84)</samp>](https://redirect.github.com/vueuse/vueuse/commit/aee846cb)
- **useElementVisibility**: Add `initialValue` option  -  by
[@&#8203;kricsleo](https://redirect.github.com/kricsleo) and
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5159](https://redirect.github.com/vueuse/vueuse/issues/5159)
[<samp>(13f36)</samp>](https://redirect.github.com/vueuse/vueuse/commit/13f361fa)
- **useMouseInElement**: Add support for tracking inline-level elements
 -  by [@&#8203;siavava](https://redirect.github.com/siavava) and
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5049](https://redirect.github.com/vueuse/vueuse/issues/5049)
[<samp>(62dfb)</samp>](https://redirect.github.com/vueuse/vueuse/commit/62dfb80a)
- **useTimeAgoIntl**: Custom units  -  by
[@&#8203;Menci](https://redirect.github.com/Menci) in
[#&#8203;5188](https://redirect.github.com/vueuse/vueuse/issues/5188)
[<samp>(c7d09)</samp>](https://redirect.github.com/vueuse/vueuse/commit/c7d09ef4)
- **useWebSocket**: `autoConnect.delay` support function  -  by
[@&#8203;YuchenWell](https://redirect.github.com/YuchenWell), **Anthony
Fu** and [@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5089](https://redirect.github.com/vueuse/vueuse/issues/5089)
[<samp>(176f2)</samp>](https://redirect.github.com/vueuse/vueuse/commit/176f2515)

#####    🐞 Bug Fixes

- Typescript type of `isIOS` constant  -  by
[@&#8203;toofishes](https://redirect.github.com/toofishes) in
[#&#8203;5163](https://redirect.github.com/vueuse/vueuse/issues/5163)
[<samp>(60888)</samp>](https://redirect.github.com/vueuse/vueuse/commit/60888d43)
- **computedWithControl**: Allow different types in watch sources array
 -  by [@&#8203;kricsleo](https://redirect.github.com/kricsleo) in
[#&#8203;5184](https://redirect.github.com/vueuse/vueuse/issues/5184)
[<samp>(bc4ac)</samp>](https://redirect.github.com/vueuse/vueuse/commit/bc4aca90)
- **types**: Allow async functions in useDebounceFn and useThrottleFn
 -  by
[@&#8203;xiaoxiaohuayu](https://redirect.github.com/xiaoxiaohuayu) in
[#&#8203;5131](https://redirect.github.com/vueuse/vueuse/issues/5131)
[<samp>(7fb7a)</samp>](https://redirect.github.com/vueuse/vueuse/commit/7fb7a05a)
- **types**: Deprecate embeded `ResizeObserverSize` types  -  by
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5127](https://redirect.github.com/vueuse/vueuse/issues/5127)
[<samp>(d7a07)</samp>](https://redirect.github.com/vueuse/vueuse/commit/d7a07010)
- **useArrayReduce**: Export `UseArrayReduceReturn` type  -  by
[@&#8203;michaelcozzolino](https://redirect.github.com/michaelcozzolino)
in [#&#8203;5177](https://redirect.github.com/vueuse/vueuse/issues/5177)
[<samp>(e1204)</samp>](https://redirect.github.com/vueuse/vueuse/commit/e1204722)
- **useAsyncQueue**: Trigger onFinished when the last task is rejected
 -  by
[@&#8203;keeplearning66](https://redirect.github.com/keeplearning66) and
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5144](https://redirect.github.com/vueuse/vueuse/issues/5144)
[<samp>(c4a46)</samp>](https://redirect.github.com/vueuse/vueuse/commit/c4a46025)
- **useClipboard**: Add readonly attribute to textarea fallback to
support Safari 15  -  by
[@&#8203;huajianjiu](https://redirect.github.com/huajianjiu) in
[#&#8203;5179](https://redirect.github.com/vueuse/vueuse/issues/5179)
[<samp>(ef0c4)</samp>](https://redirect.github.com/vueuse/vueuse/commit/ef0c4f82)
- **useInfiniteScroll**: Make canLoadMore reactive  -  by
[@&#8203;nhquyss](https://redirect.github.com/nhquyss) in
[#&#8203;5110](https://redirect.github.com/vueuse/vueuse/issues/5110)
[<samp>(3dc2d)</samp>](https://redirect.github.com/vueuse/vueuse/commit/3dc2d831)
- **useMagicKeys**: Handle empty key events to prevent errors  -  by
[@&#8203;babu-ch](https://redirect.github.com/babu-ch) and
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5149](https://redirect.github.com/vueuse/vueuse/issues/5149)
[<samp>(f8aec)</samp>](https://redirect.github.com/vueuse/vueuse/commit/f8aecd82)
- **useScroll**: Use configurable window's `getComputedStyle`  -  by
[@&#8203;9romise](https://redirect.github.com/9romise) in
[#&#8203;5150](https://redirect.github.com/vueuse/vueuse/issues/5150)
[<samp>(f74a6)</samp>](https://redirect.github.com/vueuse/vueuse/commit/f74a68d4)
- **useSpeechRecognition**: Catch the error while calling method start
 -  by [@&#8203;ben-lau](https://redirect.github.com/ben-lau),
**liubaobin** and [@&#8203;9romise](https://redirect.github.com/9romise)
in [#&#8203;5142](https://redirect.github.com/vueuse/vueuse/issues/5142)
[<samp>(94f1e)</samp>](https://redirect.github.com/vueuse/vueuse/commit/94f1e9e7)
- **useTimeout**: Fix type typo  -  by
[@&#8203;keeplearning66](https://redirect.github.com/keeplearning66),
**Robin** and **Anthony Fu** in
[#&#8203;5147](https://redirect.github.com/vueuse/vueuse/issues/5147)
[<samp>(31e5c)</samp>](https://redirect.github.com/vueuse/vueuse/commit/31e5cb0c)

#####     [View changes on
GitHub](https://redirect.github.com/vueuse/vueuse/compare/v14.0.0...v14.1.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 11:14:41 +01:00
renovate[bot]
2bd466e57b fix(deps): update module github.com/olekukonko/tablewriter to v1.1.2 (#1911)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[github.com/olekukonko/tablewriter](https://redirect.github.com/olekukonko/tablewriter)
| `v1.1.1` -> `v1.1.2` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2folekukonko%2ftablewriter/v1.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2folekukonko%2ftablewriter/v1.1.1/v1.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>olekukonko/tablewriter
(github.com/olekukonko/tablewriter)</summary>

###
[`v1.1.2`](https://redirect.github.com/olekukonko/tablewriter/compare/v1.1.1...v1.1.2)

[Compare
Source](https://redirect.github.com/olekukonko/tablewriter/compare/v1.1.1...v1.1.2)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 11:14:29 +01:00
renovate[bot]
9062f78bd3 chore(deps): update pnpm to v10.24.0 (#1912)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [pnpm](https://pnpm.io)
([source](https://redirect.github.com/pnpm/pnpm/tree/HEAD/pnpm)) |
[`10.23.0` ->
`10.24.0`](https://renovatebot.com/diffs/npm/pnpm/10.23.0/10.24.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/10.24.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/10.23.0/10.24.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>pnpm/pnpm (pnpm)</summary>

###
[`v10.24.0`](https://redirect.github.com/pnpm/pnpm/compare/v10.23.0...v10.24.0)

[Compare
Source](https://redirect.github.com/pnpm/pnpm/compare/v10.23.0...v10.24.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/go-vikunja/vikunja).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-30 23:09:13 +01:00
Copilot
166da9763d fix: handle MySQL 8 CREATE INDEX without IF NOT EXISTS support (#1903) 2025-11-28 15:57:54 +00:00
kolaente
e43bac7fbc fix(test): include response body in error 2025-11-28 16:48:08 +01:00
kolaente
7f5a08b316 fix(tasks): make sure all users get overdue reminder mails (#1901)
Fixes a regression introduced in 2a43f9b076

Resolves https://github.com/go-vikunja/vikunja/issues/1581
2025-11-28 11:06:47 +01:00
renovate[bot]
d1add5c621 chore(deps): update dev-dependencies (#1898)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-28 10:17:36 +01:00
renovate[bot]
7602a07a01 fix(deps): update module github.com/hashicorp/go-version to v1.8.0 (#1900)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-28 10:12:58 +01:00
Frederick [Bot]
f7e91f4b19 chore(i18n): update translations via Crowdin 2025-11-28 00:54:33 +00:00
kolaente
b175c7ff6e chore: 1.0.0-rc3 release preparations 2025-11-27 23:08:01 +01:00
kolaente
869a8b0ab9 fix(sharing): use the highest team sharing permission when sharing the same project with multiple teams (#1894) 2025-11-27 22:25:06 +01:00
kolaente
4de49512b0 docs: update AI instructions about plans 2025-11-27 22:24:40 +01:00
kolaente
9626382667 fix(editor): close only editor when pressing escape
This fixes a bug where when the task was opened in a modal and the user was editing the description and then pressing escape it would also close the task modal instead of only escaping from the editor itself.
2025-11-27 22:24:22 +01:00
renovate[bot]
6bee4a40ae fix(deps): update dependency @sentry/vue to v10.26.0 (#1878)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 19:53:03 +00:00
renovate[bot]
c2773ae52c chore(deps): update actions/checkout action to v6 (#1897)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 19:37:36 +00:00
renovate[bot]
a041845b02 fix(deps): update module github.com/getsentry/sentry-go/echo to v0.40.0 (#1895)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 19:33:48 +00:00
renovate[bot]
33ba7073c2 fix(deps): update dependency vue to v3.5.25 (#1888)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 19:30:18 +00:00
renovate[bot]
8097693681 fix(deps): update module github.com/labstack/echo-jwt/v4 to v4.4.0 (#1896)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 19:28:06 +00:00
kolaente
16790e19f9 fix(editor): preserve consecutive whitespace in comments in TipTap (#1893)
* fix(editor): preserve consecutive whitespace in comments in TipTap

Ensure multiple spaces in comment content are no longer collapsed when editing/saving by:
- Adding SetContentOptions with parseOptions.preserveWhitespace = 'full'
- Applying those options to all setContent calls (initial load, exit edit mode, post-upload cleanup)
- Enabling preserveWhitespace in editor parseOptions

Previously, repeated spaces were normalized away after setContent(false), causing comments with deliberate spacing to be altered unexpectedly.

No behavioral changes beyond whitespace retention; renders identical except space fidelity.

* fix(editor): use preserveWhitespace true instead of full to avoid duplicate checklist items

The 'full' mode preserves all whitespace including between block elements,
which caused the HTML parser to create extra empty list items from newlines
between <li> tags. Using 'true' preserves whitespace in inline content only,
which still achieves the goal of preserving consecutive spaces in text while
not creating spurious nodes from formatting whitespace.

---------

Co-authored-by: maggch <maggch@outlook.com>
2025-11-27 19:10:22 +00:00
renovate[bot]
cbbaf540a5 fix(deps): update dependency vue-i18n to v11.2.1 (#1891)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 20:07:57 +01:00
renovate[bot]
e4667d6d5d fix(deps): update module github.com/getsentry/sentry-go to v0.40.0 (#1892)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 20:07:42 +01:00
kolaente
5922d1fa1e fix(test): correctly set fixed time in login test 2025-11-27 19:57:23 +01:00
kolaente
566ff99e6c fix(editor): prevent upload overlay from intercepting text drag operations (#1890) 2025-11-27 17:49:38 +00:00
renovate[bot]
785fe6e306 chore(deps): update docker/metadata-action digest to c299e40 (#1887)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 17:45:12 +01:00
renovate[bot]
f7d5122638 fix(deps): update dependency marked to v17 (#1797)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 17:35:33 +01:00
renovate[bot]
c3a75bbd4d chore(deps): update pnpm to v10.23.0 (#1877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 17:35:20 +01:00
renovate[bot]
bb31798be6 chore(deps): pin mcr.microsoft.com/playwright docker tag to 6aca677 (#1884)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 17:35:14 +01:00
renovate[bot]
210baaa8ee chore(deps): update actions/checkout digest to 93cb6ef (#1885)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 17:34:15 +01:00
renovate[bot]
5c4ea721c7 fix(deps): update dependency workbox-precaching to v7.4.0 (#1879)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 16:37:42 +01:00
renovate[bot]
7f45264ff1 fix(deps): update module github.com/redis/go-redis/v9 to v9.17.1 (#1881)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 16:37:26 +01:00
kolaente
51512c1cb4 feat: migrate cypress e2e tests to playwright (#1739) 2025-11-27 16:34:48 +01:00
Frederick [Bot]
23a6ae19ea [skip ci] Updated swagger docs 2025-11-27 14:22:39 +00:00
Mithilesh Gupta
7dddc5dfa2 feat: task unread tracking (#1857)
---------

Co-authored-by: Mithilesh Gupta <guptamithilesh@protonmail.com>
Co-authored-by: kolaente <k@knt.li>
2025-11-27 15:14:42 +01:00
kolaente
2976d6f676 fix: use shift+r for reminder shortcut on apple devices 2025-11-26 23:58:01 +01:00
kolaente
869bb6e014 docs: fix sticker link 2025-11-26 23:58:01 +01:00
kolaente
f7acdf4ac1 fix: don't try to switch to project 0 when reloading the page (#1855) 2025-11-26 23:28:05 +01:00
kolaente
34575e4eb7 fix(editor): don't convert text that's pasted into a code block to markdown
Resolves https://community.vikunja.io/t/pasting-into-code-block-renders-markdown/4181
2025-11-26 23:27:33 +01:00
renovate[bot]
f7bdb996ca chore(deps): update dev-dependencies 2025-11-26 13:47:07 +01:00
kolaente
a4aad79f53 fix: TickTick import (#1871)
This change fixes a few issues with the TickTick import:

1. BOM (Byte Order Mark) Handling: Added stripBOM() function to properly handle UTF-8 BOM at the beginning of CSV files
2. Multi-line Status Section: Updated header detection to handle the multi-line status description in real TickTick exports
3. CSV Parser Configuration: Made the CSV parser more lenient with variable field counts and quote handling
4. Test Infrastructure: Added missing logger initialization for tests
5. Field Mapping: Fixed the core issue where CSV fields weren't being mapped to struct fields correctly

The main problem was in the newLineSkipDecoder function where:
- Header detection calculated line skip count on BOM-stripped content
- CSV decoder was also stripping BOM and applying the same skip count
- This caused inconsistent positioning and empty field mapping

Rewrote the decoder to use a scanner-based approach with consistent BOM handling.

Resolves https://github.com/go-vikunja/vikunja/issues/1870
2025-11-25 22:32:39 +00:00
kolaente
719d06a991 feat(editor): automatically save draft comments locally (#1868)
Resolves https://github.com/go-vikunja/vikunja/issues/1867
2025-11-24 22:23:58 +00:00
kolaente
8bcd7ec5f5 chore: update devenv 2025-11-24 22:47:21 +01:00
renovate[bot]
370a6230a0 chore(deps): update actions/create-github-app-token digest to 7e473ef (#1862)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 20:36:25 +00:00
Frederick [Bot]
fd9693cb6a chore(i18n): update translations via Crowdin 2025-11-24 00:59:57 +00:00
kolaente
4b4684961b fix: emit null 2025-11-22 17:23:03 +01:00
kolaente
e7c0f5fab3 fix(components): fix all type errors in FilterAutocomplete.ts
- Add type filters for label and project arrays to remove undefined values
- Add @ts-expect-error for projectId parameter in getAll call
- Add type assertion for userService.getAll empty object parameter
- Add default value for prefix in destructuring
- Add null check for match.index
- Add type assertions for union type property access (username/title)
- Add null check for autocompleteContext
- Fix Selection.near call with @ts-expect-error comment
- Add optional chaining for array access that could be undefined
2025-11-22 17:23:03 +01:00
kolaente
fe9bc02d10 fix(components): use ts-expect-error in mentionSuggestion.ts
- Replace 'as any' with @ts-expect-error comment
- This is needed because projectId is used for URL replacement but not part of IAbstract
2025-11-22 17:23:03 +01:00
kolaente
412fd3a221 fix(components): add type assertions in MentionUser.vue
- Import IUser type
- Add type assertion for username from node.attrs.id
- Add type assertion for fetchAvatarBlobUrl return value
2025-11-22 17:23:03 +01:00
kolaente
642cd08b9d fix(components): add type annotations in MentionList.vue
- Import PropType and MentionNodeAttrs
- Add MentionItem interface
- Add PropType for items array and command function
2025-11-22 17:23:03 +01:00
kolaente
30101308ea fix(components): add type annotations in suggestion.ts
- Import Editor and Range types from @tiptap/core
- Add TranslateFunction type for t parameter
- Add SuggestionProps interface for props parameter
- Add type annotations for all command callbacks
- Add null check for document.getElementById result
- Add null check for props.event in onKeyDown
2025-11-22 17:23:03 +01:00
kolaente
ddf018e791 fix(components): add type annotations in setLinkInEditor.ts
- Import Editor type from @tiptap/core
- Add DOMRect type for pos parameter
- Add Editor | null | undefined type for editor parameter
2025-11-22 17:23:03 +01:00
kolaente
d46afda42b fix(components): add type annotations in commands.ts
- Import Editor and Range types from @tiptap/core
- Add CommandProps interface for command callback
- Add type annotations for command callback parameters
2025-11-22 17:23:03 +01:00
kolaente
3a52a86980 fix(components): add undefined checks in TipTap.vue
- Add null check for DataTransferItem from items array
- Add undefined checks for check.children[1] before accessing
- Extract secondChild variable for safer access
2025-11-22 17:23:03 +01:00
kolaente
0987e382e4 fix(components): add type annotations for event parameters in TipTap.vue
- Add Event type for triggerImageInput and addImage
- Add MouseEvent type for setLink and clickTasklistCheckbox
- Add KeyboardEvent type for setFocusToEditor
- Add type assertions for event.target as HTMLElement
2025-11-22 17:23:03 +01:00
kolaente
bff5ed6403 fix(components): use correct SetContentOptions in TipTap.vue
- Replace false with {emitUpdate: false} in setContent calls
- Fix 3 SetContentOptions type errors
2025-11-22 17:23:03 +01:00
kolaente
eab1a211ae fix(components): fix clipboard data null checks in TipTap.vue
- Add proper null check for clipboardData.items before accessing length
- Replace for...of loop with indexed for loop to avoid iterator issue with DataTransferItemList
2025-11-22 17:23:03 +01:00
kolaente
278eae387c fix(components): add type guards and assertions in TipTap.vue
- Add HTMLImageElement instanceof check before accessing src property
- Add type assertions for getBlobUrl return value and cache access
2025-11-22 17:23:03 +01:00
kolaente
1275cb7fc5 fix(components): correct FontAwesome icon format in TipTap.vue
- Change icon format from ['fa', 'fa-bold'] to ['fas', 'bold']
- Fix 6 icon type errors by using correct IconPrefix and IconName format
2025-11-22 17:23:03 +01:00
kolaente
ab8b93e9ab fix(components): suppress complex union type error in Button.vue
- Remove BaseButtonProps extension to avoid complex union type
- Use @ts-expect-error to suppress TS2590 error caused by IconProp
- Add computed properties for variant, shadow, and wrap with proper defaults
2025-11-22 17:23:03 +01:00
kolaente
d96cecec09 fix(components): use undefined instead of empty object in ImportHint
- Replace empty object with undefined to use default parameter in TaskService.getAll
2025-11-22 17:23:03 +01:00
kolaente
a615afa934 fix(components): add undefined checks in GanttChartPrimitive
- Add undefined check for first row before accessing cellsByRow
- Add nullish coalescing for focusedRow.value in emit calls
2025-11-22 17:23:03 +01:00
kolaente
59fedb6757 fix(components): add type guards and null checks in Multiselect
- Add null check for multiselectRoot before passing to closeWhenClickedOutside
- Add early return for null object in select function
- Add type guards for string vs object in setSelectedObject
- Add type guards for focus method in preSelect function
- Remove invalid length check on Element type
2025-11-22 17:23:03 +01:00
kolaente
81f85a3849 fix(components): add generic constraints and null checks in AutocompleteDropdown
- Add 'extends string' constraint to generic type T
- Add null check for model.value with nullish coalescing
- Add null check for scroller before accessing properties
- Add instanceof check for HTMLElement before accessing offsetTop
2025-11-22 17:23:03 +01:00
kolaente
a575159424 fix(components): handle null values in DatepickerWithValues
- Add null to date ref type to match props.modelValue type
- Convert Date to string before passing to parseDateOrString
- Add null coalescing for flatpickrDate assignment
2025-11-22 17:23:03 +01:00
kolaente
8e089f5789 chore: add TYPECHECK_ISSUES.md to .gitignore 2025-11-22 17:23:03 +01:00
kolaente
658946b029 fix: resolve readonly project type issue in AppHeader.vue
Create mutable copy of currentProject from Pinia store to satisfy type
requirements. The readonly deep object from the store has readonly tasks
array which is incompatible with IProject type.

Fixes TypeScript errors on lines 25 and 40 where readonly project was
passed to getProjectTitle() and ProjectSettingsDropdown component.

Related to issue #29 from TYPECHECK_ISSUES.md
2025-11-22 17:23:03 +01:00
kolaente
f67af55204 fix: resolve readonly array type issue in Navigation.vue
Cast readonly project arrays from Pinia store to mutable IProject[] type.
The arrays are not actually mutated by the component, so the cast is safe.

Fixes TypeScript errors on lines 86, 97, 105 where readonly arrays were
incompatible with ProjectsNavigation component props.

Related to issue #28 from TYPECHECK_ISSUES.md
2025-11-22 17:23:03 +01:00
kolaente
4cd53c204d fix: add proper type definitions for CommandItem in CommandsList.vue 2025-11-22 17:23:03 +01:00
kolaente
83191eb24d fix: add null/undefined handling in GanttChart.vue 2025-11-22 17:23:03 +01:00
kolaente
618c85a0a7 fix: handle readonly arrays and type conversions in DatepickerWithRange.vue 2025-11-22 17:23:03 +01:00
kolaente
cc76b87b89 fix: use proper FontAwesome icon types in EditorToolbar.vue 2025-11-22 17:23:03 +01:00
kolaente
508f91a97b fix: initialize date ref with null instead of undefined in Datepicker.vue 2025-11-22 17:23:03 +01:00
kolaente
3742234540 fix: add comprehensive null/undefined checks in Reactions.vue 2025-11-22 17:23:03 +01:00
kolaente
575cf149b0 fix: add undefined checks and null coalescing in Datepicker.vue 2025-11-22 17:23:03 +01:00
kolaente
aadf0e4c17 fix: add null/undefined checks for maxPermission in AppHeader.vue 2025-11-22 17:23:03 +01:00
kolaente
cdb39c945c fix: add null checks for project.maxPermission in ProjectsNavigationItem.vue 2025-11-22 17:23:03 +01:00
kolaente
8f062f21d8 fix: add null checks and type assertion in ProjectsNavigation.vue 2025-11-22 17:23:03 +01:00
kolaente
75dafd18e3 fix: return undefined instead of null in ContentLinkShare getProjectRoute() 2025-11-22 17:23:03 +01:00
kolaente
4a2f961a77 fix: ContentAuth types 2025-11-22 17:23:03 +01:00
kolaente
a89855a9d1 fix: default language in App.vue types 2025-11-22 17:23:03 +01:00
kolaente
7543b3b5cd fix: types for Multiselect 2025-11-22 17:23:03 +01:00
kolaente
db531ab1c4 fix: types for DatepickerInline 2025-11-22 17:23:03 +01:00
kolaente
3fadacbb76 fix: event type 2025-11-22 17:23:03 +01:00
kolaente
5b38a825e3 fix: service worker types 2025-11-22 17:23:03 +01:00
kolaente
a6d31dad08 fix: type issues with expandable 2025-11-22 17:23:03 +01:00
kolaente
21d9724572 fix: add pagination type 2025-11-22 17:23:03 +01:00
kolaente
6831f3c347 fix: tycheck issues in Story 2025-11-22 17:23:03 +01:00
renovate[bot]
4c524dd1a0 chore(deps): update actions/setup-go digest to 4dc6199 (#1853)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 12:11:17 +01:00
kolaente
2bc2311212 feat: show task card preview when hovering over task title in list and table view (#1863) 2025-11-22 12:10:58 +01:00
renovate[bot]
953623c132 chore(deps): update cypress/browsers:latest docker digest to 7331c59 (#1852)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 01:20:13 +01:00
renovate[bot]
aea4cb83d4 chore(deps): update actions/checkout action to v6 (#1854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 01:19:54 +01:00
renovate[bot]
1b79c67256 chore(deps): update golangci/golangci-lint-action digest to e7fa5ac (#1860)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 01:16:35 +01:00
renovate[bot]
56dc049e31 fix(deps): update module github.com/coreos/go-oidc/v3 to v3.17.0 (#1859)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 15:43:06 +01:00
Weijie Zhao
cfab3ff922 fix: update mention format to use custom HTML element with usernames (#1843) 2025-11-21 15:29:15 +01:00
Weijie Zhao
b3b420121d fix(editor): prevent image insertion from triggering save (#1846)
Ensure inserting an image only updates editor content without firing implicit save or switching out of edit mode. This keeps user in edit flow and avoids unintended save events.
2025-11-21 15:22:46 +01:00
renovate[bot]
b78ab5d45a chore(deps): update mariadb:12 docker digest to e1bcd6f (#1856)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 15:08:11 +01:00
kolaente
e3ca310c05 fix: remove empty style block from Label.vue
Fixes a regression introduced in 3c75e0a9b6
2025-11-20 15:10:17 +01:00
rudd6617
b6dcde7f6c chore(i18n): add Traditional Chinese locale and translations (#1839) 2025-11-20 13:42:31 +01:00
kolaente
3c75e0a9b6 fix(table): label spacing when wrapping 2025-11-19 22:51:02 +01:00
kolaente
a76ff31dbc fix: don't hide filter icon on hover 2025-11-19 22:37:52 +01:00
renovate[bot]
82c7ca42f9 fix(deps): update module golang.org/x/crypto to v0.45.0 (#1851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 20:59:37 +00:00
renovate[bot]
23e8808df3 fix(deps): update module github.com/redis/go-redis/v9 to v9.17.0 (#1850)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 20:58:30 +00:00
renovate[bot]
b78c92b139 chore(deps): update postgres:18 docker digest to 5ec39c1 (#1842)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 21:51:15 +01:00
Copilot
9d0633268a fix: prevent panic in webhook listener when fetching project (#1848)
This fixes a panic that occurred when handling webhooks. The code was
incorrectly using webhook.CreatedByID (user ID) to fetch a project,
when it should use webhook.ProjectID. This could cause GetProjectSimpleByID
to return nil if no project exists with that ID.

Additionally, added a nil check before calling project.ReadOne() to prevent
a nil pointer dereference panic when accessing p.ID.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-19 14:43:08 +00:00
Frederick [Bot]
79aaa2a906 chore(i18n): update translations via Crowdin 2025-11-19 00:56:08 +00:00
renovate[bot]
572140f744 chore(deps): update actions/checkout digest to 93cb6ef (#1838)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 23:23:13 +00:00
renovate[bot]
39ba18a04a chore(deps): update dev-dependencies (#1832)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 23:17:58 +00:00
Copilot
5f795bb531 fix: self-assignment notification to use "themselves" instead of repeating username (#1836)
When a user assigns a task to themselves, notifications to other users now
correctly say "User A assigned Task #123 to themselves" instead of
"User A assigned Task #123 to User A"

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-17 23:07:48 +00:00
kolaente
d5a46310a7 chore: delete frontend/package-lock.json
Since we're using pnpm and not npm, this feels pretty useless.
2025-11-18 00:02:53 +01:00
renovate[bot]
b5e2ad92e4 chore(deps): update postgres:18 docker digest to 41bfa2e (#1824)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 00:01:30 +01:00
renovate[bot]
25a7cea3ed fix(deps): update module github.com/getsentry/sentry-go/echo to v0.38.0 (#1835)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 00:01:07 +01:00
Copilot
6b0a05b5ca Use Cmd+K for quick actions on macOS instead of Ctrl+K (#1837)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-17 22:56:54 +00:00
Frederick [Bot]
0b0928e273 chore(i18n): update translations via Crowdin 2025-11-17 00:57:26 +00:00
kolaente
067cc5674b fix(filters): handle multiple projects correctly in parsing, fixing autocomplete
Resolves https://github.com/go-vikunja/vikunja/issues/1395
2025-11-16 15:53:40 +01:00
kolaente
675a550247 fix(filter): restore cursor position when making changes
Related https://github.com/go-vikunja/vikunja/issues/1395
2025-11-16 15:38:24 +01:00
kolaente
2e3b2cb770 fix: ensure project filters are retained correctly across views (#1643) 2025-11-16 14:29:50 +00:00
kolaente
85fc8fffd4 feat: restrict attachment drop to files
Resolves https://github.com/go-vikunja/vikunja/issues/1663
2025-11-16 12:22:30 +01:00
kolaente
abbf2ce183 chore: copy useDropZone from vueuse 2025-11-16 12:22:30 +01:00
kolaente
3f1f92c410 fix: always allow dropping files 2025-11-16 12:22:30 +01:00
kolaente
9b78584734 fix(events): only trigger task.updated once when marking task done
Resolves https://github.com/go-vikunja/vikunja/issues/1724
2025-11-16 11:01:15 +01:00
kolaente
f96601bf18 fix(webhook): make sure the payload always contains a fully loaded project 2025-11-16 10:48:53 +01:00
renovate[bot]
374730056d chore(deps): update dev-dependencies (#1830)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-16 00:14:51 +00:00
kolaente
746f78f787 chore(ci): add debug log 2025-11-16 01:08:13 +01:00
Copilot
d057afb781 feat: display assignee names on mobile for accessibility (#1828)
Fixes https://github.com/go-vikunja/vikunja/issues/1741

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2025-11-16 01:05:40 +01:00
Copilot
7729a3dcad fix: HTML entity double-escaping in email notifications (#1829)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-15 21:37:09 +01:00
Copilot
25827f432e feat: hide link share creation form by default in sharing dialogue (#1827)
The link share creation form is now hidden by default and only shown when users explicitly click the "Create" button. This reduces confusion in the sharing dialogue where users might mistake link sharing for team sharing.

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-15 19:05:28 +00:00
Copilot
f2a1348c51 feat: add thread IDs to task notification emails for client-side threading (#1826)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2025-11-15 18:58:32 +01:00
kolaente
14c7bd88f2 fix: lint 2025-11-15 18:13:06 +01:00
Copilot
6903bb67c7 feat: add clickable labels on Labels page for task filtering (#1825)
Resolves https://community.vikunja.io/t/click-label-to-show-all-matching-tasks/3082

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2025-11-15 17:10:32 +00:00
kolaente
162ec33613 fix(filter): don't duplicate subtasks in list view
Resolves https://github.com/go-vikunja/vikunja/issues/1786
2025-11-15 17:07:40 +01:00
kolaente
411cfbef92 fix: correctly store fetched task positions
Resolves https://github.com/go-vikunja/vikunja/issues/1392#issuecomment-3516180532
2025-11-15 16:57:13 +01:00
renovate[bot]
682096e5f6 chore(deps): update postgres:18 docker digest to 435fe97 (#1821)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 08:56:04 +01:00
renovate[bot]
aa08780f52 chore(deps): update dev-dependencies (#1822)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 08:55:50 +01:00
Frederick [Bot]
832b7cf1cd chore(i18n): update translations via Crowdin 2025-11-15 00:54:42 +00:00
renovate[bot]
7923eb9d38 fix(deps): update module github.com/getsentry/sentry-go/echo to v0.37.0 (#1819)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 22:31:26 +01:00
Biagio00
5b42724205 fix(kanban): repeating tasks dates won't update when moved in done bucket (#1638) 2025-11-14 16:57:53 +00:00
renovate[bot]
28d96d5a84 chore(deps): update node.js to 2867d55 (#1815)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 11:17:46 +01:00
renovate[bot]
6aa48a2fa0 chore(deps): update postgres:18 docker digest to 28bda6d (#1816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 11:17:26 +01:00
renovate[bot]
9ccfbc9599 chore(deps): update mariadb:12 docker digest to 607835c (#1817)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 11:17:16 +01:00
renovate[bot]
026011256b fix(deps): update module github.com/getsentry/sentry-go to v0.37.0 (#1818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 11:17:03 +01:00
Copilot
7cd3f69096 fix: prevent date picker from selecting past times at hour breakpoints (#1814)
The function previously used <= for hour comparisons, which caused it to
return a breakpoint hour even if the current time had passed it. For example,
at 15:54, it would return 15 (3:00 PM), which is in the past.

Now the function checks both hours and minutes:
- If current time is before a breakpoint hour, return that hour
- If current time is exactly at a breakpoint hour with 0 minutes, return that hour
- If current time is past a breakpoint (hour with minutes > 0), return the next breakpoint

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-13 10:46:24 +01:00
Copilot
9990a1f60f fix(date picker): hide "this weekend" option on Sunday after 9pm (#1813)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-13 09:08:48 +00:00
Copilot
650fb94978 feat: add time display with configurable format (12h/24h) to non-relative date formats (#1807)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2025-11-13 08:57:06 +00:00
Copilot
b38780e246 feat(ci): update Docker PR push build strategy for forked PRs (#1812)
This change switches from pull_request to pull_request_target trigger,
allowing PRs from forks to successfully build and push Docker images.

The pull_request trigger provides a read-only GITHUB_TOKEN for fork PRs,
even when permissions.packages is set to write. This caused builds to fail
for external contributors.

Using pull_request_target is safe here because:
- We explicitly checkout the PR's head SHA
- Only Docker build happens (isolated, no arbitrary code execution)
- No untrusted scripts are run in the workflow context

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2025-11-13 08:52:20 +00:00
renovate[bot]
eaebcf68b1 chore(deps): update node.js to 54dfcc1 (#1811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 08:26:42 +01:00
Frederick [Bot]
7a0b55307b chore(i18n): update translations via Crowdin 2025-11-13 00:56:40 +00:00
Copilot
cf8ad52a27 feat: add PR Docker image builds for x86_64 (#1810)
* Initial plan

* feat: add PR docker build workflow

Add a new workflow that builds and pushes Docker images for pull requests:
- Runs independently without waiting for tests
- Builds only for linux/amd64 platform
- Pushes only to GHCR (not Docker Hub)
- Tags images with pr-<number> format

Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>

* feat: add SHA tag to PR Docker images

Add type=sha,format=long to docker tags for PR images.
This will tag images with both pr-<number> and sha-<commit_hash>.

Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-12 23:31:25 +00:00
Copilot
409edb82c6 fix: downgrade tiptap to v3.8.0 to resolve editor crash (#1806)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-12 22:47:25 +00:00
renovate[bot]
4a4a8a0013 chore(deps): update actions/create-github-app-token action to v2 (#1809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 22:46:44 +00:00
Copilot
9c81afb7b2 feat: replace PNG-based initials avatar with SVG generation (#1802)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-12 22:26:52 +00:00
renovate[bot]
5108196eda chore(deps): update node.js to v24.11.1 (#1804)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 22:49:33 +01:00
renovate[bot]
373b079223 chore(deps): pin actions/create-github-app-token action to d72941d (#1801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 20:59:03 +00:00
kolaente
b8b335fee2 chore(ci): use github app to handle issue closed comments 2025-11-12 21:46:04 +01:00
renovate[bot]
1b995024a9 fix(deps): update dependency @sentry/vue to v10.25.0 (#1780)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 21:37:36 +01:00
renovate[bot]
df5084b8e7 chore(deps): update pnpm to v10.22.0 (#1800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 21:37:23 +01:00
renovate[bot]
1a97faf8f3 fix(deps): update module github.com/jaswdr/faker/v2 to v2.9.0 (#1783)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 21:37:07 +01:00
renovate[bot]
f1d3363299 fix(deps): update module golang.org/x/net to v0.47.0 (#1792)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 20:33:28 +00:00
renovate[bot]
9ed848efe1 chore(deps): update cypress/browsers:latest docker digest to e85371f (#1798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 21:08:47 +01:00
kolaente
dcfd096588 feat: allow setting dark custom logo
Resolves https://github.com/go-vikunja/vikunja/issues/1799
2025-11-12 21:07:01 +01:00
kolaente
8862b6f69d fix(migration): return proper error message when request fails
Related to https://github.com/go-vikunja/vikunja/issues/1788
2025-11-12 20:25:17 +01:00
kolaente
9efc0baf50 fix(migration): add retry to migration request helper
Resolves https://github.com/go-vikunja/vikunja/issues/1788
2025-11-12 20:10:32 +01:00
renovate[bot]
d8446e4421 fix(deps): update module github.com/cweill/gotests to v1.9.0 (#1733)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 15:27:07 +01:00
renovate[bot]
0c59383abb chore(deps): update dev-dependencies (#1790)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 15:26:35 +01:00
renovate[bot]
d8b524d4d5 fix(deps): update module golang.org/x/image to v0.33.0 (#1791)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 15:26:09 +01:00
renovate[bot]
f08afcf66b chore(deps): update golangci/golangci-lint-action action to v9 (#1796)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 15:24:06 +01:00
renovate[bot]
441063131c fix(deps): update module golang.org/x/crypto to v0.44.0 (#1789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 14:10:33 +00:00
renovate[bot]
0c3a0a87b7 fix(deps): update module github.com/go-testfixtures/testfixtures/v3 to v3.19.0 (#1718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 14:09:35 +00:00
renovate[bot]
e336501606 fix(deps): update module golang.org/x/oauth2 to v0.33.0 (#1782)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 14:07:06 +00:00
renovate[bot]
9a1fe91a9b chore(deps): update node.js to v24.11.1 (#1787)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 13:08:44 +00:00
renovate[bot]
b633b71cb5 fix(deps): update module golang.org/x/sync to v0.18.0 (#1784)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 14:08:11 +01:00
Frederick [Bot]
a153fc8c25 [skip ci] Updated swagger docs 2025-11-11 22:08:06 +00:00
Mithilesh Gupta
01a84dd2d5 feat: add comment count to tasks (#1771) 2025-11-11 23:00:05 +01:00
renovate[bot]
e371ee6f12 chore(deps): update pnpm to v10.21.0 (#1779)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 11:56:08 +01:00
renovate[bot]
d8d118be62 fix(deps): update dependency vue to v3.5.24 (#1772)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 11:55:07 +01:00
renovate[bot]
be2bd90de4 fix(deps): update tiptap to v3.10.5 (#1773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 11:12:17 +01:00
kolaente
22fc19cd24 fix: ignore filter_include_nulls from views
The filter_include_nulls property from the filter in a view would override the property set through the query string. Because we don't have a way in the UI to set this for filters in views, this makes the setting pretty opaque and unpredictable. Since we want to remove the nulls option anyways, we can just ignore it here.

Resolves https://github.com/go-vikunja/vikunja/issues/1781
2025-11-11 11:04:33 +01:00
Frederick [Bot]
7dac1c7539 chore(i18n): update translations via Crowdin 2025-11-11 00:56:52 +00:00
renovate[bot]
aafd16fbe3 chore(deps): update softprops/action-gh-release digest to 5be0e66 (#1777)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 22:44:00 +00:00
renovate[bot]
0d954999e3 fix(deps): update module github.com/olekukonko/tablewriter to v1.1.1 (#1778)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 22:41:46 +00:00
renovate[bot]
ed0b1f766b chore(deps): update docker/metadata-action digest to 318604b (#1744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 23:27:49 +01:00
renovate[bot]
8342318cbc chore(deps): update mariadb:12 docker digest to 439d77b (#1776)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 23:27:13 +01:00
renovate[bot]
a6c114c86a chore(deps): update postgres:18 docker digest to 41fc534 (#1751)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 21:16:10 +00:00
kolaente
435d029f33 feat: show avatar for mentioned users 2025-11-10 11:25:47 +01:00
kolaente
0d83a568ce chore: reorganize mention setup 2025-11-10 11:24:45 +01:00
Weijie Zhao
43a5ae1309 feat: enable user mentions in task description & comments (#1754) 2025-11-09 19:42:38 +01:00
renovate[bot]
bf0fd2885d chore(deps): update actions/download-artifact action to v6 (#1764)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-08 15:42:50 +01:00
renovate[bot]
9efec4983e fix(deps): update dependency marked to v16.4.2 (#1767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-08 15:42:44 +01:00
Copilot
e424689ed9 fix: migrate Sentry integration to SDK v8 API (#1769) 2025-11-07 15:20:57 +00:00
Weijie Zhao
4fe0763010 fix: properly quote email sender names containing @ symbols (#1768)
When user names contain @ symbols, the email library fails to parse
the sender address format "Name @ Symbol via Vikunja <email@domain.com>".
This fix uses Go's net/mail.Address to properly format the sender
address according to RFC 5322, which automatically quotes names
containing special characters.

Fixes the error: "getting sender address: no FROM address set"
2025-11-07 11:44:24 +01:00
renovate[bot]
77779350d2 fix(deps): update dependency vue to v3.5.23 (#1760)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 11:22:22 +00:00
renovate[bot]
7e9941ea94 chore(deps): pin bitnamilegacy/minio docker tag to 451fe68 (#1759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 11:21:27 +00:00
renovate[bot]
1ae4382484 chore(deps): update dependency go to v1.25.4 (#1758)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 07:44:44 +00:00
Panagiotis Papadopoulos
f83bd60915 fix: 403 http error code on failed login (#1756) 2025-11-06 08:40:46 +01:00
Weijie Zhao
bc1368abcc feat: add S3 file storage support (#1688) 2025-11-06 08:37:04 +01:00
renovate[bot]
08525bcb4b fix(deps): update tiptap to v3.10.2 (#1735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 07:23:21 +00:00
renovate[bot]
f135ade3a0 chore(deps): update docker/setup-qemu-action digest to c7c5346 (#1757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 07:18:18 +00:00
renovate[bot]
6ee83283e0 fix(deps): update dependency @sentry/vue to v10.23.0 (#1755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 08:08:06 +01:00
renovate[bot]
5987874165 fix(deps): update dependency pinia to v3.0.4 (#1753)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 10:37:59 +00:00
kolaente
03f04f0787 fix(task): slash menu appearing behind modals (#1752)
The slash menu in the editor was appearing behind task detail modals
(z-index 4000) because it had a z-index of only 1000. This made the
menu inaccessible when editing task descriptions in Kanban view.

Updated the z-index to 4700 to place it above modals while keeping
it below system notifications (z-index 9999).

Fixes #1746
2025-11-05 10:29:19 +00:00
renovate[bot]
f4d0cc7ffa chore(deps): update cypress/browsers:latest docker digest to 33dbe61 (#1749)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 10:10:23 +01:00
renovate[bot]
7c93d9fe1b chore(deps): update dependency @cypress/vite-dev-server to v7.0.1 (#1750)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 10:10:11 +01:00
renovate[bot]
7fe4573211 fix(deps): update dependency axios to v1.13.2 (#1748)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 00:26:43 +01:00
renovate[bot]
09cc0f673c chore(deps): update postgres:18 docker digest to 6f3e42a (#1742)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 00:26:22 +01:00
renovate[bot]
615c076fcb chore(deps): update dev-dependencies (#1740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 21:54:26 +00:00
kolaente
541a38456e chore(deps): update golangci-lint to 2.6.0 (#1737) 2025-10-31 17:28:52 +00:00
renovate[bot]
15cba4cd27 chore(deps): update cypress/browsers:latest docker digest to 368e300 (#1734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 12:24:52 +00:00
renovate[bot]
4f26fae25b fix(deps): update dependency dayjs to v1.11.19 (#1736)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 12:24:34 +00:00
Frederick [Bot]
2b92564132 chore(i18n): update translations via Crowdin 2025-10-31 00:54:52 +00:00
renovate[bot]
fd75224333 fix(deps): update module github.com/gabriel-vasile/mimetype to v1.4.11 (#1730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 11:45:16 +00:00
renovate[bot]
d2a097f07d fix(deps): update tiptap to v3.9.1 (#1731)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 11:45:00 +00:00
renovate[bot]
25898a7049 chore(deps): update pnpm to v10.20.0 (#1732)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 11:44:46 +00:00
renovate[bot]
993d24a548 fix(deps): update dependency axios to v1.13.1 (#1727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 14:44:11 +00:00
renovate[bot]
5d8ab9b4b6 fix(deps): update dependency @kyvg/vue3-notification to v3.4.2 (#1726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 14:43:54 +00:00
renovate[bot]
458081986d fix(deps): update dependency @sentry/vue to v10.22.0 (#1712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 14:28:50 +00:00
renovate[bot]
63bf32cb4c chore(deps): update node.js to v24 (#1721)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 14:12:42 +00:00
renovate[bot]
75f1111e9f fix(deps): update module github.com/getsentry/sentry-go/echo to v0.36.2 (#1722)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 14:12:28 +00:00
renovate[bot]
c3b2bb92eb fix(deps): update tiptap to v3.9.0 (#1723)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 14:12:16 +00:00
renovate[bot]
c66cf8b75a fix(deps): update dependency axios to v1.13.0 (#1720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 00:34:28 +01:00
renovate[bot]
070c03bd47 chore(deps): update github artifact actions (major) (#1719)
chore(deps): update github artifact actions

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 12:47:51 +00:00
renovate[bot]
68c3c33820 chore(deps): update cypress/browsers:latest docker digest to 1b0e8df (#1697)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 12:47:28 +00:00
renovate[bot]
fedb486e70 chore(deps): update dev-dependencies (#1708)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 13:24:05 +01:00
renovate[bot]
4e945f0349 fix(deps): update module github.com/redis/go-redis/v9 to v9.16.0 (#1710)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 13:23:49 +01:00
renovate[bot]
087e6294d4 fix(deps): update tiptap to v3.8.0 (#1713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 13:23:32 +01:00
renovate[bot]
4af5dbf016 chore(deps): update postgres:18 docker digest to 1ffc019 (#1715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 13:23:16 +01:00
Frederick [Bot]
b8c8e2a03d chore(i18n): update translations via Crowdin 2025-10-27 00:59:22 +00:00
renovate[bot]
1877415e47 chore(deps): update postgres:18 docker digest to 7499fa0 (#1709)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 10:06:52 +02:00
renovate[bot]
262e890dc9 fix(deps): update vueuse to v14 (major) (#1707)
fix(deps): update vueuse to v14

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 22:55:54 +00:00
renovate[bot]
3a9c89bb43 chore(deps): update node.js to v22.21.0 (#1703)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 00:55:29 +02:00
renovate[bot]
f360bc20ce chore(deps): update pnpm to v10.19.0 (#1704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 00:55:18 +02:00
renovate[bot]
0e1c16849c fix(deps): update dependency @sentry/vue to v10.21.0 (#1706)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 00:55:06 +02:00
renovate[bot]
0a58683b7e chore(deps): update postgres:18 docker digest to 33d0aae (#1705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 22:54:23 +00:00
renovate[bot]
80f195ac73 chore(deps): update postgres:18 docker digest to b5b154c (#1695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 10:07:50 +02:00
renovate[bot]
4d51e6f52d chore(deps): pin docker/login-action action to 5e57cd1 (#1698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 10:07:23 +02:00
renovate[bot]
96318698c5 fix(deps): update module github.com/getsentry/sentry-go/echo to v0.36.1 (#1701)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 10:06:53 +02:00
renovate[bot]
9a82e4a744 chore(deps): update dev-dependencies (#1700)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 08:06:39 +00:00
kolaente
c02650346d fix(ci): login to ghcr 2025-10-21 16:29:27 +02:00
kolaente
46e37d7b7e feat(ci): run database docker images only when needed (#1696) 2025-10-21 13:47:39 +00:00
kolaente
ee9744aaa1 feat(ci): publish docker images to ghcr as well 2025-10-21 15:26:33 +02:00
renovate[bot]
b24d067900 fix(deps): update module github.com/cweill/gotests to v1.8.0 (#1694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 05:27:28 +00:00
renovate[bot]
c0d812e1db chore(deps): update dev-dependencies (#1692)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 07:11:54 +02:00
renovate[bot]
48c1853523 chore(deps): update node.js to v22.21.0 (#1693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 07:11:42 +02:00
renovate[bot]
92b83dd69d chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 741010e (#1691)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 07:11:22 +02:00
renovate[bot]
135e698f00 fix(deps): update module xorm.io/xorm to v1.3.11 (#1689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 20:05:56 +02:00
renovate[bot]
b2db608acc chore(deps): update crowdin/github-action digest to 08713f0 (#1687)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 10:11:01 +00:00
renovate[bot]
022c627ad6 chore(deps): update dev-dependencies (#1686)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 08:36:45 +02:00
Frederick [Bot]
ba84e78222 chore(i18n): update translations via Crowdin 2025-10-20 00:57:36 +00:00
renovate[bot]
07f978207e chore(deps): pin docker/build-push-action action to 2634353 (#1683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-19 20:03:10 +02:00
kolaente
040ac8906d fix(ci): remove blacksmith docker builder
Blacksmith's docker builder is fast, but it uses way too much cache
storage, ballooning our bill.
2025-10-19 19:35:09 +02:00
renovate[bot]
f077088483 chore(deps): update dev-dependencies (#1682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-19 09:21:42 +02:00
renovate[bot]
138f1d330e fix(deps): update module github.com/redis/go-redis/v9 to v9.14.1 (#1681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-19 09:21:23 +02:00
renovate[bot]
f9ed940e5d chore(deps): update cypress/browsers:latest docker digest to b7d45cd (#1680)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 09:57:00 +02:00
renovate[bot]
96359df8ad chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 842e7ed (#1678)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 23:49:16 +02:00
renovate[bot]
35167bbff3 chore(deps): update cypress/browsers:latest docker digest to f47e21d (#1674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 16:11:11 +02:00
renovate[bot]
22d03d4309 fix(deps): update tiptap to v3.7.2 (#1669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 16:10:59 +02:00
renovate[bot]
ddc78cd1d0 fix(deps): update dependency vue-router to v4.6.3 (#1671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 16:10:45 +02:00
renovate[bot]
64ad07d698 chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 4f5d59a (#1675)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 16:10:30 +02:00
renovate[bot]
2d728fff08 chore(deps): update dev-dependencies (#1676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 16:10:17 +02:00
renovate[bot]
36872d8274 fix(deps): update dependency marked to v16.4.1 (#1677)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 16:10:03 +02:00
renovate[bot]
a5e2fbbe69 chore(deps): update dev-dependencies (#1662)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 21:23:00 +00:00
renovate[bot]
71287778d3 chore(deps): update dependency happy-dom to v20.0.2 [security] (#1668)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 21:05:20 +00:00
renovate[bot]
187722b636 chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 11e1827 (#1661)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 23:03:45 +02:00
renovate[bot]
cf0b05324e fix(deps): update dependency vue-router to v4.6.2 (#1665)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 23:03:32 +02:00
renovate[bot]
79d07c833b fix(deps): update dependency @sentry/vue to v10.20.0 (#1666)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 23:03:22 +02:00
renovate[bot]
113025a12d fix(deps): update module github.com/getsentry/sentry-go/echo to v0.36.0 (#1659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 19:18:11 +00:00
renovate[bot]
1094edab80 fix(deps): update dependency vue-router to v4.6.0 (#1657)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 19:12:16 +00:00
renovate[bot]
96606bf913 fix(deps): update tiptap to v3.7.0 (#1660)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 19:04:32 +00:00
renovate[bot]
9608f4ab1b fix(deps): update module github.com/getsentry/sentry-go to v0.36.0 (#1658)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 21:02:50 +02:00
renovate[bot]
aa08a89c65 fix(deps): update dependency dompurify to v3.3.0 (#1651)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 10:42:34 +00:00
renovate[bot]
9ebaea5d9b fix(deps): update tiptap to v3.6.7 (#1656)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 10:25:57 +00:00
renovate[bot]
aaae3ccd34 chore(deps): update dev-dependencies (#1644)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 12:24:53 +02:00
renovate[bot]
d483f09a46 chore(deps): update dependency go to v1.25.3 (#1653)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 12:24:42 +02:00
renovate[bot]
abcdb2c537 chore(deps): update pnpm to v10.18.3 (#1655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 12:24:19 +02:00
renovate[bot]
09fc61fdf1 chore(deps): update actions/setup-node action to v6 (#1654)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 12:12:21 +02:00
kolaente
2977a11a2c fix: show cross-project subtasks in List view (#1649) 2025-10-13 18:43:40 +02:00
kolaente
7689b6c181 fix(ci): unxecpected token in issue close workflow 2025-10-13 11:13:29 +02:00
kolaente
c8837aeaeb fix(filters): support project filter in parentheses (#1647)
The filter regex pattern was not matching values inside parentheses correctly.
The lookahead pattern only allowed `&&`, `||`, or end-of-string after filter
values, but when filters are wrapped in parentheses like `( project = Filtertest )`,
the closing `)` appears after the value.

Fixed by adding `\)` to the lookahead pattern so it correctly handles closing
parentheses. This allows the project filter (and other filters) to work
properly when nested in parentheses.

- Added tests for project filters in parentheses (both frontend and backend)
- Backend tests confirm the backend already handled this correctly
- Frontend regex pattern now matches the backend behavior

Fixes #1645
2025-10-13 11:10:22 +02:00
kolaente
4383948275 fix: prevent duplicate CreateEdit submissions (#1541) 2025-10-11 20:24:21 +00:00
kolaente
215605db77 fix(project): correctly set last project when navigating from a saved filter (#1642) 2025-10-11 20:11:43 +00:00
renovate[bot]
a110d0f577 chore(deps): update softprops/action-gh-release digest to 6da8fa9 (#1641)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-11 20:06:18 +02:00
renovate[bot]
c723b2dffb chore(deps): update dependency happy-dom to v20 [security] (#1640)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-11 09:26:17 +02:00
renovate[bot]
cf9e5d0829 chore(deps): update pnpm to v10.18.2 (#1636)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 09:27:03 +02:00
renovate[bot]
d77853e940 chore(deps): update dependency @types/node to v22.18.9 (#1635)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 09:26:44 +02:00
renovate[bot]
95dd749ba8 chore(deps): update mariadb:12 docker digest to 5b6a1ea (#1634)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 09:26:31 +02:00
renovate[bot]
b6f61b08a7 fix(deps): update tiptap to v3.6.6 (#1633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 20:46:47 +02:00
kolaente
6425c6d2d8 fix(ci): don't run cypress tests in parallel when started from a fork (#1631)
Workaround for https://github.com/cypress-io/github-action/issues/1546
2025-10-09 12:02:24 +00:00
renovate[bot]
27015e0ab7 fix(deps): update dependency @sentry/vue to v10.19.0 (#1632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 11:58:32 +00:00
kolaente
2a43f9b076 fix(reminders): refactor and check permissions when fetching task users 2025-10-09 13:33:27 +02:00
kolaente
9358954c90 fix: cleanup team memberships, assignments and subscriptions when users lose access to a project 2025-10-09 13:33:27 +02:00
kolaente
7442fbb9c2 fix(ci): find closing PRs when they are not explicitly referenced
Works around a limitation in the GitHub api which makes it hard to find a PR that closed an issue directly
2025-10-09 11:14:05 +02:00
renovate[bot]
87d542b4b3 chore(deps): update node.js to dbcedd8 (#1627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 08:59:38 +00:00
kolaente
2dd36ad0a9 fix(sharing): make editing link share comments work
Resolves https://github.com/go-vikunja/vikunja/issues/1510
2025-10-09 10:53:18 +02:00
kolaente
7da2942ca6 fix: correctly set database path on windows (#1616) 2025-10-09 08:38:01 +00:00
renovate[bot]
4220e8219e chore(deps): update dev-dependencies (#1625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 08:34:06 +00:00
kolaente
0c602d10b8 fix: preserve link share hash on task back navigation (#1623) 2025-10-09 10:07:37 +02:00
renovate[bot]
18d41f6b76 chore(deps): update docker/dockerfile:1 docker digest to b6afd42 (#1624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 09:47:09 +02:00
renovate[bot]
88b5ce8382 chore(deps): update node.js to 605dc0b (#1626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 09:35:32 +02:00
kolaente
dd245cf35e fix: restore quick add magic modal close button on mobile (#1622)
* fix(frontend): add close control to quick add magic modal
2025-10-08 21:49:43 +00:00
kolaente
db6b82a002 fix: task.comment.deleted triggers panic in event listener which sends webhook (#1621)
Co-authored-by: Gabriel <fossecruor@gmail.com>
2025-10-08 21:46:57 +00:00
renovate[bot]
164eed0cd9 fix(deps): update module golang.org/x/image to v0.32.0 (#1619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 21:19:56 +00:00
renovate[bot]
45419e0bf6 fix(deps): update module golang.org/x/crypto to v0.43.0 (#1618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 21:04:32 +00:00
renovate[bot]
7f78a44e1c chore(deps): update useblacksmith/setup-docker-builder digest to 78f4168 (#1617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 22:43:27 +02:00
kolaente
28f9e5b83b fix: style lint 2025-10-08 18:51:44 +02:00
kolaente
1f1e0b227d fix(date parsing): 12pm/12am edge case
Fixes a regression introduced in
c2e224dbb1
2025-10-08 18:35:11 +02:00
kolaente
c63b3550d5 fix(rtl): correct spacing for user avatar menu
Resolves https://github.com/go-vikunja/vikunja/issues/1544
2025-10-08 18:28:54 +02:00
kolaente
5568d751bf fix(rtl): make sure modals are centered
Resolves https://github.com/go-vikunja/vikunja/issues/1544
2025-10-08 18:28:54 +02:00
kolaente
d3319544e3 fix(rtl): put the menu to the correct side on rtl languages
Resolves https://github.com/go-vikunja/vikunja/issues/1544
2025-10-08 18:28:54 +02:00
kolaente
c2e224dbb1 fix: correct case-sensitivity in duedate time parsing (#1613)
Co-authored-by: mechanarchy <1166756+mechanarchy@users.noreply.github.com>
2025-10-08 16:24:54 +00:00
renovate[bot]
22baaf2bbb fix(deps): update module golang.org/x/sys to v0.37.0 (#1612)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 14:11:09 +00:00
renovate[bot]
4632780f4c fix(deps): update module golang.org/x/oauth2 to v0.32.0 (#1611)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 14:10:21 +00:00
renovate[bot]
d001c7f9d9 chore(deps): update cypress/browsers:latest docker digest to 1f1adf3 (#1608)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 10:29:50 +00:00
renovate[bot]
b86fd8d2d2 chore(deps): update pnpm/action-setup digest to 41ff726 (#1609)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 10:19:31 +00:00
renovate[bot]
d44dbd116e chore(deps): update dependency go to v1.25.2 (#1607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 21:10:26 +00:00
renovate[bot]
74a86a510b chore(deps): update ghcr.io/go-vikunja/dex-testing:main docker digest to d401c06 (#1604)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 20:46:12 +00:00
renovate[bot]
aa400bb532 fix(deps): update dependency @sentry/vue to v10.18.0 (#1606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 22:35:33 +02:00
kolaente
d33e742961 chore: make condition simpler 2025-10-07 10:56:03 +02:00
kolaente
1b02f78eee fix(filter): check date boundary after timezone conversion
Resolves https://github.com/go-vikunja/vikunja/issues/1605
2025-10-07 10:55:22 +02:00
renovate[bot]
49e6d73a11 chore(deps): update useblacksmith/setup-docker-builder digest to 6679018 (#1600)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 10:01:38 +02:00
renovate[bot]
866aedf94a chore(deps): update dev-dependencies (#1601)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 10:01:25 +02:00
renovate[bot]
a05089e5cd chore(deps): update softprops/action-gh-release digest to aec2ec5 (#1602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 10:01:10 +02:00
renovate[bot]
4bf3f73df2 fix(deps): update dependency marked to v16.4.0 (#1603)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 10:00:55 +02:00
Weijie Zhao
d7d3078de5 fix: prevent keyboard events during IME composition (#1535) 2025-10-06 18:17:52 +02:00
renovate[bot]
1af48aacb9 chore(deps): update pnpm to v10.18.1 (#1598)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 13:57:53 +00:00
renovate[bot]
4206354849 fix(deps): update font awesome to v7.1.0 (#1587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 15:32:35 +02:00
renovate[bot]
adda31c60c fix(deps): update tiptap to v3.6.5 (#1595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 15:32:20 +02:00
renovate[bot]
7fce68dd9c chore(deps): update pnpm to v10.18.0 (#1596)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 15:32:06 +02:00
renovate[bot]
1e5d7bb068 fix(deps): update module github.com/coreos/go-oidc/v3 to v3.16.0 (#1597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 15:31:47 +02:00
renovate[bot]
1f75eba538 chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 48d6b3f (#1582)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 12:25:25 +00:00
renovate[bot]
5ecade6d0f fix(deps): update module github.com/go-ldap/ldap/v3 to v3.4.12 (#1586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 11:55:17 +00:00
renovate[bot]
714b8f82cf fix(deps): update dependency tailwindcss to v3.4.18 (#1585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 11:54:29 +00:00
renovate[bot]
d881de1bc3 chore(deps): update useblacksmith/setup-docker-builder digest to 18cdb72 (#1594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 11:29:01 +00:00
renovate[bot]
5628ca72de chore(deps): update useblacksmith/build-push-action digest to 30c7116 (#1593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 11:07:17 +00:00
renovate[bot]
779143f8e8 chore(deps): update postgres:18 docker digest to 073e7c8 (#1583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 10:36:36 +00:00
renovate[bot]
7f69506f09 chore(deps): update mariadb:12 docker digest to 03a03a6 (#1592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 10:09:41 +00:00
renovate[bot]
4177363e46 chore(deps): update dev-dependencies (#1584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 11:51:37 +02:00
kolaente
5f1d684551 fix(ci): use correct quotes for commit description 2025-10-01 11:58:25 +02:00
kolaente
67ebd876d3 fix(views): migrate filter bucket configuration
Resolves https://github.com/go-vikunja/vikunja/issues/1580
2025-10-01 11:54:24 +02:00
renovate[bot]
d594d3b8dd chore(deps): update postgres:18 docker digest to 67a7c38 (#1579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 06:58:29 +00:00
renovate[bot]
c0bd5539f3 chore(deps): update dev-dependencies (#1578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 08:47:32 +02:00
kolaente
ec89b08fd5 fix(attachments): extend upload file size to form data (#1577)
Resolves https://github.com/go-vikunja/vikunja/issues/1494
2025-09-30 22:23:07 +00:00
kolaente
31c1f98270 fix(caldav): remove METHOD:PUBLISH from caldav exports (#1576) 2025-09-30 18:16:07 +00:00
kolaente
2d37c0fede fix(user): race condition during email confirmation (#1575) 2025-09-30 20:14:25 +02:00
kolaente
5c4f6dab6b fix(test): set cypress api url when running tests locally 2025-09-30 18:56:53 +02:00
kolaente
46b4f65da0 fix(test): prune leftover task duplicates 2025-09-30 16:50:11 +02:00
renovate[bot]
e63b349b5f fix(deps): update dependency @sentry/vue to v10.17.0 (#1574)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 14:12:49 +00:00
renovate[bot]
bf43e6736c chore(deps): update postgres:18 docker digest to ad98a3b (#1572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 12:21:45 +02:00
renovate[bot]
8263ca9021 chore(deps): update postgres docker tag to v18 (#1562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 07:54:23 +00:00
renovate[bot]
2e666e8021 chore(deps): update ghcr.io/go-vikunja/dex-testing:main docker digest to 738f7db (#1571)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 07:30:15 +00:00
renovate[bot]
df0e9ccefa chore(deps): update dev-dependencies (#1569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 09:16:11 +02:00
renovate[bot]
93e17929a9 fix(deps): update dependency @sentry/vue to v10.16.0 (#1568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 16:33:42 +00:00
renovate[bot]
8f867e7f74 fix(deps): update module github.com/wneessen/go-mail to v0.7.2 (#1567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 16:11:23 +00:00
renovate[bot]
75e05c45c8 fix(deps): update tiptap to v3.6.2 (#1566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 17:46:17 +02:00
renovate[bot]
63361770b3 chore(deps): update docker/login-action digest to 5e57cd1 (#1565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 17:45:54 +02:00
kolaente
ff8e98e6e2 fix: process multiple reminders in the same time window (#1564)
Resolves https://github.com/go-vikunja/vikunja/issues/1550
2025-09-29 10:43:12 +02:00
renovate[bot]
6c7ccfdc20 chore(deps): update dev-dependencies (#1563)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 07:47:24 +02:00
renovate[bot]
2625348347 chore(deps): update node.js to v22.20.0 (#1561)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 23:24:58 +02:00
renovate[bot]
21ccaa7f72 fix(deps): update module github.com/wneessen/go-mail to v0.7.1 (#1560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 20:26:34 +00:00
renovate[bot]
20a2b5a53b fix(deps): update module github.com/jaswdr/faker/v2 to v2.8.1 (#1559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 20:08:11 +00:00
renovate[bot]
47239e546a chore(deps): pin useblacksmith/setup-docker-builder action to 80c45ee (#1553)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 19:42:11 +00:00
renovate[bot]
5a3a0eecae chore(deps): update actions/cache digest to 0057852 (#1554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 19:16:34 +00:00
renovate[bot]
be4daaad8e chore(deps): update cypress/browsers:latest docker digest to 79a28dd (#1555)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 20:51:37 +02:00
renovate[bot]
3e45f9d87e chore(deps): update ghcr.io/go-vikunja/dex-testing:main docker digest to e374904 (#1556)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 20:51:25 +02:00
renovate[bot]
5af935dfa2 chore(deps): update postgres:17 docker digest to cecd364 (#1557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 20:51:12 +02:00
renovate[bot]
f6947952de fix(deps): update dependency vue to v3.5.22 (#1558)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 20:51:00 +02:00
renovate[bot]
a6d4a8333f fix(deps): update dependency @sentry/vue to v10.13.0 (#1538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 17:04:57 +00:00
renovate[bot]
f6c6540f9d chore(deps): update dependency go to v1.25.1 (#1403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 16:22:29 +00:00
renovate[bot]
c71d56ee5e fix(deps): update tiptap to v3.4.5 (#1534)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 16:21:43 +00:00
renovate[bot]
f9ec824c49 chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 7ba49d1 (#1539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 15:45:36 +00:00
renovate[bot]
2e72455b39 chore(deps): update pnpm to v10.17.1 (#1537)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 15:17:09 +00:00
renovate[bot]
8bc5668958 chore(deps): update useblacksmith/build-push-action action to v2 (#1235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2025-09-28 14:35:22 +00:00
kolaente
90605f8fcd chore(deps): update tar-fs to 3.1.1 2025-09-28 16:25:16 +02:00
kolaente
a7eebaffb0 fix: position dropdown absolute, not fixed (#1552)
Fixes a regression introduced in 32501bc93b
2025-09-28 16:18:25 +02:00
Frederick [Bot]
48d202f3ce chore(i18n): update translations via Crowdin 2025-09-26 00:51:58 +00:00
kolaente
9aea4f3b22 fix(ci): download correct unstable binary during migration smoke test 2025-09-25 10:48:23 +02:00
kolaente
b1fc9ac6eb feat(ci): change s3 service 2025-09-25 10:43:46 +02:00
kolaente
8c4fc4780e fix: lint 2025-09-22 14:10:46 +02:00
kolaente
e3f3eeea6b fix(ci): comment on closed issues when closed by commit or PR 2025-09-22 14:06:56 +02:00
kolaente
32501bc93b fix(menu): make sure dropdown menu changes direction when screen is too
small

Resolves https://github.com/go-vikunja/vikunja/issues/1523
2025-09-22 14:03:13 +02:00
renovate[bot]
f09b903153 fix(deps): update module github.com/olekukonko/tablewriter to v1.1.0 (#1532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-21 20:18:00 +02:00
renovate[bot]
0654712cbd chore(deps): update dev-dependencies (#1527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-21 20:17:38 +02:00
Frederick [Bot]
faa4231523 chore(i18n): update translations via Crowdin 2025-09-21 00:57:00 +00:00
renovate[bot]
ba523adc16 chore(deps): update paradedb/paradedb:latest-pg17 docker digest to e7771c3 (#1525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 08:39:12 +02:00
Frederick [Bot]
d53f4079ae chore(i18n): update translations via Crowdin 2025-09-19 00:53:14 +00:00
renovate[bot]
3242650713 chore(deps): update dev-dependencies (#1522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 08:21:53 +02:00
Frederick [Bot]
3397d88f0e chore(i18n): update translations via Crowdin 2025-09-18 00:51:01 +00:00
renovate[bot]
a9239ecc62 chore(deps): update pnpm to v10.17.0 (#1521)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 19:58:21 +02:00
renovate[bot]
26b251172c fix(deps): update tiptap to v3.4.4 (#1519)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 15:07:30 +00:00
renovate[bot]
4ba9537e84 fix(deps): update dependency dompurify to v3.2.7 (#1517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 15:07:06 +00:00
kolaente
fb426a6e22 fix(webhook): actually fetch project before enriching details
This fixes a bug where the project is fetched before adding more details
through ReadOne since ReadOne does not fetch the project. In the normal
project reading flow through the api, this is done in the permission
check.

Resolves https://github.com/go-vikunja/vikunja/issues/1498
2025-09-17 17:04:39 +02:00
renovate[bot]
fc666e57da chore(deps): update ghcr.io/go-vikunja/dex-testing:main docker digest to 5713289 (#1511)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 16:47:01 +02:00
renovate[bot]
8bf9b8b68a chore(deps): update mariadb:12 docker digest to 8a061ef (#1512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 16:46:45 +02:00
renovate[bot]
ba8ad45150 chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 046971a (#1514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 16:46:31 +02:00
renovate[bot]
908df8cd5f fix(deps): update dependency @sentry/vue to v10.12.0 (#1515)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 16:46:16 +02:00
Frederick [Bot]
57b4e27278 chore(i18n): update translations via Crowdin 2025-09-17 00:51:22 +00:00
renovate[bot]
4e9cc597aa fix(deps): update tiptap to v3.4.3 (#1506)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 22:37:09 +02:00
renovate[bot]
22bef1ff7d fix(deps): update module github.com/getsentry/sentry-go/echo to v0.35.3 (#1505)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 15:48:47 +00:00
renovate[bot]
35d11efec7 chore(deps): update dev-dependencies (#1316)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2025-09-15 15:31:47 +00:00
renovate[bot]
e0e22386eb fix(deps): update module github.com/getsentry/sentry-go to v0.35.3 (#1504)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 15:20:12 +00:00
renovate[bot]
fcb80a9ef6 fix(deps): update module github.com/wneessen/go-mail to v0.7.0 (#1497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 16:53:46 +02:00
renovate[bot]
243a0f51c6 chore(deps): update pnpm to v10.16.1 (#1496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 16:52:40 +02:00
renovate[bot]
9bb1f89383 fix(deps): update dependency marked to v16.3.0 (#1500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 16:52:06 +02:00
renovate[bot]
d33b3f2052 fix(deps): update dependency axios to v1.12.2 (#1495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 15:41:19 +02:00
Frederick [Bot]
a1c4d46d37 chore(i18n): update translations via Crowdin 2025-09-14 00:55:43 +00:00
Frederick [Bot]
aad0764cce chore(i18n): update translations via Crowdin 2025-09-13 00:47:39 +00:00
renovate[bot]
d56db81e75 fix(deps): update module github.com/redis/go-redis/v9 to v9.14.0 (#1493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-12 15:47:20 +02:00
kolaente
583623bd6c fix(task): go back to project or saved filter, depending on where the user came from
Resolves https://github.com/go-vikunja/vikunja/issues/1492
2025-09-12 15:45:52 +02:00
renovate[bot]
dbea2ce92a fix(deps): update module github.com/getsentry/sentry-go/echo to v0.35.2 (#1484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-12 10:15:46 +00:00
renovate[bot]
e6abbd5fca fix(deps): update dependency @intlify/unplugin-vue-i18n to v11.0.1 (#1488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-12 11:55:33 +02:00
renovate[bot]
5111f245ad fix(deps): update dependency axios to v1.12.0 [security] (#1487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-12 09:52:34 +00:00
renovate[bot]
b2b1ea776d fix(deps): update dependency @sentry/vue to v10.11.0 (#1485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-12 08:23:30 +02:00
kolaente
46f7c87200 chore: 1.0.0-rc2 release preparations 2025-09-12 00:15:30 +02:00
kolaente
b99ea2deb0 fix(filters): persist url filter query across views (#1482) 2025-09-11 22:12:33 +00:00
kolaente
fcc204dc88 fix(link share): add better error handling, ensure projects are shown (#1481) 2025-09-11 22:39:16 +02:00
kolaente
decee24e12 fix(task): provide back button when opening task detail (#1475) 2025-09-11 22:26:52 +02:00
renovate[bot]
2c44730821 chore(deps): update docker/dockerfile:1 docker digest to dabfc09 (#1479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 21:12:13 +02:00
renovate[bot]
dd895e2c09 fix(deps): update module github.com/getsentry/sentry-go to v0.35.2 (#1480)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 21:11:52 +02:00
kolaente
d14443d2f2 feat(gantt): natural day-boundary rounding in Gantt chart (#1476) 2025-09-11 15:51:15 +00:00
kolaente
0506b9215a fix(filters): initialize task positions for saved filters (#1477) 2025-09-11 17:39:56 +02:00
renovate[bot]
3fd73f3ca8 chore(deps): update ghcr.io/go-vikunja/dex-testing:main docker digest to 408e999 (#1444)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 15:32:14 +00:00
kolaente
0dee1789a2 fix(filter): ensure filter query param is added to the page correctly (#1471) 2025-09-11 15:24:12 +00:00
kolaente
8ce8d445ba fix(kanban): guard task modal race conditions (#1472) 2025-09-11 17:15:57 +02:00
renovate[bot]
a8b72e7363 chore(deps): update paradedb/paradedb:latest-pg17 docker digest to cdad8f0 (#1473)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 17:12:54 +02:00
renovate[bot]
c25b906e6f fix(deps): update dependency @fortawesome/vue-fontawesome to v3.1.2 (#1474)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 17:12:41 +02:00
kolaente
f0cb752f2c fix(task): preserve done timestamp when moving tasks between projects (#1470) 2025-09-11 17:07:43 +02:00
kolaente
25b33102f1 fix(task): only save description when clicking away if it actually changed 2025-09-11 15:53:27 +02:00
kolaente
d147a01c18 fix(filter): add close button for filter popup on mobile with accurate spacing (#1466)
Resolves #1464
2025-09-11 11:27:05 +00:00
renovate[bot]
f2be02ae9b chore(deps): update postgres:17 docker digest to feff5b2 (#1445)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 13:21:19 +02:00
renovate[bot]
f5d59a99cc chore(deps): update cypress/browsers:latest docker digest to 34cbe59 (#1454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 13:21:03 +02:00
Frederick [Bot]
f1de3a9c19 [skip ci] Updated swagger docs 2025-09-11 07:53:45 +00:00
kolaente
e5e0413b70 fix(task): ensure done_at can never be set by user (#1461) 2025-09-11 07:45:42 +00:00
kolaente
4353b1e9c7 fix: guard saved filter requests (#1462) 2025-09-11 09:36:47 +02:00
kolaente
6914badeb7 fix: reload list view when marking recurring task done (#1457) 2025-09-11 08:56:08 +02:00
kolaente
a1e9578971 chore(deps): update devenv 2025-09-10 19:12:42 +02:00
Frederick [Bot]
801ddf19e4 [skip ci] Updated swagger docs 2025-09-10 16:49:05 +00:00
kolaente
db123674a7 feat: share logic for bulk update (#1456)
This change refactors the bulk task update logic so that it updates all fields a single task update would update as well.

Could be improved in the future so that it is more efficient, instead of calling the update function repeatedly. Right now, this reduces the complexity by a lot and it should be fast enough for most cases using this.

Resolves #1452
2025-09-10 16:40:59 +00:00
kolaente
74189b6cf9 chore: update magefile to reference up to date golangci lint 2025-09-10 13:54:59 +02:00
renovate[bot]
9e6e5f993f chore(deps): update dependency vite to v7.1.5 [security] (#1451)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-10 13:25:10 +02:00
renovate[bot]
2538806512 fix(deps): update tiptap to v3.4.2 (#1446)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 21:10:01 +02:00
kolaente
cd711fd11c fix(task): autosave description when closing task modal
Resolves https://github.com/go-vikunja/vikunja/issues/1437
2025-09-09 12:27:55 +02:00
renovate[bot]
9cc9991006 chore(deps): update postgres:17 docker digest to ab24d83 (#1443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 07:20:19 +00:00
renovate[bot]
e0d9a1c148 fix(deps): update module github.com/spf13/viper to v1.21.0 (#1442)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 09:12:44 +02:00
renovate[bot]
088ec40156 fix(deps): update module github.com/spf13/afero to v1.15.0 (#1441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 09:12:08 +02:00
renovate[bot]
43a3888ad9 fix(deps): update module golang.org/x/crypto to v0.42.0 (#1438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 22:28:04 +00:00
renovate[bot]
56c3c32a4e fix(deps): update module golang.org/x/image to v0.31.0 (#1439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 20:17:19 +02:00
renovate[bot]
a78045a611 fix(deps): update module golang.org/x/term to v0.35.0 (#1434)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 08:47:36 +00:00
renovate[bot]
b666362ac7 fix(deps): update module golang.org/x/text to v0.29.0 (#1435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 08:09:50 +00:00
renovate[bot]
f6656d414d chore(deps): update postgres:17 docker digest to 5250e61 (#1433)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 07:45:08 +00:00
renovate[bot]
5c8862bc32 fix(deps): update module golang.org/x/sync to v0.17.0 (#1430)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 07:14:43 +00:00
renovate[bot]
dbc854dcee fix(deps): update module golang.org/x/sys to v0.36.0 (#1432)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-07 17:31:53 +00:00
renovate[bot]
ac6e8d45c7 fix(deps): update module golang.org/x/oauth2 to v0.31.0 (#1429)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-07 18:34:45 +02:00
renovate[bot]
1234eb1fc6 chore(deps): update softprops/action-gh-release digest to 6cbd405 (#1428)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-07 13:42:56 +02:00
Frederick [Bot]
6f45d56055 chore(i18n): update translations via Crowdin 2025-09-07 00:56:28 +00:00
renovate[bot]
f119d01bc1 chore(deps): update postgres:17 docker digest to d17be73 (#1427)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 08:27:08 +02:00
renovate[bot]
fb154d2935 fix(deps): update module github.com/prometheus/client_golang to v1.23.2 (#1426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 00:53:44 +02:00
renovate[bot]
367ab7e9ec chore(deps): update paradedb/paradedb:latest-pg17 docker digest to aaa1861 (#1425)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 00:53:27 +02:00
kolaente
61f0cc9507 fix(frontend): hide drag handle for fixed project lists (#1421) 2025-09-05 10:51:44 +00:00
renovate[bot]
02da0116a9 fix(deps): update dependency vue-i18n to v11.1.12 (#1420)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 09:38:51 +00:00
Frederick [Bot]
70175c31c2 [skip ci] Updated swagger docs 2025-09-04 16:33:42 +00:00
renovate[bot]
d296352a01 fix(deps): update dependency @intlify/unplugin-vue-i18n to v11 (#1418)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 16:15:37 +00:00
kolaente
1b5a9dbdea refactor: use helper function to check user local 2025-09-04 18:09:21 +02:00
kolaente
b8afdcf62d fix(user): do not reject 2fa for local users
https://github.com/go-vikunja/vikunja/issues/1402
2025-09-04 18:09:21 +02:00
kolaente
bd74733632 fix: show pagination controls for task comments (#1413)
Resolves https://community.vikunja.io/t/task-comment-pagination-in-1-0-0-rc1/3988
2025-09-04 16:04:05 +00:00
renovate[bot]
f49dbf04ef chore(deps): update actions/github-script action to v8 (#1417)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 15:50:12 +00:00
dependabot[bot]
daf063785f chore(deps-dev): bump electron from 37.3.0 to 37.3.1 in /desktop (#1415)
Bumps [electron](https://github.com/electron/electron) from 37.3.0 to 37.3.1.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v37.3.0...v37.3.1)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 37.3.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 15:48:02 +00:00
renovate[bot]
5bde234859 fix(deps): update dependency @sentry/vue to v10.10.0 (#1416)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 15:46:03 +00:00
kolaente
12842a5e35 fix: remove leftover console.log 2025-09-04 17:13:41 +02:00
kolaente
bb07b33dc2 fix(task): update task comment when switching between related tasks 2025-09-04 17:06:34 +02:00
renovate[bot]
6892e8e447 fix(deps): update tiptap to v3.4.1 (#1412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 14:51:03 +00:00
renovate[bot]
fdd0eba241 fix(deps): update module github.com/prometheus/client_golang to v1.23.1 (#1411)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 14:47:32 +00:00
kolaente
4393d0cdb8 fix(kanban): update task on board when changing details
Fixes a regression introduced in 7ef7163bfe
2025-09-04 16:33:06 +02:00
renovate[bot]
b3c454ea03 chore(deps): update actions/setup-node action to v5 (#1408)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 10:05:48 +00:00
renovate[bot]
057a318f23 chore(deps): update actions/setup-go action to v6 (#1407)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 08:53:45 +02:00
renovate[bot]
7605fc255b fix(deps): update tiptap to v3.4.0 (#1406)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 06:33:01 +00:00
renovate[bot]
bfe0206032 chore(deps): update crowdin/github-action digest to 0749939 (#1396)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 08:15:53 +02:00
renovate[bot]
e270c29642 chore(deps): update dependency electron to v37.3.1 [security] (#1404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 08:15:11 +02:00
renovate[bot]
7cf0a9dc9a chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 646cd3f (#1405)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 08:14:50 +02:00
renovate[bot]
df3482849f fix(deps): update module github.com/redis/go-redis/v9 to v9.13.0 (#1401)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 16:09:10 +02:00
renovate[bot]
d78291acf7 fix(deps): update dependency @sentry/vue to v10.9.0 (#1400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 16:09:05 +02:00
Frederick [Bot]
98a9e736f4 chore(i18n): update translations via Crowdin 2025-09-03 00:50:31 +00:00
renovate[bot]
d34a3a2c77 fix(deps): update font awesome to v7.0.1 (#1394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 21:14:14 +00:00
Copilot
91f81e7dee feat(kanban): allow folding done column by clicking green checkmarks in Kanban view (#1393) 2025-09-02 21:06:57 +00:00
renovate[bot]
a519312d55 fix(deps): update module github.com/threedotslabs/watermill to v1.5.1 (#1391) 2025-09-02 18:11:45 +00:00
Copilot
70ff047588 fix(avatar): recover gracefully from broken avatar cache (#1379) 2025-09-02 14:03:58 +00:00
renovate[bot]
795afad9c1 fix(deps): update tiptap to v3.3.1 (#1386)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 14:01:23 +00:00
kolaente
15ea38183c fix: bypass Typesense in user export (#1385) 2025-09-02 15:49:17 +02:00
renovate[bot]
7dafd07879 fix(deps): update dependency vue to v3.5.21 (#1382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 13:05:44 +00:00
renovate[bot]
bc04abedb5 chore(deps): update ghcr.io/go-vikunja/dex-testing:main docker digest to 10cc6d5 (#1377)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 07:12:37 +00:00
renovate[bot]
7fbda38e85 chore(deps): update mariadb:12 docker digest to a5af517 (#1376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 09:04:27 +02:00
Frederick [Bot]
4b47f0c3cd chore(i18n): update translations via Crowdin 2025-09-02 00:54:00 +00:00
renovate[bot]
d38092acc8 fix(deps): update module github.com/spf13/cobra to v1.10.1 (#1373)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 21:23:00 +00:00
renovate[bot]
67befec053 fix(deps): update vueuse to v13.9.0 (#1374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 21:18:09 +00:00
renovate[bot]
9800b2a8f8 chore(deps): update pnpm to v10.15.1 (#1371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 21:09:27 +00:00
kolaente
bd310f50ad fix: show error when user list filter is empty (#1372) 2025-09-01 21:08:13 +00:00
renovate[bot]
24f6c48fce chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 5d3cc02 (#1370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 21:03:53 +00:00
William Guinaudie
fda440bd08 fix: mobile device minimum width (#1337) 2025-09-01 22:49:42 +02:00
Frederick [Bot]
11d9462fdc chore(i18n): update translations via Crowdin 2025-09-01 01:03:21 +00:00
kolaente
27c0d68e21 chore(dev): update devenv 2025-08-31 22:13:26 +02:00
kolaente
ed04638726 fix(task): only load first comments page when loading comments with task
Resolves
https://community.vikunja.io/t/task-comment-pagination-in-1-0-0-rc1/3988
2025-08-31 21:58:46 +02:00
kolaente
0262ab7d9e fix(editor): do not apply filter input styles to all editor instances 2025-08-31 21:34:29 +02:00
kolaente
9610ae780f fix(multiselect): do not try getting label when value is undefined
Resolves https://github.com/go-vikunja/vikunja/issues/1346
2025-08-31 21:31:42 +02:00
kolaente
3baf6cd477 fix(task): do not parse task comment reactions so that they actually
appear

Resolves https://github.com/go-vikunja/vikunja/issues/1343
2025-08-31 21:25:48 +02:00
Copilot
14e1bd2f55 fix: prevent null history.state errors in modal routing and task navigation (#1368) 2025-08-31 19:14:25 +00:00
kolaente
7510f8d4be fix(task): default to medium priority when none is configured
Resolves https://github.com/go-vikunja/vikunja/issues/1328
2025-08-31 21:07:30 +02:00
zapp88
a73d0258e4 fix(task): priority label task link spacing (#1322) 2025-08-31 18:36:56 +00:00
zapp88
cbbc5e731a fix(frontend): add missing permission translation keys (#1320) 2025-08-31 18:06:55 +00:00
kolaente
7ef7163bfe fix(task): set project after loading task details
Resolves https://github.com/go-vikunja/vikunja/issues/1342
2025-08-31 19:40:54 +02:00
andreymal
1047e62978 feat(i18n): add pluralization rules for Russian (#1334) 2025-08-31 17:40:41 +00:00
kolaente
61400e93a9 feat(task): make default project relation configurable
Resolves https://community.vikunja.io/t/change-default-task-relation/3637
2025-08-31 19:29:13 +02:00
Simone (Saxos)
4c9f112103 fix(filter): don't crash when filtering for labels in (#1333) 2025-08-31 14:35:16 +00:00
William Guinaudie
07d6630891 fix: stop event propagation for clicks inside the DeferTask popup (#1338) 2025-08-31 14:21:40 +00:00
renovate[bot]
db9d842a5b fix(deps): update vueuse to v13.8.0 (#1367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 15:59:55 +02:00
renovate[bot]
9d28b2247a fix(deps): update tiptap to v3.3.0 (#1366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 10:24:47 +00:00
renovate[bot]
6dc0aebd06 fix(deps): update module github.com/threedotslabs/watermill to v1.5.0 (#1365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 10:22:43 +00:00
renovate[bot]
f3234cf6aa fix(deps): update module github.com/stretchr/testify to v1.11.1 (#1363)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 07:35:10 +00:00
renovate[bot]
5865eca797 fix(deps): update module github.com/go-testfixtures/testfixtures/v3 to v3.18.0 (#1362)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 07:33:59 +00:00
renovate[bot]
1f88680a27 fix(deps): update dependency dayjs to v1.11.18 (#1360)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 09:23:28 +02:00
Copilot
c7a26d81fe fix(auth): do not panic with invalid openid provider configuration (#1354) 2025-08-31 07:17:50 +00:00
renovate[bot]
a5ac5dfcf1 fix(deps): update dependency @sentry/vue to v10.8.0 (#1361)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 00:34:25 +02:00
Copilot
5ca637a7e6 feat(auth): add oauth require availability configuration on startup (#1358) 2025-08-30 22:15:20 +00:00
kolaente
523dad5134 chore: add feature issue template 2025-08-30 16:47:32 +02:00
renovate[bot]
9a14ccf487 chore(deps): update node.js to v22.19.0 (#1356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-29 20:57:56 +00:00
renovate[bot]
37b501c029 fix(deps): update dependency dayjs to v1.11.17 (#1355)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-29 20:53:17 +00:00
kolaente
e29561e49c fix: require publicurl when cors enabled (#1351) 2025-08-29 20:51:31 +00:00
renovate[bot]
af8c977ccf fix(deps): update module github.com/gabriel-vasile/mimetype to v1.4.10 (#1353)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-29 15:44:25 +00:00
renovate[bot]
150c75286c chore(deps): update cypress/browsers:latest docker digest to eeb9b35 (#1352)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-29 15:39:28 +00:00
kolaente
fd8a36c9bb fix: use correct filepath
Resolves https://github.com/go-vikunja/vikunja/issues/1345
2025-08-29 17:20:43 +02:00
renovate[bot]
136690b31e fix(deps): update dependency marked to v16.2.1 (#1350)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 17:36:31 +00:00
renovate[bot]
d45b381a96 fix(deps): update dependency dayjs to v1.11.15 (#1349)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 17:28:32 +00:00
renovate[bot]
daafb3843d chore(deps): update ghcr.io/go-vikunja/dex-testing:main docker digest to 3877bee (#1348)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 19:11:34 +02:00
renovate[bot]
b1415ea67d chore(deps): update crowdin/github-action digest to 9787f4f (#1347)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 19:11:20 +02:00
renovate[bot]
1d25c29e9a fix(deps): update dependency vue to v3.5.20 (#1327)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 19:11:02 +02:00
renovate[bot]
f099fce4fe chore(deps): update pnpm to v10.15.0 (#1314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 13:20:27 +02:00
renovate[bot]
9d03beefec chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 744e637 (#1325)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 13:20:15 +02:00
dependabot[bot]
d90a43c51b chore(deps): bump github.com/go-viper/mapstructure/v2 from 2.3.0 to 2.4.0 (#1331)
chore(deps): bump github.com/go-viper/mapstructure/v2

Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-28 13:19:50 +02:00
renovate[bot]
b37e2b9389 fix(deps): update dependency @floating-ui/dom to v1.7.4 (#1332)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 13:19:23 +02:00
renovate[bot]
ad7f386ddc chore(deps): update cypress/browsers:latest docker digest to 4e0a347 (#1297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 13:18:47 +02:00
Frederick [Bot]
f6b6fadb51 chore(i18n): update translations via Crowdin 2025-08-23 00:52:11 +00:00
Frederick [Bot]
1b5ddfd11c chore(i18n): update translations via Crowdin 2025-08-22 00:54:32 +00:00
Frederick [Bot]
8ca2487784 chore(i18n): update translations via Crowdin 2025-08-21 00:53:22 +00:00
Frederick [Bot]
871861aad3 chore(i18n): update translations via Crowdin 2025-08-20 00:54:16 +00:00
renovate[bot]
f05d799185 chore(deps): update ghcr.io/go-vikunja/dex-testing:main docker digest to 4abbc4f (#1312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 11:08:15 +02:00
renovate[bot]
9f9a71a01f chore(deps): update dev-dependencies (#1310)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 09:43:13 +02:00
Frederick [Bot]
f5c6295f42 chore(i18n): update translations via Crowdin 2025-08-19 00:56:39 +00:00
renovate[bot]
4d9434b990 chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 6c9cd0a (#1309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 21:43:46 +00:00
renovate[bot]
06b9a9d549 chore(deps): update paradedb/paradedb:latest-pg17 docker digest to 12f4078 (#1308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 20:05:27 +02:00
renovate[bot]
b794874cd4 fix(deps): update vueuse to v13.7.0 (#1305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 10:42:54 +00:00
kolaente
ea4f7f2c87 docs: clarify publicurl requirements 2025-08-18 11:59:13 +02:00
kolaente
c1ce33019c fix: set test fixture in e2e test
Fixes a regression introduced in 63b1082951
2025-08-18 11:55:55 +02:00
kolaente
86fb28c732 chore(i18n): clarify wording 2025-08-18 11:44:41 +02:00
kolaente
63b1082951 chore(i18n): improve wording 2025-08-18 11:39:12 +02:00
kolaente
e2e9b28d4e feat(config): validate publicurl 2025-08-18 11:37:44 +02:00
renovate[bot]
99c1035191 chore(deps): update dev-dependencies (#1298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 07:20:45 +00:00
renovate[bot]
6178daf9b2 fix(deps): update dependency marked to v16.2.0 (#1304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 09:08:17 +02:00
Frederick [Bot]
a5407e3f99 chore(i18n): update translations via Crowdin 2025-08-18 01:01:59 +00:00
kolaente
eb676aebef feat: add subsets for all supported languages
This adds unicode ranges for all supported languages: Cyrillic (Russian,
Ukrainian, Bulgarian), CJK (Chinese, Japanese, Korean), Arabic, Hebrew,
Vietnamese diacritics, and various European languages.
2025-08-17 23:11:30 +02:00
kolaente
f01f2af4cf feat: use variable fonts
Resolves https://github.com/go-vikunja/vikunja/issues/493
2025-08-17 23:02:07 +02:00
kolaente
5a385aca1c docs: add GitHub actions status badge to readme 2025-08-17 21:38:50 +02:00
373 changed files with 20280 additions and 8207 deletions

View File

@@ -11,7 +11,7 @@ body:
value: |
Please fill out this issue template to report a bug.
1. If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
1. If you want to propose a new feature, please use the Feature template or open a discussion thread in the forum: https://community.vikunja.io
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
3. Make sure you are using the latest release and
take a moment to check that your issue hasn't been reported before.

28
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Feature Request
description: Found something you weren't expecting? Report it here!
type: Feature
body:
- type: markdown
attributes:
value: |
NOTE: If your issue is a security concern, please send an email to security@vikunja.io instead of opening a public issue. [More information about our security policy](https://vikunja.io/contact/#security).
- type: markdown
attributes:
value: |
Please fill out this issue template to request a new feature.
1. If you want to report a bug, please use the Bug template.
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
3. Make sure you are using the latest release and take a moment to check that your feature hasn't been requested before.
4. Please include all relevant information in the feature request to allow users to discuss this fully.
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of the feature you are looking for.
- type: textarea
id: alternatives
attributes:
label: Which alternatives did you consider using instead?

View File

@@ -13,13 +13,14 @@ runs:
- if: inputs.install-e2e-binaries == 'false'
shell: bash
run: |
echo "CYPRESS_INSTALL_BINARY=0" >> $GITHUB_ENV
echo "PUPPETEER_SKIP_DOWNLOAD=true" >> $GITHUB_ENV
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4
echo "CYPRESS_INSTALL_BINARY=0" >> $GITHUB_ENV
echo "PUPPETEER_SKIP_DOWNLOAD=true" >> $GITHUB_ENV
echo "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1" >> $GITHUB_ENV
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
run_install: false
package_json_file: frontend/package.json
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version-file: frontend/.nvmrc
cache: 'pnpm'

View File

@@ -9,19 +9,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
persist-credentials: true
- name: push source files
uses: crowdin/github-action@590c05e09a29f392b203faf4d6aa8e0cd32c7835 # v2
uses: crowdin/github-action@60debf382ee245b21794321190ad0501db89d8c1 # v2
with:
command: 'push'
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: pull translations
uses: crowdin/github-action@590c05e09a29f392b203faf4d6aa8e0cd32c7835 # v2
uses: crowdin/github-action@60debf382ee245b21794321190ad0501db89d8c1 # v2
with:
command: 'download'
command_args: '--export-only-approved --skip-untranslated-strings'
@@ -29,7 +29,7 @@ jobs:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version-file: frontend/.nvmrc
- name: Ensure file permissions

View File

@@ -1,4 +1,4 @@
name: Comment on PR when issue is closed
name: Comment on issue when it is closed automatically
on:
issues:
@@ -7,14 +7,19 @@ on:
jobs:
comment-on-issue-closure:
runs-on: ubuntu-latest
# Only run if the issue was closed by a commit (not manually)
if: ${{ github.event.issue.closed_by.type == 'Bot' || github.event.issue.closed_by.login != null }}
steps:
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
- name: Check if issue was closed by commit
id: check-commit
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ steps.generate-token.outputs.token }}
script: |
const issueNumber = context.payload.issue.number;
@@ -29,20 +34,28 @@ jobs:
const closedEvent = events
.filter(event => event.event === 'closed')
.pop(); // Get the last (most recent) closed event
// Find the most recent "referenced" event
const referencedEvent = events
.filter(event => event.event === 'referenced')
.pop(); // Get the last (most recent) referenced event
if (closedEvent && closedEvent.commit_id) {
console.log(`✅ Issue #${issueNumber} was closed by commit: ${closedEvent.commit_id}`);
console.log({closedEvent, referencedEvent});
if (closedEvent && (closedEvent.commit_id || referencedEvent)) {
const commitId = closedEvent.commit_id ?? referencedEvent.commit_id
console.log(`✅ Issue #${issueNumber} was closed by commit: ${commitId}`);
// Get commit details
const { data: commit } = await github.rest.git.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: closedEvent.commit_id
commit_sha: commitId
});
core.setOutput('closed_by_commit', 'true');
core.setOutput('commit_sha', closedEvent.commit_id);
core.setOutput('commit_message', commit.message);
core.setOutput('commit_sha', commitId);
// Escape backslashes, backticks and ${ to prevent breaking JS template strings
const escapedMessage = commit.message.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
core.setOutput('commit_message', escapedMessage);
core.setOutput('commit_url', closedEvent.commit_url);
} else {
console.log(` Issue #${issueNumber} was closed manually (not by commit)`);
@@ -51,13 +64,13 @@ jobs:
- name: Determine closure method and comment on issue
if: steps.check-commit.outputs.closed_by_commit == 'true'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ steps.generate-token.outputs.token }}
script: |
const issueNumber = context.payload.issue.number;
const commitSha = '${{ steps.check-commit.outputs.commit_sha }}';
const commitMessage = '${{ steps.check-commit.outputs.commit_message }}';
const commitMessage = `${{ steps.check-commit.outputs.commit_message }}`;
const commitUrl = '${{ steps.check-commit.outputs.commit_url }}';
try {
@@ -114,7 +127,7 @@ jobs:
? `#${closingPR.number}`
: `[\`${commitSha.substring(0, 7)}\`](${commitUrl})`
const comment = `This issue has been fixed in ${closedRef}, please check with the next unstable build (should be ready for deployment in ~30min, also on [the demo](https://try.vikunja.io).`
const comment = `This issue has been fixed in ${closedRef}, please check with the next unstable build (should be ready for deployment in ~30min, also on [the demo](https://try.vikunja.io)).`
await github.rest.issues.createComment({
owner: context.repo.owner,

59
.github/workflows/pr-docker.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: PR Docker Build
on:
# pull_request_target gives write access to GHCR even for PRs from forks.
# This is safe because:
# 1. We explicitly checkout the PR's head commit (no base branch code execution)
# 2. We ONLY build a Docker image (isolated container, no workflow scripts from PR)
# 3. No actions that execute PR code in the workflow context (no github-script, etc)
# 4. Build happens in isolated Docker container with well-defined Dockerfile
pull_request_target:
jobs:
docker:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
# For pull_request_target, we need to explicitly fetch the PR ref from forks
# since the PR's commit SHA is not reachable in the base repository.
# This is safe because no PR code is executed in workflow context.
# Only Docker build uses the PR code (isolated in container).
ref: refs/pull/${{ github.event.pull_request.number }}/head
- name: Git describe
id: ghd
uses: proudust/gh-describe@v2
- name: Login to GHCR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
with:
version: latest
- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: ghcr.io/go-vikunja/vikunja
tags: |
type=ref,event=pr
type=sha,format=long
- name: Build and push PR image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
RELEASE_VERSION=${{ steps.ghd.outputs.describe }}

View File

@@ -10,13 +10,19 @@ jobs:
- name: Git describe
id: ghd
uses: proudust/gh-describe@v2
- name: Login to GHCR
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Login to GHCR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
with:
@@ -24,10 +30,11 @@ jobs:
- name: Docker meta version
if: ${{ github.ref_type == 'tag' }}
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: |
vikunja/vikunja
ghcr.io/go-vikunja/vikunja
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -35,28 +42,34 @@ jobs:
type=raw,value=latest
- name: Build and push unstable
if: ${{ github.ref_type != 'tag' }}
uses: useblacksmith/build-push-action@574eb0ee0b59c6a687ace24192f0727dfb65d6d7 # v1
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
push: true
tags: vikunja/vikunja:unstable
tags: |
vikunja/vikunja:unstable
ghcr.io/go-vikunja/vikunja:unstable
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
RELEASE_VERSION=${{ steps.ghd.outputs.describe }}
- name: Build and push version
if: ${{ github.ref_type == 'tag' }}
uses: useblacksmith/build-push-action@574eb0ee0b59c6a687ace24192f0727dfb65d6d7 # v1
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
RELEASE_VERSION=${{ steps.ghd.outputs.describe }}
binaries:
runs-on: blacksmith-8vcpu-ubuntu-2204
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Git describe
id: ghd
uses: proudust/gh-describe@v2
@@ -64,11 +77,11 @@ jobs:
with:
go-version: stable
- name: Download Mage Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: mage_bin
- name: get frontend
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: frontend_dist
path: frontend/dist
@@ -108,21 +121,21 @@ jobs:
- name: Upload
uses: kolaente/s3-action@41963184b524ccac734ea4d8c964ac74b5b1af89 # v1.2.1
with:
s3-access-key-id: ${{ secrets.HETZNER_S3_ACCESS_KEY }}
s3-secret-access-key: ${{ secrets.HETZNER_S3_SECRET_KEY }}
s3-endpoint: "https://fsn1.your-objectstorage.com"
s3-bucket: "vikunja"
s3-region: "fsn1"
s3-access-key-id: ${{ secrets.S3_ACCESS_KEY }}
s3-secret-access-key: ${{ secrets.S3_SECRET_KEY }}
s3-endpoint: ${{ secrets.S3_ENDPOINT }}
s3-bucket: ${{ secrets.S3_BUCKET }}
s3-region: ${{ secrets.S3_REGION }}
target-path: /vikunja/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }}
files: "dist/zip/*"
strip-path-prefix: dist/zip/
- name: Store Binaries
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: vikunja_bins
path: ./dist/binaries/*
- name: Store Binary Packages
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
if: ${{ github.ref_type == 'tag' }}
with:
name: vikunja_bin_packages
@@ -141,9 +154,9 @@ jobs:
- archlinux
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Download Vikunja Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_bins
pattern: vikunja-*-linux-amd64
@@ -151,7 +164,7 @@ jobs:
id: ghd
uses: proudust/gh-describe@v2
- name: Download Mage Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: mage_bin
- name: Prepare
@@ -173,16 +186,16 @@ jobs:
- name: Upload
uses: kolaente/s3-action@41963184b524ccac734ea4d8c964ac74b5b1af89 # v1.2.1
with:
s3-access-key-id: ${{ secrets.HETZNER_S3_ACCESS_KEY }}
s3-secret-access-key: ${{ secrets.HETZNER_S3_SECRET_KEY }}
s3-endpoint: "https://fsn1.your-objectstorage.com"
s3-bucket: "vikunja"
s3-region: "fsn1"
s3-access-key-id: ${{ secrets.S3_ACCESS_KEY }}
s3-secret-access-key: ${{ secrets.S3_SECRET_KEY }}
s3-endpoint: ${{ secrets.S3_ENDPOINT }}
s3-bucket: ${{ secrets.S3_BUCKET }}
s3-region: ${{ secrets.S3_REGION }}
target-path: /vikunja/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }}
files: "dist/os-packages/*"
strip-path-prefix: dist/os-packages/
- name: Store OS Packages
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
if: ${{ github.ref_type == 'tag' }}
with:
name: vikunja_os_package_${{ matrix.package }}
@@ -191,12 +204,12 @@ jobs:
config-yaml:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Git describe
id: ghd
uses: proudust/gh-describe@v2
- name: Download Mage Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: mage_bin
- name: generate
@@ -206,11 +219,11 @@ jobs:
- name: Upload to S3
uses: kolaente/s3-action@41963184b524ccac734ea4d8c964ac74b5b1af89 # v1.2.1
with:
s3-access-key-id: ${{ secrets.HETZNER_S3_ACCESS_KEY }}
s3-secret-access-key: ${{ secrets.HETZNER_S3_SECRET_KEY }}
s3-endpoint: "https://fsn1.your-objectstorage.com"
s3-bucket: "vikunja"
s3-region: "fsn1"
s3-access-key-id: ${{ secrets.S3_ACCESS_KEY }}
s3-secret-access-key: ${{ secrets.S3_SECRET_KEY }}
s3-endpoint: ${{ secrets.S3_ENDPOINT }}
s3-bucket: ${{ secrets.S3_BUCKET }}
s3-region: ${{ secrets.S3_REGION }}
target-path: /vikunja/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }}
files: "config.yml.sample"
@@ -224,16 +237,16 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Git describe
id: ghd
uses: proudust/gh-describe@v2
- name: Install pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
package_json_file: desktop/package.json
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version-file: frontend/.nvmrc
cache: pnpm
@@ -244,7 +257,7 @@ jobs:
sudo apt-get update
sudo apt-get install --no-install-recommends -y libopenjp2-tools rpm libarchive-tools
- name: get frontend
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: frontend_dist
path: frontend/dist
@@ -256,17 +269,17 @@ jobs:
- name: Upload to S3
uses: kolaente/s3-action@41963184b524ccac734ea4d8c964ac74b5b1af89 # v1.2.1
with:
s3-access-key-id: ${{ secrets.HETZNER_S3_ACCESS_KEY }}
s3-secret-access-key: ${{ secrets.HETZNER_S3_SECRET_KEY }}
s3-endpoint: "https://fsn1.your-objectstorage.com"
s3-bucket: "vikunja"
s3-region: "fsn1"
s3-access-key-id: ${{ secrets.S3_ACCESS_KEY }}
s3-secret-access-key: ${{ secrets.S3_SECRET_KEY }}
s3-endpoint: ${{ secrets.S3_ENDPOINT }}
s3-bucket: ${{ secrets.S3_BUCKET }}
s3-region: ${{ secrets.S3_REGION }}
files: "desktop/dist/Vikunja*"
target-path: /desktop/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }}
strip-path-prefix: desktop/dist/
exclude: "desktop/dist/*.blockmap"
- name: Store Desktop Package
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
if: ${{ github.ref_type == 'tag' }}
with:
name: vikunja_desktop_packages_${{ matrix.os }}
@@ -280,16 +293,16 @@ jobs:
contents: write
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
persist-credentials: true
- name: Download Mage Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: mage_bin
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: stable
- name: generate
@@ -330,47 +343,47 @@ jobs:
contents: write
steps:
- name: Download Binaries
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_bin_packages
- name: Download OS Package rpm
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_os_package_rpm
- name: Download OS Package deb
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_os_package_deb
- name: Download OS Package apk
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_os_package_apk
- name: Download OS Package archlinux
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_os_package_archlinux
- name: Download Desktop Package Linux
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_desktop_packages_ubuntu-latest
- name: Download Desktop Package MacOS
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_desktop_packages_macos-latest
- name: Download Desktop Package Windows
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_desktop_packages_windows-latest
- name: Release
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
if: github.ref_type == 'tag'
with:
draft: true

View File

@@ -8,14 +8,14 @@ jobs:
runs-on: ubuntu-latest
name: prepare-mage
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: stable
- name: Cache Mage
id: cache-mage
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
key: ${{ runner.os }}-build-mage-${{ hashFiles('magefile.go') }}
path: |
@@ -27,7 +27,7 @@ jobs:
version: latest
args: -compile ./mage-static
- name: Store Mage Binary
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: mage_bin
path: ./mage-static
@@ -36,16 +36,16 @@ jobs:
runs-on: ubuntu-latest
needs: mage
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Download Mage Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: mage_bin
- name: Git describe
id: ghd
uses: proudust/gh-describe@v2
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: stable
- name: Build
@@ -57,7 +57,7 @@ jobs:
chmod +x ./mage-static
./mage-static build
- name: Store Vikunja Binary
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: vikunja_bin
path: ./vikunja
@@ -65,8 +65,8 @@ jobs:
api-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: stable
- name: prepare frontend files
@@ -74,17 +74,17 @@ jobs:
mkdir -p frontend/dist
touch frontend/dist/index.html
- name: golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9
with:
version: v2.4.0
version: v2.6.0
api-check-translations:
runs-on: ubuntu-latest
needs: mage
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Download Mage Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: mage_bin
- name: Check
@@ -104,14 +104,14 @@ jobs:
- mysql
services:
migration-smoke-db-mysql:
image: mariadb:12@sha256:b30cc65b57a11a2e791ad5c06284e599fe9f1bf3fe9081a88d85bcf36389be4a
image: mariadb:12@sha256:e1bcd6f85781f4a875abefb11c4166c1d79e4237c23de597bf0df81fec225b40
env:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
ports:
- 3306:3306
migration-smoke-db-postgres:
image: postgres:17@sha256:29e0bb09c8e7e7fc265ea9f4367de9622e55bae6b0b97e7cce740c2d63c2ebc0
image: postgres:18@sha256:5ec39c188013123927f30a006987c6b0e20f3ef2b54b140dfa96dac6844d883f
env:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@@ -120,10 +120,10 @@ jobs:
steps:
- name: Download Unstable
run: |
wget https://dl.vikunja.io/api/unstable/vikunja-unstable-linux-amd64-full.zip -q -O vikunja-latest.zip
wget https://dl.vikunja.io/vikunja/unstable/vikunja-unstable-linux-amd64-full.zip -q -O vikunja-latest.zip
unzip vikunja-latest.zip vikunja-unstable-linux-amd64
- name: Download Vikunja Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_bin
- name: run migration
@@ -136,6 +136,7 @@ jobs:
VIKUNJA_DATABASE_SSLMODE: disable
VIKUNJA_LOG_DATABASE: stdout
VIKUNJA_LOG_DATABASELEVEL: debug
VIKUNJA_SERVICE_PUBLICURL: http://127.0.0.1:3456
run: |
# Wait for MySQL to be ready if using MySQL
if [ "$VIKUNJA_DATABASE_TYPE" = "mysql" ]; then
@@ -179,21 +180,21 @@ jobs:
- web
services:
db-mysql:
image: mariadb:12@sha256:b30cc65b57a11a2e791ad5c06284e599fe9f1bf3fe9081a88d85bcf36389be4a
image: ${{ matrix.db == 'mysql' && 'mariadb:12@sha256:5b6a1eac15b85b981a61afb89aea2a22bf76b5f58809d05f0bcc13ab6ec44cb8' || '' }}
env:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
ports:
- 3306:3306
db-postgres:
image: postgres:17@sha256:29e0bb09c8e7e7fc265ea9f4367de9622e55bae6b0b97e7cce740c2d63c2ebc0
image: ${{ matrix.db == 'postgres' && 'postgres:18@sha256:073e7c8b84e2197f94c8083634640ab37105effe1bc853ca4d5fbece3219b0e8' || '' }}
env:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
ports:
- 5432:5432
db-paradedb:
image: paradedb/paradedb:latest-pg17@sha256:df0a755596c88b153954115f17ca2269ab53264bf9ca914d41b4bdd5630c403b
image: ${{ matrix.db == 'paradedb' && 'paradedb/paradedb:latest-pg17@sha256:741010eaa8894d292203d9407d46fc95ee4d0cd587915513bf92e6bd70cbd65e' || '' }}
env:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@@ -204,13 +205,13 @@ jobs:
ports:
- 389:389
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Download Mage Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: mage_bin
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: stable
- name: Configure Postgres for faster tests
@@ -238,16 +239,59 @@ jobs:
VIKUNJA_AUTH_LDAP_BINDDN: uid=gitea,ou=service,dc=planetexpress,dc=com
VIKUNJA_AUTH_LDAP_BINDPASSWORD: password
VIKUNJA_AUTH_LDAP_USERFILTER: "(&(objectclass=inetorgperson)(uid=%s))"
VIKUNJA_SERVICE_PUBLICURL: http://127.0.0.1:3456
run: |
mkdir -p frontend/dist
touch frontend/dist/index.html
chmod +x mage-static
./mage-static test:${{ matrix.test }}
test-s3-integration:
runs-on: ubuntu-latest
needs:
- mage
services:
test-minio:
image: bitnamilegacy/minio:latest@sha256:451fe6858cb770cc9d0e77ba811ce287420f781c7c1b806a386f6896471a349c
env:
MINIO_ROOT_USER: vikunja
MINIO_ROOT_PASSWORD: vikunjatest
MINIO_DEFAULT_BUCKETS: vikunja-test
ports:
- 9000:9000
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Download Mage Binary
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: mage_bin
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version: stable
- name: test S3 file storage integration
env:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: sqlite
VIKUNJA_FILES_TYPE: s3
VIKUNJA_FILES_S3_ENDPOINT: http://localhost:9000
VIKUNJA_FILES_S3_BUCKET: vikunja-test
VIKUNJA_FILES_S3_REGION: us-east-1
VIKUNJA_FILES_S3_ACCESSKEY: vikunja
VIKUNJA_FILES_S3_SECRETKEY: vikunjatest
VIKUNJA_FILES_S3_USEPATHSTYLE: true
VIKUNJA_SERVICE_PUBLICURL: http://127.0.0.1:3456
run: |
mkdir -p frontend/dist
touch frontend/dist/index.html
chmod +x mage-static
# Run only the S3 file storage integration tests
./mage-static test:filter "TestFileStorageIntegration"
frontend-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: ./.github/actions/setup-frontend
- name: Lint
working-directory: frontend
@@ -256,7 +300,7 @@ jobs:
frontend-stylelint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: ./.github/actions/setup-frontend
- name: Lint styles
working-directory: frontend
@@ -265,7 +309,7 @@ jobs:
frontend-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: ./.github/actions/setup-frontend
- name: Typecheck
continue-on-error: true
@@ -275,7 +319,7 @@ jobs:
test-frontend-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: ./.github/actions/setup-frontend
- name: Run unit tests
working-directory: frontend
@@ -284,7 +328,7 @@ jobs:
frontend-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: ./.github/actions/setup-frontend
- name: Git describe
id: ghd
@@ -297,43 +341,109 @@ jobs:
working-directory: frontend
run: pnpm build
- name: Store Frontend
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: frontend_dist
path: ./frontend/dist
test-frontend-e2e:
test-frontend-e2e-playwright:
runs-on: ubuntu-latest
needs:
- api-build
- frontend-build
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6]
total-shards: [6]
services:
dex:
image: ghcr.io/go-vikunja/dex-testing:main@sha256:d401c06a9f8fd36ece446a07499b827232af7f21eb36872a76c9eac4d0c77bab
ports:
- 5556:5556
container:
image: mcr.microsoft.com/playwright:v1.57.0-jammy@sha256:6aca677c27a967caf7673d108ac67ffaf8fed134f27e17b27a05464ca0ace831
options: --user 1001
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Download Vikunja Binary
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_bin
- uses: ./.github/actions/setup-frontend
with:
install-e2e-binaries: false # Playwright browsers already in container
- name: Download Frontend
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: frontend_dist
path: ./frontend/dist
- run: chmod +x ./vikunja
- name: Run Playwright tests
timeout-minutes: 20
working-directory: frontend
run: |
pnpm run preview:vikunja &
pnpm run preview &
# Wait for services to be ready (using GET method)
pnpx wait-on http-get://127.0.0.1:4173 http-get://127.0.0.1:3456/api/v1/info --timeout 60000
pnpm run test:e2e --shard=${{ matrix.shard }}/${{ matrix.total-shards }}
env:
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS: 1
TEST_SECRET: averyLongSecretToSe33dtheDB
VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB
VIKUNJA_LOG_LEVEL: DEBUG
VIKUNJA_CORS_ENABLE: 1
VIKUNJA_SERVICE_PUBLICURL: http://127.0.0.1:3456
VIKUNJA_DATABASE_PATH: memory
VIKUNJA_DATABASE_TYPE: sqlite
VIKUNJA_RATELIMIT_NOAUTHLIMIT: 1000
VIKUNJA_AUTH_OPENID_ENABLED: 1
VIKUNJA_AUTH_OPENID_PROVIDERS_DEX_NAME: Dex
VIKUNJA_AUTH_OPENID_PROVIDERS_DEX_AUTHURL: http://dex:5556
VIKUNJA_AUTH_OPENID_PROVIDERS_DEX_CLIENTID: vikunja
VIKUNJA_AUTH_OPENID_PROVIDERS_DEX_CLIENTSECRET: secret
- name: Upload Playwright Report
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
if: always()
with:
name: playwright-report-${{ matrix.shard }}
path: frontend/playwright-report/
retention-days: 30
- name: Upload Test Results
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
if: always()
with:
name: playwright-test-results-${{ matrix.shard }}
path: frontend/test-results/
retention-days: 30
test-frontend-e2e-cypress:
runs-on: ubuntu-latest
needs:
- api-build
- frontend-build
services:
dex:
image: ghcr.io/go-vikunja/dex-testing:main@sha256:7de4780bdb0c03c50e55d3803c802cce19b9af48a293757abb21122bd22b54c9
image: ghcr.io/go-vikunja/dex-testing:main@sha256:d401c06a9f8fd36ece446a07499b827232af7f21eb36872a76c9eac4d0c77bab
ports:
- 5556:5556
strategy:
# when one test fails, DO NOT cancel the other
# containers, because this will kill Cypress processes
# leaving Cypress Cloud hanging ...
# https://github.com/cypress-io/github-action/issues/48
fail-fast: false
matrix:
containers: [1, 2, 3, 4]
container:
image: cypress/browsers:latest@sha256:2c4e1047f7187ca028dcd968cbd3aafc9430bbd30a3ec4d6b41f9eabfdb39e59
image: cypress/browsers:latest@sha256:ff79e75249941c52c301821a7979db306e2659aa2d6038428bcf77c6f5f125e1
options: --user 1001
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Download Vikunja Binary
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: vikunja_bin
- uses: ./.github/actions/setup-frontend
with:
install-e2e-binaries: true
- name: Download Frontend
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: frontend_dist
path: ./frontend/dist
@@ -342,7 +452,6 @@ jobs:
timeout-minutes: 20
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
CYPRESS_API_URL: http://127.0.0.1:3456/api/v1
CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000
@@ -350,6 +459,7 @@ jobs:
VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB
VIKUNJA_LOG_LEVEL: DEBUG
VIKUNJA_CORS_ENABLE: 1
VIKUNJA_SERVICE_PUBLICURL: http://127.0.0.1:3456
VIKUNJA_DATABASE_PATH: memory
VIKUNJA_DATABASE_TYPE: sqlite
VIKUNJA_RATELIMIT_NOAUTHLIMIT: 1000
@@ -362,19 +472,10 @@ jobs:
install: false
working-directory: frontend
browser: chrome
record: true
parallel: true
record: false
parallel: false
start: |
pnpm run preview:vikunja
pnpm run preview
wait-on: http://127.0.0.1:4173,http://127.0.0.1:3456/api/v1/info
wait-on-timeout: 10
# This step only exists so that we can make it required, because we can't make
# the actual test step required due to the matrix
test-frontend-e2e-success:
runs-on: ubuntu-latest
needs:
- test-frontend-e2e
steps:
- run: exit 0

View File

@@ -11,6 +11,14 @@ The project consists of:
- `desktop/` Electron wrapper application
- `docs/` Documentation website
## Plans
When the user asks you to create a plan to fix or implement something:
- ALWAYS write that plan to the plans/ directory on the root of the repo.
- NEVER commit plans to git
- Give the plan a descriptive name
## Development Commands
### Backend (Go)

View File

@@ -7,6 +7,570 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/vikunja/releases.
## [1.0.0-rc-3] - 2025-11-27
### Bug Fixes
* *(attachments)* Extend upload file size to form data (#1577)
* *(caldav)* Remove METHOD:PUBLISH from caldav exports (#1576)
* *(ci)* Comment on closed issues when closed by commit or PR
* *(ci)* Download correct unstable binary during migration smoke test
* *(ci)* Use correct quotes for commit description
* *(ci)* Find closing PRs when they are not explicitly referenced
* *(ci)* Don't run cypress tests in parallel when started from a fork (#1631)
* *(ci)* Unxecpected token in issue close workflow
* *(ci)* Remove blacksmith docker builder
* *(ci)* Login to ghcr
* *(components)* Handle null values in DatepickerWithValues
* *(components)* Add generic constraints and null checks in AutocompleteDropdown
* *(components)* Add type guards and null checks in Multiselect
* *(components)* Add undefined checks in GanttChartPrimitive
* *(components)* Use undefined instead of empty object in ImportHint
* *(components)* Suppress complex union type error in Button.vue
* *(components)* Correct FontAwesome icon format in TipTap.vue
* *(components)* Add type guards and assertions in TipTap.vue
* *(components)* Fix clipboard data null checks in TipTap.vue
* *(components)* Use correct SetContentOptions in TipTap.vue
* *(components)* Add type annotations for event parameters in TipTap.vue
* *(components)* Add undefined checks in TipTap.vue
* *(components)* Add type annotations in commands.ts
* *(components)* Add type annotations in setLinkInEditor.ts
* *(components)* Add type annotations in suggestion.ts
* *(components)* Add type annotations in MentionList.vue
* *(components)* Add type assertions in MentionUser.vue
* *(components)* Use ts-expect-error in mentionSuggestion.ts
* *(components)* Fix all type errors in FilterAutocomplete.ts
* *(date parsing)* 12pm/12am edge case
* *(date picker)* Hide "this weekend" option on Sunday after 9pm (#1813)
* *(editor)* Prevent image insertion from triggering save (#1846)
* *(editor)* Don't convert text that's pasted into a code block to markdown
* *(editor)* Prevent upload overlay from intercepting text drag operations (#1890)
* *(editor)* Preserve consecutive whitespace in comments in TipTap (#1893)
* *(editor)* Close only editor when pressing escape
* *(events)* Only trigger task.updated once when marking task done
* *(filter)* Check date boundary after timezone conversion
* *(filter)* Don't duplicate subtasks in list view
* *(filter)* Restore cursor position when making changes
* *(filters)* Support project filter in parentheses (#1647)
* *(filters)* Handle multiple projects correctly in parsing, fixing autocomplete
* *(kanban)* Repeating tasks dates won't update when moved in done bucket (#1638)
* *(migration)* Add retry to migration request helper
* *(migration)* Return proper error message when request fails
* *(project)* Correctly set last project when navigating from a saved filter (#1642)
* *(reminders)* Refactor and check permissions when fetching task users
* *(rtl)* Put the menu to the correct side on rtl languages
* *(rtl)* Make sure modals are centered
* *(rtl)* Correct spacing for user avatar menu
* *(sharing)* Make editing link share comments work
* *(sharing)* Use the highest team sharing permission when sharing the same project with multiple teams (#1894)
* *(table)* Label spacing when wrapping
* *(task)* Go back to project or saved filter, depending on where the user came from
* *(task)* Slash menu appearing behind modals (#1752)
* *(test)* Prune leftover task duplicates
* *(test)* Set cypress api url when running tests locally
* *(test)* Correctly set fixed time in login test
* *(user)* Race condition during email confirmation (#1575)
* *(views)* Migrate filter bucket configuration
* *(webhook)* Actually fetch project before enriching details
* *(webhook)* Make sure the payload always contains a fully loaded project
* Fix(menu): make sure dropdown menu changes direction when screen is too small
Resolves https://github.com/go-vikunja/vikunja/issues/1523 ([32501bc](32501bc93b002951a2e7ab0048f64ceaa8424814))
* Lint ([8c4fc47](8c4fc4780ed1d10d667bb57f3c32ef472844254e))
* Position dropdown absolute, not fixed (#1552) ([a7eebaf](a7eebaffb07f96f50abd5c5b17d7e89fdce7aa6d))
* Process multiple reminders in the same time window (#1564) ([ff8e98e](ff8e98e6e2626450106d4e75dfaa94ed61a0dff8))
* Prevent keyboard events during IME composition (#1535) ([d7d3078](d7d3078de5a25a20c8938818dd5c4b675c71a3d4))
* Correct case-sensitivity in duedate time parsing (#1613) ([c2e224d](c2e224dbb19ece72e72711d069704eefa9eae124))
* Style lint ([28f9e5b](28f9e5b83b375aefd8a5a8cb9945a3c531dbad1c))
* Task.comment.deleted triggers panic in event listener which sends webhook (#1621) ([db6b82a](db6b82a002b61558fb9010b18f2d372a4f451576))
* Restore quick add magic modal close button on mobile (#1622) ([dd245cf](dd245cf35ef2b98dd9e80990710978a4cb4bb966))
* Preserve link share hash on task back navigation (#1623) ([0c602d1](0c602d10b82b3ca86acda7c6a8c1cbfbc908d7b7))
* Correctly set database path on windows (#1616) ([7da2942](7da2942ca6481995059dc194ea7ed1bfd1b91250))
* Cleanup team memberships, assignments and subscriptions when users lose access to a project ([9358954](9358954c9043d305c639fa76fb764c60b6da5e2d))
* Prevent duplicate CreateEdit submissions (#1541) ([4383948](438394827592d4a44d853779680db0bdca8ad512))
* Show cross-project subtasks in List view (#1649) ([2977a11](2977a11a2ca6e305a5d34d194575c995b6351498))
* 403 http error code on failed login (#1756) ([f83bd60](f83bd60915840d3241e18d9fd9b2cb6c0d6e8621))
* Properly quote email sender names containing @ symbols (#1768) ([4fe0763](4fe07630107e6653b9292f7b8da27a2e2a155cbe))
* Migrate Sentry integration to SDK v8 API (#1769) ([e424689](e424689ed9dc0760798c5a4d6018a15d67ab8d2e))
* Ignore filter_include_nulls from views ([22fc19c](22fc19cd246b178e92c13e5c1044c4517b4e596a))
* Downgrade tiptap to v3.8.0 to resolve editor crash (#1806) ([409edb8](409edb82c62399afd7acd47a551191cbda72615a))
* Prevent date picker from selecting past times at hour breakpoints (#1814) ([7cd3f69](7cd3f69096b5c4ce0c3e9d8566b61310a78258b8))
* Correctly store fetched task positions ([411cfbe](411cfbef92d6662b226f41b4b2e3b9a84138ea46))
* Lint ([14c7bd8](14c7bd88f2e76bf1975676e93e26ccfbe312b3a0))
* HTML entity double-escaping in email notifications (#1829) ([7729a3d](7729a3dcad2a512304dfd270a4b4092a5321a649))
* Always allow dropping files ([3f1f92c](3f1f92c410f549662b5c116d17680e2bbd438bff))
* Ensure project filters are retained correctly across views (#1643) ([2e3b2cb](2e3b2cb770cceeb5bb3969f9797a2e0abb9192aa))
* Self-assignment notification to use "themselves" instead of repeating username (#1836) ([5f795bb](5f795bb531eefb1ada2d4597a47074af0e8fbc90))
* Prevent panic in webhook listener when fetching project (#1848) ([9d06332](9d0633268a984733b23b345d354c18256d717aa1))
* Don't hide filter icon on hover ([a76ff31](a76ff31dbcbf8e09652fc022313a70593759478d))
* Remove empty style block from Label.vue ([e3ca310](e3ca310c056e466bfbf689baabe5b23bf8aa108b))
* Update mention format to use custom HTML element with usernames (#1843) ([cfab3ff](cfab3ff92270e6b0add6c3190e47f54ea2ddf141))
* Tycheck issues in Story ([6831f3c](6831f3c347d619f357ef2f6f589ab244fb9285a0))
* Add pagination type ([21d9724](21d9724572fc239ea65675cf0950e861d9aea7d9))
* Type issues with expandable ([a6d31da](a6d31dad0883f3c15b77cdb21cb3c00c1e6b307a))
* Service worker types ([5b38a82](5b38a825e3cee383e5ca23276f90fb9fd2b2c569))
* Event type ([3fadacb](3fadacbb76b334842a6c5cdcd69c514bafbb015f))
* Types for DatepickerInline ([db531ab](db531ab1c45f0c5cf869171fbc503c5dad699714))
* Types for Multiselect ([7543b3b](7543b3b5cdcd5f3f0c0a449cbb95811b759ef320))
* Default language in App.vue types ([a89855a](a89855a9d19c8b0652982fecb6004e4914789518))
* ContentAuth types ([4a2f961](4a2f961a778242d1ea98a0458bbea0af3b8ca4fb))
* Return undefined instead of null in ContentLinkShare getProjectRoute() ([75dafd1](75dafd18e3f41282dde23cf5a8c8b656434f2f3a))
* Add null checks and type assertion in ProjectsNavigation.vue ([8f062f2](8f062f21d8a7ee51b4ec95f94887d40242ce1682))
* Add null checks for project.maxPermission in ProjectsNavigationItem.vue ([cdb39c9](cdb39c945cc1b41466c0100266648fd8cf10a0b8))
* Add null/undefined checks for maxPermission in AppHeader.vue ([aadf0e4](aadf0e4c171a38c1e23b8ab6d4b2531d777518c5))
* Add undefined checks and null coalescing in Datepicker.vue ([575cf14](575cf149b0c95bc0bf336ea6611967aa5aaf14d0))
* Add comprehensive null/undefined checks in Reactions.vue ([3742234](3742234540e44bfd123596d58fdd334e91004b8e))
* Initialize date ref with null instead of undefined in Datepicker.vue ([508f91a](508f91a97be43379c49ca91d6fd212b620770d25))
* Use proper FontAwesome icon types in EditorToolbar.vue ([cc76b87](cc76b87b893f8d1eb3aa9b96f2da9cff3ae97fa3))
* Handle readonly arrays and type conversions in DatepickerWithRange.vue ([618c85a](618c85a0a77168d250e7ad92e55f7685da300a0a))
* Add null/undefined handling in GanttChart.vue ([83191eb](83191eb24d7100e6dab561a32f053b2fdc123ed4))
* Add proper type definitions for CommandItem in CommandsList.vue ([4cd53c2](4cd53c204d48b617e4a4279609da840888428c0f))
* Resolve readonly array type issue in Navigation.vue ([f67af55](f67af5520469c3b2cd639687b0b19b29a7d3d43e))
* Resolve readonly project type issue in AppHeader.vue ([658946b](658946b02999b6b1c7ed6ebbe8c778daf5a6bd45))
* Emit null ([4b46849](4b4684961baa31417886f722aacaa95cd355ab8c))
* TickTick import (#1871) ([a4aad79](a4aad79f53f6cd4c2118d8f33f5d692da7ea3c00))
* Don't try to switch to project 0 when reloading the page (#1855) ([f7acdf4](f7acdf4ac1fe41849bc2f9ec500ea7326c9e048c))
* Use shift+r for reminder shortcut on apple devices ([2976d6f](2976d6f6767559a48a9ceb1a1111a0fc0924c1c0))
### Dependencies
* *(deps)* Update dependency @sentry/vue to v10.11.0 (#1485)
* *(deps)* Update dependency axios to v1.12.0 [security] (#1487)
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v11.0.1 (#1488)
* *(deps)* Update module github.com/getsentry/sentry-go/echo to v0.35.2 (#1484)
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.14.0 (#1493)
* *(deps)* Update dependency axios to v1.12.2 (#1495)
* *(deps)* Update dependency marked to v16.3.0 (#1500)
* *(deps)* Update pnpm to v10.16.1 (#1496)
* *(deps)* Update module github.com/wneessen/go-mail to v0.7.0 (#1497)
* *(deps)* Update module github.com/getsentry/sentry-go to v0.35.3 (#1504)
* *(deps)* Update dev-dependencies (#1316)
* *(deps)* Update module github.com/getsentry/sentry-go/echo to v0.35.3 (#1505)
* *(deps)* Update tiptap to v3.4.3 (#1506)
* *(deps)* Update dependency @sentry/vue to v10.12.0 (#1515)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 046971a (#1514)
* *(deps)* Update mariadb:12 docker digest to 8a061ef (#1512)
* *(deps)* Update ghcr.io/go-vikunja/dex-testing:main docker digest to 5713289 (#1511)
* *(deps)* Update dependency dompurify to v3.2.7 (#1517)
* *(deps)* Update tiptap to v3.4.4 (#1519)
* *(deps)* Update pnpm to v10.17.0 (#1521)
* *(deps)* Update dev-dependencies (#1522)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to e7771c3 (#1525)
* *(deps)* Update dev-dependencies (#1527)
* *(deps)* Update module github.com/olekukonko/tablewriter to v1.1.0 (#1532)
* *(deps)* Update tar-fs to 3.1.1
* *(deps)* Update useblacksmith/build-push-action action to v2 (#1235)
* *(deps)* Update pnpm to v10.17.1 (#1537)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 7ba49d1 (#1539)
* *(deps)* Update tiptap to v3.4.5 (#1534)
* *(deps)* Update dependency go to v1.25.1 (#1403)
* *(deps)* Update dependency @sentry/vue to v10.13.0 (#1538)
* *(deps)* Update dependency vue to v3.5.22 (#1558)
* *(deps)* Update postgres:17 docker digest to cecd364 (#1557)
* *(deps)* Update ghcr.io/go-vikunja/dex-testing:main docker digest to e374904 (#1556)
* *(deps)* Update cypress/browsers:latest docker digest to 79a28dd (#1555)
* *(deps)* Update actions/cache digest to 0057852 (#1554)
* *(deps)* Pin useblacksmith/setup-docker-builder action to 80c45ee (#1553)
* *(deps)* Update module github.com/jaswdr/faker/v2 to v2.8.1 (#1559)
* *(deps)* Update module github.com/wneessen/go-mail to v0.7.1 (#1560)
* *(deps)* Update node.js to v22.20.0 (#1561)
* *(deps)* Update dev-dependencies (#1563)
* *(deps)* Update docker/login-action digest to 5e57cd1 (#1565)
* *(deps)* Update tiptap to v3.6.2 (#1566)
* *(deps)* Update module github.com/wneessen/go-mail to v0.7.2 (#1567)
* *(deps)* Update dependency @sentry/vue to v10.16.0 (#1568)
* *(deps)* Update dev-dependencies (#1569)
* *(deps)* Update ghcr.io/go-vikunja/dex-testing:main docker digest to 738f7db (#1571)
* *(deps)* Update postgres docker tag to v18 (#1562)
* *(deps)* Update postgres:18 docker digest to ad98a3b (#1572)
* *(deps)* Update dependency @sentry/vue to v10.17.0 (#1574)
* *(deps)* Update dev-dependencies (#1578)
* *(deps)* Update postgres:18 docker digest to 67a7c38 (#1579)
* *(deps)* Update dev-dependencies (#1584)
* *(deps)* Update mariadb:12 docker digest to 03a03a6 (#1592)
* *(deps)* Update postgres:18 docker digest to 073e7c8 (#1583)
* *(deps)* Update useblacksmith/build-push-action digest to 30c7116 (#1593)
* *(deps)* Update useblacksmith/setup-docker-builder digest to 18cdb72 (#1594)
* *(deps)* Update dependency tailwindcss to v3.4.18 (#1585)
* *(deps)* Update module github.com/go-ldap/ldap/v3 to v3.4.12 (#1586)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 48d6b3f (#1582)
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.16.0 (#1597)
* *(deps)* Update pnpm to v10.18.0 (#1596)
* *(deps)* Update tiptap to v3.6.5 (#1595)
* *(deps)* Update font awesome to v7.1.0 (#1587)
* *(deps)* Update pnpm to v10.18.1 (#1598)
* *(deps)* Update dependency marked to v16.4.0 (#1603)
* *(deps)* Update softprops/action-gh-release digest to aec2ec5 (#1602)
* *(deps)* Update dev-dependencies (#1601)
* *(deps)* Update useblacksmith/setup-docker-builder digest to 6679018 (#1600)
* *(deps)* Update dependency @sentry/vue to v10.18.0 (#1606)
* *(deps)* Update ghcr.io/go-vikunja/dex-testing:main docker digest to d401c06 (#1604)
* *(deps)* Update dependency go to v1.25.2 (#1607)
* *(deps)* Update pnpm/action-setup digest to 41ff726 (#1609)
* *(deps)* Update cypress/browsers:latest docker digest to 1f1adf3 (#1608)
* *(deps)* Update module golang.org/x/oauth2 to v0.32.0 (#1611)
* *(deps)* Update module golang.org/x/sys to v0.37.0 (#1612)
* *(deps)* Update useblacksmith/setup-docker-builder digest to 78f4168 (#1617)
* *(deps)* Update module golang.org/x/crypto to v0.43.0 (#1618)
* *(deps)* Update module golang.org/x/image to v0.32.0 (#1619)
* *(deps)* Update node.js to 605dc0b (#1626)
* *(deps)* Update docker/dockerfile:1 docker digest to b6afd42 (#1624)
* *(deps)* Update dev-dependencies (#1625)
* *(deps)* Update node.js to dbcedd8 (#1627)
* *(deps)* Update dependency @sentry/vue to v10.19.0 (#1632)
* *(deps)* Update tiptap to v3.6.6 (#1633)
* *(deps)* Update mariadb:12 docker digest to 5b6a1ea (#1634)
* *(deps)* Update dependency @types/node to v22.18.9 (#1635)
* *(deps)* Update pnpm to v10.18.2 (#1636)
* *(deps)* Update dependency happy-dom to v20 [security] (#1640)
* *(deps)* Update softprops/action-gh-release digest to 6da8fa9 (#1641)
* *(deps)* Update actions/setup-node action to v6 (#1654)
* *(deps)* Update pnpm to v10.18.3 (#1655)
* *(deps)* Update dependency go to v1.25.3 (#1653)
* *(deps)* Update dev-dependencies (#1644)
* *(deps)* Update tiptap to v3.6.7 (#1656)
* *(deps)* Update dependency dompurify to v3.3.0 (#1651)
* *(deps)* Update module github.com/getsentry/sentry-go to v0.36.0 (#1658)
* *(deps)* Update tiptap to v3.7.0 (#1660)
* *(deps)* Update dependency vue-router to v4.6.0 (#1657)
* *(deps)* Update module github.com/getsentry/sentry-go/echo to v0.36.0 (#1659)
* *(deps)* Update dependency @sentry/vue to v10.20.0 (#1666)
* *(deps)* Update dependency vue-router to v4.6.2 (#1665)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 11e1827 (#1661)
* *(deps)* Update dependency happy-dom to v20.0.2 [security] (#1668)
* *(deps)* Update dev-dependencies (#1662)
* *(deps)* Update dependency marked to v16.4.1 (#1677)
* *(deps)* Update dev-dependencies (#1676)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 4f5d59a (#1675)
* *(deps)* Update dependency vue-router to v4.6.3 (#1671)
* *(deps)* Update tiptap to v3.7.2 (#1669)
* *(deps)* Update cypress/browsers:latest docker digest to f47e21d (#1674)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 842e7ed (#1678)
* *(deps)* Update cypress/browsers:latest docker digest to b7d45cd (#1680)
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.14.1 (#1681)
* *(deps)* Update dev-dependencies (#1682)
* *(deps)* Pin docker/build-push-action action to 2634353 (#1683)
* *(deps)* Update dev-dependencies (#1686)
* *(deps)* Update crowdin/github-action digest to 08713f0 (#1687)
* *(deps)* Update module xorm.io/xorm to v1.3.11 (#1689)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 741010e (#1691)
* *(deps)* Update node.js to v22.21.0 (#1693)
* *(deps)* Update dev-dependencies (#1692)
* *(deps)* Update module github.com/cweill/gotests to v1.8.0 (#1694)
* *(deps)* Update dev-dependencies (#1700)
* *(deps)* Update module github.com/getsentry/sentry-go/echo to v0.36.1 (#1701)
* *(deps)* Pin docker/login-action action to 5e57cd1 (#1698)
* *(deps)* Update postgres:18 docker digest to b5b154c (#1695)
* *(deps)* Update postgres:18 docker digest to 33d0aae (#1705)
* *(deps)* Update dependency @sentry/vue to v10.21.0 (#1706)
* *(deps)* Update pnpm to v10.19.0 (#1704)
* *(deps)* Update node.js to v22.21.0 (#1703)
* *(deps)* Update vueuse to v14 (major) (#1707)
* *(deps)* Update postgres:18 docker digest to 7499fa0 (#1709)
* *(deps)* Update postgres:18 docker digest to 1ffc019 (#1715)
* *(deps)* Update tiptap to v3.8.0 (#1713)
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.16.0 (#1710)
* *(deps)* Update dev-dependencies (#1708)
* *(deps)* Update cypress/browsers:latest docker digest to 1b0e8df (#1697)
* *(deps)* Update github artifact actions (major) (#1719)
* *(deps)* Update dependency axios to v1.13.0 (#1720)
* *(deps)* Update tiptap to v3.9.0 (#1723)
* *(deps)* Update module github.com/getsentry/sentry-go/echo to v0.36.2 (#1722)
* *(deps)* Update node.js to v24 (#1721)
* *(deps)* Update dependency @sentry/vue to v10.22.0 (#1712)
* *(deps)* Update dependency @kyvg/vue3-notification to v3.4.2 (#1726)
* *(deps)* Update dependency axios to v1.13.1 (#1727)
* *(deps)* Update pnpm to v10.20.0 (#1732)
* *(deps)* Update tiptap to v3.9.1 (#1731)
* *(deps)* Update module github.com/gabriel-vasile/mimetype to v1.4.11 (#1730)
* *(deps)* Update dependency dayjs to v1.11.19 (#1736)
* *(deps)* Update cypress/browsers:latest docker digest to 368e300 (#1734)
* *(deps)* Update golangci-lint to 2.6.0 (#1737)
* *(deps)* Update dev-dependencies (#1740)
* *(deps)* Update postgres:18 docker digest to 6f3e42a (#1742)
* *(deps)* Update dependency axios to v1.13.2 (#1748)
* *(deps)* Update dependency @cypress/vite-dev-server to v7.0.1 (#1750)
* *(deps)* Update cypress/browsers:latest docker digest to 33dbe61 (#1749)
* *(deps)* Update dependency pinia to v3.0.4 (#1753)
* *(deps)* Update dependency @sentry/vue to v10.23.0 (#1755)
* *(deps)* Update docker/setup-qemu-action digest to c7c5346 (#1757)
* *(deps)* Update tiptap to v3.10.2 (#1735)
* *(deps)* Update dependency go to v1.25.4 (#1758)
* *(deps)* Pin bitnamilegacy/minio docker tag to 451fe68 (#1759)
* *(deps)* Update dependency vue to v3.5.23 (#1760)
* *(deps)* Update dependency marked to v16.4.2 (#1767)
* *(deps)* Update actions/download-artifact action to v6 (#1764)
* *(deps)* Update postgres:18 docker digest to 41fc534 (#1751)
* *(deps)* Update mariadb:12 docker digest to 439d77b (#1776)
* *(deps)* Update docker/metadata-action digest to 318604b (#1744)
* *(deps)* Update module github.com/olekukonko/tablewriter to v1.1.1 (#1778)
* *(deps)* Update softprops/action-gh-release digest to 5be0e66 (#1777)
* *(deps)* Update tiptap to v3.10.5 (#1773)
* *(deps)* Update dependency vue to v3.5.24 (#1772)
* *(deps)* Update pnpm to v10.21.0 (#1779)
* *(deps)* Update module golang.org/x/sync to v0.18.0 (#1784)
* *(deps)* Update node.js to v24.11.1 (#1787)
* *(deps)* Update module golang.org/x/oauth2 to v0.33.0 (#1782)
* *(deps)* Update module github.com/go-testfixtures/testfixtures/v3 to v3.19.0 (#1718)
* *(deps)* Update module golang.org/x/crypto to v0.44.0 (#1789)
* *(deps)* Update golangci/golangci-lint-action action to v9 (#1796)
* *(deps)* Update module golang.org/x/image to v0.33.0 (#1791)
* *(deps)* Update dev-dependencies (#1790)
* *(deps)* Update module github.com/cweill/gotests to v1.9.0 (#1733)
* *(deps)* Update cypress/browsers:latest docker digest to e85371f (#1798)
* *(deps)* Update module golang.org/x/net to v0.47.0 (#1792)
* *(deps)* Update module github.com/jaswdr/faker/v2 to v2.9.0 (#1783)
* *(deps)* Update pnpm to v10.22.0 (#1800)
* *(deps)* Update dependency @sentry/vue to v10.25.0 (#1780)
* *(deps)* Pin actions/create-github-app-token action to d72941d (#1801)
* *(deps)* Update node.js to v24.11.1 (#1804)
* *(deps)* Update actions/create-github-app-token action to v2 (#1809)
* *(deps)* Update node.js to 54dfcc1 (#1811)
* *(deps)* Update module github.com/getsentry/sentry-go to v0.37.0 (#1818)
* *(deps)* Update mariadb:12 docker digest to 607835c (#1817)
* *(deps)* Update postgres:18 docker digest to 28bda6d (#1816)
* *(deps)* Update node.js to 2867d55 (#1815)
* *(deps)* Update module github.com/getsentry/sentry-go/echo to v0.37.0 (#1819)
* *(deps)* Update dev-dependencies (#1822)
* *(deps)* Update postgres:18 docker digest to 435fe97 (#1821)
* *(deps)* Update dev-dependencies (#1830)
* *(deps)* Update module github.com/getsentry/sentry-go/echo to v0.38.0 (#1835)
* *(deps)* Update postgres:18 docker digest to 41bfa2e (#1824)
* *(deps)* Update dev-dependencies (#1832)
* *(deps)* Update actions/checkout digest to 93cb6ef (#1838)
* *(deps)* Update postgres:18 docker digest to 5ec39c1 (#1842)
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.17.0 (#1850)
* *(deps)* Update module golang.org/x/crypto to v0.45.0 (#1851)
* *(deps)* Update mariadb:12 docker digest to e1bcd6f (#1856)
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.17.0 (#1859)
* *(deps)* Update golangci/golangci-lint-action digest to e7fa5ac (#1860)
* *(deps)* Update actions/checkout action to v6 (#1854)
* *(deps)* Update cypress/browsers:latest docker digest to 7331c59 (#1852)
* *(deps)* Update actions/setup-go digest to 4dc6199 (#1853)
* *(deps)* Update actions/create-github-app-token digest to 7e473ef (#1862)
* *(deps)* Update dev-dependencies
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.17.1 (#1881)
* *(deps)* Update dependency workbox-precaching to v7.4.0 (#1879)
* *(deps)* Update actions/checkout digest to 93cb6ef (#1885)
* *(deps)* Pin mcr.microsoft.com/playwright docker tag to 6aca677 (#1884)
* *(deps)* Update pnpm to v10.23.0 (#1877)
* *(deps)* Update dependency marked to v17 (#1797)
* *(deps)* Update docker/metadata-action digest to c299e40 (#1887)
* *(deps)* Update module github.com/getsentry/sentry-go to v0.40.0 (#1892)
* *(deps)* Update dependency vue-i18n to v11.2.1 (#1891)
* *(deps)* Update module github.com/labstack/echo-jwt/v4 to v4.4.0 (#1896)
* *(deps)* Update dependency vue to v3.5.25 (#1888)
* *(deps)* Update module github.com/getsentry/sentry-go/echo to v0.40.0 (#1895)
* *(deps)* Update actions/checkout action to v6 (#1897)
* *(deps)* Update dependency @sentry/vue to v10.26.0 (#1878)
### Documentation
* Fix sticker link ([869bb6e](869bb6e014ca15fee60ead33e6d65ace78369ac8))
* Update AI instructions about plans ([4de4951](4de49512b068141c9b3989d930618ca8659ab173))
### Features
* *(ci)* Change s3 service
* *(ci)* Publish docker images to ghcr as well
* *(ci)* Run database docker images only when needed (#1696)
* *(ci)* Update Docker PR push build strategy for forked PRs (#1812)
* *(editor)* Automatically save draft comments locally (#1868)
* Add S3 file storage support (#1688) ([bc1368a](bc1368abcc2c2e4aaf8bad50d080dc622299cafe))
* Enable user mentions in task description & comments (#1754) ([43a5ae1](43a5ae13096fa454889d48698abfd429572f4d5f))
* Show avatar for mentioned users ([435d029](435d029f33e4b0d38c72b378288abc81a56d5f64))
* Add comment count to tasks (#1771) ([01a84dd](01a84dd2d5de8548ddaec9d3236e0ccd61aadf68))
* Allow setting dark custom logo ([dcfd096](dcfd0965889e2c6a92d617c6c05c15aec4d3646b))
* Replace PNG-based initials avatar with SVG generation (#1802) ([9c81afb](9c81afb7b29833e4fa4a025df06d3d3b3c312137))
* Add PR Docker image builds for x86_64 (#1810) ([cf8ad52](cf8ad52a27485655bb79453e6dc361e89abb9faf))
* Add time display with configurable format (12h/24h) to non-relative date formats (#1807) ([650fb94](650fb949782fd9d5256d6a8d994e5389d767b0ca))
* Add clickable labels on Labels page for task filtering (#1825) ([6903bb6](6903bb67c71c3cc865d5af3ea885f23a8bf51e1a))
* Add thread IDs to task notification emails for client-side threading (#1826) ([f2a1348](f2a1348c5182c0c96ff9c8695b0450da85ef3d9e))
* Hide link share creation form by default in sharing dialogue (#1827) ([25827f4](25827f432ea36286ee0f7768750b06047dd90570))
* Display assignee names on mobile for accessibility (#1828) ([d057afb](d057afb7810266472abbd3fe303b0172d8de0d7d))
* Restrict attachment drop to files ([85fc8ff](85fc8fffd4b4210ff1f709cf99ebabdaa3ee9ee9))
* Show task card preview when hovering over task title in list and table view (#1863) ([2bc2311](2bc2311212a2d3b1c8497c202ed96cda2b326136))
* Task unread tracking (#1857) ([7dddc5d](7dddc5dfa2ea82e883da8daa4deaf5eb3d4aa382))
* Migrate cypress e2e tests to playwright (#1739) ([51512c1](51512c1cb41111496912e80c4cb64618dcc1d108))
### Miscellaneous Tasks
* *(ci)* Use github app to handle issue closed comments
* *(ci)* Add debug log
* *(i18n)* Update translations via Crowdin
* *(i18n)* Add Traditional Chinese locale and translations (#1839)
* *(i18n)* Update translations via Crowdin
* Make condition simpler ([d33e742](d33e742961479e5f07f0cdb2e1969d3b32c4d48d))
* Reorganize mention setup ([0d83a56](0d83a568ce6dd5382a97d227660253ecd7bdccf9))
* Copy useDropZone from vueuse ([abbf2ce](abbf2ce1838dbb4114e5ea3bf0ba5c78762834de))
* Delete frontend/package-lock.json ([d5a4631](d5a46310a7b84c4ff9add156994d9f5e213d566e))
* Add TYPECHECK_ISSUES.md to .gitignore ([8e089f5](8e089f578934e1a44d8a213787f622f65310887b))
* Update devenv ([8bcd7ec](8bcd7ec5f5d0c039834c4c77a9dac27971263c23))
### Other
* *(other)* [skip ci] Updated swagger docs
* *(other)* Use Cmd+K for quick actions on macOS instead of Ctrl+K (#1837)
## [1.0.0-rc2] - 2025-09-12
### Bug Fixes
* *(auth)* Do not panic with invalid openid provider configuration (#1354)
* *(avatar)* Recover gracefully from broken avatar cache (#1379)
* *(editor)* Do not apply filter input styles to all editor instances
* *(filter)* Don't crash when filtering for labels in (#1333)
* *(filter)* Add close button for filter popup on mobile with accurate spacing (#1466)
* *(filter)* Ensure filter query param is added to the page correctly (#1471)
* *(filters)* Initialize task positions for saved filters (#1477)
* *(filters)* Persist url filter query across views (#1482)
* *(frontend)* Add missing permission translation keys (#1320)
* *(frontend)* Hide drag handle for fixed project lists (#1421)
* *(kanban)* Update task on board when changing details
* *(kanban)* Guard task modal race conditions (#1472)
* *(link share)* Add better error handling, ensure projects are shown (#1481)
* *(multiselect)* Do not try getting label when value is undefined
* *(task)* Set project after loading task details
* *(task)* Priority label task link spacing (#1322)
* *(task)* Default to medium priority when none is configured
* *(task)* Only load first comments page when loading comments with task
* *(task)* Update task comment when switching between related tasks
* *(task)* Autosave description when closing task modal
* *(task)* Ensure done_at can never be set by user (#1461)
* *(task)* Only save description when clicking away if it actually changed
* *(task)* Preserve done timestamp when moving tasks between projects (#1470)
* *(task)* Provide back button when opening task detail (#1475)
* *(user)* Do not reject 2fa for local users
* Set test fixture in e2e test ([c1ce330](c1ce33019c5e8bdfb7d07c657799255b3a53c41e))
* Use correct filepath ([fd8a36c](fd8a36c9bb89b1510906cad805ca782956a42e9e))
* Require publicurl when cors enabled (#1351) ([e29561e](e29561e49c3cedfdac18d0301019863bb50ceced))
* Stop event propagation for clicks inside the DeferTask popup (#1338) ([07d6630](07d66308910dc53f597a5f8b197eef49cb269d0e))
* Prevent null history.state errors in modal routing and task navigation (#1368) ([14e1bd2](14e1bd2f55f28407482120af8e675fa8566f3cb3))
* Fix(task): do not parse task comment reactions so that they actually appear([3baf6cd](3baf6cd477f62eccb43854f20fa1dfeab05c454d))
* Mobile device minimum width (#1337) ([fda440b](fda440bd0818c58996160bda4bca08a8195dbc1f))
* Show error when user list filter is empty (#1372) ([bd310f5](bd310f50ad05fe4263b44853e9a8721dd93ba346))
* Bypass Typesense in user export (#1385) ([15ea381](15ea38183cc99b87006dd8767abcefe78fe1d0cd))
* Remove leftover console.log ([12842a5](12842a5e3573b7279cbe52697e86e106d8b938fb))
* Show pagination controls for task comments (#1413) ([bd74733](bd7473363226720cb7d1778e78082f1f8ea86166))
* Reload list view when marking recurring task done (#1457) ([6914bad](6914badeb7cfa2cdef1ebfb620d303ab396de32d))
* Guard saved filter requests (#1462) ([4353b1e](4353b1e9c7dde177308ca2dfea436adfbacbac0e))
### Dependencies
* *(deps)* Bump github.com/go-viper/mapstructure/v2 from 2.3.0 to 2.4.0 (#1331)
* *(deps)* Update actions/github-script action to v8 (#1417)
* *(deps)* Update actions/setup-go action to v6 (#1407)
* *(deps)* Update actions/setup-node action to v5 (#1408)
* *(deps)* Update crowdin/github-action digest to 0749939 (#1396)
* *(deps)* Update crowdin/github-action digest to 9787f4f (#1347)
* *(deps)* Update cypress/browsers:latest docker digest to 34cbe59 (#1454)
* *(deps)* Update cypress/browsers:latest docker digest to 4e0a347 (#1297)
* *(deps)* Update cypress/browsers:latest docker digest to eeb9b35 (#1352)
* *(deps)* Update dependency @floating-ui/dom to v1.7.4 (#1332)
* *(deps)* Update dependency @fortawesome/vue-fontawesome to v3.1.2 (#1474)
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v11 (#1418)
* *(deps)* Update dependency @sentry/vue to v10.10.0 (#1416)
* *(deps)* Update dependency @sentry/vue to v10.8.0 (#1361)
* *(deps)* Update dependency @sentry/vue to v10.9.0 (#1400)
* *(deps)* Update dependency dayjs to v1.11.15 (#1349)
* *(deps)* Update dependency dayjs to v1.11.17 (#1355)
* *(deps)* Update dependency dayjs to v1.11.18 (#1360)
* *(deps)* Update dependency electron to v37.3.1 [security] (#1404)
* *(deps)* Update dependency marked to v16.2.0 (#1304)
* *(deps)* Update dependency marked to v16.2.1 (#1350)
* *(deps)* Update dependency vite to v7.1.5 [security] (#1451)
* *(deps)* Update dependency vue to v3.5.20 (#1327)
* *(deps)* Update dependency vue to v3.5.21 (#1382)
* *(deps)* Update dependency vue-i18n to v11.1.12 (#1420)
* *(deps)* Update dev-dependencies (#1298)
* *(deps)* Update dev-dependencies (#1310)
* *(deps)* Update devenv
* *(deps)* Update docker/dockerfile:1 docker digest to dabfc09 (#1479)
* *(deps)* Update font awesome to v7.0.1 (#1394)
* *(deps)* Update ghcr.io/go-vikunja/dex-testing:main docker digest to 10cc6d5 (#1377)
* *(deps)* Update ghcr.io/go-vikunja/dex-testing:main docker digest to 3877bee (#1348)
* *(deps)* Update ghcr.io/go-vikunja/dex-testing:main docker digest to 408e999 (#1444)
* *(deps)* Update ghcr.io/go-vikunja/dex-testing:main docker digest to 4abbc4f (#1312)
* *(deps)* Update mariadb:12 docker digest to a5af517 (#1376)
* *(deps)* Update module github.com/gabriel-vasile/mimetype to v1.4.10 (#1353)
* *(deps)* Update module github.com/getsentry/sentry-go to v0.35.2 (#1480)
* *(deps)* Update module github.com/go-testfixtures/testfixtures/v3 to v3.18.0 (#1362)
* *(deps)* Update module github.com/prometheus/client_golang to v1.23.1 (#1411)
* *(deps)* Update module github.com/prometheus/client_golang to v1.23.2 (#1426)
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.13.0 (#1401)
* *(deps)* Update module github.com/spf13/afero to v1.15.0 (#1441)
* *(deps)* Update module github.com/spf13/cobra to v1.10.1 (#1373)
* *(deps)* Update module github.com/spf13/viper to v1.21.0 (#1442)
* *(deps)* Update module github.com/stretchr/testify to v1.11.1 (#1363)
* *(deps)* Update module github.com/threedotslabs/watermill to v1.5.0 (#1365)
* *(deps)* Update module github.com/threedotslabs/watermill to v1.5.1 (#1391)
* *(deps)* Update module golang.org/x/crypto to v0.42.0 (#1438)
* *(deps)* Update module golang.org/x/image to v0.31.0 (#1439)
* *(deps)* Update module golang.org/x/oauth2 to v0.31.0 (#1429)
* *(deps)* Update module golang.org/x/sync to v0.17.0 (#1430)
* *(deps)* Update module golang.org/x/sys to v0.36.0 (#1432)
* *(deps)* Update module golang.org/x/term to v0.35.0 (#1434)
* *(deps)* Update module golang.org/x/text to v0.29.0 (#1435)
* *(deps)* Update node.js to v22.19.0 (#1356)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 12f4078 (#1308)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 5d3cc02 (#1370)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 646cd3f (#1405)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 6c9cd0a (#1309)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to 744e637 (#1325)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to aaa1861 (#1425)
* *(deps)* Update paradedb/paradedb:latest-pg17 docker digest to cdad8f0 (#1473)
* *(deps)* Update pnpm to v10.15.0 (#1314)
* *(deps)* Update pnpm to v10.15.1 (#1371)
* *(deps)* Update postgres:17 docker digest to 5250e61 (#1433)
* *(deps)* Update postgres:17 docker digest to ab24d83 (#1443)
* *(deps)* Update postgres:17 docker digest to d17be73 (#1427)
* *(deps)* Update postgres:17 docker digest to feff5b2 (#1445)
* *(deps)* Update softprops/action-gh-release digest to 6cbd405 (#1428)
* *(deps)* Update tiptap to v3.3.0 (#1366)
* *(deps)* Update tiptap to v3.3.1 (#1386)
* *(deps)* Update tiptap to v3.4.0 (#1406)
* *(deps)* Update tiptap to v3.4.1 (#1412)
* *(deps)* Update tiptap to v3.4.2 (#1446)
* *(deps)* Update vueuse to v13.7.0 (#1305)
* *(deps)* Update vueuse to v13.8.0 (#1367)
* *(deps)* Update vueuse to v13.9.0 (#1374)
* *(deps-dev)* Bump electron from 37.3.0 to 37.3.1 in /desktop (#1415)
### Documentation
* Add GitHub actions status badge to readme ([5a385ac](5a385aca1c104a8494cdaf85c54627d42c5756f2))
* Clarify publicurl requirements ([ea4f7f2](ea4f7f2c8751f14eda3ea877e0792b4acbf29150))
### Features
* *(auth)* Add oauth require availability configuration on startup (#1358)
* *(config)* Validate publicurl
* *(gantt)* Natural day-boundary rounding in Gantt chart (#1476)
* *(i18n)* Add pluralization rules for Russian (#1334)
* *(kanban)* Allow folding done column by clicking green checkmarks in Kanban view (#1393)
* *(task)* Make default project relation configurable
* Use variable fonts ([f01f2af](f01f2af4cf5a7862803349c1990e326e95b01f09))
* Add subsets for all supported languages ([eb676ae](eb676aebef4864e12827c4e9c51f3d561cd29cdf))
* Share logic for bulk update (#1456) ([db12367](db123674a7ece09022e4e1f4d48e72c5b5539b85))
### Miscellaneous Tasks
* *(dev)* Update devenv
* *(i18n)* Update translations via Crowdin
* *(i18n)* Improve wording
* *(i18n)* Clarify wording
* *(i18n)* Update translations via Crowdin
* Add feature issue template ([523dad5](523dad5134454e42321116426e0189c94d515402))
* Update magefile to reference up to date golangci lint ([74189b6](74189b6cf9d58b8d6eba1c56c2109f0409b9e535))
### Other
* *(other)* [skip ci] Updated swagger docs
### Refactor
* Use helper function to check user local ([1b5a9db](1b5a9dbdeadf476dc2fee19326508f956d3b79d0))
## [1.0.0-rc1] - 2025-08-17
### Bug Fixes

View File

@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1@sha256:38387523653efa0039f8e1c89bb74a30504e76ee9f565e25c9a09841f9427b05
FROM --platform=$BUILDPLATFORM node:22.18.0-alpine@sha256:1b2479dd35a99687d6638f5976fd235e26c5b37e8122f786fcd5fe231d63de5b AS frontendbuilder
# syntax=docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6
FROM --platform=$BUILDPLATFORM node:24.11.1-alpine@sha256:682368d8253e0c3364b803956085c456a612d738bd635926d73fa24db3ce53d7 AS frontendbuilder
WORKDIR /build

View File

@@ -1,8 +1,8 @@
<img src="https://vikunja.io/images/vikunja-logo.svg" alt="" style="display: block;width: 50%;margin: 0 auto;" width="50%"/>
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/vikunjaa/status.svg)](https://drone.kolaente.de/vikunja/vikunja)
[![Build Status](https://github.com/go-vikunja/vikunja/actions/workflows/ci.yml/badge.svg)](https://github.com/go-vikunja/vikunja/actions/workflows/ci.yml)
[![License: AGPL-3.0-or-later](https://img.shields.io/badge/License-AGPL--3.0--or--later-blue.svg)](LICENSE)
[![Install](https://img.shields.io/badge/download-v1.0.0rc1-brightgreen.svg)](https://vikunja.io/docs/installing)
[![Install](https://img.shields.io/badge/download-v1.0.0rc3-brightgreen.svg)](https://vikunja.io/docs/installing)
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/vikunja.svg)](https://hub.docker.com/r/vikunja/vikunja/)
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/vikunja)](https://goreportcard.com/report/kolaente.dev/vikunja/vikunja)
@@ -11,7 +11,7 @@
> The Todo-app to organize your life.
If Vikunja is useful to you, please consider [buying me a coffee](https://www.buymeacoffee.com/kolaente), [sponsoring me on GitHub](https://github.com/sponsors/kolaente) or buying [a sticker pack](https://vikunja.cloud/stickers).
If Vikunja is useful to you, please consider [buying me a coffee](https://www.buymeacoffee.com/kolaente), [sponsoring me on GitHub](https://github.com/sponsors/kolaente) or buying [a sticker pack](https://vikunja.io/stickers).
I'm also offering [a hosted version of Vikunja](https://vikunja.cloud/) if you want a hassle-free solution for yourself or your team.
## Table of contents

View File

@@ -36,7 +36,7 @@
{
"key": "publicurl",
"default_value": "",
"comment": "The public facing URL where your users can reach Vikunja. Used in emails and for the communication between api and frontend."
"comment": "The public facing URL where your users can reach Vikunja. Used in emails and for the communication between api and frontend. The url must be a valid http or https url. This setting is required when cors.enable is true."
},
{
"key": "rootpath",
@@ -123,6 +123,11 @@
"default_value": "",
"comment": "Allow using a custom logo via external URL."
},
{
"key": "customlogourldark",
"default_value": "",
"comment": "Allow using a custom logo for dark mode via external URL. If not set, the regular logo will be used for both light and dark modes."
},
{
"key": "enablepublicteams",
"default_value": "false",
@@ -196,7 +201,7 @@
{
"key": "path",
"default_value": "./vikunja.db",
"comment": "When using sqlite, this is the path where to store the data"
"comment": "When using sqlite, this is the path where to store the database file. Can be an absolute path or relative path. \u003cbr/\u003e\nRelative paths are resolved as follows: \u003cbr/\u003e\n- If `service.rootpath` is explicitly configured (differs from the binary location), the database path is resolved relative to that directory. \u003cbr/\u003e\n- Otherwise, relative paths are resolved to a platform-specific user data directory to prevent database files from being created in system directories (like `C:\\Windows\\System32` on Windows when running as a service): \u003cbr/\u003e\n - **Windows**: `%LOCALAPPDATA%\\Vikunja` (e.g., `C:\\Users\\username\\AppData\\Local\\Vikunja`) \u003cbr/\u003e\n - **macOS**: `~/Library/Application Support/Vikunja` \u003cbr/\u003e\n - **Linux**: `$XDG_DATA_HOME/vikunja` or `~/.local/share/vikunja` \u003cbr/\u003e\n**Recommendation**: Use an absolute path for production deployments, especially when running Vikunja as a Windows service, to have full control over the database location."
},
{
"key": "maxopenconnections",
@@ -484,6 +489,47 @@
"key": "maxsize",
"default_value": "20MB",
"comment": "The maximum size of a file, as a human-readable string.\nWarning: The max size is limited 2^64-1 bytes due to the underlying datatype"
},
{
"key": "type",
"default_value": "local",
"comment": "The type of file storage backend. Supported values are `local` and `s3`."
},
{
"key": "s3",
"comment": "Configuration for S3 storage backend",
"children": [
{
"key": "endpoint",
"default_value": "",
"comment": "The S3 endpoint to use. Can be used with S3-compatible services like MinIO or Backblaze B2."
},
{
"key": "bucket",
"default_value": "",
"comment": "The name of the S3 bucket to store files in."
},
{
"key": "region",
"default_value": "",
"comment": "The S3 region where the bucket is located."
},
{
"key": "accesskey",
"default_value": "",
"comment": "The S3 access key ID."
},
{
"key": "secretkey",
"default_value": "",
"comment": "The S3 secret access key."
},
{
"key": "usepathstyle",
"default_value": "false",
"comment": "Whether to use path-style addressing (e.g., https://s3.amazonaws.com/bucket/key) instead of virtual-hosted-style (e.g., https://bucket.s3.amazonaws.com/key). This is commonly needed for self-hosted S3-compatible services. Some providers only support one style or the other."
}
]
}
]
},
@@ -717,6 +763,11 @@
"key": "forceuserinfo",
"default_value": "false",
"comment": "This option forces the use of the OpenID Connect UserInfo endpoint to retrieve user information instead of relying on claims from the ID token. When set to `true`, user data (email, name, username) will always be obtained from the UserInfo endpoint even if the information is available in the token claims. This is useful for providers that don't include complete user information in their tokens or when you need the most up-to-date user data. Allowed value is either `true` or `false`."
},
{
"key": "requireavailability",
"default_value": "false",
"comment": "This option requires the OpenID Connect provider to be available during Vikunja startup. When set to `true`, Vikunja will crash if it cannot connect to the provider during initialization, allowing container orchestrators like Kubernetes to handle the failure by restarting the application. This is useful in environments where you want to ensure all authentication providers are available before the application starts serving requests. Allowed value is either `true` or `false`."
}
]
}

View File

@@ -5,7 +5,7 @@
"main": "main.js",
"repository": "https://code.vikunja.io/desktop",
"license": "GPL-3.0-or-later",
"packageManager": "pnpm@10.14.0",
"packageManager": "pnpm@10.24.0",
"author": {
"email": "maintainers@vikunja.io",
"name": "Vikunja Team"
@@ -52,12 +52,12 @@
}
},
"devDependencies": {
"electron": "37.3.0",
"electron": "37.10.3",
"electron-builder": "26.0.12",
"unzipper": "0.12.3"
},
"dependencies": {
"express": "5.1.0"
"express": "5.2.1"
},
"pnpm": {
"onlyBuiltDependencies": [

436
desktop/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,10 @@
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1753201456,
"lastModified": 1761922975,
"owner": "cachix",
"repo": "devenv",
"rev": "39662b2c5b94ad06573c9aac4fe9f671260d1587",
"rev": "c9f0b47815a4895fadac87812de8a4de27e0ace1",
"type": "github"
},
"original": {
@@ -19,10 +19,10 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"lastModified": 1761588595,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
@@ -31,39 +31,18 @@
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1750779888,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"lastModified": 1762808025,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
"type": "github"
},
"original": {
@@ -74,10 +53,10 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1750441195,
"lastModified": 1761313199,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "0ceffe312871b443929ff3006960d29b120dc627",
"rev": "d1c30452ebecfc55185ae6d1c983c09da0c274ff",
"type": "github"
},
"original": {
@@ -89,10 +68,10 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1752950548,
"lastModified": 1761672384,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c87b95e25065c028d31a94f06a62927d18763fdf",
"rev": "08dacfca559e1d7da38f3cf05f1f45ee9bfd213c",
"type": "github"
},
"original": {
@@ -102,15 +81,33 @@
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1763988335,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "50b9238891e388c9fdc6a5c49e49c42533a1b5ce",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable",
"pre-commit-hooks": [
"git-hooks"
]
"pre-commit-hooks": "pre-commit-hooks"
}
}
},

View File

@@ -23,9 +23,7 @@ in {
python3Packages.pip
python3Packages.fonttools
python3Packages.brotli
] ++ lib.optionals (!pkgs.stdenv.isDarwin) [
# Frontend tools (exclude on Darwin)
pkgs-unstable.cypress
nodejs
];
languages = {
@@ -49,6 +47,13 @@ in {
enable = true;
package = pkgs-unstable.mailpit;
};
env = {
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = "1";
# PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs-unstable.chromium}/bin/chromium";
VIKUNJA_SERVICE_TESTINGTOKEN = "test";
};
devcontainer = {
enable = true;

3
frontend/.gitignore vendored
View File

@@ -20,6 +20,8 @@ coverage
# Test files
cypress/screenshots
cypress/videos
playwright-report/
test-results/
# local env files
.env.local
@@ -41,3 +43,4 @@ cypress/videos
# histoire
.histoire
package-lock.json

View File

@@ -1 +1 @@
22.18.0
24.11.1

View File

@@ -1,10 +1,10 @@
services:
dex:
image: ghcr.io/go-vikunja/dex-testing:main@sha256:7de4780bdb0c03c50e55d3803c802cce19b9af48a293757abb21122bd22b54c9
image: ghcr.io/go-vikunja/dex-testing:main@sha256:d401c06a9f8fd36ece446a07499b827232af7f21eb36872a76c9eac4d0c77bab
ports:
- 5556:5556
cypress:
image: cypress/browsers:latest@sha256:2c4e1047f7187ca028dcd968cbd3aafc9430bbd30a3ec4d6b41f9eabfdb39e59
image: cypress/browsers:latest@sha256:ff79e75249941c52c301821a7979db306e2659aa2d6038428bcf77c6f5f125e1
volumes:
- ..:/project
- $HOME/.cache:/home/node/.cache/

View File

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

View File

@@ -2,25 +2,27 @@ import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
import {ProjectViewFactory} from "../../factories/project_view";
export function createDefaultViews(projectId) {
ProjectViewFactory.truncate()
export function createDefaultViews(projectId: number, startViewId = 1, truncate: boolean = true) {
if (truncate) {
ProjectViewFactory.truncate()
}
const list = ProjectViewFactory.create(1, {
id: 1,
id: startViewId,
project_id: projectId,
view_kind: 0,
}, false)
const gantt = ProjectViewFactory.create(1, {
id: 2,
id: startViewId + 1,
project_id: projectId,
view_kind: 1,
}, false)
const table = ProjectViewFactory.create(1, {
id: 3,
id: startViewId + 2,
project_id: projectId,
view_kind: 2,
}, false)
const kanban = ProjectViewFactory.create(1, {
id: 4,
id: startViewId + 3,
project_id: projectId,
view_kind: 3,
bucket_configuration_mode: 1,
@@ -34,12 +36,19 @@ export function createDefaultViews(projectId) {
]
}
export function createProjects() {
const projects = ProjectFactory.create(1, {
title: 'First Project'
export function createProjects(count: number = 1) {
const projects = ProjectFactory.create(count, {
title: i => count === 1 ? 'First Project' : `Project ${i + 1}`,
})
TaskFactory.truncate()
projects.views = createDefaultViews(projects[0].id)
ProjectViewFactory.truncate()
for (let i = 0; i < projects.length; i++) {
const views = createDefaultViews(projects[i].id, i * 4 + 1, false)
projects[i].views = views
}
return projects
}

View File

@@ -1,59 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ProjectFactory} from '../../factories/project'
import {prepareProjects} from './prepareProjects'
import {ProjectViewFactory} from '../../factories/project_view'
describe('Project History', () => {
createFakeUserAndLogin()
prepareProjects()
it('should show a project history on the home page', () => {
cy.intercept(Cypress.env('API_URL') + '/projects*').as('loadProjectArray')
cy.intercept(Cypress.env('API_URL') + '/projects/*').as('loadProject')
const projects = ProjectFactory.create(7)
ProjectViewFactory.truncate()
projects.forEach(p => ProjectViewFactory.create(1, {
id: p.id,
project_id: p.id,
}, false))
cy.visit('/')
cy.wait('@loadProjectArray')
cy.get('body')
.should('not.contain', 'Last viewed')
cy.visit(`/projects/${projects[0].id}/${projects[0].id}`)
cy.wait('@loadProject')
cy.visit(`/projects/${projects[1].id}/${projects[1].id}`)
cy.wait('@loadProject')
cy.visit(`/projects/${projects[2].id}/${projects[2].id}`)
cy.wait('@loadProject')
cy.visit(`/projects/${projects[3].id}/${projects[3].id}`)
cy.wait('@loadProject')
cy.visit(`/projects/${projects[4].id}/${projects[4].id}`)
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
cy.get('nav.menu.top-menu a')
.contains('Overview')
.click()
cy.get('body')
.should('contain', 'Last viewed')
cy.get('[data-cy="projectCardGrid"]')
.should('not.contain', projects[0].title)
.should('contain', projects[1].title)
.should('contain', projects[2].title)
.should('contain', projects[3].title)
.should('contain', projects[4].title)
.should('contain', projects[5].title)
.should('contain', projects[6].title)
})
})

View File

@@ -1,155 +0,0 @@
import dayjs from 'dayjs'
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {prepareProjects} from './prepareProjects'
describe('Project View Gantt', () => {
createFakeUserAndLogin()
prepareProjects()
it('Hides tasks with no dates', () => {
const tasks = TaskFactory.create(1)
cy.visit('/projects/1/2')
cy.get('.gantt-rows')
.should('not.contain', tasks[0].title)
})
it('Shows tasks from the current and next month', () => {
const now = Date.UTC(2022, 8, 25)
cy.clock(now, ['Date'])
const nextMonth = new Date(now)
nextMonth.setDate(1)
nextMonth.setMonth(9)
cy.visit('/projects/1/2')
cy.get('.gantt-timeline-months')
.should('contain', dayjs(now).format('MMMM YYYY'))
.should('contain', dayjs(nextMonth).format('MMMM YYYY'))
})
it('Shows tasks with dates', () => {
const now = new Date()
const tasks = TaskFactory.create(1, {
start_date: now.toISOString(),
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
})
cy.visit('/projects/1/2')
cy.get('.gantt-rows')
.should('not.be.empty')
.should('contain', tasks[0].title)
})
it('Shows tasks with no dates after enabling them', () => {
const tasks = TaskFactory.create(1, {
start_date: null,
end_date: null,
})
cy.visit('/projects/1/2')
cy.get('.gantt-options .fancy-checkbox')
.contains('Show tasks without dates')
.click()
cy.get('.gantt-rows')
.should('not.be.empty')
.should('contain', tasks[0].title)
})
it('Drags a task around', () => {
cy.intercept(Cypress.env('API_URL') + '/tasks/*').as('taskUpdate')
const now = new Date()
TaskFactory.create(1, {
start_date: now.toISOString(),
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
})
cy.visit('/projects/1/2')
cy.get('.gantt-rows .gantt-row-bars .gantt-bar')
.first()
.then($bar => {
// Get the current position of the bar
const rect = $bar[0].getBoundingClientRect()
const startX = rect.left + rect.width / 2
const startY = rect.top + rect.height / 2
// Trigger pointer events with proper coordinates and delays
cy.wrap($bar)
.trigger('pointerdown', {
clientX: startX,
clientY: startY,
pointerId: 1,
which: 1
})
.wait(100) // Wait to ensure double-click detection doesn't interfere
.trigger('pointermove', {
clientX: startX + 10, // Small initial movement to trigger drag
clientY: startY,
pointerId: 1
})
.trigger('pointermove', {
clientX: startX + 150, // Move 150px to the right (about 5 days)
clientY: startY,
pointerId: 1
})
.trigger('pointerup', {
clientX: startX + 150,
clientY: startY,
pointerId: 1,
force: true
})
})
cy.wait('@taskUpdate')
})
it('Should change the query parameters when selecting a date range', () => {
const now = Date.UTC(2022, 10, 9)
cy.clock(now, ['Date'])
cy.visit('/projects/1/2')
cy.get('.project-gantt .gantt-options .field .control input.input.form-control')
.click()
cy.get('.flatpickr-calendar .flatpickr-innerContainer .dayContainer .flatpickr-day')
.first()
.click()
cy.get('.flatpickr-calendar .flatpickr-innerContainer .dayContainer .flatpickr-day')
.last()
.click()
cy.url().should('contain', 'dateFrom=2022-09-25')
cy.url().should('contain', 'dateTo=2022-11-05')
})
it('Should change the date range based on date query parameters', () => {
cy.visit('/projects/1/2?dateFrom=2022-09-25&dateTo=2022-11-05')
cy.get('.gantt-timeline-months')
.should('contain', 'September 2022')
.should('contain', 'October 2022')
.should('contain', 'November 2022')
cy.get('.project-gantt .gantt-options .field .control input.input.form-control')
.should('have.value', '25 Sep 2022 to 5 Nov 2022')
})
it('Should open a task when double clicked on it', () => {
const now = new Date()
const tasks = TaskFactory.create(1, {
start_date: dayjs(now).format(),
end_date: dayjs(now.setDate(now.getDate() + 4)).format(),
})
cy.visit('/projects/1/2')
cy.get('.gantt-container .gantt-row-bars .gantt-bar')
.dblclick()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`)
})
})

View File

@@ -1,305 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {BucketFactory} from '../../factories/bucket'
import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
import {prepareProjects} from './prepareProjects'
import {ProjectViewFactory} from "../../factories/project_view";
import {TaskBucketFactory} from "../../factories/task_buckets";
function createSingleTaskInBucket(count = 1, attrs = {}) {
const projects = ProjectFactory.create(1)
const views = ProjectViewFactory.create(1, {
id: 1,
project_id: projects[0].id,
view_kind: 3,
bucket_configuration_mode: 1,
})
const buckets = BucketFactory.create(2, {
project_view_id: views[0].id,
})
const tasks = TaskFactory.create(count, {
project_id: projects[0].id,
...attrs,
})
TaskBucketFactory.create(1, {
task_id: tasks[0].id,
bucket_id: buckets[0].id,
project_view_id: views[0].id,
})
return {
task: tasks[0],
view: views[0],
project: projects[0],
}
}
function createTaskWithBuckets(buckets, count = 1) {
const data = TaskFactory.create(count, {
project_id: 1,
})
TaskBucketFactory.truncate()
data.forEach(t => TaskBucketFactory.create(1, {
task_id: t.id,
bucket_id: buckets[0].id,
project_view_id: buckets[0].project_view_id,
}, false))
return data
}
describe('Project View Kanban', () => {
createFakeUserAndLogin()
prepareProjects()
let buckets
beforeEach(() => {
buckets = BucketFactory.create(2, {
project_view_id: 4,
})
})
it('Shows all buckets with their tasks', () => {
const data = createTaskWithBuckets(buckets, 10)
cy.visit('/projects/1/4')
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
.should('exist')
cy.get('.kanban .bucket .title')
.contains(buckets[1].title)
.should('exist')
cy.get('.kanban .bucket')
.first()
.should('contain', data[0].title)
})
it('Can add a new task to a bucket', () => {
createTaskWithBuckets(buckets, 2)
cy.visit('/projects/1/4')
cy.get('.kanban .bucket')
.contains(buckets[0].title)
.get('.bucket-footer .button')
.contains('Add another task')
.click()
cy.get('.kanban .bucket')
.contains(buckets[0].title)
.get('.bucket-footer .field .control input.input')
.type('New Task{enter}')
cy.get('.kanban .bucket')
.first()
.should('contain', 'New Task')
})
it('Can create a new bucket', () => {
cy.visit('/projects/1/4')
cy.get('.kanban .bucket.new-bucket .button')
.click()
cy.get('.kanban .bucket.new-bucket input.input')
.type('New Bucket{enter}')
cy.wait(1000) // Wait for the request to finish
cy.get('.kanban .bucket .title')
.contains('New Bucket')
.should('exist')
})
it('Can set a bucket limit', () => {
cy.visit('/projects/1/4')
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Limit: Not Set')
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .field input.input')
.first()
.type('3')
cy.get('[data-cy="setBucketLimit"]')
.first()
.click()
cy.get('.kanban .bucket .bucket-header span.limit')
.contains('0/3')
.should('exist')
})
it('Can rename a bucket', () => {
cy.visit('/projects/1/4')
cy.get('.kanban .bucket .bucket-header .title')
.first()
.type('{selectall}New Bucket Title{enter}')
cy.get('.kanban .bucket .bucket-header .title')
.first()
.should('contain', 'New Bucket Title')
})
it('Can delete a bucket', () => {
cy.visit('/projects/1/4')
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Delete')
.click()
cy.get('.modal-mask .modal-container .modal-content .modal-header')
.should('contain', 'Delete the bucket')
cy.get('.modal-mask .modal-container .modal-content .actions .button')
.contains('Do it!')
.click()
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
.should('not.exist')
cy.get('.kanban .bucket .title')
.contains(buckets[1].title)
.should('exist')
})
it('Can drag tasks around', () => {
const tasks = createTaskWithBuckets(buckets, 2)
cy.visit('/projects/1/4')
cy.get('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.first()
.drag('.kanban .bucket:nth-child(2) .tasks')
cy.get('.kanban .bucket:nth-child(2) .tasks')
.should('contain', tasks[0].title)
cy.get('.kanban .bucket:nth-child(1) .tasks')
.should('not.contain', tasks[0].title)
})
it('Should navigate to the task when the task card is clicked', () => {
const tasks = createTaskWithBuckets(buckets, 5)
cy.visit('/projects/1/4')
cy.get('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.should('be.visible')
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`, {timeout: 1000})
})
it('Should remove a task from the kanban board when moving it to another project', () => {
const projects = ProjectFactory.create(2)
const views = ProjectViewFactory.create(2, {
project_id: '{increment}',
view_kind: 3,
bucket_configuration_mode: 1,
})
BucketFactory.create(2)
const tasks = TaskFactory.create(5, {
id: '{increment}',
project_id: 1,
})
TaskBucketFactory.create(5, {
project_view_id: 1,
})
const task = tasks[0]
cy.visit('/projects/1/'+views[0].id)
cy.get('.kanban .bucket .tasks .task')
.contains(task.title)
.should('be.visible')
.click()
cy.get('.task-view .action-buttons .button', {timeout: 3000})
.contains('Move')
.click()
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
.type(`${projects[1].title}{enter}`)
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
// presses enter and we can't simulate pressing on enter to select the item.
cy.get('.task-view .content.details .field .multiselect.control .search-results')
.children()
.first()
.click()
cy.get('.global-notification', {timeout: 1000})
.should('contain', 'Success')
cy.go('back')
cy.get('.kanban .bucket')
.should('not.contain', task.title)
})
it('Shows a button to filter the kanban board', () => {
cy.visit('/projects/1/4')
cy.get('.project-kanban .filter-container .base-button')
.should('exist')
})
it('Should remove a task from the board when deleting it', () => {
const {task, view} = createSingleTaskInBucket(5)
cy.visit(`/projects/1/${view.id}`)
cy.get('.kanban .bucket .tasks .task')
.contains(task.title)
.should('be.visible')
.click()
cy.get('.task-view .action-buttons .button')
.should('be.visible')
.contains('Delete')
.click()
cy.get('.modal-mask .modal-container .modal-content .modal-header')
.should('contain', 'Delete this task')
cy.get('.modal-mask .modal-container .modal-content .actions .button')
.contains('Do it!')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.kanban .bucket .tasks')
.should('not.contain', task.title)
})
it('Should show a task description icon if the task has a description', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/1/views/*/tasks**').as('loadTasks')
const {task, view} = createSingleTaskInBucket(1, {
description: 'Lorem Ipsum',
})
cy.visit(`/projects/${task.project_id}/${view.id}`)
cy.wait('@loadTasks')
cy.get('.bucket .tasks .task .footer .icon svg')
.should('exist')
})
it('Should not show a task description icon if the task has an empty description', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/1/views/*/tasks**').as('loadTasks')
const {task, view} = createSingleTaskInBucket(1, {
description: '',
})
cy.visit(`/projects/${task.project_id}/${view.id}`)
cy.wait('@loadTasks')
cy.get('.bucket .tasks .task .footer .icon svg')
.should('not.exist')
})
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/1/views/*/tasks**').as('loadTasks')
const {task, view} = createSingleTaskInBucket(1, {
description: '<p></p>',
})
cy.visit(`/projects/${task.project_id}/${view.id}`)
cy.wait('@loadTasks')
cy.get('.bucket .tasks .task .footer .icon svg')
.should('not.exist')
})
})

View File

@@ -2,113 +2,57 @@ import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {UserProjectFactory} from '../../factories/users_project'
import {TaskFactory} from '../../factories/task'
import {TaskRelationFactory} from '../../factories/task_relation'
import {UserFactory} from '../../factories/user'
import {ProjectFactory} from '../../factories/project'
import {prepareProjects} from './prepareProjects'
import {prepareProjects, createProjects} from './prepareProjects'
import {BucketFactory} from '../../factories/bucket'
import {
createTasksWithPriorities,
createTasksWithSearch,
} from '../../support/filterTestHelpers'
describe('Project View List', () => {
createFakeUserAndLogin()
prepareProjects()
it('Should be an empty project', () => {
cy.visit('/projects/1')
it('Should respect filter query parameter from URL', () => {
const {highPriorityTasks, lowPriorityTasks} = createTasksWithPriorities()
cy.visit('/projects/1/1?filter=priority%20>=%204')
cy.url()
.should('contain', '/projects/1/1')
cy.get('.project-title')
.should('contain', 'First Project')
cy.get('.project-title-dropdown')
.should('include', 'filter=priority')
cy.contains('.tasks', highPriorityTasks[0].title, {timeout: 10000})
.should('exist')
cy.get('p')
.contains('This project is currently empty.')
cy.get('.tasks')
.should('contain', highPriorityTasks[0].title)
cy.get('.tasks')
.should('contain', highPriorityTasks[1].title)
cy.get('.tasks')
.should('not.contain', lowPriorityTasks[0].title)
cy.get('.tasks')
.should('not.contain', lowPriorityTasks[1].title)
})
it('Should respect search query parameter from URL', () => {
const {searchableTask} = createTasksWithSearch()
cy.visit('/projects/1/1?s=meeting')
cy.url()
.should('include', 's=meeting')
cy.contains('.tasks', searchableTask.title, {timeout: 10000})
.should('exist')
})
it('Should create a new task', () => {
BucketFactory.create(2, {
project_view_id: 4,
})
const newTaskTitle = 'New task'
cy.visit('/projects/1')
cy.get('.task-add textarea')
.type(newTaskTitle+'{enter}')
cy.get('.tasks')
.should('contain.text', newTaskTitle)
})
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
project_id: 1,
})
cy.visit('/projects/1/1')
cy.get('.tasks .task .tasktext')
.contains(tasks[0].title)
.first()
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`)
})
it('Should not see any elements for a project which is shared read only', () => {
UserFactory.create(2)
UserProjectFactory.create(1, {
project_id: 2,
user_id: 1,
permission: 0,
})
const projects = ProjectFactory.create(2, {
owner_id: '{increment}',
})
cy.visit(`/projects/${projects[1].id}/`)
cy.get('.project-title-wrapper .icon')
.should('not.exist')
cy.get('input.input[placeholder="Add a task…"]')
.should('not.exist')
})
it('Should only show the color of a project in the navigation and not in the list view', () => {
const projects = ProjectFactory.create(1, {
hex_color: '00db60',
})
TaskFactory.create(10, {
project_id: projects[0].id,
})
cy.visit(`/projects/${projects[0].id}/`)
cy.get('.menu-list li .list-menu-link .color-bubble')
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
cy.get('.tasks .color-bubble')
.should('not.exist')
})
it('Should paginate for > 50 tasks', () => {
const tasks = TaskFactory.create(100, {
id: '{increment}',
title: i => `task${i}`,
project_id: 1,
})
cy.visit('/projects/1/1')
cy.get('.tasks')
.should('contain', tasks[20].title)
cy.get('.tasks')
.should('not.contain', tasks[99].title)
.should('contain', searchableTask.title)
cy.get('.card-content .pagination .pagination-link')
.contains('2')
.click()
cy.url()
.should('contain', '?page=2')
cy.get('.tasks')
.should('contain', tasks[99].title)
cy.get('.tasks')
.should('not.contain', tasks[20].title)
cy.get('.tasks .task')
.should('have.length', 1)
})
})

View File

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

View File

@@ -1,171 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {ProjectFactory} from '../../factories/project'
import {prepareProjects} from './prepareProjects'
describe('Projects', () => {
createFakeUserAndLogin()
let projects
prepareProjects((newProjects) => (projects = newProjects))
it('Should create a new project', () => {
cy.visit('/projects')
cy.get('.project-header [data-cy=new-project]')
.click()
cy.url()
.should('contain', '/projects/new')
cy.get('.card-header-title')
.contains('New project')
cy.get('input[name=projectTitle]')
.type('New Project')
cy.get('.button')
.contains('Create')
.click()
cy.get('.global-notification', {timeout: 1000}) // Waiting until the request to create the new project is done
.should('contain', 'Success')
cy.url()
.should('contain', '/projects/')
cy.get('.project-title')
.should('contain', 'New Project')
})
it('Should redirect to a specific project view after visited', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/*/views/*/tasks**').as('loadBuckets')
cy.visit('/projects/1/4')
cy.url()
.should('contain', '/projects/1/4')
cy.wait('@loadBuckets')
cy.visit('/projects/1')
cy.url()
.should('contain', '/projects/1/4')
})
it('Should rename the project in all places', () => {
TaskFactory.create(5, {
id: '{increment}',
project_id: 1,
})
const newProjectName = 'New project name'
cy.visit('/projects/1')
cy.get('.project-title')
.should('contain', 'First Project')
cy.get('.menu-container .menu-list li:first-child .dropdown .menu-list-dropdown-trigger')
.click()
cy.get('.menu-container .menu-list li:first-child .dropdown .dropdown-content')
.contains('Edit')
.click()
cy.get('#title:not(:disabled)')
.type(`{selectall}${newProjectName}`)
cy.get('footer.card-footer .button')
.contains('Save')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.project-title')
.should('contain', newProjectName)
.should('not.contain', projects[0].title)
cy.get('.menu-container .menu-list li:first-child')
.should('contain', newProjectName)
.should('not.contain', projects[0].title)
cy.visit('/')
cy.get('.project-grid')
.should('contain', newProjectName)
.should('not.contain', projects[0].title)
})
it('Should remove a project when deleting it', () => {
cy.visit(`/projects/${projects[0].id}`)
cy.get('.menu-container .menu-list li:first-child .dropdown .menu-list-dropdown-trigger')
.click()
cy.get('.menu-container .menu-list li:first-child .dropdown .dropdown-content')
.contains('Delete')
.click()
cy.url()
.should('contain', '/settings/delete')
cy.get('[data-cy="modalPrimary"]')
.contains('Do it')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.menu-container .menu-list')
.should('not.contain', projects[0].title)
cy.location('pathname')
.should('equal', '/')
})
it('Should archive a project', () => {
cy.visit(`/projects/${projects[0].id}`)
cy.get('.project-title-dropdown')
.click()
cy.get('.project-title-dropdown .dropdown-menu .dropdown-item')
.contains('Archive')
.click()
cy.get('.modal-content')
.should('contain.text', 'Archive this project')
cy.get('.modal-content [data-cy=modalPrimary]')
.click()
cy.get('.menu-container .menu-list')
.should('not.contain', projects[0].title)
cy.get('main.app-content')
.should('contain.text', 'This project is archived. It is not possible to create new or edit tasks for it.')
})
it('Should show all projects on the projects page', () => {
const projects = ProjectFactory.create(10)
cy.visit('/projects')
projects.forEach(p => {
cy.get('[data-cy="projects-list"]')
.should('contain', p.title)
})
})
it('Should not show archived projects if the filter is not checked', () => {
ProjectFactory.create(1, {
id: 2,
}, false)
ProjectFactory.create(1, {
id: 3,
is_archived: true,
}, false)
// Initial
cy.visit('/projects')
cy.get('.project-grid')
.should('not.contain', 'Archived')
// Show archived
cy.get('[data-cy="show-archived-check"] label span')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
.should('be.checked')
cy.get('.project-grid')
.should('contain', 'Archived')
// Don't show archived
cy.get('[data-cy="show-archived-check"] label span')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
// Second time visiting after unchecking
cy.visit('/projects')
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
cy.get('.project-grid')
.should('not.contain', 'Archived')
})
})

View File

@@ -1,61 +0,0 @@
import {LinkShareFactory} from '../../factories/link_sharing'
import {TaskFactory} from '../../factories/task'
import {UserFactory} from '../../factories/user'
import {createProjects} from '../project/prepareProjects'
function prepareLinkShare() {
UserFactory.create()
const projects = createProjects()
const tasks = TaskFactory.create(10, {
project_id: projects[0].id,
})
const linkShares = LinkShareFactory.create(1, {
project_id: projects[0].id,
permission: 0,
})
return {
share: linkShares[0],
project: projects[0],
tasks,
}
}
describe('Link shares', () => {
it('Can view a link share', () => {
const {share, project, tasks} = prepareLinkShare()
cy.visit(`/share/${share.hash}/auth`)
cy.get('h1.title')
.should('contain', project.title)
cy.get('input.input[placeholder="Add a task…"]')
.should('not.exist')
cy.get('.tasks')
.should('contain', tasks[0].title)
cy.url().should('contain', `/projects/${project.id}/1#share-auth-token=${share.hash}`)
})
it('Should work when directly viewing a project with share hash present', () => {
const {share, project, tasks} = prepareLinkShare()
cy.visit(`/projects/${project.id}/1#share-auth-token=${share.hash}`)
cy.get('h1.title')
.should('contain', project.title)
cy.get('input.input[placeholder="Add a task…"]')
.should('not.exist')
cy.get('.tasks')
.should('contain', tasks[0].title)
})
it('Should work when directly viewing a task with share hash present', () => {
const {share, project, tasks} = prepareLinkShare()
cy.visit(`/tasks/${tasks[0].id}#share-auth-token=${share.hash}`)
cy.get('h1.title')
.should('contain', tasks[0].title)
})
})

View File

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

View File

@@ -1,57 +0,0 @@
import {UserFactory} from '../../factories/user'
import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
import {login} from '../../support/authenticateUser'
import {DATE_DISPLAY} from '../../../src/constants/dateDisplay'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
dayjs.extend(relativeTime)
const createdDate = new Date(Date.UTC(2022, 6, 25, 12))
const now = new Date(Date.UTC(2022, 6, 30, 12))
const expectedFormats = {
[DATE_DISPLAY.RELATIVE]: dayjs(createdDate).from(now),
[DATE_DISPLAY.MM_DD_YYYY]: dayjs(createdDate).format('MM-DD-YYYY'),
[DATE_DISPLAY.DD_MM_YYYY]: dayjs(createdDate).format('DD-MM-YYYY'),
[DATE_DISPLAY.YYYY_MM_DD]: dayjs(createdDate).format('YYYY-MM-DD'),
[DATE_DISPLAY.MM_SLASH_DD_YYYY]: dayjs(createdDate).format('MM/DD/YYYY'),
[DATE_DISPLAY.DD_SLASH_MM_YYYY]: dayjs(createdDate).format('DD/MM/YYYY'),
[DATE_DISPLAY.YYYY_SLASH_MM_DD]: dayjs(createdDate).format('YYYY/MM/DD'),
[DATE_DISPLAY.DAY_MONTH_YEAR]: new Intl.DateTimeFormat('en', {
day: 'numeric',
month: 'long',
year: 'numeric',
}).format(createdDate),
[DATE_DISPLAY.WEEKDAY_DAY_MONTH_YEAR]: new Intl.DateTimeFormat('en', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
}).format(createdDate),
}
describe('Date display setting', () => {
Object.entries(expectedFormats).forEach(([format, expected]) => {
it(`shows ${format}`, () => {
const user = UserFactory.create(1, {
frontend_settings: JSON.stringify({dateDisplay: format}),
})[0]
const project = ProjectFactory.create(1, {owner_id: user.id})[0]
TaskFactory.truncate()
const task = TaskFactory.create(1, {
id: 1,
project_id: project.id,
created_by_id: user.id,
created: createdDate.toISOString(),
updated: createdDate.toISOString(),
})[0]
cy.clock(now, ['Date'])
login(user)
cy.visit(`/tasks/${task.id}`)
cy.get('.task-view .created time span').should('contain', expected)
})
})
})

View File

@@ -37,30 +37,6 @@ function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
describe('Home Page Task Overview', () => {
createFakeUserAndLogin()
it('Should show tasks with a near due date first on the home page overview', () => {
const taskCount = 50
const {tasks} = seedTasks(taskCount)
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.each(([task], index) => {
expect(task.innerText).to.contain(tasks[index].title)
})
})
it('Should show overdue tasks first, then show other tasks', () => {
const now = new Date()
const oldDate = new Date(new Date(now).setDate(now.getDate() - 14))
const taskCount = 50
const {tasks} = seedTasks(taskCount, oldDate)
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.each(([task], index) => {
expect(task.innerText).to.contain(tasks[index].title)
})
})
it('Should show a new task with a very soon due date at the top', () => {
const {tasks} = seedTasks(49)
const newTaskTitle = 'New Task'
@@ -130,22 +106,4 @@ describe('Home Page Task Overview', () => {
.should('contain.text', newTaskTitle)
})
it('Should show the cta buttons for new project when there are no tasks', () => {
TaskFactory.truncate()
cy.visit('/')
cy.get('.home.app-content .content')
.should('contain.text', 'Import your projects and tasks from other services into Vikunja:')
})
it('Should not show the cta buttons for new project when there are tasks', () => {
seedTasks()
cy.visit('/')
cy.get('.home.app-content .content')
.should('not.contain.text', 'You can create a new project for your new tasks:')
.should('not.contain.text', 'Or import your projects and tasks from other services into Vikunja:')
})
})

View File

@@ -1,70 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
import {ProjectViewFactory} from '../../factories/project_view'
function createViews(projectId: number, projectViewId: number) {
return ProjectViewFactory.create(1, {
id: projectViewId,
project_id: projectId,
view_kind: 0,
}, false)[0]
}
describe('Subtask duplicate handling', () => {
createFakeUserAndLogin()
let projectA
let projectB
let parentA
let parentB
let subtask
beforeEach(() => {
ProjectFactory.truncate()
ProjectViewFactory.truncate()
TaskFactory.truncate()
projectA = ProjectFactory.create(1, {id: 1, title: 'Project A'})[0]
createViews(projectA.id, 1)
projectB = ProjectFactory.create(1, {id: 2, title: 'Project B'}, false)[0]
createViews(projectB.id, 2)
parentA = TaskFactory.create(1, {id: 10, title: 'Parent A', project_id: projectA.id}, false)[0]
parentB = TaskFactory.create(1, {id: 11, title: 'Parent B', project_id: projectB.id}, false)[0]
subtask = TaskFactory.create(1, {id: 12, title: 'Shared subtask', project_id: projectA.id}, false)[0]
cy.request({
method: 'PUT',
url: `${Cypress.env('API_URL')}/tasks/${parentA.id}/relations`,
headers: {
'Authorization': `Bearer ${window.localStorage.getItem('token')}`,
},
body: {
other_task_id: subtask.id,
relation_kind: 'subtask',
},
})
cy.request({
method: 'PUT',
url: `${Cypress.env('API_URL')}/tasks/${parentB.id}/relations`,
headers: {
'Authorization': `Bearer ${window.localStorage.getItem('token')}`,
},
body: {
other_task_id: subtask.id,
relation_kind: 'subtask',
},
})
})
it('shows subtask only once in each project list', () => {
cy.visit(`/projects/${projectA.id}/1`)
cy.get('.subtask-nested .task-link').contains(subtask.title).should('exist')
cy.get('.tasks .task-link').contains(subtask.title).should('have.length', 1)
cy.visit(`/projects/${projectB.id}/1`)
cy.get('.subtask-nested .task-link').contains(subtask.title).should('exist')
cy.get('.tasks .task-link').contains(subtask.title).should('have.length', 1)
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -31,13 +31,6 @@ context('Login', () => {
UserFactory.create(1, {username: credentials.username})
})
it('Should log in with the right credentials', () => {
cy.visit('/login')
login()
cy.clock(1625656161057) // 13:00
cy.get('h2').should('contain', `Hi ${credentials.username}!`)
})
it('Should fail with a bad password', () => {
const fixture = {
username: 'test',
@@ -47,20 +40,6 @@ context('Login', () => {
testAndAssertFailed(fixture)
})
it('Should fail with a bad username', () => {
const fixture = {
username: 'loremipsum',
password: '1234',
}
testAndAssertFailed(fixture)
})
it('Should redirect to /login when no user is logged in', () => {
cy.visit('/')
cy.url().should('include', '/login')
})
it('Should redirect to the previous route after logging in', () => {
const projects = ProjectFactory.create(1)
cy.visit(`/projects/${projects[0].id}/1`)

View File

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

View File

@@ -1,16 +0,0 @@
context('OpenID Login', () => {
it('logs in via Dex provider', () => {
cy.visit('/login')
cy.contains('Dex').click()
cy.origin('http://dex:5556', () => {
cy.get('#login').type('test@example.com')
cy.get('#password').type('12345678')
cy.get('#submit-login').click()
})
cy.url().should('include', '/')
cy.get('main.app-content .content h2')
.should('contain', 'test!')
cy.get('.show-tasks h3')
.should('contain', 'Current Tasks')
})
})

View File

@@ -1,58 +0,0 @@
import {UserFactory, type UserAttributes} from '../../factories/user'
import {TokenFactory, type TokenAttributes} from '../../factories/token'
context('Password Reset', () => {
let user: UserAttributes
beforeEach(() => {
UserFactory.truncate()
TokenFactory.truncate()
user = UserFactory.create(1)[0] as UserAttributes
})
it('Should allow a user to reset their password with a valid token', () => {
const tokenArray = TokenFactory.create(1, {user_id: user.id as number, kind: 1})
const token: TokenAttributes = tokenArray[0] as TokenAttributes
cy.visit(`/?userPasswordReset=${token.token}`)
cy.url().should('include', `/password-reset?userPasswordReset=${token.token}`)
const newPassword = 'newSecurePassword123'
cy.get('input[id=password]').type(newPassword)
cy.get('button').contains('Reset your password').click()
cy.get('.message.success').should('contain', 'The password was updated successfully.')
cy.get('.button').contains('Login').click()
cy.url().should('include', '/login')
// Try to login with the new password
cy.get('input[id=username]').type(user.username)
cy.get('input[id=password]').type(newPassword)
cy.get('.button').contains('Login').click()
cy.url().should('not.include', '/login')
})
it('Should show an error for an invalid token', () => {
cy.visit('/?userPasswordReset=invalidtoken123')
cy.url().should('include', '/password-reset?userPasswordReset=invalidtoken123')
// Attempt to reset password
const newPassword = 'newSecurePassword123'
cy.get('input[id=password]').type(newPassword)
cy.get('button').contains('Reset your password').click()
cy.get('.message').should('contain', 'Invalid token')
})
it('Should redirect to login if no token is present in query param when visiting /password-reset directly', () => {
cy.visit('/password-reset')
cy.url().should('not.include', '/password-reset')
cy.wait(1000) // Wait for the redirect to happen - this seems to be flaky in CI
cy.url().should('include', '/login')
})
it('Should redirect to login if userPasswordReset token is not present in query param when visiting root', () => {
cy.visit('/')
cy.url().should('include', '/login')
})
})

View File

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

View File

@@ -0,0 +1,18 @@
import {Factory} from '../support/factory'
export class TaskRelationFactory extends Factory {
static table = 'task_relations'
static factory() {
const now = new Date()
return {
id: '{increment}',
task_id: '{increment}',
other_task_id: '{increment}',
relation_kind: 'related',
created_by_id: 1,
created: now.toISOString(),
}
}
}

View File

@@ -40,7 +40,26 @@ export class Factory {
data.push(entry)
}
seed(this.table, data, truncate)
// Create a flattened copy of the data for seeding
// This removes nested objects/arrays that the backend can't handle
const flatData = data.map(item => {
const flatItem = {}
for (const key in item) {
const value = item[key]
// Only include primitive values (string, number, boolean, null, Date)
if (value === null || value === undefined ||
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean' ||
value instanceof Date) {
flatItem[key] = value
}
// Skip arrays, objects, and other complex types
}
return flatItem
})
seed(this.table, flatData, truncate)
return data
}

View File

@@ -0,0 +1,119 @@
import {TaskFactory} from '../factories/task'
import {TaskBucketFactory} from '../factories/task_buckets'
export function createTasksWithPriorities(buckets?: any[]) {
TaskFactory.truncate()
const highPriorityTask1 = TaskFactory.create(1, {
id: 1,
project_id: 1,
priority: 4,
title: 'High Priority Task 1',
}, false)[0]
const highPriorityTask2 = TaskFactory.create(1, {
id: 2,
project_id: 1,
priority: 4,
title: 'High Priority Task 2',
}, false)[0]
const lowPriorityTask1 = TaskFactory.create(1, {
id: 3,
project_id: 1,
priority: 1,
title: 'Low Priority Task 1',
}, false)[0]
const lowPriorityTask2 = TaskFactory.create(1, {
id: 4,
project_id: 1,
priority: 1,
title: 'Low Priority Task 2',
}, false)[0]
// If buckets are provided (for Kanban), add tasks to buckets
if (buckets && buckets.length > 0) {
TaskBucketFactory.truncate()
TaskBucketFactory.create(1, {
task_id: highPriorityTask1.id,
bucket_id: buckets[0].id,
project_view_id: buckets[0].project_view_id,
}, false)
TaskBucketFactory.create(1, {
task_id: highPriorityTask2.id,
bucket_id: buckets[0].id,
project_view_id: buckets[0].project_view_id,
}, false)
TaskBucketFactory.create(1, {
task_id: lowPriorityTask1.id,
bucket_id: buckets[0].id,
project_view_id: buckets[0].project_view_id,
}, false)
TaskBucketFactory.create(1, {
task_id: lowPriorityTask2.id,
bucket_id: buckets[0].id,
project_view_id: buckets[0].project_view_id,
}, false)
}
return {
highPriorityTasks: [highPriorityTask1, highPriorityTask2],
lowPriorityTasks: [lowPriorityTask1, lowPriorityTask2],
}
}
export function createTasksWithSearch(buckets?: any[]) {
TaskFactory.truncate()
const task1 = TaskFactory.create(1, {
id: 1,
project_id: 1,
title: 'Regular task 1',
}, false)[0]
const task2 = TaskFactory.create(1, {
id: 2,
project_id: 1,
title: 'Regular task 2',
}, false)[0]
const task3 = TaskFactory.create(1, {
id: 3,
project_id: 1,
title: 'Regular task 3',
}, false)[0]
const searchableTask = TaskFactory.create(1, {
id: 4,
project_id: 1,
title: 'Meeting notes for project',
}, false)[0]
// If buckets are provided (for Kanban), add tasks to buckets
if (buckets && buckets.length > 0) {
TaskBucketFactory.truncate()
TaskBucketFactory.create(1, {
task_id: task1.id,
bucket_id: buckets[0].id,
project_view_id: buckets[0].project_view_id,
}, false)
TaskBucketFactory.create(1, {
task_id: task2.id,
bucket_id: buckets[0].id,
project_view_id: buckets[0].project_view_id,
}, false)
TaskBucketFactory.create(1, {
task_id: task3.id,
bucket_id: buckets[0].id,
project_view_id: buckets[0].project_view_id,
}, false)
TaskBucketFactory.create(1, {
task_id: searchableTask.id,
bucket_id: buckets[0].id,
project_view_id: buckets[0].project_view_id,
}, false)
}
return { searchableTask }
}

View File

@@ -1,6 +1,10 @@
import pluginVue from 'eslint-plugin-vue'
import js from '@eslint/js'
import vueTsEslintConfig from '@vue/eslint-config-typescript'
import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url))
export default [
js.configs.recommended,
@@ -69,7 +73,7 @@ export default [
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 'latest',
tsconfigRootDir: '.',
tsconfigRootDir: __dirname,
},
},

View File

@@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@10.14.0",
"packageManager": "pnpm@10.24.0",
"keywords": [
"todo",
"productivity",
@@ -35,11 +35,15 @@
"lint:fix": "pnpm run lint --fix",
"lint:styles": "stylelint 'src/**/*.{css,scss,vue}'",
"lint:styles:fix": "pnpm run lint:styles --fix",
"test:e2e": "start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chrome'",
"test:e2e-nix": "CYPRESS_RUN_BINARY=`which Cypress` start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chromium'",
"test:e2e-record-test": "start-server-and-test preview:test http://127.0.0.1:4173 'cypress run --e2e --browser chrome --record'",
"test:e2e-dev-dev": "start-server-and-test preview:dev http://127.0.0.1:4173 'cypress open --e2e'",
"test:e2e-dev": "start-server-and-test preview http://127.0.0.1:4173 'cypress open --e2e'",
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed",
"test:e2e:ui": "playwright test --ui-host=0.0.0.0",
"test:cypress:headed": "start-server-and-test preview http://127.0.0.1:4173 'cypress open --e2e'",
"test:cypress:e2e": "start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chrome'",
"test:cypress:e2e-nix": "CYPRESS_RUN_BINARY=`which Cypress` CYPRESS_API_URL=http://127.0.0.1:3456/api/v1 start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chromium'",
"test:cypress:e2e-record-test": "start-server-and-test preview:test http://127.0.0.1:4173 'cypress run --e2e --browser chrome --record'",
"test:cypress:e2e-dev-dev": "start-server-and-test preview:dev http://127.0.0.1:4173 'cypress open --e2e'",
"test:cypress:e2e-dev": "start-server-and-test preview http://127.0.0.1:4173 'cypress open --e2e'",
"test:unit": "vitest --dir ./src",
"typecheck": "vue-tsc --build --force",
"fonts:update": "pnpm fonts:download && pnpm fonts:subset",
@@ -50,38 +54,38 @@
"story:preview": "histoire preview"
},
"dependencies": {
"@floating-ui/dom": "1.7.3",
"@fortawesome/fontawesome-svg-core": "7.0.0",
"@fortawesome/free-regular-svg-icons": "7.0.0",
"@fortawesome/free-solid-svg-icons": "7.0.0",
"@fortawesome/vue-fontawesome": "3.1.1",
"@floating-ui/dom": "1.7.4",
"@fortawesome/fontawesome-svg-core": "7.1.0",
"@fortawesome/free-regular-svg-icons": "7.1.0",
"@fortawesome/free-solid-svg-icons": "7.1.0",
"@fortawesome/vue-fontawesome": "3.1.2",
"@github/hotkey": "3.1.1",
"@intlify/unplugin-vue-i18n": "6.0.8",
"@kyvg/vue3-notification": "3.4.1",
"@sentry/tracing": "7.120.4",
"@sentry/vue": "10.5.0",
"@tiptap/core": "3.2.0",
"@tiptap/extension-code-block-lowlight": "3.2.0",
"@tiptap/extension-hard-break": "3.2.0",
"@tiptap/extension-image": "3.2.0",
"@tiptap/extension-link": "3.2.0",
"@tiptap/extension-list": "3.2.0",
"@tiptap/extension-table": "3.2.0",
"@tiptap/extension-typography": "3.2.0",
"@tiptap/extension-underline": "3.2.0",
"@tiptap/extensions": "3.2.0",
"@tiptap/pm": "3.2.0",
"@tiptap/starter-kit": "3.2.0",
"@tiptap/suggestion": "3.2.0",
"@tiptap/vue-3": "3.2.0",
"@vueuse/core": "13.6.0",
"@vueuse/router": "13.6.0",
"axios": "1.11.0",
"@intlify/unplugin-vue-i18n": "11.0.1",
"@kyvg/vue3-notification": "3.4.2",
"@sentry/vue": "10.27.0",
"@tiptap/core": "3.8.0",
"@tiptap/extension-code-block-lowlight": "3.8.0",
"@tiptap/extension-hard-break": "3.8.0",
"@tiptap/extension-image": "3.8.0",
"@tiptap/extension-link": "3.8.0",
"@tiptap/extension-list": "3.8.0",
"@tiptap/extension-mention": "3.8.0",
"@tiptap/extension-table": "3.8.0",
"@tiptap/extension-typography": "3.8.0",
"@tiptap/extension-underline": "3.8.0",
"@tiptap/extensions": "3.8.0",
"@tiptap/pm": "3.8.0",
"@tiptap/starter-kit": "3.8.0",
"@tiptap/suggestion": "3.8.0",
"@tiptap/vue-3": "3.8.0",
"@vueuse/core": "14.1.0",
"@vueuse/router": "14.1.0",
"axios": "1.13.2",
"blurhash": "2.0.5",
"bulma-css-variables": "0.9.33",
"change-case": "5.4.4",
"dayjs": "1.11.13",
"dompurify": "3.2.6",
"dayjs": "1.11.19",
"dompurify": "3.3.0",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.43",
@@ -89,72 +93,74 @@
"is-touch-device": "1.0.1",
"klona": "2.0.6",
"lowlight": "3.3.0",
"marked": "16.1.2",
"pinia": "3.0.3",
"marked": "17.0.1",
"pinia": "3.0.4",
"register-service-worker": "1.7.2",
"sortablejs": "1.15.6",
"tailwindcss": "3.4.17",
"tailwindcss": "3.4.18",
"ufo": "1.6.1",
"vue": "3.5.18",
"vue": "3.5.25",
"vue-advanced-cropper": "2.8.9",
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "11.1.11",
"vue-router": "4.5.1",
"vue-i18n": "11.2.2",
"vue-router": "4.6.3",
"vuemoji-picker": "0.3.2",
"workbox-precaching": "7.3.0",
"workbox-precaching": "7.4.0",
"zhyswan-vuedraggable": "4.1.3"
},
"devDependencies": {
"@4tw/cypress-drag-drop": "2.3.0",
"@cypress/vite-dev-server": "7.0.0",
"@4tw/cypress-drag-drop": "2.3.1",
"@cypress/vite-dev-server": "7.0.1",
"@cypress/vue": "6.0.2",
"@faker-js/faker": "9.9.0",
"@histoire/plugin-screenshot": "1.0.0-alpha.3",
"@histoire/plugin-vue": "1.0.0-alpha.3",
"@tsconfig/node22": "22.0.2",
"@types/codemirror": "5.60.16",
"@histoire/plugin-screenshot": "1.0.0-alpha.5",
"@histoire/plugin-vue": "1.0.0-alpha.5",
"@playwright/test": "1.57.0",
"@tsconfig/node22": "22.0.5",
"@types/codemirror": "5.60.17",
"@types/is-touch-device": "1.0.3",
"@types/node": "22.17.1",
"@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "8.39.1",
"@typescript-eslint/parser": "8.39.1",
"@vitejs/plugin-vue": "6.0.1",
"@types/node": "22.19.1",
"@types/sortablejs": "1.15.9",
"@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.48.0",
"@vitejs/plugin-vue": "6.0.2",
"@vue/eslint-config-typescript": "14.6.0",
"@vue/test-utils": "2.4.6",
"@vue/tsconfig": "0.7.0",
"autoprefixer": "10.4.21",
"browserslist": "4.25.2",
"caniuse-lite": "1.0.30001735",
"csstype": "3.1.3",
"@vue/tsconfig": "0.8.1",
"@vueuse/shared": "14.1.0",
"autoprefixer": "10.4.22",
"browserslist": "4.28.0",
"caniuse-lite": "1.0.30001757",
"csstype": "3.2.3",
"cypress": "14.5.4",
"esbuild": "0.25.9",
"eslint": "9.33.0",
"eslint-plugin-vue": "10.4.0",
"happy-dom": "18.0.1",
"histoire": "1.0.0-alpha.3",
"esbuild": "0.27.0",
"eslint": "9.39.1",
"eslint-plugin-vue": "10.6.2",
"happy-dom": "20.0.11",
"histoire": "1.0.0-alpha.5",
"postcss": "8.5.6",
"postcss-easing-gradients": "3.0.1",
"postcss-preset-env": "10.2.4",
"rollup": "4.46.2",
"rollup-plugin-visualizer": "6.0.3",
"sass-embedded": "1.90.0",
"start-server-and-test": "2.0.13",
"stylelint": "16.23.1",
"postcss-preset-env": "10.4.0",
"rollup": "4.53.3",
"rollup-plugin-visualizer": "6.0.5",
"sass-embedded": "1.93.3",
"start-server-and-test": "2.1.3",
"stylelint": "16.26.1",
"stylelint-config-property-sort-order-smacss": "10.0.0",
"stylelint-config-recommended-vue": "1.6.1",
"stylelint-config-standard-scss": "15.0.1",
"stylelint-use-logical": "2.1.2",
"typescript": "5.9.2",
"typescript": "5.9.3",
"unplugin-inject-preload": "3.0.0",
"vite": "7.1.2",
"vite-plugin-pwa": "1.0.2",
"vite": "7.2.4",
"vite-plugin-pwa": "1.2.0",
"vite-plugin-sentry": "1.4.1",
"vite-plugin-vue-devtools": "8.0.0",
"vite-plugin-vue-devtools": "8.0.5",
"vite-svg-loader": "5.1.0",
"vitest": "3.2.4",
"vue-tsc": "3.0.5",
"wait-on": "8.0.4",
"workbox-cli": "7.3.0"
"vue-tsc": "3.1.5",
"wait-on": "8.0.5",
"workbox-cli": "7.4.0"
},
"pnpm": {
"patchedDependencies": {

View File

@@ -0,0 +1,42 @@
import {defineConfig, devices} from '@playwright/test'
import {execSync} from 'child_process'
// Find system chromium - for UI mode, set PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH env var
const getChromiumPath = () => {
// Check if env var is already set (for UI mode)
if (process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH) {
return process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
}
try {
return execSync('which chromium', {encoding: 'utf-8'}).trim()
} catch {
return undefined
}
}
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: 1, // No parallelization initially
reporter: process.env.CI ? [['html'], ['list']] : 'html',
use: {
baseURL: 'http://127.0.0.1:4173',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
testIdAttribute: 'data-cy', // Preserve existing data-cy selectors
serviceWorkers: 'block',
launchOptions: {
executablePath: getChromiumPath(),
},
},
projects: [
{
name: 'chromium',
use: {...devices['Desktop Chrome']},
},
],
// webServer configuration removed - we manually start services in CI
// For local development, run `pnpm preview` and `pnpm preview:vikunja` separately
})

5172
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,15 +34,18 @@ trap err_report ERR
mkdir -p $TEMP_FOLDER
# the latin subset that google uses on GoogleFonts
# this is the same as the latin subset range that google uses on GoogleFonts
# see for examle the unicode-range definition here:
# https://fonts.googleapis.com/css2?family=Open+Sans
# Include the basic latin range plus latin-ext characters so glyphs with
# diacritics (used for example in Polish) are present in the generated fonts.
# Includes basic Latin, extended Latin, Cyrillic, CJK, Arabic, Hebrew, and other scripts
# to support all languages supported by Vikunja
UNICODE_LATIN_SUBSET="U+0000-00FF,U+0100-017F,U+0131,U+0152-0153,U+02BB-02BC,\
U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,\
U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD"
U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD,\
U+0180-024F,U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,\
U+1EA0-1EF9,U+0400-04FF,U+0500-052F,U+2DE0-2DFF,U+A640-A69F,\
U+4E00-9FFF,U+3400-4DBF,U+20000-2A6DF,U+2A700-2B73F,U+2B740-2B81F,U+2B820-2CEAF,\
U+3040-309F,U+30A0-30FF,U+31F0-31FF,U+3200-32FF,U+3300-33FF,U+FF00-FFEF,\
AC00-D7AF,U+1100-11FF,U+3130-318F,U+A960-A97F,U+D7B0-D7FF,\
U+0600-06FF,U+0750-077F,U+08A0-08FF,U+FB50-FDFF,U+FE70-FEFF,\
U+0590-05FF,U+FB1D-FB4F"
get_filename_without_type() {
filename=$1
@@ -168,32 +171,20 @@ else
fi
echo "\nOpen Sans"
# we drop the wdth axis for all
# we drop the wdth axis and keep only variable weight range
instance_and_subset "${ORIGINAL_FONTS}/OpenSans[wdth,wght].ttf" "wdth=drop wght=400:700" "OpenSans[wght]"
# we restrict the wght range
instance_and_subset "${ORIGINAL_FONTS}/OpenSans[wdth,wght].ttf" "wdth=drop wght=400" "OpenSans-Regular"
instance_and_subset "${ORIGINAL_FONTS}/OpenSans[wdth,wght].ttf" "wdth=drop wght=700" "OpenSans-Bold"
echo "\nOpen Sans Italic"
# we drop the wdth axis for all
# we drop the wdth axis and keep only variable weight range
instance_and_subset "${ORIGINAL_FONTS}/OpenSans-Italic[wdth,wght].ttf" "wdth=drop wght=400:700" "OpenSans-Italic[wght]"
# we restrict the wght range
instance_and_subset "${ORIGINAL_FONTS}/OpenSans-Italic[wdth,wght].ttf" "wdth=drop wght=400" "OpenSans-RegularItalic"
instance_and_subset "${ORIGINAL_FONTS}/OpenSans-Italic[wdth,wght].ttf" "wdth=drop wght=700" "OpenSans-BoldItalic"
echo "\nQuicksand"
# keep only variable weight range
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=400:700"
# we restrict the wght range
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=400" "Quicksand-Regular"
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=600" "Quicksand-SemiBold"
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=700" "Quicksand-Bold"
echo "\nSubsetting files complete"
echo ""
@@ -249,27 +240,6 @@ for file in $FONT_FOLDER/*.woff2; do
Quicksand\[wght\]_*.woff2)
update_font_reference "Quicksand\[wght\]" "$basename"
;;
Quicksand-Regular_*.woff2)
update_font_reference "Quicksand-Regular" "$basename"
;;
Quicksand-SemiBold_*.woff2)
update_font_reference "Quicksand-SemiBold" "$basename"
;;
Quicksand-Bold_*.woff2)
update_font_reference "Quicksand-Bold" "$basename"
;;
OpenSans-Regular_*.woff2)
update_font_reference "OpenSans-Regular" "$basename"
;;
OpenSans-RegularItalic_*.woff2)
update_font_reference "OpenSans-RegularItalic" "$basename"
;;
OpenSans-Bold_*.woff2)
update_font_reference "OpenSans-Bold" "$basename"
;;
OpenSans-BoldItalic_*.woff2)
update_font_reference "OpenSans-BoldItalic" "$basename"
;;
esac
fi
done

View File

@@ -25,7 +25,7 @@
<script lang="ts" setup>
import {computed, watch} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'
import isTouchDevice from 'is-touch-device'
@@ -39,7 +39,7 @@ import ContentLinkShare from '@/components/home/ContentLinkShare.vue'
import NoAuthWrapper from '@/components/misc/NoAuthWrapper.vue'
import Ready from '@/components/misc/Ready.vue'
import {setLanguage} from '@/i18n'
import {DEFAULT_LANGUAGE, setLanguage} from '@/i18n'
import {useAuthStore} from '@/stores/auth'
import {useBaseStore} from '@/stores/base'
@@ -55,7 +55,6 @@ import {success} from '@/message'
const authStore = useAuthStore()
const baseStore = useBaseStore()
const router = useRouter()
const route = useRoute()
useBodyClass('is-touch', isTouchDevice())
@@ -77,18 +76,7 @@ watch(accountDeletionConfirm, async (accountDeletionConfirm) => {
authStore.refreshUserInfo()
}, { immediate: true })
// setup email verification redirect
const userEmailConfirm = computed(() => route.query?.userEmailConfirm as (string | undefined))
watch(userEmailConfirm, (userEmailConfirm) => {
if (userEmailConfirm === undefined) {
return
}
localStorage.setItem('emailConfirmToken', userEmailConfirm)
router.push({name: 'user.login'})
}, { immediate: true })
setLanguage(authStore.settings.language)
setLanguage(authStore.settings.language ?? DEFAULT_LANGUAGE)
useColorScheme()
</script>

View File

@@ -1,10 +1,10 @@
<script lang="ts" setup>
import {logEvent} from 'histoire/client'
import {reactive} from 'vue'
import {reactive, type App} from 'vue'
import {createRouter, createMemoryHistory} from 'vue-router'
import BaseButton from './BaseButton.vue'
function setupApp({ app }) {
function setupApp({ app }: { app: App }) {
// Router mock
app.use(createRouter({
history: createMemoryHistory(),

View File

@@ -42,20 +42,26 @@
<script lang="ts" setup>
import {computed} from 'vue'
interface PaginationPage {
number: number
isEllipsis: boolean
}
const props = defineProps<{
totalPages: number,
currentPage: number
}>()
function createPagination(totalPages: number, currentPage: number) {
const pages = []
function createPagination(totalPages: number, currentPage: number): PaginationPage[] {
const pages: PaginationPage[] = []
for (let i = 0; i < totalPages; i++) {
if (
i > 0 &&
(i + 1) < totalPages &&
((i + 1) > currentPage + 1 || (i + 1) < currentPage - 1)
) {
if (pages[i - 1] && !pages[i - 1].isEllipsis) {
const prevPage = pages[i - 1]
if (prevPage && !prevPage.isEllipsis) {
pages.push({
number: 0,
isEllipsis: true,
@@ -63,7 +69,7 @@ function createPagination(totalPages: number, currentPage: number) {
}
continue
}
pages.push({
number: i + 1,
isEllipsis: false,

View File

@@ -83,47 +83,51 @@ function forceLayout(el: HTMLElement) {
# see: https://vuejs.org/guide/built-ins/transition.html#javascript-hooks
###################################################################### */
function beforeEnter(el: HTMLElement) {
el.style.height = '0'
el.style.willChange = 'height'
el.style.backfaceVisibility = 'hidden'
forceLayout(el)
function beforeEnter(el: Element) {
const htmlEl = el as HTMLElement
htmlEl.style.height = '0'
htmlEl.style.willChange = 'height'
htmlEl.style.backfaceVisibility = 'hidden'
forceLayout(htmlEl)
}
// the done callback is optional when
// used in combination with CSS
function enter(el: HTMLElement) {
const height = getHeight(el) // Get the natural height
el.style.height = height // Update the height
function enter(el: Element) {
const htmlEl = el as HTMLElement
const height = getHeight(htmlEl) // Get the natural height
htmlEl.style.height = height // Update the height
}
function afterEnter(el: HTMLElement) {
removeHeight(el)
function afterEnter(el: Element) {
removeHeight(el as HTMLElement)
}
function enterCancelled(el: HTMLElement) {
removeHeight(el)
function enterCancelled(el: Element) {
removeHeight(el as HTMLElement)
}
function beforeLeave(el: HTMLElement) {
function beforeLeave(el: Element) {
const htmlEl = el as HTMLElement
// Give the element a height to change from
el.style.height = `${el.scrollHeight}px`
forceLayout(el)
htmlEl.style.height = `${htmlEl.scrollHeight}px`
forceLayout(htmlEl)
}
function leave(el: HTMLElement) {
function leave(el: Element) {
const htmlEl = el as HTMLElement
// Set the height back to 0
el.style.height = '0'
el.style.willChange = ''
el.style.backfaceVisibility = ''
htmlEl.style.height = '0'
htmlEl.style.willChange = ''
htmlEl.style.backfaceVisibility = ''
}
function afterLeave(el: HTMLElement) {
removeHeight(el)
function afterLeave(el: Element) {
removeHeight(el as HTMLElement)
}
function leaveCancelled(el: HTMLElement) {
removeHeight(el)
function leaveCancelled(el: Element) {
removeHeight(el as HTMLElement)
}
function removeHeight(el: HTMLElement) {

View File

@@ -24,7 +24,7 @@
v-for="(value, text) in DATE_RANGES"
:key="text"
:class="{'is-active': from === value[0] && to === value[1]}"
@click="setDateRange(value)"
@click="setDateRange([...value])"
>
{{ $t(`input.datepickerRange.ranges.${text}`) }}
</BaseButton>
@@ -136,7 +136,7 @@ const flatPickerConfig = computed(() => ({
dateFormat: 'Y-m-d H:i',
enableTime: false,
wrap: true,
mode: 'range',
mode: 'range' as const,
locale: useFlatpickrLanguage().value,
}))
@@ -150,8 +150,8 @@ const to = ref('')
watch(
() => props.modelValue,
newValue => {
from.value = newValue.dateFrom
to.value = newValue.dateTo
from.value = typeof newValue.dateFrom === 'string' ? newValue.dateFrom : newValue.dateFrom.toISOString()
to.value = typeof newValue.dateTo === 'string' ? newValue.dateTo : newValue.dateTo.toISOString()
// Only set the date back to flatpickr when it's an actual date.
// Otherwise flatpickr runs in an endless loop and slows down the browser.
const dateFrom = parseDateOrString(from.value, false)
@@ -200,8 +200,8 @@ function setDateRange(range: string[] | null) {
return
}
from.value = range[0]
to.value = range[1]
from.value = range[0] ?? ''
to.value = range[1] ?? ''
}
const customRangeActive = computed<boolean>(() => {

View File

@@ -121,7 +121,7 @@ const showHowItWorks = ref(false)
const flatpickrDate = ref('')
const date = ref<string|Date>('')
const date = ref<string|Date|null>('')
watch(
() => props.modelValue,
@@ -129,9 +129,10 @@ watch(
date.value = newValue
// Only set the date back to flatpickr when it's an actual date.
// Otherwise flatpickr runs in an endless loop and slows down the browser.
const parsed = parseDateOrString(date.value, false)
const dateValueAsString = date.value instanceof Date ? date.value.toISOString() : date.value
const parsed = parseDateOrString(dateValueAsString, false)
if (parsed instanceof Date) {
flatpickrDate.value = date.value
flatpickrDate.value = date.value instanceof Date ? date.value.toISOString() : (date.value ?? '')
}
},
)

View File

@@ -40,7 +40,7 @@
>
<div class="gantt-row-content">
<GanttRowBars
:bars="ganttBars[index]"
:bars="ganttBars[index] ?? []"
:total-width="totalWidth"
:date-from-date="dateFromDate"
:date-to-date="dateToDate"
@@ -48,7 +48,7 @@
:is-dragging="isDragging"
:is-resizing="isResizing"
:drag-state="dragState"
:focused-row="focusedRow"
:focused-row="focusedRow ?? null"
:focused-cell="focusedCell"
:row-id="rowId"
@barPointerDown="handleBarPointerDown"
@@ -85,6 +85,7 @@ import GanttTimelineHeader from '@/components/gantt/GanttTimelineHeader.vue'
import Loading from '@/components/misc/Loading.vue'
import {MILLISECONDS_A_DAY} from '@/constants/date'
import {roundToNaturalDayBoundary} from '@/helpers/time/roundToNaturalDayBoundary'
const props = defineProps<{
isLoading: boolean,
@@ -149,16 +150,16 @@ const ganttBars = ref<GanttBarModel[][]>([])
const ganttRows = ref<string[]>([])
const cellsByRow = ref<Record<string, string[]>>({})
function getRoundedDate(value: string | Date | undefined, fallback: Date | string, isStart: boolean) {
return roundToNaturalDayBoundary(value ? new Date(value) : new Date(fallback), isStart)
}
function transformTaskToGanttBar(t: ITask): GanttBarModel {
const startDate = t.startDate
? new Date(t.startDate)
: new Date(props.defaultTaskStartDate)
const endDate = t.endDate
? new Date(t.endDate)
: new Date(props.defaultTaskEndDate)
const startDate = getRoundedDate(t.startDate ?? undefined, props.defaultTaskStartDate, true)
const endDate = getRoundedDate(t.endDate ?? undefined, props.defaultTaskEndDate, false)
const taskColor = getHexColor(t.hexColor)
const bar = {
id: String(t.id),
start: startDate,
@@ -171,7 +172,7 @@ function transformTaskToGanttBar(t: ITask): GanttBarModel {
isDone: t.done,
},
}
return bar
}
@@ -181,22 +182,18 @@ watch(
const bars: GanttBarModel[] = []
const rows: string[] = []
const cells: Record<string, string[]> = {}
const filteredTasks = Array.from(tasks.value.values()).filter(task => {
if (!filters.value.showTasksWithoutDates && (!task.startDate || !task.endDate)) {
return false
}
const taskStart = task.startDate
? new Date(task.startDate)
: new Date(props.defaultTaskStartDate)
const taskEnd = task.endDate
? new Date(task.endDate)
: new Date(props.defaultTaskEndDate)
const taskStart = getRoundedDate(task.startDate ?? undefined, props.defaultTaskStartDate, true)
const taskEnd = getRoundedDate(task.endDate ?? undefined, props.defaultTaskEndDate, false)
// Task is visible if it overlaps with the current date range
return taskStart <= dateToDate.value
&& taskEnd >= dateFromDate.value
return taskStart <= dateToDate.value
&& taskEnd >= dateFromDate.value
})
filteredTasks.forEach((t, index) => {
@@ -225,8 +222,8 @@ watch(
function updateGanttTask(id: string, newStart: Date, newEnd: Date) {
emit('update:task', {
id: Number(id),
startDate: dayjs(newStart).startOf('day').toDate(),
endDate: dayjs(newEnd).endOf('day').toDate(),
startDate: roundToNaturalDayBoundary(newStart, true),
endDate: roundToNaturalDayBoundary(newEnd),
})
}

View File

@@ -104,6 +104,7 @@ import dayjs from 'dayjs'
import type {GanttBarModel} from '@/composables/useGanttBar'
import {getTextColor, LIGHT} from '@/helpers/color/getTextColor'
import {MILLISECONDS_A_DAY} from '@/constants/date'
import {roundToNaturalDayBoundary} from '@/helpers/time/roundToNaturalDayBoundary'
import GanttBarPrimitive from './primitives/GanttBarPrimitive.vue'
@@ -152,7 +153,10 @@ function computeBarX(startDate: Date) {
}
function getDaysDifference(startDate: Date, endDate: Date): number {
return Math.floor((endDate.getTime() - startDate.getTime()) / MILLISECONDS_A_DAY)
return Math.ceil(
(roundToNaturalDayBoundary(endDate).getTime() - roundToNaturalDayBoundary(startDate, true).getTime()) /
MILLISECONDS_A_DAY,
)
}
function computeBarWidth(bar: GanttBarModel) {

View File

@@ -35,9 +35,12 @@ const focusedCellIndex = ref<number | null>(null)
const focusedRow = computed(() => focusedRowIndex.value === null
? null
: props.rows[focusedRowIndex.value])
const cellsCount = computed(() => props.rows.length
? props.cellsByRow[props.rows[0]].length
: 0)
const cellsCount = computed(() => {
const firstRow = props.rows[0]
return firstRow !== undefined && props.cellsByRow[firstRow]
? props.cellsByRow[firstRow].length
: 0
})
onClickOutside(chartRef, () => {
focusedRowIndex.value = null
@@ -49,6 +52,9 @@ function onKeyDown(e: KeyboardEvent) {
if (focusedRowIndex.value === null || focusedCellIndex.value === null) return
if (e.key === 'Enter') {
if (e.isComposing) {
return
}
e.preventDefault()
emit('enterPressed', { row: focusedRow.value!, cell: focusedCellIndex.value })
return
@@ -60,7 +66,7 @@ function initializeFocus() {
if (focusedRowIndex.value === null && props.rows.length > 0) {
focusedRowIndex.value = 0
focusedCellIndex.value = 0
emit('update:focused', { row: focusedRow.value, cell: focusedCellIndex.value })
emit('update:focused', { row: focusedRow.value ?? null, cell: focusedCellIndex.value })
}
}
@@ -69,7 +75,7 @@ function setFocus(rowId: string, cellIndex: number = 0) {
if (rowIndex !== -1) {
focusedRowIndex.value = rowIndex
focusedCellIndex.value = Math.max(0, Math.min(cellIndex, cellsCount.value - 1))
emit('update:focused', { row: focusedRow.value, cell: focusedCellIndex.value })
emit('update:focused', { row: focusedRow.value ?? null, cell: focusedCellIndex.value })
}
}

View File

@@ -36,6 +36,9 @@ function onFocus() {
function onKeyDown(e: KeyboardEvent) {
if (e.key === 'Enter' || e.key === ' ') {
if (e.isComposing) {
return
}
e.preventDefault()
onSelect()
}

View File

@@ -133,11 +133,16 @@ import { isEditorContentEmpty } from '@/helpers/editorContentEmpty'
import { useBaseStore } from '@/stores/base'
import { useConfigStore } from '@/stores/config'
import { useAuthStore } from '@/stores/auth'
import type { IProject } from '@/modelTypes/IProject'
const baseStore = useBaseStore()
const currentProject = computed(() => baseStore.currentProject)
// Create a mutable copy to satisfy type requirements (readonly deep -> mutable)
const currentProject = computed<IProject | null>(() => {
const project = baseStore.currentProject
return project ? { ...project } as IProject : null
})
const background = computed(() => baseStore.background)
const canWriteCurrentProject = computed(() => baseStore.currentProject?.maxPermission > Permissions.READ)
const canWriteCurrentProject = computed(() => baseStore.currentProject?.maxPermission !== null && baseStore.currentProject?.maxPermission !== undefined && baseStore.currentProject.maxPermission > Permissions.READ)
const menuActive = computed(() => baseStore.menuActive)
const authStore = useAuthStore()
@@ -253,7 +258,7 @@ $user-dropdown-width-mobile: 5rem;
.navbar-end {
margin-inline-start: 0; // overrides bulma core styles
margin-inline-start: auto;
margin-inline-end: 0; // overrides bulma core styles
flex: 0 0 auto;
display: flex;
align-items: stretch;

View File

@@ -15,7 +15,10 @@
<div
:class="{'is-visible': background}"
class="app-container-background background-fade-in d-print-none"
:style="{'background-image': background && `url(${background})`}"
:style="{
'background-image': background && `url(${background})`,
'filter': backgroundBrightness && `brightness(${backgroundBrightness}%)`
}"
/>
<Navigation class="d-print-none" />
<main
@@ -81,6 +84,12 @@ import {useProjectStore} from '@/stores/projects'
import {useRouteWithModal} from '@/composables/useRouteWithModal'
import {useRenewTokenOnFocus} from '@/composables/useRenewTokenOnFocus'
import { useAuthStore } from '@/stores/auth'
const authStore = useAuthStore()
const backgroundBrightness = computed(() =>
authStore.settings?.frontendSettings?.backgroundBrightness,
)
const {routeWithModal, currentModal, closeModal} = useRouteWithModal()

View File

@@ -12,12 +12,37 @@
v-if="logoVisible"
class="logo"
/>
<h1
<Message
v-if="projectLoadError"
variant="danger"
class="mbe-4"
>
{{ $t('sharing.projectLoadError') }}
<BaseButton
variant="secondary"
class="mls-2"
@click="retryProjectLoad"
>
{{ $t('sharing.retry') }}
</BaseButton>
</Message>
<BaseButton
v-if="!projectLoadError && currentProject"
:to="getProjectRoute()"
variant="text"
class="project-title-button"
:class="{'m-0': !logoVisible}"
>
<h1 class="title clickable-title">
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
</h1>
</BaseButton>
<h1
v-else-if="!projectLoadError"
:class="{'m-0': !logoVisible}"
:style="{ 'opacity': currentProject?.title === '' ? '0': '1' }"
class="title"
>
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
{{ $t('misc.loading') }}
</h1>
<div class="box has-text-start view">
<RouterView />
@@ -28,29 +53,88 @@
</template>
<script lang="ts" setup>
import {computed} from 'vue'
import {computed, ref, watch, onMounted} from 'vue'
import {useRoute} from 'vue-router'
import {useBaseStore} from '@/stores/base'
import {useRoute} from 'vue-router'
import {useProjectStore} from '@/stores/projects'
import {useLabelStore} from '@/stores/labels'
import {useAuthStore} from '@/stores/auth'
import Logo from '@/components/home/Logo.vue'
import PoweredByLink from './PoweredByLink.vue'
import {useProjectStore} from '@/stores/projects'
import {useLabelStore} from '@/stores/labels'
import BaseButton from '@/components/base/BaseButton.vue'
import Message from '@/components/misc/Message.vue'
import {PROJECT_VIEW_KINDS} from '@/modelTypes/IProjectView'
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const authStore = useAuthStore()
const route = useRoute()
const currentProject = computed(() => baseStore.currentProject)
const background = computed(() => baseStore.background)
const logoVisible = computed(() => baseStore.logoVisible)
const projectLoadError = ref(false)
const projectStore = useProjectStore()
projectStore.loadAllProjects()
const labelStore = useLabelStore()
labelStore.loadAllLabels()
const route = useRoute()
// Ensure project is loaded for link share
async function ensureProjectLoaded() {
if (!authStore.authLinkShare || !route.params.projectId) {
return
}
try {
projectLoadError.value = false
// Load project if not already loaded
const projectId = Number(route.params.projectId)
if (!currentProject.value || currentProject.value.id !== projectId) {
await projectStore.loadProject(projectId)
}
} catch (e) {
console.error('Failed to load project for link share:', e)
projectLoadError.value = true
}
}
async function retryProjectLoad() {
await ensureProjectLoaded()
}
// Watch for route changes and ensure project is loaded
watch(() => route.params.projectId, ensureProjectLoaded, { immediate: true })
onMounted(ensureProjectLoaded)
function getProjectRoute() {
if (!currentProject.value) return undefined
const hash = route.hash // Preserve link share hash
// Default to the first available view or list view
const projectId = currentProject.value.id
const firstView = projectStore.projects[projectId]?.views?.[0]
if (firstView) {
return {
name: 'project.view',
params: { projectId, viewId: firstView.id },
hash,
}
}
return {
name: 'project.index',
params: { projectId },
hash,
}
}
const isFullWidth = computed(() => {
const viewId = route.params?.viewId ?? null
const projectId = route.params?.projectId ?? null
@@ -82,6 +166,27 @@ const isFullWidth = computed(() => {
text-shadow: 0 0 1rem var(--white);
}
.project-title-button {
background: none !important;
border: none !important;
padding: 0 !important;
text-decoration: none !important;
&:hover .clickable-title {
opacity: 0.8;
cursor: pointer;
}
}
.clickable-title {
text-shadow: 0 0 1rem var(--white);
margin: 0;
&:hover {
text-decoration: underline;
}
}
.link-share-view {
inline-size: 100%;
max-inline-size: $desktop;

View File

@@ -27,7 +27,7 @@ onMounted(async () => {
}
const taskService = new TaskService()
const tasks = await taskService.getAll({}, {per_page: 1})
const tasks = await taskService.getAll(undefined, {per_page: 1})
show.value = tasks.length === 0
})
</script>

View File

@@ -2,6 +2,7 @@
import { computed } from 'vue'
import { useNow } from '@vueuse/core'
import { useAuthStore } from '@/stores/auth'
import { useColorScheme } from '@/composables/useColorScheme'
import LogoFull from '@/assets/logo-full.svg?component'
import LogoFullPride from '@/assets/logo-full-pride.svg?component'
@@ -12,12 +13,24 @@ const now = useNow({
})
const authStore = useAuthStore()
const Logo = computed(() => window.ALLOW_ICON_CHANGES
&& authStore.settings.frontendSettings.allowIconChanges
&& now.value.getMonth() === 5
? LogoFullPride
const { isDark } = useColorScheme()
const Logo = computed(() => window.ALLOW_ICON_CHANGES
&& authStore.settings.frontendSettings.allowIconChanges
&& now.value.getMonth() === 5
? LogoFullPride
: LogoFull)
const CustomLogo = computed(() => window.CUSTOM_LOGO_URL)
const CustomLogo = computed(() => {
const lightLogo = window.CUSTOM_LOGO_URL
const darkLogo = window.CUSTOM_LOGO_URL_DARK
if (!lightLogo && !darkLogo) return ''
if (!darkLogo) return lightLogo
if (!lightLogo) return darkLogo
return isDark.value ? darkLogo : lightLogo
})
</script>
<template>

View File

@@ -126,13 +126,15 @@ import Loading from '@/components/misc/Loading.vue'
import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue'
import type {IProject} from '@/modelTypes/IProject'
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const projects = computed(() => projectStore.notArchivedRootProjects)
const favoriteProjects = computed(() => projectStore.favoriteProjects)
const savedFilterProjects = computed(() => projectStore.savedFilterProjects)
// Cast readonly arrays to mutable type - the arrays are not actually mutated by the component
const projects = computed(() => projectStore.notArchivedRootProjects as IProject[])
const favoriteProjects = computed(() => projectStore.favoriteProjects as IProject[])
const savedFilterProjects = computed(() => projectStore.savedFilterProjects as IProject[])
</script>
<style lang="scss" scoped>
@@ -164,8 +166,6 @@ const savedFilterProjects = computed(() => projectStore.savedFilterProjects)
overflow-y: auto;
[dir="rtl"] & {
inset-inline-start: auto;
inset-inline-end: 0;
transform: translateX(100%);
}

View File

@@ -26,6 +26,7 @@
:project="project"
:is-loading="projectUpdating[project.id]"
:can-collapse="canCollapse"
:can-edit-order="canEditOrder"
:data-project-id="project.id"
/>
</template>
@@ -81,10 +82,15 @@ async function saveProjectPosition(e: SortableEvent) {
// To work around that we're explicitly checking that case here and decrease the index.
const newIndex = e.newIndex === projectsActive.length ? e.newIndex - 1 : e.newIndex
const projectId = parseInt(e.item.dataset.projectId)
const project = projectStore.projects[projectId]
const projectIdStr = e.item.dataset.projectId
if (!projectIdStr) return
const parentProjectId = e.to.parentNode.dataset.projectId ? parseInt(e.to.parentNode.dataset.projectId) : 0
const projectId = parseInt(projectIdStr)
const project = projectStore.projects[projectId]
if (!project) return
const parentNode = e.to.parentNode as HTMLElement | null
const parentProjectId = parentNode?.dataset?.projectId ? parseInt(parentNode.dataset.projectId) : 0
const projectBefore = projectsActive[newIndex - 1] ?? null
const projectAfter = projectsActive[newIndex + 1] ?? null
projectUpdating.value[project.id] = true
@@ -100,7 +106,7 @@ async function saveProjectPosition(e: SortableEvent) {
...project,
position,
parentProjectId,
})
} as IProject)
emit('update:modelValue', availableProjects.value)
} finally {
projectUpdating.value[project.id] = false

View File

@@ -15,7 +15,7 @@
/>
</BaseButton>
<span
v-if="project.id > 0 && project.maxPermission > PERMISSIONS.READ"
v-if="canEditOrder && project.id > 0 && project.maxPermission !== null && project.maxPermission > PERMISSIONS.READ"
class="icon menu-item-icon handle drag-handle-standalone"
@mousedown.stop
@click.stop.prevent
@@ -48,7 +48,7 @@
<span class="project-menu-title">{{ getProjectTitle(project) }}</span>
</BaseButton>
<BaseButton
v-if="project.id > 0 && project.maxPermission > PERMISSIONS.READ"
v-if="project.id > 0 && project.maxPermission !== null && project.maxPermission > PERMISSIONS.READ"
class="favorite"
:class="{'is-favorite': project.isFavorite}"
@click="projectStore.toggleProjectFavorite(project)"
@@ -57,9 +57,10 @@
<Icon :icon="project.isFavorite ? 'star' : ['far', 'star']" />
</BaseButton>
<ProjectSettingsDropdown
v-if="project.maxPermission > PERMISSIONS.READ"
v-if="project.maxPermission !== null && project.maxPermission > PERMISSIONS.READ"
class="menu-list-dropdown"
:project="project"
:simple="true"
>
<template #trigger="{toggleOpen}">
<BaseButton
@@ -103,6 +104,7 @@ const props = defineProps<{
project: IProject,
isLoading?: boolean,
canCollapse?: boolean,
canEditOrder?: boolean,
}>()
const projectStore = useProjectStore()
@@ -158,11 +160,8 @@ const childProjects = computed(() => {
opacity: 1;
}
.list-menu:hover .color-bubble-wrapper > {
.saved-filter-icon,
.color-bubble {
opacity: 0;
}
.list-menu:hover .color-bubble-wrapper > .color-bubble {
opacity: 0;
}
.is-touch .color-bubble {

View File

@@ -23,7 +23,7 @@ import {useBaseStore} from '@/stores/base'
const baseStore = useBaseStore()
const updateAvailable = computed(() => baseStore.updateAvailable)
const registration = ref(null)
const registration = ref<ServiceWorkerRegistration | null>(null)
const refreshing = ref(false)
document.addEventListener('swUpdated', showRefreshUI, {once: true})
@@ -38,7 +38,8 @@ navigator?.serviceWorker?.addEventListener(
function showRefreshUI(e: Event) {
console.log('recieved refresh event', e)
registration.value = e.detail
const customEvent = e as CustomEvent<ServiceWorkerRegistration>
registration.value = customEvent.detail
baseStore.setUpdateAvailable(true)
}

View File

@@ -1,4 +1,4 @@
<script setup lang="ts" generic="T">
<script setup lang="ts" generic="T extends string">
import {type ComponentPublicInstance, nextTick, ref, watch} from 'vue'
const props = withDefaults(defineProps<{
@@ -30,15 +30,21 @@ const editorRef = ref<HTMLTextAreaElement | null>(null)
watch(
() => model.value,
newValue => {
val.value = newValue
val.value = newValue ?? ''
},
)
function updateSuggestionScroll() {
nextTick(() => {
const scroller = suggestionScrollerRef.value
const selectedItem = scroller?.querySelector('.selected')
scroller.scrollTop = selectedItem ? selectedItem.offsetTop : 0
if (!scroller) return
const selectedItem = scroller.querySelector('.selected')
if (selectedItem && selectedItem instanceof HTMLElement) {
scroller.scrollTop = selectedItem.offsetTop
} else {
scroller.scrollTop = 0
}
})
}
@@ -53,7 +59,7 @@ function onFocusField() {
setState('focused')
}
function onKeydown(e) {
function onKeydown(e: KeyboardEvent) {
switch (e.keyCode || e.which) {
case ESCAPE:
e.preventDefault()
@@ -116,9 +122,9 @@ function onSelectValue(value: T) {
setState('unfocused')
}
function onUpdateField(e) {
function onUpdateField(e: Event) {
setState('focused')
model.value = e.currentTarget.value
model.value = (e.currentTarget as HTMLTextAreaElement).value
}
</script>

View File

@@ -37,12 +37,20 @@
<script setup lang="ts">
import {computed} from 'vue'
import BaseButton, {type BaseButtonProps} from '@/components/base/BaseButton.vue'
import BaseButton from '@/components/base/BaseButton.vue'
import type {IconProp} from '@fortawesome/fontawesome-svg-core'
const props = defineProps<ButtonProps>()
const VARIANT_CLASS_MAP = {
primary: 'is-primary',
secondary: 'is-outlined',
tertiary: 'is-text is-inverted underline-none',
} as const
export type ButtonTypes = keyof typeof VARIANT_CLASS_MAP
export interface ButtonProps extends /* @vue-ignore */ BaseButtonProps {
export interface ButtonProps {
variant?: ButtonTypes
icon?: IconProp
iconColor?: string
@@ -52,25 +60,13 @@ export interface ButtonProps extends /* @vue-ignore */ BaseButtonProps {
wrap?: boolean
}
const props = withDefaults(defineProps<ButtonProps>(), {
variant: 'primary',
icon: undefined,
iconColor: undefined,
loading: false,
disabled: false,
shadow: true,
wrap: true,
})
defineOptions({name: 'XButton'})
const VARIANT_CLASS_MAP = {
primary: 'is-primary',
secondary: 'is-outlined',
tertiary: 'is-text is-inverted underline-none',
} as const
const variantClass = computed(() => VARIANT_CLASS_MAP[props.variant])
// @ts-expect-error - Complex union type from IconProp causes TS2590, but the code is correct
const variant = computed(() => (props.variant ?? 'primary') as ButtonTypes)
const shadow = computed(() => (props.shadow ?? true) as boolean)
const wrap = computed(() => (props.wrap ?? true) as boolean)
const variantClass = computed<string>(() => VARIANT_CLASS_MAP[variant.value])
</script>
<style lang="scss" scoped>

View File

@@ -62,7 +62,7 @@ const emit = defineEmits<{
'closeOnChange': [value: boolean],
}>()
const date = ref<Date | null>()
const date = ref<Date | null>(null)
const show = ref(false)
const changed = ref(false)
@@ -86,7 +86,7 @@ function setDateValue(dateString: string | Date | null) {
function updateData() {
changed.value = true
emit('update:modelValue', date.value)
emit('update:modelValue', date.value ?? null)
}
function toggleDatePopup() {
@@ -99,7 +99,7 @@ function toggleDatePopup() {
const datepickerPopup = ref<HTMLElement | null>(null)
function hideDatePopup(e: MouseEvent) {
if (show.value) {
if (show.value && datepickerPopup.value) {
closeWhenClickedOutside(e, datepickerPopup.value, close)
}
}

View File

@@ -31,6 +31,7 @@
</span>
</BaseButton>
<BaseButton
v-if="!((new Date()).getDay() === 0 && (new Date()).getHours() >= 21)"
class="datepicker__quick-select-date"
@click.stop="setDate('thisWeekend')"
>
@@ -94,7 +95,7 @@ const emit = defineEmits<{
const {t} = useI18n({useScope: 'global'})
const date = ref<Date | null>()
const date = ref<Date | null>(null)
const changed = ref(false)
const modelValue = toRef(props, 'modelValue')
@@ -104,7 +105,7 @@ watch(
{immediate: true},
)
const flatPickrRef = ref<HTMLElement | null>(null)
const flatPickrRef = ref<InstanceType<typeof flatPickr> | null>(null)
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,
@@ -151,14 +152,14 @@ const flatPickrDate = computed({
onMounted(() => {
const inputs = flatPickrRef.value?.$el.parentNode.querySelectorAll('.numInputWrapper > input.numInput')
inputs.forEach(i => {
inputs?.forEach((i: Element) => {
i.addEventListener('input', handleFlatpickrInput)
})
})
onBeforeUnmount(() => {
const inputs = flatPickrRef.value?.$el.parentNode.querySelectorAll('.numInputWrapper > input.numInput')
inputs.forEach(i => {
inputs?.forEach((i: Element) => {
i.removeEventListener('input', handleFlatpickrInput)
})
})
@@ -167,19 +168,20 @@ onBeforeUnmount(() => {
// That means it will usually only trigger when the focus is moved out of the input field.
// This is fine most of the time. However, since we're displaying flatpickr in a popup,
// the whole html dom instance might get destroyed, before the change event had a
// chance to fire. In that case, it would not update the date value. To fix
// chance to fire. In that case, it would not update the date value. To fix
// this, we're now listening on every change and bubble them up as soon
// as they happen.
function handleFlatpickrInput(e) {
function handleFlatpickrInput(e: Event) {
const newDate = new Date(date?.value || 'now')
if (e.target.classList.contains('flatpickr-minute')) {
newDate.setMinutes(e.target.value)
const target = e.target as HTMLInputElement
if (target.classList.contains('flatpickr-minute')) {
newDate.setMinutes(Number(target.value))
}
if (e.target.classList.contains('flatpickr-hour')) {
newDate.setHours(e.target.value)
if (target.classList.contains('flatpickr-hour')) {
newDate.setHours(Number(target.value))
}
if (e.target.classList.contains('cur-year')) {
newDate.setFullYear(e.target.value)
if (target.classList.contains('cur-year')) {
newDate.setFullYear(Number(target.value))
}
flatPickrDate.value = newDate
}

View File

@@ -11,7 +11,7 @@ const isCheckedInitiallyEnabled = ref(true)
const isCheckedDisabled = ref(false)
const withoutInitialState = ref<boolean | undefined>()
const withoutInitialState = ref(false)
</script>

View File

@@ -170,7 +170,7 @@ const props = withDefaults(defineProps<{
}>(), {
loading: false,
placeholder: '',
searchResults: () => [],
searchResults: () => [] as T[],
label: '',
creatable: false,
createPlaceholder: () => useI18n().t('input.multiselect.createPlaceholder'),
@@ -248,8 +248,8 @@ const searchResultsVisible = computed(() => {
})
const creatableAvailable = computed(() => {
const hasResult = filteredSearchResults.value.some((elem) => elementInResults(elem, props.label, query.value as string))
const hasQueryAlreadyAdded = Array.isArray(internalValue.value) && internalValue.value.some(elem => elementInResults(elem, props.label, query.value))
const hasResult = filteredSearchResults.value.some((elem: T) => elementInResults(elem, props.label, query.value as string))
const hasQueryAlreadyAdded = Array.isArray(internalValue.value) && internalValue.value.some((elem: T) => elementInResults(elem, props.label, query.value))
return props.creatable
&& query.value !== ''
@@ -259,7 +259,7 @@ const creatableAvailable = computed(() => {
const filteredSearchResults = computed(() => {
const currentInternal = internalValue.value
if (props.multiple && currentInternal !== null && Array.isArray(currentInternal)) {
return searchResults.value.filter((item) => !currentInternal.some(e => e === item))
return searchResults.value.filter((item: T) => !currentInternal.some((e: T) => e === item))
}
return searchResults.value
@@ -302,7 +302,9 @@ function search() {
const multiselectRoot = ref<HTMLElement | null>(null)
function hideSearchResultsHandler(e: MouseEvent) {
closeWhenClickedOutside(e, multiselectRoot.value, closeSearchResults)
if (multiselectRoot.value) {
closeWhenClickedOutside(e, multiselectRoot.value, closeSearchResults)
}
}
function closeSearchResults() {
@@ -318,6 +320,10 @@ function handleFocus() {
}
function select(object: T | null) {
if (object === null) {
return
}
if (props.multiple) {
if (internalValue.value === null) {
internalValue.value = []
@@ -336,7 +342,7 @@ function select(object: T | null) {
}
}
function setSelectedObject(object: string | T | null, resetOnly = false) {
function setSelectedObject(object: string | T | null | undefined, resetOnly = false) {
internalValue.value = object
// We assume we're getting an array when multiple is enabled and can therefore leave the query
@@ -346,7 +352,7 @@ function setSelectedObject(object: string | T | null, resetOnly = false) {
return
}
if (object === null) {
if (object === null || typeof object === 'undefined') {
query.value = ''
return
}
@@ -355,7 +361,13 @@ function setSelectedObject(object: string | T | null, resetOnly = false) {
return
}
query.value = props.label !== '' ? object[props.label] : object
if (typeof object === 'string') {
query.value = object
} else if (props.label !== '') {
query.value = object[props.label] as string
} else {
query.value = String(object)
}
}
const results = ref<(Element | ComponentPublicInstance)[]>([])
@@ -375,16 +387,20 @@ function preSelect(index: number) {
}
const elems = results.value[index]
if (typeof elems === 'undefined' || elems.length === 0) {
if (typeof elems === 'undefined') {
return
}
if (Array.isArray(elems)) {
elems[0].focus()
if (elems.length > 0 && 'focus' in elems[0]) {
(elems[0] as HTMLElement).focus()
}
return
}
elems.focus()
if ('focus' in elems) {
(elems as HTMLElement).focus()
}
}
function create() {
@@ -405,7 +421,7 @@ function createOrSelectOnEnter() {
if (!creatableAvailable.value) {
// Check if there's an exact match for our search term
const exactMatch = filteredSearchResults.value.find((elem) => elementInResults(elem, props.label, query.value as string))
const exactMatch = filteredSearchResults.value.find((elem: T) => elementInResults(elem, props.label, query.value as string))
if (exactMatch) {
select(exactMatch)
}

View File

@@ -41,10 +41,13 @@ async function addReaction(value: string) {
model.value = {}
}
if (typeof model.value[reaction.value] === 'undefined') {
model.value[reaction.value] = [authStore.info]
if (!authStore.info || !model.value) return
const modelValue = model.value
if (typeof modelValue[reaction.value] === 'undefined') {
modelValue[reaction.value] = [authStore.info]
} else {
model.value[reaction.value].push(authStore.info)
modelValue[reaction.value]!.push(authStore.info)
}
}
@@ -56,35 +59,39 @@ async function removeReaction(value: string) {
})
await reactionService.delete(reaction)
showEmojiPicker.value = false
const userIndex = model.value[reaction.value].findIndex(u => u.id === authStore.info?.id)
if (!model.value) return
const modelValue = model.value
const userIndex = modelValue[reaction.value]?.findIndex(u => u.id === authStore.info?.id) ?? -1
if (userIndex !== -1) {
model.value[reaction.value].splice(userIndex, 1)
modelValue[reaction.value]!.splice(userIndex, 1)
}
if(model.value[reaction.value].length === 0) {
delete model.value[reaction.value]
if(modelValue[reaction.value]!.length === 0) {
delete modelValue[reaction.value]
}
}
function getReactionTooltip(users: IUser[], value: string) {
function getReactionTooltip(users: IUser[], value: string | number) {
const names = users.map(u => getDisplayName(u))
const valueStr = String(value)
if (names.length === 1) {
return t('reaction.reactedWith', {user: names[0], value})
return t('reaction.reactedWith', {user: names[0], value: valueStr})
}
if (names.length > 1 && names.length < 10) {
return t('reaction.reactedWithAnd', {
users: names.slice(0, names.length - 1).join(', '),
lastUser: names[names.length - 1],
value,
value: valueStr,
})
}
return t('reaction.reactedWithAndMany', {
users: names.slice(0, 10).join(', '),
num: names.length - 10,
value,
value: valueStr,
})
}
@@ -92,8 +99,8 @@ const showEmojiPicker = ref(false)
const emojiPickerRef = ref<HTMLElement | null>(null)
function hideEmojiPicker(e: MouseEvent) {
if (showEmojiPicker.value) {
closeWhenClickedOutside(e, emojiPickerRef.value.$el, () => showEmojiPicker.value = false)
if (showEmojiPicker.value && emojiPickerRef.value) {
closeWhenClickedOutside(e, emojiPickerRef.value, () => showEmojiPicker.value = false)
}
}
@@ -106,29 +113,33 @@ const emojiPickerPosition = ref()
function toggleEmojiPicker() {
if (!showEmojiPicker.value) {
const rect = emojiPickerButtonRef.value?.$el.getBoundingClientRect()
const rect = emojiPickerButtonRef.value?.$el?.getBoundingClientRect()
const container = reactionContainerRef.value?.getBoundingClientRect()
const left = rect.left - container.left + rect.width
if (rect && container) {
const left = rect.left - container.left + rect.width
emojiPickerPosition.value = {
'inset-inline-start': left === 0 ? undefined : left,
emojiPickerPosition.value = {
'inset-inline-start': left === 0 ? undefined : left,
}
}
}
nextTick(() => showEmojiPicker.value = !showEmojiPicker.value)
}
function hasCurrentUserReactedWithEmoji(value: string): boolean {
const user = model.value[value].find(u => u.id === authStore.info.id)
function hasCurrentUserReactedWithEmoji(value: string | number): boolean {
if (!model.value || !authStore.info) return false
const user = model.value[String(value)]?.find(u => u.id === authStore.info!.id)
return typeof user !== 'undefined'
}
async function toggleReaction(value: string) {
if (hasCurrentUserReactedWithEmoji(value)) {
return removeReaction(value)
async function toggleReaction(value: string | number) {
const valueStr = String(value)
if (hasCurrentUserReactedWithEmoji(valueStr)) {
return removeReaction(valueStr)
}
return addReaction(value)
return addReaction(valueStr)
}
</script>

View File

@@ -26,15 +26,25 @@
<script lang="ts">
/* eslint-disable vue/component-api-style */
import type {PropType} from 'vue'
import type {IconProp} from '@fortawesome/fontawesome-svg-core'
interface CommandItem {
icon: IconProp
title: string
description: string
command: () => void
}
export default {
props: {
items: {
type: Array,
type: Array as PropType<CommandItem[]>,
required: true,
},
command: {
type: Function,
type: Function as PropType<(item: CommandItem) => void>,
required: true,
},
},
@@ -52,7 +62,7 @@ export default {
},
methods: {
onKeyDown({event}) {
onKeyDown({event}: {event: KeyboardEvent}) {
if (event.key === 'ArrowUp') {
this.upHandler()
return true
@@ -64,6 +74,9 @@ export default {
}
if (event.key === 'Enter') {
if (event.isComposing) {
return false
}
this.enterHandler()
return true
}
@@ -83,7 +96,7 @@ export default {
this.selectItem(this.selectedIndex)
},
selectItem(index) {
selectItem(index: number) {
const item = this.items[index]
if (item) {

View File

@@ -8,7 +8,7 @@
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-header']" />
<Icon :icon="['fas', 'header']" />
<span
class="icon__lower-text"
aria-hidden="true"
@@ -23,7 +23,7 @@
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-header']" />
<Icon :icon="['fas', 'header']" />
<span
class="icon__lower-text"
aria-hidden="true"
@@ -38,7 +38,7 @@
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-header']" />
<Icon :icon="['fas', 'header']" />
<span
class="icon__lower-text"
aria-hidden="true"
@@ -56,7 +56,7 @@
@click="editor.chain().focus().toggleBold().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-bold']" />
<Icon :icon="['fas', 'bold']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.bold') }}</span>
</BaseButton>
@@ -67,7 +67,7 @@
@click="editor.chain().focus().toggleItalic().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-italic']" />
<Icon :icon="['fas', 'italic']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.italic') }}</span>
</BaseButton>
@@ -78,7 +78,7 @@
@click="editor.chain().focus().toggleUnderline().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-underline']" />
<Icon :icon="['fas', 'underline']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.underline') }}</span>
</BaseButton>
@@ -89,7 +89,7 @@
@click="editor.chain().focus().toggleStrike().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-strikethrough']" />
<Icon :icon="['fas', 'strikethrough']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.strikethrough') }}</span>
</BaseButton>
@@ -103,7 +103,7 @@
@click="editor.chain().focus().toggleCodeBlock().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-code']" />
<Icon :icon="['fas', 'code']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.code') }}</span>
</BaseButton>
@@ -114,7 +114,7 @@
@click="editor.chain().focus().toggleBlockquote().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-quote-right']" />
<Icon :icon="['fas', 'quote-right']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.quote') }}</span>
</BaseButton>
@@ -128,7 +128,7 @@
@click="editor.chain().focus().toggleBulletList().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-list-ul']" />
<Icon :icon="['fas', 'list-ul']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.bulletList') }}</span>
</BaseButton>
@@ -139,7 +139,7 @@
@click="editor.chain().focus().toggleOrderedList().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-list-ol']" />
<Icon :icon="['fas', 'list-ol']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.orderedList') }}</span>
</BaseButton>
@@ -150,7 +150,7 @@
@click="editor.chain().focus().toggleTaskList().run()"
>
<span class="icon">
<Icon icon="fa-list-check" />
<Icon icon="list-check" />
</span>
<span class="is-sr-only">{{ $t('input.editor.taskList') }}</span>
</BaseButton>
@@ -163,7 +163,7 @@
@click="e => emit('imageUploadClicked', e)"
>
<span class="icon">
<Icon icon="fa-image" />
<Icon icon="image" />
</span>
<span class="is-sr-only">{{ $t('input.editor.image') }}</span>
</BaseButton>
@@ -178,7 +178,7 @@
@click="setLink"
>
<span class="icon">
<Icon :icon="['fa', 'fa-link']" />
<Icon :icon="['fas', 'link']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.link') }}</span>
</BaseButton>
@@ -190,7 +190,7 @@
@click="editor.chain().focus().setParagraph().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-paragraph']" />
<Icon :icon="['fas', 'paragraph']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.text') }}</span>
</BaseButton>
@@ -201,7 +201,7 @@
@click="editor.chain().focus().setHorizontalRule().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-ruler-horizontal']" />
<Icon :icon="['fas', 'ruler-horizontal']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.horizontalRule') }}</span>
</BaseButton>
@@ -214,7 +214,7 @@
@click="editor.chain().focus().undo().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-undo']" />
<Icon :icon="['fas', 'undo']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.undo') }}</span>
</BaseButton>
@@ -224,7 +224,7 @@
@click="editor.chain().focus().redo().run()"
>
<span class="icon">
<Icon :icon="['fa', 'fa-redo']" />
<Icon :icon="['fas', 'redo']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.redo') }}</span>
</BaseButton>
@@ -239,7 +239,7 @@
@click="toggleTableMode"
>
<span class="icon">
<Icon :icon="['fa', 'fa-table']" />
<Icon :icon="['fas', 'table']" />
</span>
<span class="is-sr-only">{{ $t('input.editor.table.title') }}</span>
</BaseButton>
@@ -381,8 +381,8 @@ function toggleTableMode() {
tableMode.value = !tableMode.value
}
function setLink(event) {
setLinkInEditor(event.target.getBoundingClientRect(), props.editor)
function setLink(event: MouseEvent) {
setLinkInEditor((event.target as HTMLElement).getBoundingClientRect(), props.editor)
}
</script>

View File

@@ -19,7 +19,7 @@
:class="{ 'is-active': editor.isActive('bold') }"
@click="() => editor?.chain().focus().toggleBold().run()"
>
<Icon :icon="['fa', 'fa-bold']" />
<Icon :icon="['fas', 'bold']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.italic')"
@@ -27,7 +27,7 @@
:class="{ 'is-active': editor.isActive('italic') }"
@click="() => editor?.chain().focus().toggleItalic().run()"
>
<Icon :icon="['fa', 'fa-italic']" />
<Icon :icon="['fas', 'italic']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.underline')"
@@ -35,7 +35,7 @@
:class="{ 'is-active': editor.isActive('underline') }"
@click="() => editor?.chain().focus().toggleUnderline().run()"
>
<Icon :icon="['fa', 'fa-underline']" />
<Icon :icon="['fas', 'underline']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.strikethrough')"
@@ -43,7 +43,7 @@
:class="{ 'is-active': editor.isActive('strike') }"
@click="() => editor?.chain().focus().toggleStrike().run()"
>
<Icon :icon="['fa', 'fa-strikethrough']" />
<Icon :icon="['fas', 'strikethrough']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.code')"
@@ -51,7 +51,7 @@
:class="{ 'is-active': editor.isActive('code') }"
@click="() => editor?.chain().focus().toggleCode().run()"
>
<Icon :icon="['fa', 'fa-code']" />
<Icon :icon="['fas', 'code']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.link')"
@@ -59,7 +59,7 @@
:class="{ 'is-active': editor.isActive('link') }"
@click="setLink"
>
<Icon :icon="['fa', 'fa-link']" />
<Icon :icon="['fas', 'link']" />
</BaseButton>
</div>
</BubbleMenu>
@@ -145,8 +145,8 @@ import {eventToHotkeyString} from '@github/hotkey'
import EditorToolbar from './EditorToolbar.vue'
import StarterKit from '@tiptap/starter-kit'
import {Extension, mergeAttributes} from '@tiptap/core'
import {EditorContent, type Extensions, useEditor} from '@tiptap/vue-3'
import {Extension, mergeAttributes, type SetContentOptions} from '@tiptap/core'
import {EditorContent, type Extensions, useEditor, VueNodeViewRenderer} from '@tiptap/vue-3'
import {Plugin, PluginKey} from '@tiptap/pm/state'
import {marked} from 'marked'
import {BubbleMenu} from '@tiptap/vue-3/menus'
@@ -158,6 +158,7 @@ import Typography from '@tiptap/extension-typography'
import Image from '@tiptap/extension-image'
import Underline from '@tiptap/extension-underline'
import {Placeholder} from '@tiptap/extensions'
import Mention from '@tiptap/extension-mention'
import {TaskItem, TaskList} from '@tiptap/extension-list'
import HardBreak from '@tiptap/extension-hard-break'
@@ -166,6 +167,8 @@ import {Node} from '@tiptap/pm/model'
import Commands from './commands'
import suggestionSetup from './suggestion'
import mentionSuggestionSetup from './mention/mentionSuggestion'
import MentionUser from './mention/MentionUser.vue'
import {common, createLowlight} from 'lowlight'
@@ -180,6 +183,7 @@ import XButton from '@/components/input/Button.vue'
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
import inputPrompt from '@/helpers/inputPrompt'
import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
import {saveEditorDraft, loadEditorDraft, clearEditorDraft} from '@/helpers/editorDraftStorage'
const props = withDefaults(defineProps<{
modelValue: string,
@@ -190,6 +194,9 @@ const props = withDefaults(defineProps<{
placeholder?: string,
editShortcut?: string,
enableDiscardShortcut?: boolean,
enableMentions?: boolean,
mentionProjectId?: number,
storageKey?: string,
}>(), {
uploadCallback: undefined,
isEditEnabled: true,
@@ -198,6 +205,9 @@ const props = withDefaults(defineProps<{
placeholder: '',
editShortcut: '',
enableDiscardShortcut: false,
enableMentions: false,
mentionProjectId: 0,
storageKey: '',
})
const emit = defineEmits(['update:modelValue', 'save'])
@@ -206,6 +216,12 @@ const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
const {t} = useI18n()
const defaultSetContentOptions: SetContentOptions = {
parseOptions: {
preserveWhitespace: true,
},
}
const CustomTableCell = TableCell.extend({
addAttributes() {
return {
@@ -267,17 +283,17 @@ const CustomImage = Image.extend({
const img = document.getElementById(id)
if (!img) return
if (!img || !(img instanceof HTMLImageElement)) return
if (typeof loadedAttachments.value[cacheKey] === 'undefined') {
const attachment = new AttachmentModel({taskId: taskId, id: attachmentId})
const attachmentService = new AttachmentService()
loadedAttachments.value[cacheKey] = await attachmentService.getBlobUrl(attachment)
loadedAttachments.value[cacheKey] = await attachmentService.getBlobUrl(attachment) as string
}
img.src = loadedAttachments.value[cacheKey]
img.src = loadedAttachments.value[cacheKey] as string
})
return ['img', mergeAttributes(this.options.HTMLAttributes, {
@@ -348,12 +364,13 @@ const PasteHandler = Extension.create({
key: new PluginKey('pasteHandler'),
props: {
handlePaste: (view, event) => {
// Handle images pasted from clipboard
if (typeof props.uploadCallback !== 'undefined' && event.clipboardData?.items?.length > 0) {
for (const item of event.clipboardData.items) {
if (item.kind === 'file' && item.type.startsWith('image/')) {
// Handle images pasted from clipboard
if (typeof props.uploadCallback !== 'undefined' && event.clipboardData?.items && event.clipboardData.items.length > 0) {
for (let i = 0; i < event.clipboardData.items.length; i++) {
const item = event.clipboardData.items[i]
if (item && item.kind === 'file' && item.type.startsWith('image/')) {
const file = item.getAsFile()
if (file) {
uploadAndInsertFiles([file])
@@ -362,12 +379,18 @@ const PasteHandler = Extension.create({
}
}
}
const text = event.clipboardData?.getData('text/plain') || ''
if (!text) {
return false
}
// Don't convert markdown when pasting inside a code block
const $from = view.state.selection.$from
if ($from.parent.type.name === 'codeBlock') {
return false
}
const hasMarkdownSyntax = new RegExp('[*`_\\[\\]#-]').test(text)
if (!hasMarkdownSyntax) {
return false
@@ -480,6 +503,35 @@ const extensions : Extensions = [
PasteHandler,
]
// Add mention extension if enabled
if (props.enableMentions && props.mentionProjectId > 0) {
extensions.push(
Mention.configure({
HTMLAttributes: {
class: 'mention',
},
suggestion: mentionSuggestionSetup(props.mentionProjectId),
}).extend({
parseHTML() {
return [
{
tag: 'mention-user',
},
]
},
renderHTML({ HTMLAttributes }) {
return ['mention-user', mergeAttributes(HTMLAttributes)]
},
addNodeView() {
return VueNodeViewRenderer(MentionUser)
},
}),
)
}
// Add a custom extension for the Escape key
if (props.enableDiscardShortcut) {
extensions.push(Extension.create({
@@ -503,6 +555,9 @@ const editor = useEditor({
onUpdate: () => {
bubbleNow()
},
parseOptions: {
preserveWhitespace: true,
},
})
watch(
@@ -534,12 +589,25 @@ function bubbleNow() {
}
contentHasChanged.value = true
emit('update:modelValue', editor.value?.getHTML())
const newContent = editor.value?.getHTML()
// Save to localStorage if storageKey is provided
if (props.storageKey) {
saveEditorDraft(props.storageKey, newContent || '')
}
emit('update:modelValue', newContent)
}
function bubbleSave() {
bubbleNow()
lastSavedState = editor.value?.getHTML() ?? ''
// Clear draft from localStorage when saved
if (props.storageKey) {
clearEditorDraft(props.storageKey)
}
emit('save', lastSavedState)
if (isEditing.value) {
internalMode.value = 'preview'
@@ -547,7 +615,16 @@ function bubbleSave() {
}
function exitEditMode() {
editor.value?.commands.setContent(lastSavedState, false)
editor.value?.commands.setContent(lastSavedState, {
...defaultSetContentOptions,
emitUpdate: false,
})
// Clear draft from localStorage when discarding changes
if (props.storageKey) {
clearEditorDraft(props.storageKey)
}
if (isEditing.value) {
internalMode.value = 'preview'
}
@@ -567,7 +644,13 @@ function setEdit(focus: boolean = true) {
}
}
onBeforeUnmount(() => editor.value?.destroy())
onBeforeUnmount(() => {
if (props.enableDiscardShortcut) {
tiptapInstanceRef.value?.removeEventListener('keydown', handleEscapeKey)
}
editor.value?.destroy()
})
const uploadInputRef = ref<HTMLInputElement | null>(null)
@@ -593,14 +676,17 @@ function uploadAndInsertFiles(files: File[] | FileList) {
})
const html = editor.value?.getHTML().replace(UPLOAD_PLACEHOLDER_ELEMENT, '') ?? ''
editor.value?.commands.setContent(html, false)
bubbleSave()
editor.value?.commands.setContent(html, {
...defaultSetContentOptions,
emitUpdate: false,
})
bubbleNow()
})
}
function triggerImageInput(event) {
function triggerImageInput(event: Event) {
if (typeof props.uploadCallback !== 'undefined') {
uploadInputRef.value?.click()
return
@@ -609,7 +695,7 @@ function triggerImageInput(event) {
addImage(event)
}
async function addImage(event) {
async function addImage(event: Event) {
if (typeof props.uploadCallback !== 'undefined') {
const files = uploadInputRef.value?.files
@@ -627,12 +713,13 @@ async function addImage(event) {
if (url) {
editor.value?.chain().focus().setImage({src: url}).run()
bubbleSave()
bubbleNow()
}
}
function setLink(event) {
setLinkInEditor(event.target.getBoundingClientRect(), editor.value)
function setLink(event: MouseEvent) {
const target = event.target as HTMLElement
setLinkInEditor(target.getBoundingClientRect(), editor.value)
}
onMounted(async () => {
@@ -640,8 +727,27 @@ onMounted(async () => {
document.addEventListener('keydown', setFocusToEditor)
}
// Add Escape key handler to prevent event bubbling when editing
if (props.enableDiscardShortcut) {
tiptapInstanceRef.value?.addEventListener('keydown', handleEscapeKey)
}
await nextTick()
// Load draft from localStorage if available
if (props.storageKey) {
const draft = loadEditorDraft(props.storageKey)
if (draft && isEditorContentEmpty(props.modelValue)) {
// Only load draft if current content is empty
// Set content and force edit mode for immediate editing
editor.value?.commands.setContent(draft, {emitUpdate: false})
internalMode.value = 'edit'
// Emit the model update so parent sees the restored content
emit('update:modelValue', draft)
return
}
}
setModeAndValue(props.modelValue)
})
@@ -653,13 +759,17 @@ onBeforeUnmount(() => {
function setModeAndValue(value: string) {
internalMode.value = isEditorContentEmpty(value) ? 'edit' : 'preview'
editor.value?.commands.setContent(value, false)
editor.value?.commands.setContent(value, {
...defaultSetContentOptions,
emitUpdate: false,
})
}
// See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660
function setFocusToEditor(event) {
if (event.target.shadowRoot) {
function setFocusToEditor(event: KeyboardEvent) {
const target = event.target as HTMLElement
if (target.shadowRoot) {
return
}
@@ -687,10 +797,29 @@ function focusIfEditing() {
}
}
function clickTasklistCheckbox(event) {
function handleEscapeKey(event: KeyboardEvent) {
// Only intercept Escape when discard shortcut is enabled
if (event.key !== 'Escape' || !props.enableDiscardShortcut) {
return
}
// Check if the event originated from within the ProseMirror editor
const target = event.target as HTMLElement
const isInEditor = target.contentEditable === 'true' || target.closest('.ProseMirror')
if (!isInEditor) {
return
}
// Stop propagation to prevent modal/parent handlers from firing
event.stopPropagation()
// Don't preventDefault - let ProseMirror's extension handle the actual exit
}
function clickTasklistCheckbox(event: MouseEvent) {
event.stopImmediatePropagation()
if (event.target.localName !== 'p') {
const target = event.target as HTMLElement
if (target.localName !== 'p') {
return
}
@@ -721,7 +850,10 @@ watch(
// We assume the first child contains the label element with the checkbox and the second child the actual label
// When the actual label is clicked, we forward that click to the checkbox.
check.children[1].removeEventListener('click', clickTasklistCheckbox)
const secondChild = check.children[1]
if (secondChild) {
secondChild.removeEventListener('click', clickTasklistCheckbox)
}
})
return
@@ -734,8 +866,11 @@ watch(
// We assume the first child contains the label element with the checkbox and the second child the actual label
// When the actual label is clicked, we forward that click to the checkbox.
check.children[1].removeEventListener('click', clickTasklistCheckbox)
check.children[1].addEventListener('click', clickTasklistCheckbox)
const secondChild = check.children[1]
if (secondChild) {
secondChild.removeEventListener('click', clickTasklistCheckbox)
secondChild.addEventListener('click', clickTasklistCheckbox)
}
})
},
{immediate: true},

View File

@@ -1,8 +1,13 @@
import {Extension} from '@tiptap/core'
import type {Editor, Range} from '@tiptap/core'
import Suggestion from '@tiptap/suggestion'
// Copied and adjusted from https://github.com/ueberdosis/tiptap/tree/252acb32d27a0f9af14813eeed83d8a50059a43a/demos/src/Experiments/Commands/Vue
interface CommandProps {
command: (params: {editor: Editor, range: Range}) => void
}
export default Extension.create({
name: 'slash-menu-commands',
@@ -10,7 +15,7 @@ export default Extension.create({
return {
suggestion: {
char: '/',
command: ({editor, range, props}) => {
command: ({editor, range, props}: {editor: Editor, range: Range, props: CommandProps}) => {
props.command({editor, range})
},
},

View File

@@ -0,0 +1,191 @@
<template>
<div class="mention-items">
<template v-if="items.length">
<button
v-for="(item, index) in items"
:key="item.id"
class="mention-item"
:class="{ 'is-selected': index === selectedIndex }"
@click="selectItem(index)"
>
<img
:src="item.avatarUrl"
alt=""
class="mention-avatar"
>
<div class="mention-info">
<p class="mention-name">
{{ item.label }}
</p>
<p
v-if="item.label !== item.username"
class="mention-username"
>
@{{ item.username }}
</p>
</div>
</button>
</template>
<div
v-else
class="mention-item no-results"
>
{{ $t('task.mention.noUsersFound') }}
</div>
</div>
</template>
<script lang="ts">
/* eslint-disable vue/component-api-style */
import type {PropType} from 'vue'
import type {MentionNodeAttrs} from '@tiptap/extension-mention'
interface MentionItem extends MentionNodeAttrs {
id: string
label: string
username: string
avatarUrl: string
}
export default {
props: {
items: {
type: Array as PropType<MentionItem[]>,
required: true,
},
command: {
type: Function as PropType<(item: MentionItem) => void>,
required: true,
},
},
data() {
return {
selectedIndex: 0,
}
},
watch: {
items() {
this.selectedIndex = 0
},
},
methods: {
onKeyDown({event}: {event: KeyboardEvent}) {
if (event.key === 'ArrowUp') {
this.upHandler()
return true
}
if (event.key === 'ArrowDown') {
this.downHandler()
return true
}
if (event.key === 'Enter') {
if (event.isComposing) {
return false
}
this.enterHandler()
return true
}
return false
},
upHandler() {
this.selectedIndex = ((this.selectedIndex + this.items.length) - 1) % this.items.length
},
downHandler() {
this.selectedIndex = (this.selectedIndex + 1) % this.items.length
},
enterHandler() {
this.selectItem(this.selectedIndex)
},
selectItem(index: number) {
const item = this.items[index]
if (item) {
this.command(item)
}
},
},
}
</script>
<style lang="scss" scoped>
.mention-items {
padding: 0.2rem;
position: relative;
border-radius: 0.5rem;
background: var(--white);
color: var(--grey-900);
overflow: hidden;
font-size: 0.9rem;
box-shadow: var(--shadow-md);
min-inline-size: 200px;
max-block-size: 300px;
overflow-y: auto;
}
.mention-item {
display: flex;
align-items: center;
margin: 0;
inline-size: 100%;
text-align: start;
background: transparent;
border-radius: $radius;
border: 0;
padding: 0.4rem 0.6rem;
transition: background-color $transition;
&.is-selected, &:hover {
background: var(--grey-100);
cursor: pointer;
}
&.no-results {
color: var(--grey-500);
cursor: default;
}
}
.mention-avatar {
inline-size: 32px;
block-size: 32px;
border-radius: 50%;
margin-inline-end: 0.75rem;
flex-shrink: 0;
}
.mention-info {
display: flex;
flex-direction: column;
min-inline-size: 0;
flex: 1;
p {
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.mention-name {
font-size: 0.9rem;
color: var(--grey-800);
font-weight: 500;
}
.mention-username {
font-size: 0.75rem;
color: var(--grey-500);
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<NodeViewWrapper class="mention-user">
<img :src="avatarUrl">
<span class="mention__label">
{{ node.attrs.label ?? node.attrs.id }}
</span>
</NodeViewWrapper>
</template>
<script lang="ts" setup>
import { fetchAvatarBlobUrl } from '@/models/user'
import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
import { watch, ref } from 'vue'
import type { IUser } from '@/modelTypes/IUser'
const props = defineProps(nodeViewProps)
const avatarUrl = ref('')
watch(
() => props.node.attrs.id,
async () => {
const username = props.node.attrs.id as string
const url = await fetchAvatarBlobUrl({username} as IUser, 32)
avatarUrl.value = url as string
},
{immediate: true},
)
</script>
<style lang="scss">
.tiptap .mention-user {
display: inline-flex;
align-items: center;
position: relative;
inset-block-end: 0;
padding-inline-start: 1.75rem;
> img {
border-radius: 100%;
inline-size: 1.5rem;
block-size: 1.5rem;
position: absolute;
inset-inline-start: 0;
}
}
</style>

View File

@@ -0,0 +1,190 @@
import { VueRenderer } from '@tiptap/vue-3'
import { computePosition, flip, shift, offset, autoUpdate } from '@floating-ui/dom'
import type { Editor } from '@tiptap/core'
import MentionList from './MentionList.vue'
import ProjectUserService from '@/services/projectUsers'
import { fetchAvatarBlobUrl, getDisplayName } from '@/models/user'
import type { IUser } from '@/modelTypes/IUser'
import type { MentionNodeAttrs } from '@tiptap/extension-mention'
interface MentionItem extends MentionNodeAttrs {
id: string
label: string
username: string
avatarUrl: string
}
async function searchUsersForProject(projectId: number, query: string): Promise<MentionItem[]> {
const projectUserService = new ProjectUserService()
// Use server-side search with the 's' parameter
// @ts-expect-error - projectId is used for URL replacement but not part of IAbstract
const users = await projectUserService.getAll({ projectId }, { s: query }) as IUser[]
// Fetch avatar URLs for all users
const usersWithAvatars = await Promise.all(
users.map(async (user) => {
const avatarUrl = await fetchAvatarBlobUrl(user, 32)
return {
id: user.username,
label: getDisplayName(user),
username: user.username,
avatarUrl: avatarUrl as string,
}
}),
)
return usersWithAvatars
}
export default function mentionSuggestionSetup(projectId: number) {
let debounceTimer: ReturnType<typeof setTimeout> | null = null
return {
char: '@',
items: async ({ query }: { query: string }): Promise<MentionItem[]> => {
if (!projectId) {
return []
}
// Clear existing timer
if (debounceTimer) {
clearTimeout(debounceTimer)
}
// Return a promise that resolves after debounce delay
return new Promise((resolve) => {
debounceTimer = setTimeout(async () => {
try {
// Use server-side search - the backend will handle searching by username and display name
const users = await searchUsersForProject(projectId, query)
// Limit results to avoid overwhelming the UI
const limit = query ? 10 : 5
resolve(users.slice(0, limit))
} catch (error) {
console.error('Failed to fetch users for mentions:', error)
resolve([])
}
}, 300) // 300ms debounce delay
})
},
render: () => {
let component: VueRenderer
let popupElement: HTMLElement | null = null
let cleanupFloating: (() => void) | null = null
const virtualReference = {
getBoundingClientRect: () => ({
width: 0,
height: 0,
x: 0,
y: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
} as DOMRect),
}
return {
onStart: (props: {
editor: Editor
clientRect?: (() => DOMRect | null) | null
items: MentionItem[]
command: (item: MentionItem) => void
}) => {
component = new VueRenderer(MentionList, {
props,
editor: props.editor,
})
if (!props.clientRect) {
return
}
// Create popup element
popupElement = document.createElement('div')
popupElement.style.position = 'absolute'
popupElement.style.top = '0'
popupElement.style.left = '0'
popupElement.style.zIndex = '4700'
popupElement.appendChild(component.element!)
document.body.appendChild(popupElement) // Update virtual reference
const rect = props.clientRect()
if (rect) {
virtualReference.getBoundingClientRect = () => rect
// Set up floating positioning
const updatePosition = () => {
computePosition(virtualReference, popupElement!, {
placement: 'bottom-start',
middleware: [
offset(8),
flip(),
shift({ padding: 8 }),
],
}).then(({ x, y }) => {
if (popupElement) {
popupElement.style.left = `${x}px`
popupElement.style.top = `${y}px`
}
})
}
updatePosition()
cleanupFloating = autoUpdate(virtualReference, popupElement, updatePosition)
}
},
onUpdate(props: {
editor: Editor
clientRect?: (() => DOMRect | null) | null
items: MentionItem[]
command: (item: MentionItem) => void
}) {
component.updateProps(props)
if (!props.clientRect || !popupElement) {
return
}
// Update virtual reference
const rect = props.clientRect()
if (rect) {
virtualReference.getBoundingClientRect = () => rect
}
},
onKeyDown(props: { event: KeyboardEvent }) {
if (props.event.key === 'Escape') {
if (props.event.isComposing) {
return false
}
if (popupElement) {
popupElement.style.display = 'none'
}
return true
}
return component.ref?.onKeyDown(props)
},
onExit() {
if (cleanupFloating) {
cleanupFloating()
}
if (popupElement) {
document.body.removeChild(popupElement)
popupElement = null
}
component.destroy()
},
}
},
}
}

View File

@@ -1,6 +1,7 @@
import type {Editor} from '@tiptap/core'
import inputPrompt from '@/helpers/inputPrompt'
export async function setLinkInEditor(pos, editor) {
export async function setLinkInEditor(pos: DOMRect, editor: Editor | null | undefined) {
const previousUrl = editor?.getAttributes('link').href || ''
const url = await inputPrompt(pos, previousUrl)

View File

@@ -1,9 +1,20 @@
import type {Editor, Range} from '@tiptap/core'
import {VueRenderer} from '@tiptap/vue-3'
import {computePosition, flip, shift, offset, autoUpdate} from '@floating-ui/dom'
import CommandsList from './CommandsList.vue'
export default function suggestionSetup(t) {
type TranslateFunction = (key: string) => string
interface SuggestionProps {
editor: Editor
clientRect?: () => DOMRect
command: (item: {command: (params: {editor: Editor, range: Range}) => void}) => void
items: unknown[]
event?: KeyboardEvent
}
export default function suggestionSetup(t: TranslateFunction) {
return {
items: ({query}: { query: string }) => {
return [
@@ -11,7 +22,7 @@ export default function suggestionSetup(t) {
title: t('input.editor.text'),
description: t('input.editor.textTooltip'),
icon: 'fa-font',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -24,7 +35,7 @@ export default function suggestionSetup(t) {
title: t('input.editor.heading1'),
description: t('input.editor.heading1Tooltip'),
icon: 'fa-header',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -37,7 +48,7 @@ export default function suggestionSetup(t) {
title: t('input.editor.heading2'),
description: t('input.editor.heading2Tooltip'),
icon: 'fa-header',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -50,7 +61,7 @@ export default function suggestionSetup(t) {
title: t('input.editor.heading3'),
description: t('input.editor.heading3Tooltip'),
icon: 'fa-header',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -63,7 +74,7 @@ export default function suggestionSetup(t) {
title: t('input.editor.bulletList'),
description: t('input.editor.bulletListTooltip'),
icon: 'fa-list-ul',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -76,7 +87,7 @@ export default function suggestionSetup(t) {
title: t('input.editor.orderedList'),
description: t('input.editor.orderedListTooltip'),
icon: 'fa-list-ol',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -89,7 +100,7 @@ export default function suggestionSetup(t) {
title: t('input.editor.taskList'),
description: t('input.editor.taskListTooltip'),
icon: 'fa-list-check',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -102,7 +113,7 @@ export default function suggestionSetup(t) {
title: t('input.editor.quote'),
description: t('input.editor.quoteTooltip'),
icon: 'fa-quote-right',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -115,7 +126,7 @@ export default function suggestionSetup(t) {
title: t('input.editor.code'),
description: t('input.editor.codeTooltip'),
icon: 'fa-code',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -128,20 +139,23 @@ export default function suggestionSetup(t) {
title: t('input.editor.image'),
description: t('input.editor.imageTooltip'),
icon: 'fa-image',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.run()
document.getElementById('tiptap__image-upload').click()
const uploadElement = document.getElementById('tiptap__image-upload')
if (uploadElement) {
uploadElement.click()
}
},
},
{
title: t('input.editor.horizontalRule'),
description: t('input.editor.horizontalRuleTooltip'),
icon: 'fa-ruler-horizontal',
command: ({editor, range}) => {
command: ({editor, range}: {editor: Editor, range: Range}) => {
editor
.chain()
.focus()
@@ -172,7 +186,7 @@ export default function suggestionSetup(t) {
}
return {
onStart: props => {
onStart: (props: SuggestionProps) => {
component = new VueRenderer(CommandsList, {
// using vue 2:
// parent: this,
@@ -190,7 +204,7 @@ export default function suggestionSetup(t) {
popupElement.style.position = 'absolute'
popupElement.style.top = '0'
popupElement.style.left = '0'
popupElement.style.zIndex = '1000'
popupElement.style.zIndex = '4700'
popupElement.appendChild(component.element!)
document.body.appendChild(popupElement)
@@ -219,7 +233,7 @@ export default function suggestionSetup(t) {
cleanupFloating = autoUpdate(virtualReference, popupElement, updatePosition)
},
onUpdate(props) {
onUpdate(props: SuggestionProps) {
component.updateProps(props)
if (!props.clientRect || !popupElement) {
@@ -231,8 +245,12 @@ export default function suggestionSetup(t) {
virtualReference.getBoundingClientRect = () => rect
},
onKeyDown(props) {
if (props.event.key === 'Escape') {
onKeyDown(props: SuggestionProps) {
if (props.event && props.event.key === 'Escape') {
if (props.event.isComposing) {
return false
}
if (popupElement) {
popupElement.style.display = 'none'
}

View File

@@ -167,7 +167,7 @@ export default Extension.create<FilterAutocompleteOptions>({
const fetchSuggestions = async (autocompleteContext: AutocompleteContext, fieldType: AutocompleteField): Promise<SuggestionItem[]> => {
try {
if (fieldType === 'labels') {
return labelStore.filterLabelsByQuery([], autocompleteContext.search)
return labelStore.filterLabelsByQuery([], autocompleteContext.search).filter((label): label is ILabel => label !== undefined) as SuggestionItem[]
}
if (fieldType === 'assignees') {
@@ -181,9 +181,10 @@ export default Extension.create<FilterAutocompleteOptions>({
let assigneeSuggestions: SuggestionItem[] = []
try {
if (this.options.projectId) {
assigneeSuggestions = await projectUserService.getAll({projectId: this.options.projectId}, {s: autocompleteContext.search})
// @ts-expect-error - projectId is used for URL replacement but not part of IAbstract
assigneeSuggestions = await projectUserService.getAll({projectId: this.options.projectId}, {s: autocompleteContext.search}) as SuggestionItem[]
} else {
assigneeSuggestions = await userService.getAll({}, {s: autocompleteContext.search})
assigneeSuggestions = await userService.getAll({} as IUser, {s: autocompleteContext.search}) as SuggestionItem[]
}
// For assignees, show suggestions even with empty search, but limit if we have many
if (autocompleteContext.search === '' && assigneeSuggestions.length > 10) {
@@ -197,9 +198,9 @@ export default Extension.create<FilterAutocompleteOptions>({
}, 300)
})
}
if (fieldType === 'projects' && !this.options.projectId) {
return projectStore.searchProject(autocompleteContext.search)
return projectStore.searchProject(autocompleteContext.search).filter((project): project is IProject => project !== undefined) as SuggestionItem[]
}
} catch (error) {
console.error('Error fetching suggestions:', error)
@@ -257,14 +258,14 @@ export default Extension.create<FilterAutocompleteOptions>({
const pattern = new RegExp(`(${field}\\s*${FILTER_OPERATORS_REGEX}\\s*)(["']?)([^"'&|()]*)?$`, 'ig')
const match = pattern.exec(textUpToCursor)
if (match) {
const [, prefix, , , keyword = ''] = match
if (match && match.index !== undefined) {
const [, prefix = '', , , keyword = ''] = match
let search = keyword.trim()
const operator = match[0].match(new RegExp(FILTER_OPERATORS_REGEX))?.[0] || ''
if (operator === 'in' || operator === '?=') {
const keywords = keyword.split(',')
search = keywords[keywords.length - 1].trim()
search = keywords[keywords.length - 1]?.trim() ?? ''
}
// Check if this expression is complete
@@ -327,23 +328,28 @@ export default Extension.create<FilterAutocompleteOptions>({
items,
command: (item: AutocompleteItem) => {
// Handle selection
const newValue = item.fieldType === 'assignees' ? item.item.username : item.item.title
const newValue = item.fieldType === 'assignees'
? (item.item as IUser).username
: (item.item as IProject | ILabel).title
const {from} = view.state.selection
const context = autocompleteContext
if (!context) {
return
}
const operator = context.operator
let insertValue: string = newValue ?? ''
const replaceFrom = Math.max(0, from - context.search.length)
const replaceTo = from
// Handle multi-value operators
if (isMultiValueOperator(operator) && context.keyword.includes(',')) {
// For multi-value fields, we need to replace only the current search term
const keywords = context.keyword.split(',')
const currentKeywordIndex = keywords.length - 1
// If we're not adding the first item, add comma prefix
if (currentKeywordIndex > 0 && keywords[currentKeywordIndex].trim() === context.search.trim()) {
if (currentKeywordIndex > 0 && keywords[currentKeywordIndex]?.trim() === context.search.trim()) {
// We're replacing the last incomplete keyword
insertValue = newValue ?? ''
} else {
@@ -351,7 +357,7 @@ export default Extension.create<FilterAutocompleteOptions>({
insertValue = ',' + newValue
}
}
const tr = view.state.tr.replaceWith(
replaceFrom,
replaceTo,
@@ -359,6 +365,7 @@ export default Extension.create<FilterAutocompleteOptions>({
)
// Position cursor after the inserted text
const newPos = replaceFrom + insertValue.length
// @ts-expect-error - Selection.near is a static method but TypeScript doesn't recognize it on constructor
tr.setSelection(view.state.selection.constructor.near(tr.doc.resolve(newPos)))
view.dispatch(tr)

View File

@@ -78,6 +78,9 @@ function onKeyDown({event}: { event: KeyboardEvent }) {
}
if (event.key === 'Enter') {
if (event.isComposing) {
return false
}
event.preventDefault()
event.stopPropagation()
enterHandler()

View File

@@ -154,9 +154,18 @@ function setEditorContentFromModelValue(newValue: string | undefined) {
) : ''
if (editor.value.getText() !== content) {
// Preserve cursor position before updating content
const currentPosition = editor.value.state.selection.from
editor.value.commands.setContent(content, {
emitUpdate: false,
})
// Restore cursor position after content update
// Ensure position is within the new content bounds
const maxPosition = editor.value.state.doc.content.size
const safePosition = Math.min(currentPosition, maxPosition)
editor.value.commands.setTextSelection(safePosition)
}
}
@@ -227,6 +236,79 @@ defineExpose({
.filter-datepicker {
position: absolute;
}
.ProseMirror {
outline: none;
white-space: pre-wrap;
padding: 0 !important;
.field {
color: var(--code-literal);
}
.operator {
color: var(--code-keyword);
}
.value {
border-radius: $radius;
padding: .125rem .25rem;
background: var(--grey-100);
}
.label-value {
border-radius: $radius;
padding: .125rem .25rem;
}
.date-value {
background-color: var(--primary);
color: var(--white);
border-radius: $radius;
padding: 0.125em 0.25em;
cursor: pointer;
transition: background-color var(--transition);
&:hover {
background-color: var(--primary-dark);
}
}
.grouping, .logical {
color: var(--code-section);
}
.user-value {
position: relative;
padding-inline-start: 1.5em;
&::before {
content: attr(data-user);
position: absolute;
inset-inline-start: 0;
inset-block-start: 50%;
transform: translateY(-50%);
inline-size: 1.2em;
block-size: 1.2em;
background-color: #3b82f6;
color: white;
border-radius: 50%;
font-size: 0.8em;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
}
}
p.is-editor-empty:first-child::before {
color: var(--grey-500);
content: attr(data-placeholder);
float: inline-start;
block-size: 0;
pointer-events: none;
}
}
}
.editor-wrapper {
@@ -237,77 +319,4 @@ defineExpose({
line-height: 1.5;
padding: .5rem .75rem;
}
.ProseMirror {
outline: none;
white-space: pre-wrap;
padding: 0 !important;
.field {
color: var(--code-literal);
}
.operator {
color: var(--code-keyword);
}
.value {
border-radius: $radius;
padding: .125rem .25rem;
background: var(--grey-100);
}
.label-value {
border-radius: $radius;
padding: .125rem .25rem;
}
.date-value {
background-color: var(--primary);
color: var(--white);
border-radius: $radius;
padding: 0.125em 0.25em;
cursor: pointer;
transition: background-color var(--transition);
&:hover {
background-color: var(--primary-dark);
}
}
.grouping, .logical {
color: var(--code-section);
}
.user-value {
position: relative;
padding-inline-start: 1.5em;
&::before {
content: attr(data-user);
position: absolute;
inset-inline-start: 0;
inset-block-start: 50%;
transform: translateY(-50%);
inline-size: 1.2em;
block-size: 1.2em;
background-color: #3b82f6;
color: white;
border-radius: 50%;
font-size: 0.8em;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
}
}
p.is-editor-empty:first-child::before {
color: var(--grey-500);
content: attr(data-placeholder);
float: inline-start;
block-size: 0;
pointer-events: none;
}
}
</style>

View File

@@ -8,6 +8,7 @@ import {
FILTER_OPERATORS_REGEX,
getFilterFieldRegexPattern,
LABEL_FIELDS,
PROJECT_FIELDS,
} from '@/helpers/filters'
import {useLabelStore} from '@/stores/labels'
import {colorIsDark} from '@/helpers/color/colorIsDark.ts'
@@ -83,64 +84,9 @@ function decorateDocument(doc: Node) {
while ((labelMatch = pattern.exec(text)) !== null) {
const labelValue = labelMatch[4]?.trim()
const operator = labelMatch[2]?.trim()
if (labelValue) { // If there's a value
const valueStart = labelMatch.index + labelMatch[0].lastIndexOf(labelValue)
const valueEnd = valueStart + labelValue.length
const addLabelDecoration = (labelValue: string, start: number, end: number) => {
const label = labelStore.getLabelByExactTitle(labelValue)
const from = findPosForIndex(doc, start)
const to = findPosForIndex(doc, end)
if (from === null || to === null) {
return
}
valueRanges.push({start, end})
if (label) {
// Use label color if found
decorations.push(
Decoration.inline(from, to, {
class: 'label-value',
style: `background-color: ${label.hexColor}; color: ${label.hexColor && colorIsDark(label.hexColor) ? 'white' : 'black'};`,
}),
)
return
}
// Fallback to generic value styling
decorations.push(
Decoration.inline(from, to, {class: 'value'}),
)
}
// Check if this is a multi-value operator and the value contains commas
const isMultiValueOperator = ['in', '?=', 'not in', '?!='].includes(operator)
if (isMultiValueOperator && labelValue.includes(',')) {
// Split by commas and create decorations for each individual label
const labels = labelValue.split(',').map(l => l.trim()).filter(l => l.length > 0)
let currentOffset = 0
labels.forEach(individualLabel => {
// Find the position of this individual label within the full value
const labelIndex = labelValue.indexOf(individualLabel, currentOffset)
if (labelIndex !== -1) {
const individualStart = valueStart + labelIndex
const individualEnd = individualStart + individualLabel.length
addLabelDecoration(individualLabel, individualStart, individualEnd)
currentOffset = labelIndex + individualLabel.length
}
})
continue
}
addLabelDecoration(labelValue, valueStart, valueEnd)
if(!labelValue) {
continue
}
const valueStart = labelMatch.index + labelMatch[0].lastIndexOf(labelValue)
@@ -203,12 +149,70 @@ function decorateDocument(doc: Node) {
}
})
// Match other values - anything coming after an operator (excluding labels and dates)
// Handle project fields with multi-value support
PROJECT_FIELDS.forEach(projectField => {
const pattern = getFilterFieldRegexPattern(projectField)
let projectMatch
while ((projectMatch = pattern.exec(text)) !== null) {
const projectValue = projectMatch[4]?.trim()
const operator = projectMatch[2]?.trim()
if(!projectValue) {
continue
}
const valueStart = projectMatch.index + projectMatch[0].lastIndexOf(projectValue)
const valueEnd = valueStart + projectValue.length
const addProjectDecoration = (projectValue: string, start: number, end: number) => {
const from = findPosForIndex(doc, start)
const to = findPosForIndex(doc, end)
if (from === null || to === null) {
return
}
valueRanges.push({start, end})
// Use generic value styling for projects
decorations.push(
Decoration.inline(from, to, {class: 'value'}),
)
}
// Check if this is a multi-value operator and the value contains commas
const isMultiValueOperator = ['in', '?=', 'not in', '?!='].includes(operator)
if (isMultiValueOperator && projectValue.includes(',')) {
// Split by commas and create decorations for each individual project
const projects = projectValue.split(',').map(p => p.trim()).filter(p => p.length > 0)
let currentOffset = 0
projects.forEach(individualProject => {
// Find the position of this individual project within the full value
const projectIndex = projectValue.indexOf(individualProject, currentOffset)
if (projectIndex !== -1) {
const individualStart = valueStart + projectIndex
const individualEnd = individualStart + individualProject.length
addProjectDecoration(individualProject, individualStart, individualEnd)
currentOffset = projectIndex + individualProject.length
}
})
continue
}
addProjectDecoration(projectValue, valueStart, valueEnd)
}
})
// Match other values - anything coming after an operator (excluding labels, dates, and projects)
fieldValueRegex.lastIndex = 0
while ((match = fieldValueRegex.exec(text)) !== null) {
const [fullMatch, field, operator, value] = match
if (LABEL_FIELDS.includes(field) || DATE_FIELDS.includes(field)) {
if (LABEL_FIELDS.includes(field) || DATE_FIELDS.includes(field) || PROJECT_FIELDS.includes(field)) {
continue
}

View File

@@ -9,7 +9,7 @@
:shadow="false"
:padding="false"
class="has-text-start"
:loading="loading"
:loading="currentLoading"
:show-close="true"
@close="$router.back()"
>
@@ -37,8 +37,9 @@
v-if="hasPrimaryAction"
variant="primary"
:icon="primaryIcon"
:disabled="primaryDisabled || loading"
:disabled="isBusy"
class="mis-2"
:loading="currentLoading"
@click.prevent.stop="primary"
>
{{ primaryLabel || $t('misc.create') }}
@@ -52,7 +53,9 @@
<script setup lang="ts">
import type {IconProp} from '@fortawesome/fontawesome-svg-core'
withDefaults(defineProps<{
import {computed, ref, toRef, watch} from 'vue'
const props = withDefaults(defineProps<{
title: string,
primaryLabel?: string,
primaryIcon?: IconProp,
@@ -60,7 +63,7 @@ withDefaults(defineProps<{
hasPrimaryAction?: boolean,
tertiary?: string,
wide?: boolean,
loading?: boolean
loading?: boolean,
}>(), {
primaryLabel: '',
primaryIcon: 'plus',
@@ -68,17 +71,42 @@ withDefaults(defineProps<{
hasPrimaryAction: true,
tertiary: '',
wide: false,
loading: false,
})
const emit = defineEmits<{
'create': [event: MouseEvent],
'primary': [event: MouseEvent],
'tertiary': [event: MouseEvent]
'tertiary': [event: MouseEvent],
'update:loading': [value: boolean],
}>()
const loadingProp = toRef(props, 'loading')
const currentLoading = ref(false)
watch(
loadingProp,
(value) => {
if (value !== undefined) {
currentLoading.value = value
}
},
{immediate: true},
)
const isBusy = computed(() => props.primaryDisabled || currentLoading.value)
function setLoading(value: boolean) {
currentLoading.value = value
emit('update:loading', value)
}
function primary(event: MouseEvent) {
if (isBusy.value) {
return
}
emit('create', event)
emit('primary', event)
setLoading(true)
}
</script>

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