Compare commits

..

42 Commits

Author SHA1 Message Date
renovate a9a0bd5d25 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is failing Details
2024-04-16 01:07:18 +00:00
kolaente ecbefdb921
fix(buckets): return correct task count for tasks in buckets
continuous-integration/drone/push Build is passing Details
2024-04-14 17:21:53 +02:00
kolaente d8ca1a2de1
fix(favorites): make favorites work with configurable views
continuous-integration/drone/push Build is failing Details
2024-04-14 17:12:16 +02:00
kolaente 2d084c091e
feat: new login image
continuous-integration/drone/push Build is passing Details
2024-04-14 12:43:22 +02:00
kolaente 5a84d37fca
fix(kanban): do not focus on task list in bucket when clicking on a task
continuous-integration/drone/push Build is passing Details
2024-04-14 11:21:59 +02:00
kolaente fd520dab0a
fix(reminders): do not fall back to hours when the reminder interval is minutes
Resolves https://github.com/go-vikunja/vikunja/issues/225
2024-04-14 11:20:20 +02:00
kolaente 144a6e4140
fix(kanban): do not add bottom spacing to view
continuous-integration/drone/push Build is failing Details
2024-04-14 11:15:53 +02:00
kolaente a7aa74227a
fix(kanban): do not focus kanban board
continuous-integration/drone/push Build is failing Details
2024-04-14 11:12:26 +02:00
kolaente d2adbc53c6
fix(test): add task to bucket in test
continuous-integration/drone/push Build is failing Details
2024-04-14 11:00:41 +02:00
kolaente 422e4371f8
fix(project): add more spacing between filter button and view switcher on mobile
continuous-integration/drone/push Build is failing Details
2024-04-14 00:06:26 +02:00
kolaente 6e5b31f1e0
fix(filters): always persist filter or search in query path and load it correctly into filter query input when loading the page
continuous-integration/drone/push Build is failing Details
Previously, when using the filter query as a search input, it would load the search as requested but the filter query parameter in the url would be empty, which meant the search would not be loaded correctly when reloading (or otherwise newly accessing) the page. We're now persisting the filter and search in the task loading logic, to make sure they are always populated correctly.
2024-04-13 23:34:25 +02:00
kolaente 5756da412b
fix(project): return full project after duplicating it
continuous-integration/drone/push Build is failing Details
2024-04-13 22:39:40 +02:00
kolaente 4e05b8e97c
fix(project): do not crash when duplicating a project with no tasks
continuous-integration/drone/push Build is failing Details
2024-04-13 22:36:41 +02:00
kolaente 5177f516c4
fix(views): make sure view changes are reflected in switcher
continuous-integration/drone/push Build is failing Details
2024-04-13 22:24:12 +02:00
kolaente 637c8f6ba5
fix(views): make sure the view is saved properly in localStorage
continuous-integration/drone/push Build is failing Details
2024-04-13 22:15:41 +02:00
kolaente 1460d212ee
fix: do not push nil errors to sentry
continuous-integration/drone/push Build is failing Details
2024-04-13 21:46:07 +02:00
kolaente e9de7d8a24
fix(project): delete all related entities when deleting a project
continuous-integration/drone/push Build is failing Details
2024-04-13 21:43:44 +02:00
kolaente ce1d7778c7
fix(export): make export work with project views and new task positions
continuous-integration/drone/push Build is passing Details
2024-04-13 21:07:06 +02:00
kolaente 9a16f6f817
fix: license in cmd help text
continuous-integration/drone/push Build is passing Details
2024-04-13 20:13:24 +02:00
kolaente 7d755fcb89
fix: lint
continuous-integration/drone/push Build is passing Details
2024-04-13 17:58:53 +02:00
kolaente 77e95642a9
fix(tasks): make fetching tasks in buckets via typesense work
continuous-integration/drone/push Build is failing Details
2024-04-13 17:52:47 +02:00
kolaente a5d02380a3
fix(typesense): make fetching task positions per view more efficient
continuous-integration/drone/push Build is failing Details
2024-04-13 17:26:38 +02:00
kolaente 3519b8b2fe
fix(tasks): index and order by task position when using typesense 2024-04-13 17:19:27 +02:00
kolaente cb648e5ad8
fix(typesense): fix reindexing views and positions in typesense 2024-04-13 16:38:45 +02:00
kolaente 75f830457b
fix(comments): order comments by created timestamp instead of id
continuous-integration/drone/push Build is failing Details
Partially resolves https://community.vikunja.io/t/trello-import-comments-and-assignments/2174/14
2024-04-13 14:45:12 +02:00
kolaente 6e2b540394
fix(migration): import task comments with original timestamps
Partially resolves https://community.vikunja.io/t/trello-import-comments-and-assignments/2174/14
2024-04-13 14:44:55 +02:00
kolaente bf3c8ac9da
fix(views): check if bucket index already exists before creating new index
continuous-integration/drone/push Build is failing Details
Resolves #2243
2024-04-13 14:20:27 +02:00
kolaente 3e7225ebee
fix(editor): do not prevent shift+enter to add a line break in text
continuous-integration/drone/push Build is failing Details
Resolves https://github.com/go-vikunja/vikunja/issues/250
2024-04-13 14:08:27 +02:00
kolaente 9eb19e0362
fix(project): do not crash when views were not loaded yet
continuous-integration/drone/push Build is passing Details
The project view crashed when accessing a task from /projects because the currentProject in store was not set, hence the views weren't set either. This change adds a fallback to it.

Related to #2246
Related to https://community.vikunja.io/t/vikunja-freezes/2246/5
2024-04-13 13:18:14 +02:00
kolaente 73bf119409
docs: clarify version checkout when building from source
continuous-integration/drone/push Build is passing Details
Related to #2270 (comment)
2024-04-12 23:39:27 +02:00
kolaente 500b761fe6
fix(projects): do not return parent project id when authenticating as link share
continuous-integration/drone/push Build is passing Details
Related to https://community.vikunja.io/t/vikunja-freezes/2246
Related to https://github.com/go-vikunja/vikunja/issues/233
2024-04-12 18:02:39 +02:00
kolaente 0bc9a670d7
fix(task): do not crash when loading a task if parent projects are not loaded
continuous-integration/drone/push Build is failing Details
Related to https://community.vikunja.io/t/vikunja-freezes/2246
Related to https://github.com/go-vikunja/vikunja/issues/233
2024-04-12 17:56:19 +02:00
renovate a3e5e98c64 fix(deps): update module github.com/arran4/golang-ical to v0.2.8
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-11 16:07:49 +00:00
Elscrux a3a4d05e89 feat(editor): checklist visual improvements (#2264)
continuous-integration/drone/push Build is passing Details
This makes task lists (especially big ones) easier to read. I've set a margin so there is a distance between task items which makes them easier to stand out.
I've also changed the visuals of the checked elements (strike through + grey font color) so the unchecked ones stand out more. Note that this currently seems to be a big bugged outside of edit mode as `data-checked` doesn't seem to be updating correctly in this state which seems to be an issue that is already noted for the TipTap editor.

Co-authored-by: Elscrux <nickposer2102@gmail.com>
Reviewed-on: #2264
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Elscrux <elscrux@gmail.com>
Co-committed-by: Elscrux <elscrux@gmail.com>
2024-04-11 15:46:10 +00:00
renovate 72c3e1a03f chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build encountered an error Details
2024-04-11 00:09:10 +00:00
Elscrux 61ee0bd5e2 feat(migration): include non upload attachments from Trello (#2261)
continuous-integration/drone/push Build is passing Details
This makes the Trello migrator include attachments that are not file uploads. To include them in Vikunja without missing data, their text (usually links) will be appended to the Vikunja description.

Co-authored-by: Elscrux <nickposer2102@gmail.com>
Reviewed-on: #2261
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Elscrux <elscrux@gmail.com>
Co-committed-by: Elscrux <elscrux@gmail.com>
2024-04-10 22:12:06 +00:00
kolaente 423558f58a
fix(migration): invalid field in organization struct
continuous-integration/drone/push Build is failing Details
2024-04-10 23:52:10 +02:00
kolaente 75fd17c750
docs: clarify vikunja cli usage in docker
continuous-integration/drone/push Build is passing Details
2024-04-10 23:05:45 +02:00
kolaente 4e49ec9e16
docs: clarify automatic openid team creation 2024-04-10 23:05:45 +02:00
renovate 58e0ec3d35 fix(deps): update tiptap to v2.3.0
continuous-integration/drone/push Build is failing Details
2024-04-10 20:59:51 +00:00
kolaente ed4be389ab
fix(navigation): scrolling when many projects are present
continuous-integration/drone/push Build is failing Details
Regression from ee3d20e1d2
Resolves https://github.com/go-vikunja/vikunja/issues/249
2024-04-10 22:54:36 +02:00
renovate cb2c2eeae8 fix(deps): update dependency vue-i18n to v9.11.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-10 17:06:05 +00:00
42 changed files with 893 additions and 451 deletions

View File

@ -104,3 +104,7 @@ issues:
text: "parameter 'tx' seems to be unused, consider removing or renaming it as"
linters:
- revive
- path: pkg/models/typesense.go
text: 'structtag: struct field Position repeats json tag "position" also at'
linters:
- govet

View File

@ -51,7 +51,7 @@
}
},
"devDependencies": {
"electron": "29.2.0",
"electron": "30.0.0",
"electron-builder": "24.13.3"
},
"dependencies": {

View File

@ -769,10 +769,10 @@ electron-publish@24.13.1:
lazy-val "^1.0.5"
mime "^2.5.2"
electron@29.2.0:
version "29.2.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.2.0.tgz#98e9d45dcebda124fb0bd1ff20fc509ec692101c"
integrity sha512-ALKrCN52RG4g9prx4DriXSPnY5WoiyRUCNp7zEVQuoiNOpHTNqMMpRidQAHzntV4hajF1LMWHVoBkwqIs1jHhg==
electron@30.0.0:
version "30.0.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-30.0.0.tgz#6b72a27dcc46759fac5f12e147ef64554e596391"
integrity sha512-GRwKphq/TUhSlb44OwSckXKl50f5OR/pm9MvF3rBLyqcxwfu7L11xejrZ0hDea1eKyCkzGd4B+cIqaQiDguPEA==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^20.9.0"

View File

@ -18,6 +18,7 @@ To fully build Vikunja from source files, you need to build the api and frontend
1. Make sure you have git installed
2. Clone the repo with `git clone https://code.vikunja.io/vikunja` and switch into the directory.
3. Check out the version you want to build with `git checkout VERSION` - replace `VERSION` with the version want to use. If you don't do this, you'll build the [latest unstable build]({{< ref "versions.md">}}), which might contain bugs.
## Frontend

View File

@ -225,10 +225,13 @@ go install github.com/magefile/mage
```
mkdir /mnt/GO/code.vikunja.io
cd /mnt/GO/code.vikunja.io
git clone https://code.vikunja.io/api
cd /mnt/GO/code.vikunja.io/api
git clone https://code.vikunja.io/vikunja
cd vikunja
```
**Note:** Ceck out the version you want to build with `git checkout VERSION` - replace `VERSION` with the version want to use.
If you don't do this, you'll build the [latest unstable build]({{< ref "versions.md">}}), which might contain bugs.
### Compile binaries
```

View File

@ -103,7 +103,7 @@ auth:
## Automatically assign users to teams
Vikunja is capable of automatically adding users to a team based on OIDC claims added by the identity provider.
Starting with version 0.24.0, Vikunja is capable of automatically adding users to a team based on OIDC claims added by the identity provider.
If configured, Vikunja will sync teams, automatically create new ones and make sure the members are part of the configured teams.
Teams which exist only because they were created from oidc attributes are not editable in Vikunja.

View File

@ -28,13 +28,21 @@ All commands use the same standard [config file]({{< ref "../setup/config.md">}}
## Using the cli in docker
When running Vikunja in docker, you'll need to execute all commands in the `api` container.
When running Vikunja in docker, you'll need to execute all commands in the `vikunja` container.
Instead of running the `vikunja` binary directly, run it like this:
```sh
docker exec <name of the vikunja api container> /app/vikunja/vikunja <subcommand>
docker exec <name of the vikunja container> /app/vikunja/vikunja <subcommand>
```
If you need to run a bunch of Vikunja commands, you can also create a shell alias for it:
```sh
alias vikunja-docker='docker exec <name of the vikunja container> /app/vikunja/vikunja'
```
Then use it as `vikunja-docker <subcommand>`.
### `dump`
Creates a zip file with all vikunja-related files.

View File

@ -13,6 +13,7 @@ import {BucketFactory} from '../../factories/bucket'
import {TaskAttachmentFactory} from '../../factories/task_attachments'
import {TaskReminderFactory} from '../../factories/task_reminders'
import {createDefaultViews} from "../project/prepareProjects";
import { TaskBucketFactory } from '../../factories/task_buckets'
function addLabelToTaskAndVerify(labelTitle: string) {
cy.get('.task-view .action-buttons .button')
@ -48,7 +49,7 @@ function uploadAttachmentAndVerify(taskId: number) {
describe('Task', () => {
createFakeUserAndLogin()
let projects
let projects: {}[]
let buckets
beforeEach(() => {
@ -470,6 +471,10 @@ describe('Task', () => {
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
TaskBucketFactory.create(1, {
task_id: tasks[0].id,
bucket_id: buckets[0].id,
})
cy.visit(`/projects/${projects[0].id}/4`)

View File

@ -60,39 +60,39 @@
"@kyvg/vue3-notification": "3.2.1",
"@sentry/tracing": "7.109.0",
"@sentry/vue": "7.109.0",
"@tiptap/core": "2.2.6",
"@tiptap/extension-blockquote": "2.2.6",
"@tiptap/extension-bold": "2.2.6",
"@tiptap/extension-bullet-list": "2.2.6",
"@tiptap/extension-code": "2.2.6",
"@tiptap/extension-code-block-lowlight": "2.2.6",
"@tiptap/extension-document": "2.2.6",
"@tiptap/extension-dropcursor": "2.2.6",
"@tiptap/extension-gapcursor": "2.2.6",
"@tiptap/extension-hard-break": "2.2.6",
"@tiptap/extension-heading": "2.2.6",
"@tiptap/extension-history": "2.2.6",
"@tiptap/extension-horizontal-rule": "2.2.6",
"@tiptap/extension-image": "2.2.6",
"@tiptap/extension-italic": "2.2.6",
"@tiptap/extension-link": "2.2.6",
"@tiptap/extension-list-item": "2.2.6",
"@tiptap/extension-ordered-list": "2.2.6",
"@tiptap/extension-paragraph": "2.2.6",
"@tiptap/extension-placeholder": "2.2.6",
"@tiptap/extension-strike": "2.2.6",
"@tiptap/extension-table": "2.2.6",
"@tiptap/extension-table-cell": "2.2.6",
"@tiptap/extension-table-header": "2.2.6",
"@tiptap/extension-table-row": "2.2.6",
"@tiptap/extension-task-item": "2.2.6",
"@tiptap/extension-task-list": "2.2.6",
"@tiptap/extension-text": "2.2.6",
"@tiptap/extension-typography": "2.2.6",
"@tiptap/extension-underline": "2.2.6",
"@tiptap/pm": "2.2.6",
"@tiptap/suggestion": "2.2.6",
"@tiptap/vue-3": "2.2.6",
"@tiptap/core": "2.3.0",
"@tiptap/extension-blockquote": "2.3.0",
"@tiptap/extension-bold": "2.3.0",
"@tiptap/extension-bullet-list": "2.3.0",
"@tiptap/extension-code": "2.3.0",
"@tiptap/extension-code-block-lowlight": "2.3.0",
"@tiptap/extension-document": "2.3.0",
"@tiptap/extension-dropcursor": "2.3.0",
"@tiptap/extension-gapcursor": "2.3.0",
"@tiptap/extension-hard-break": "2.3.0",
"@tiptap/extension-heading": "2.3.0",
"@tiptap/extension-history": "2.3.0",
"@tiptap/extension-horizontal-rule": "2.3.0",
"@tiptap/extension-image": "2.3.0",
"@tiptap/extension-italic": "2.3.0",
"@tiptap/extension-link": "2.3.0",
"@tiptap/extension-list-item": "2.3.0",
"@tiptap/extension-ordered-list": "2.3.0",
"@tiptap/extension-paragraph": "2.3.0",
"@tiptap/extension-placeholder": "2.3.0",
"@tiptap/extension-strike": "2.3.0",
"@tiptap/extension-table": "2.3.0",
"@tiptap/extension-table-cell": "2.3.0",
"@tiptap/extension-table-header": "2.3.0",
"@tiptap/extension-table-row": "2.3.0",
"@tiptap/extension-task-item": "2.3.0",
"@tiptap/extension-task-list": "2.3.0",
"@tiptap/extension-text": "2.3.0",
"@tiptap/extension-typography": "2.3.0",
"@tiptap/extension-underline": "2.3.0",
"@tiptap/pm": "2.3.0",
"@tiptap/suggestion": "2.3.0",
"@tiptap/vue-3": "2.3.0",
"@types/is-touch-device": "1.0.2",
"@types/lodash.clonedeep": "4.5.9",
"@vueuse/core": "10.9.0",
@ -121,7 +121,7 @@
"vue": "3.4.21",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "9.11.0",
"vue-i18n": "9.11.1",
"vue-router": "4.3.0",
"vuemoji-picker": "0.2.1",
"workbox-precaching": "7.0.0",
@ -134,7 +134,7 @@
"@faker-js/faker": "8.4.1",
"@histoire/plugin-screenshot": "0.17.17",
"@histoire/plugin-vue": "0.17.17",
"@rushstack/eslint-patch": "1.10.1",
"@rushstack/eslint-patch": "1.10.2",
"@tsconfig/node18": "18.2.4",
"@types/codemirror": "5.60.15",
"@types/dompurify": "3.0.5",

View File

@ -33,7 +33,7 @@ dependencies:
version: 2.3.2(dayjs@1.11.10)(vue@3.4.21)
'@intlify/unplugin-vue-i18n':
specifier: 3.0.1
version: 3.0.1(rollup@4.14.1)(vue-i18n@9.11.0)
version: 3.0.1(rollup@4.14.1)(vue-i18n@9.11.1)
'@kyvg/vue3-notification':
specifier: 3.2.1
version: 3.2.1(vue@3.4.21)
@ -44,104 +44,104 @@ dependencies:
specifier: 7.109.0
version: 7.109.0(vue@3.4.21)
'@tiptap/core':
specifier: 2.2.6
version: 2.2.6(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/extension-blockquote':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-bold':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-bullet-list':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-code':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-code-block-lowlight':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/extension-code-block@2.1.12)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/extension-code-block@2.1.12)(@tiptap/pm@2.3.0)
'@tiptap/extension-document':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-dropcursor':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-gapcursor':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-hard-break':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-heading':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-history':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-horizontal-rule':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-image':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-italic':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-link':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-list-item':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-ordered-list':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-paragraph':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-placeholder':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-strike':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-table':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-table-cell':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-table-header':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-table-row':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-task-item':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-task-list':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-text':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-typography':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/extension-underline':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)
'@tiptap/pm':
specifier: 2.2.6
version: 2.2.6
specifier: 2.3.0
version: 2.3.0
'@tiptap/suggestion':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/vue-3':
specifier: 2.2.6
version: 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)(vue@3.4.21)
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)(vue@3.4.21)
'@types/is-touch-device':
specifier: 1.0.2
version: 1.0.2
@ -227,8 +227,8 @@ dependencies:
specifier: 11.0.5
version: 11.0.5(vue@3.4.21)
vue-i18n:
specifier: 9.11.0
version: 9.11.0(vue@3.4.21)
specifier: 9.11.1
version: 9.11.1(vue@3.4.21)
vue-router:
specifier: 4.3.0
version: 4.3.0(vue@3.4.21)
@ -262,8 +262,8 @@ devDependencies:
specifier: 0.17.17
version: 0.17.17(histoire@0.17.17)(vite@5.2.8)(vue@3.4.21)
'@rushstack/eslint-patch':
specifier: 1.10.1
version: 1.10.1
specifier: 1.10.2
version: 1.10.2
'@tsconfig/node18':
specifier: 18.2.4
version: 18.2.4
@ -2608,7 +2608,7 @@ packages:
- '@vue/composition-api'
dev: false
/@intlify/bundle-utils@7.4.0(vue-i18n@9.11.0):
/@intlify/bundle-utils@7.4.0(vue-i18n@9.11.1):
resolution: {integrity: sha512-AQfjBe2HUxzyN8ignIk3WhhSuVcSuirgzOzkd17nb337rCbI4Gv/t1R60UUyIqFoFdviLb/wLcDUzTD/xXjv9w==}
engines: {node: '>= 14.16'}
peerDependencies:
@ -2621,7 +2621,7 @@ packages:
optional: true
dependencies:
'@intlify/message-compiler': 9.10.1
'@intlify/shared': 9.10.1
'@intlify/shared': 9.11.0
acorn: 8.11.2
escodegen: 2.0.0
estree-walker: 2.0.2
@ -2629,16 +2629,16 @@ packages:
magic-string: 0.30.7
mlly: 1.4.2
source-map-js: 1.2.0
vue-i18n: 9.11.0(vue@3.4.21)
vue-i18n: 9.11.1(vue@3.4.21)
yaml-eslint-parser: 1.2.2
dev: false
/@intlify/core-base@9.11.0:
resolution: {integrity: sha512-cveOqAstjLZIiyatcP/HrzrQ87cZI8ScPQna3yvoM8zjcjcIRK1MRvmxUNlPdg0rTNJMZw7rixPVM58O5aHVPA==}
/@intlify/core-base@9.11.1:
resolution: {integrity: sha512-qWXBBlEA+DC0CsHkfJiQK9ELm11c9I6lDpodY4FoOf99eMas1R6JR4woPhrfAcrtxFHp1UmXWdrQNKDegSW9IA==}
engines: {node: '>= 16'}
dependencies:
'@intlify/message-compiler': 9.11.0
'@intlify/shared': 9.11.0
'@intlify/message-compiler': 9.11.1
'@intlify/shared': 9.11.1
dev: false
/@intlify/message-compiler@9.10.1:
@ -2649,11 +2649,11 @@ packages:
source-map-js: 1.2.0
dev: false
/@intlify/message-compiler@9.11.0:
resolution: {integrity: sha512-x31Gl7cscnoI4UUY1yaIy8e7vVMVW1VVlTXZz4SIHKqoSEUkfmgqK8NAx1e7RcoHEbICR7uyCbud0ZL1s4OGXQ==}
/@intlify/message-compiler@9.11.1:
resolution: {integrity: sha512-y/aWx7DkaTKK2qWUw0hVbJpon8+urWXngeqh15DuIXZh6n/V/oPQiO/Ho1hUKbwap6MVMuz0OcnAJvqh3p9YPg==}
engines: {node: '>= 16'}
dependencies:
'@intlify/shared': 9.11.0
'@intlify/shared': 9.11.1
source-map-js: 1.2.0
dev: false
@ -2667,7 +2667,12 @@ packages:
engines: {node: '>= 16'}
dev: false
/@intlify/unplugin-vue-i18n@3.0.1(rollup@4.14.1)(vue-i18n@9.11.0):
/@intlify/shared@9.11.1:
resolution: {integrity: sha512-yuDG82vjgId2oasNRgZ0PKJrF65zlL33MNyITP5itbLcP4AYOR/NcIuD+/DiI+GHXdxASMKJU0ZiITLc6RC+qw==}
engines: {node: '>= 16'}
dev: false
/@intlify/unplugin-vue-i18n@3.0.1(rollup@4.14.1)(vue-i18n@9.11.1):
resolution: {integrity: sha512-q1zJhA/WpoLBzAAuKA5/AEp0e+bMOM10ll/HxT4g1VAw/9JhC4TTobP9KobKH90JMZ4U2daLFlYQfKNd29lpqw==}
engines: {node: '>= 14.16'}
peerDependencies:
@ -2682,7 +2687,7 @@ packages:
vue-i18n-bridge:
optional: true
dependencies:
'@intlify/bundle-utils': 7.4.0(vue-i18n@9.11.0)
'@intlify/bundle-utils': 7.4.0(vue-i18n@9.11.1)
'@intlify/shared': 9.10.1
'@rollup/pluginutils': 5.1.0(rollup@4.14.1)
'@vue/compiler-sfc': 3.4.21
@ -2694,7 +2699,7 @@ packages:
picocolors: 1.0.0
source-map-js: 1.0.2
unplugin: 1.1.0
vue-i18n: 9.11.0(vue@3.4.21)
vue-i18n: 9.11.1(vue@3.4.21)
transitivePeerDependencies:
- rollup
- supports-color
@ -3043,8 +3048,8 @@ packages:
requiresBuild: true
optional: true
/@rushstack/eslint-patch@1.10.1:
resolution: {integrity: sha512-S3Kq8e7LqxkA9s7HKLqXGTGck1uwis5vAXan3FnU5yw1Ec5hsSGnq4s/UCaSqABPOnOTg7zASLyst7+ohgWexg==}
/@rushstack/eslint-patch@1.10.2:
resolution: {integrity: sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==}
dev: true
/@sentry-internal/feedback@7.109.0:
@ -3193,301 +3198,301 @@ packages:
defer-to-connect: 1.1.3
dev: true
/@tiptap/core@2.2.6(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-v7S7RhQhTXQo9KSk2jM/jJlTd3clU2FsJA3Omjz7GbgYtPSy67qSiaTbH/tWq12GzDHbKymx+oQnKmyx+yPucA==}
/@tiptap/core@2.3.0(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-Gk2JN3i5CMkYGmsbyFI7cBUftWa+F7QYmeCLTWfbuy+hCM2OBsnYVKxhggFPGXRL5KLBEgBWeCeWMHfIw3B2MA==}
peerDependencies:
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/pm': 2.2.6
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-blockquote@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-Qoq4Tl4wyEGfuBrMFth5hWP1SroJtgDYPnyzAZeLiGzF3Yxtu7FFqjGtD1/Bos9ftnFVCAj+nIXnuKsM1YUaGg==}
/@tiptap/extension-blockquote@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-Cztt77t7f+f0fuPy+FWUL8rKTIpcdsVT0z0zYQFFafvGaom0ZALQSOdTR/q+Kle9I4DaCMO3/Q0mwax/D4k4+A==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-bold@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-PI/jNH7rmi6hBvWy/z+3KUTYqeaDXBUjidM74gWP6OLV28HTJ5SkIPCriYe4u2j2Wc/nk3gPxs4/hPOAu/YiXA==}
/@tiptap/extension-bold@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-SzkbJibHXFNU7TRaAebTtwbXUEhGZ8+MhlBn12aQ4QhdjNtFpQwKXQPyYeDyZGcyiOFgtFTb+WIfCGm8ZX0Fpw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-bubble-menu@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-nRWxbgkInhdGUL+e6iISgALcWh8A1PxeVB66w7yYZHS/WoZO0DXdXYT/BWb/XmEJ8r6B4c9SDZRklCiXT8dSXw==}
/@tiptap/extension-bubble-menu@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-dqyfQ8idTlhapvt0fxCGvkyjw92pBEwPqmkJ01h3EE8wTh53j0ytOHyMSf1KBuzardxpd8Yya3zlrAcR0Z3DlQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
tippy.js: 6.3.7
dev: false
/@tiptap/extension-bullet-list@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-bSrmYlWfj/bXXoBMVB+gCTlsficVVzWi1jcAjAn+qNAENkhampmlFIUG4DiKGYtn18ZoTbyLgQGDMCO3SBdeDQ==}
/@tiptap/extension-bullet-list@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-4nU4vJ5FjRDLqHm085vYAkuo68UK84Wl6CDSjm7sPVcu0FvQX02Okqt65azoSYQeS1SSSd5qq9YZuGWcYdp4Cw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-code-block-lowlight@2.2.6(@tiptap/core@2.2.6)(@tiptap/extension-code-block@2.1.12)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-W/8C5nIwgGLvxjc+PfnCcWkfrUuJsIKjyZGXmq1hVXTTVA9eVGbS7m1YB/fsYTEg1ccwoM2JjKO9yuKCeR2xiQ==}
/@tiptap/extension-code-block-lowlight@2.3.0(@tiptap/core@2.3.0)(@tiptap/extension-code-block@2.1.12)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-xMxWr/Fvv0hnN+u+6SW0OI3RVan+C6nJDU8xKh2Tx2DlBXJ0yODmq5v8WJJpW38AbaLkFuJuY/OA3AZ6n9pNbg==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/extension-code-block': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/extension-code-block': 2.1.12(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/extension-code-block': 2.1.12(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-code-block@2.1.12(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
/@tiptap/extension-code-block@2.1.12(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-RXtSYCVsnk8D+K80uNZShClfZjvv1EgO42JlXLVGWQdIgaNyuOv/6I/Jdf+ZzhnpsBnHufW+6TJjwP5vJPSPHA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-code@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-UGsSFvVWrWWWQFU4atk+b/qeewTLadOZG/BHZXQDloyP5eJ1SkgUVy9nv3y2cT8QWRbvF6sxkV+SdFoWnvaG3Q==}
/@tiptap/extension-code@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-O2FZmosiIRoVbW82fZy8xW4h4gb2xAzxWzHEcsHPlwCbE3vYvcBMmbkQ5p+33eRtuRQInzl3Q/cwupv9ctIepQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-document@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-yT9m5Oo9U/xAypcylaLiDE8qmVd3SCZSc8s5lqyC1OW+psb1oC0d14+TgKetO2s8K2wAbW2DxYG3yoxWffGYsQ==}
/@tiptap/extension-document@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-WC55SMrtlsNOnHXpzbXDzJOp7eKmZV0rXooKmvCDqoiLO/DKpyQXyF+0UHfcRPmUAi2GWFPaer7+p1H9xzcjXg==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-dropcursor@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-mCeIbbfe4rl8CuxVQvT7iYSKGVX/ls1LOwALwlHJz5Uw5l3VknAJdjEmHt6hNFdHu162JivL02Il0QYQ8BZwvA==}
/@tiptap/extension-dropcursor@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-WWxxGQPWdbzxyYP6jtBYSq4wMRhINhI0wBC8pgkxTVwCIWftMuYj++FP4LLIpuWgj78PWApuoM0QQxk4Lj7FOw==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-floating-menu@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-6ONKC6Dx8zCc5YffXpnQ9FxGRoUp5Jm9mOO3losgiDFhdJqaO7SCk1ziOiD7enoWqIc2shZh8ADnqttCfnFVFQ==}
/@tiptap/extension-floating-menu@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-bNY43/yU/+wGfmk2eDV7EPDAN/akbC+YnSKTA5VPJADzscvlrL2HlQrxbd/STIdlwKqdPU5MokcvCChhfZ4f6w==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
tippy.js: 6.3.7
dev: false
/@tiptap/extension-gapcursor@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-HDYu+FmL9V+khsiT5904Dy2qG6KrAvnXEjZk1+vVul0TabnQvl2rqHjTxmev3P1rOYTgePmaWXazxAWFIvbMBQ==}
/@tiptap/extension-gapcursor@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-OxcXcfD0uzNcXdXu2ZpXFAtXIsgK2MBHvFUs0t0gxtcL/t43pTOQBLy+29Ei30BxpwLghtX8jQ6IDzMiybq/sA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-hard-break@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-gwavC76sn26XQLyDaDtf28KIcbhMYPP+C5pkbRvAhVSckQB3Ebz3GRttVbm/jp+Uifp3bmoQEzISGCONEdKQoQ==}
/@tiptap/extension-hard-break@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-9pXi69SzLabbjY5KZ54UKzu7HAHTla9aYZKH56VatOAiJOPKJppFbU2/NfJwGzDrEtfOiDqr3dYbUDF3RuCFoQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-heading@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-XOmY+uezm42xSO1ero2bRBMdQxWytpxLJS+2shK0QogZ3sDplnfWfP5KV9Z2juXjTdPgPWG0ZaHzIIaLquEcfA==}
/@tiptap/extension-heading@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-YcZoUYfqb0nohoPgem4f8mjn5OqDomFrbJiC9VRHUOCIuEu+aJEYwp8mmdkLnS3f+LRCZ6G76cJJ50lkzSAZRw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-history@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-c2Aeozc+pHcpqghLjXRX/tGU/C+Gp6hApUWPXdhZw5Y/ARj6ZRwx2/ym2K8MOrJ36/W7gc7Xyxd9ZbG7m7pcjA==}
/@tiptap/extension-history@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-EF5Oq9fe/VBzU1Lsow2ubOlx1e1r4OQT1WUPGsRnL7pr94GH1Skpk7/hs9COJ9K6kP3Ebt42XjP0JEQodR58YA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-horizontal-rule@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-zyLU+Xlk8y3yBCblE8pFwqAP2Rju1csyAu45hi3NCJ6HDGQGdjy8oh+Xa8y2kTPxRNMZARxqB+vCiEoW3YZn2A==}
/@tiptap/extension-horizontal-rule@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-4DB8GU3uuDzzyqUmONIb3CHXcQ6Nuy4mHHkFSmUyEjg1i5eMQU5H7S6mNvZbltcJB2ImgCSwSMlj1kVN3MLIPg==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-image@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-MoDVjvi0AgYYSY9QR3ff2TOKk9IVVfh+BInmLCrwejSE2q8N3p/vSI+N1GKLEfW9mqn1zdI95ev17Z12Avwv7A==}
/@tiptap/extension-image@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-v1fLEEzrfXWavsLFUEkTiYYxwm1WDNrjuUriU5tG2Jv22NL1BL4BLVbZbGdkAk+qHWy8QWszrDJbcgGh2VNCoQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-italic@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-wB+Y6p2gbc1f2hKYeGNXRQ7P2xi3+JzD3PjSyC9Ss/yyujZhxSOtxBF0nzFXdI+7nmN0Qm4inwPDU/DVrIPb+A==}
/@tiptap/extension-italic@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-jdFjLjdt5JtPlGMpoS6TEq5rznjbAYVlPwcw5VkYENVIYIGIR1ylIw2JwK1nUEsQ+OgYwVxHLejcUXWG1dCi2g==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-link@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-Jj0oXSfQ8gZlzzwd669B8sEKBkoK8xV31Lu55tRv9PKHSU6p9CUqBuxY8qR+cquCtO28f3u0cdl5o4HzeIUL5A==}
/@tiptap/extension-link@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-CnJAlV0ZOdEhKmDfYKuHJVG8g79iCFQ85cX/CROTWyuMfXz9uhj2rLpZ6nfidVbonqxAhQp7NAIr2y+Fj5/53A==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
linkifyjs: 4.1.1
dev: false
/@tiptap/extension-list-item@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-3xig1q0jtOyV49TkAbvxBoOJdNypwq6vLYerfblhj6dK+hIIZUM33S+SmGl2+QaB25VwyeSHjiCvrJjB9PKWHQ==}
/@tiptap/extension-list-item@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-mHU+IuRa56OT6YCtxf5Z7OSUrbWdKhGCEX7RTrteDVs5oMB6W3oF9j88M5qQmZ1WDcxvQhAOoXctnMt6eX9zcA==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-ordered-list@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-h4HOv+TAMnoueh3CzUY2/Pp2n8eCdEQtKSfiMtHSO3NTTSlst0XEvq+3Z4K81F+ni3baXc+JUALP5dRVpI4apQ==}
/@tiptap/extension-ordered-list@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-gkf0tltXjlUj0cqyfDV2r7xy9YPKtcVSWwlCPun6OOi0KzKFiAMqQpA9hy2W6gJ+KCp8+KNRMClZOfH4TnnBfg==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-paragraph@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-M2rM3pfzziUb7xS9x2dANCokO89okbqg5IqU4VPkZhk0Mfq9czyCatt58TYkAsE3ccsGhdTYtFBTDeKBtsHUqg==}
/@tiptap/extension-paragraph@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-peCpA7DFqkd0cHb+cHv4YHNoMsXG8tKFNJlCHpLmsZWl2hWmpKgKmUrXAUfzjcFSvkZxn0xYc5oWbqUgg+2LzA==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-placeholder@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-eHPadx48gneDD8bTZeRnG4hOvRvctBPY5JlA03QQIoarrbmqsyv3zZSW8smBsRai9kwbXLhytdEFGruTKV9PjQ==}
/@tiptap/extension-placeholder@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-1BOyxVLzyUYf6yOOeJ8CfpP6DSCS4L6HjBZqj6WP1z1NyBV8RAfhf3UuLNcimfSWAETXFR3g0ZbaxxWffI1cEg==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-strike@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-0fRh0SwPgqi+ZKD2NpRrmIAHdsgf27ddEUfvlIuFG5b9zqFa6pRZGpXW/6LyBwU0+0bkjW8/Wg3otyaRGjvZGw==}
/@tiptap/extension-strike@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-gOW4ALeH8gkJiUGGXVy/AOd5lAPTX0bzoOW1+sCLcTA7t8dluBW7M2ngNYxTEtlKqyv7aLfrgsYSiqucmmfSLw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-table-cell@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-i/O27W0O2Bp+txeYUz8thZJaIHTb8BvPseYENiYBs9qOjpwiqXgQXVa5wTAm93GtHoK2ifs50a/xp2CIDTYsgw==}
/@tiptap/extension-table-cell@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-jsFp5lc+be04AsuMiTGlluLnsmJl/51+sv0DewYHeidh7iyvk3R5y2pyA+Bk1V/txFdaH5GxOQvSH3RonEVMAg==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-table-header@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-1IFGBVV/wJwO7aM8u6gN90OmT35ulJoNFQEssoxrx8g+tj+mZy5vXq2JGPqFg7AVjIpek1DJrzzcXGnbtXS57w==}
/@tiptap/extension-table-header@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-wLvJqDBaXc/xs+NBJZoSIfO7fVYqcrIlsdtQRlBec3vTpSG0w0zlrM/JY4mjQKHzWsDk6hb9mvbK2scChOu5TA==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-table-row@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-sUTpzzR6zR6iFLXszK4qak0/qd8Gjz9/mnj9sdRJhXtVT+VPssU0iVBMyIfn01nnoUzMPpakcO1Vd5dSJNhoVg==}
/@tiptap/extension-table-row@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-i2o/S8Mggw1GDxF5N5i8SvDvmOvbHu8MuWpdhFwfOkbrnEdtHlU/GjWIEstPymg4QyrfAEQa/KDffkrX0T7RNw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-table@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-O26Uj/A9AR8QJZvjSaTCI6Jpuc7MD8ul0vjqJ9EKulzMaAJbiOJidcBPr7rhVxAIRrXQz+DQW+zFbZvTj5i5mQ==}
/@tiptap/extension-table@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-1cU0C5LFyF7Nm8K2X6sntuunpLDE11XV8gV4ALbXv0FNKx2rG9Wq0tQlGHAjhYZMhWt386T21N7o13aMAov1GA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-task-item@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-8JJI9JBDdU5optPZVmlpCk5eOXv1p8bsFQYgHmIaHmDvsWvNlRph1LBYEAyiImBwVgZp+M8FI4KD5rNdALhelw==}
/@tiptap/extension-task-item@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-WvQJiQSskI1dZLPgNH4hmYPW0HFyR/EHwogzVnY7XCn2/5isV0ewyaVuSfqTXvfEA/R5uCi95opwz61NFBc2nQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/extension-task-list@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-4ofrnm0jwk9OC5QH+b5wR4ck+J5wi+uOq7Qm2234GAqohjOzr4ndae68NSIv2XviDk04askCoZc+yZBxNwkhmQ==}
/@tiptap/extension-task-list@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-TBgqf4s3DpUV97w7AAj1WZDnZ3rZQ8B645d9bBayo4VfRzHCLefv5cVP/Ye9GA23T4FZoHNR+yIPrM7SfhkmPA==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-text@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-wVpo0I/2tJsBK/2yNZfRXOsThOfHCdTY+FDNO/USx9MCJaJ3LPs3H1AuGO549zNmZgkD+1MqcZqrYt9n4i03cw==}
/@tiptap/extension-text@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-zkudl0TyKRy/8vHtyo5dMzjBRD0HEUnsS8YOsjR4xwQq5EYUXleRgM1s6lb6Yms2sLUAZRWdDddoQ686iq4zQg==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-typography@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-S8rvwSLq6IW1pEo/1Gcyez2tnXvUbwFBsAGNKPzcX/0HvlgXYWdE+qcjT3yoY/gAHRdvqfAP1Cd4Up7O3f1//A==}
/@tiptap/extension-typography@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-bI9t6dVp3wvzp3RVhJYRAV5Gi4pCSnumYith62TJmEk7fI24XuwMAXJu32+RTtBkaWHX/nwSGPh/ol0PRmtzKw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/extension-underline@2.2.6(@tiptap/core@2.2.6):
resolution: {integrity: sha512-RaYEWuBHS6VQ2KXk+pP2b3xDN4vxmTb7+CF84mumR+CJUK4uAx01IDBUof+h/a4Sa58suNLQ6eHY33NmxPppnQ==}
/@tiptap/extension-underline@2.3.0(@tiptap/core@2.3.0):
resolution: {integrity: sha512-vmmcwCPmWqGKYHZevz50+bxrpHyiu5y6YZweAE476hn8Mud6vYg7RpkXgW8bjkCOky6UA51uelslSc0XrLE6uw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
dev: false
/@tiptap/pm@2.2.6:
resolution: {integrity: sha512-gSKJtsaMLiYNwcAdwgnlTVM9zHiHy6/WgJvXFmIoOnUgvMN10Bbr+KO5hoffwgLCCSpIWw6qJoVKMpHBexLm0w==}
/@tiptap/pm@2.3.0:
resolution: {integrity: sha512-4WYqShZBwDyReKvapC0nmeYdOtZbZ31y4MjolpKQaSD4I7kg/oZspC+byUGdvIRsNpRN7i2X0IyvdISKk8gw5Q==}
dependencies:
prosemirror-changeset: 2.2.1
prosemirror-collab: 1.3.1
@ -3509,27 +3514,27 @@ packages:
prosemirror-view: 1.32.7
dev: false
/@tiptap/suggestion@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6):
resolution: {integrity: sha512-/pooAwvUkUB1gKxe4AHAvaNpAvL3FsZrIBJKYR0PF6BNROOHiu4IZY0Zy27ea4vmj72pYrMVlTmeYtSGPzrICg==}
/@tiptap/suggestion@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0):
resolution: {integrity: sha512-QngwR9ahodVfwqp/kXxJvuL3zNb6XZu+vCuWy8RJrGP8DA7SCI9t8t7iB6NfG4kSsRGxM+3DuLi+2xOZQUaEVQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/vue-3@2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)(vue@3.4.21):
resolution: {integrity: sha512-F8hC133AF/48cvZReJun5TV35NtRcoH8LVEGsuHGNkH7BvJjXAciomvEO4HlSfqz1YT8M/hzRGNg1/R6ixv3bw==}
/@tiptap/vue-3@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)(vue@3.4.21):
resolution: {integrity: sha512-Jgsoouq7gD6SkUf7McOJnKOHqVTVDJkPqhXZUZyJbJ22wD+7drxlauWwWexEymbs95ByhKblreLwcumvbOztgg==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
vue: ^3.0.0
dependencies:
'@tiptap/core': 2.2.6(@tiptap/pm@2.2.6)
'@tiptap/extension-bubble-menu': 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
'@tiptap/extension-floating-menu': 2.2.6(@tiptap/core@2.2.6)(@tiptap/pm@2.2.6)
'@tiptap/pm': 2.2.6
'@tiptap/core': 2.3.0(@tiptap/pm@2.3.0)
'@tiptap/extension-bubble-menu': 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-floating-menu': 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
vue: 3.4.21(typescript@5.4.5)
dev: false
@ -10031,14 +10036,14 @@ packages:
vue: 3.4.21(typescript@5.4.5)
dev: false
/vue-i18n@9.11.0(vue@3.4.21):
resolution: {integrity: sha512-vU4gY6lu8Pdfs9BgKGiDAJmFDf88cceR47KcSB0VW4xJzUrXR/7qwqM7A8dQ2nedhoIDxoOm5Ro4pFd2KvJqbA==}
/vue-i18n@9.11.1(vue@3.4.21):
resolution: {integrity: sha512-S7Xi8DkLQG4xnnbxkxzipJK6CdfLdZkmApn95st89HFGp8LTmTH0Tv+Zw6puhOCZJCFrH73PHo3Ylwd2+Bmdxg==}
engines: {node: '>= 16'}
peerDependencies:
vue: ^3.0.0
dependencies:
'@intlify/core-base': 9.11.0
'@intlify/shared': 9.11.0
'@intlify/core-base': 9.11.1
'@intlify/shared': 9.11.1
'@vue/devtools-api': 6.6.1
vue: 3.4.21(typescript@5.4.5)
dev: false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 218 KiB

View File

@ -162,7 +162,7 @@ projectStore.loadAllProjects()
.app-content {
z-index: 10;
position: relative;
padding: 1.5rem 0.5rem 1rem;
padding: 1.5rem 0.5rem 0;
// TODO refactor: DRY `transition-timing-function` with `./navigation.vue`.
transition: margin-left $transition-duration;
@ -172,7 +172,7 @@ projectStore.loadAllProjects()
}
@media screen and (min-width: $tablet) {
padding: $navbar-height + 1.5rem 1.5rem 1rem 1.5rem;
padding: $navbar-height + 1.5rem 1.5rem 0 1.5rem;
}
&.is-menu-enabled {

View File

@ -156,6 +156,7 @@ const savedFilterProjects = computed(() => projectStore.savedFilterProjects)
left: 0;
transform: translateX(-100%);
width: $navbar-width;
overflow-y: auto;
@media screen and (max-width: $tablet) {
top: 0;

View File

@ -338,10 +338,12 @@ const editor = useEditor({
HardBreak.extend({
addKeyboardShortcuts() {
return {
'Shift-Enter': () => this.editor.commands.setHardBreak(),
'Mod-Enter': () => {
if (contentHasChanged.value) {
bubbleSave()
}
return true
},
}
},
@ -891,8 +893,14 @@ ul[data-type='taskList'] {
padding: 0;
margin-left: 0;
li[data-checked='true'] {
color: var(--grey-500);
text-decoration: line-through;
}
li {
display: flex;
margin-top: 0.25rem;
> label {
flex: 0 0 auto;

View File

@ -88,7 +88,7 @@ const currentProject = computed<IProject>(() => {
})
useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value) : '')
const views = computed(() => currentProject.value?.views)
const views = computed(() => projectStore.projects[projectId]?.views)
// watchEffect would be called every time the prop would get a value assigned, even if that value was the same as before.
// This resulted in loading and setting the project multiple times, even when navigating away from it.
@ -161,6 +161,7 @@ function getViewTitle(view: IProjectView) {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
@media screen and (max-width: $tablet) {
justify-content: center;

View File

@ -30,45 +30,27 @@ import {computed, ref, watch} from 'vue'
import Filters from '@/components/project/partials/filters.vue'
import {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
import {useRouteQuery} from '@vueuse/router'
import {type TaskFilterParams} from '@/services/taskCollection'
const modelValue = defineModel<TaskFilterParams>({})
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = ref<TaskFilterParams>({})
const filter = useRouteQuery('filter')
watch(
() => modelValue.value,
() => props.modelValue,
(modelValue: TaskFilterParams) => {
value.value = modelValue
if (value.value.filter !== '' && value.value.filter !== getDefaultTaskFilterParams().filter) {
filter.value = value.value.filter
}
},
{immediate: true},
)
watch(
() => filter.value,
val => {
if (modelValue.value?.filter === val || typeof val === 'undefined') {
return
}
modelValue.value.filter = val
},
{immediate: true},
)
function emitChanges(newValue: TaskFilterParams) {
filter.value = newValue.filter
if (modelValue.value?.filter === newValue.filter && modelValue.value?.s === newValue.s) {
return
}
modelValue.value.filter = newValue.filter
modelValue.value.s = newValue.s
emit('update:modelValue', {
...value.value,
filter: newValue.filter,
s: newValue.s,
})
}
const hasFilters = computed(() => {

View File

@ -5,7 +5,7 @@
role="search"
>
<FilterInput
v-model="params.filter"
v-model="filterQuery"
:project-id="projectId"
@blur="change()"
/>
@ -28,7 +28,7 @@
<x-button
variant="secondary"
class="mr-2"
:disabled="params.filter === ''"
:disabled="filterQuery === ''"
@click.prevent.stop="clearFiltersAndEmit"
>
{{ $t('filters.clear') }}
@ -87,6 +87,16 @@ const params = ref<TaskFilterParams>({
s: '',
})
const filterQuery = ref('')
watch(
() => [params.value.filter, params.value.s],
() => {
const filter = params.value.filter || ''
const s = params.value.s || ''
filterQuery.value = filter || s
},
)
// Using watchDebounced to prevent the filter re-triggering itself.
watch(
() => modelValue,
@ -107,7 +117,7 @@ const projectStore = useProjectStore()
function change() {
const filter = transformFilterStringForApi(
params.value.filter,
filterQuery.value,
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
projectTitle => {
const found = projectStore.findProjectByExactname(projectTitle)
@ -142,7 +152,7 @@ function changeAndEmitButton() {
}
function clearFiltersAndEmit() {
params.value.filter = ''
filterQuery.value = ''
changeAndEmitButton()
}
</script>

View File

@ -773,11 +773,6 @@ $crazy-height-calculation: '100vh - 4.5rem - 1.5rem - 1rem - 1.5rem - 11px';
$crazy-height-calculation-tasks: '#{$crazy-height-calculation} - 1rem - 2.5rem - 2rem - #{$button-height} - 1rem';
$filter-container-height: '1rem - #{$switch-view-height}';
// FIXME:
.app-content.project\.kanban, .app-content.task\.detail {
padding-bottom: 0 !important;
}
.kanban {
overflow-x: auto;
overflow-y: hidden;
@ -785,6 +780,10 @@ $filter-container-height: '1rem - #{$switch-view-height}';
margin: 0 -1.5rem;
padding: 0 1.5rem;
&:focus, .bucket .tasks:focus {
box-shadow: none;
}
@media screen and (max-width: $tablet) {
height: calc(#{$crazy-height-calculation} - #{$filter-container-height});
scroll-snap-type: x mandatory;

View File

@ -1,5 +1,5 @@
import {ref, shallowReactive, watch, computed, type ComputedGetter} from 'vue'
import {useRoute} from 'vue-router'
import {useRoute, useRouter} from 'vue-router'
import {useRouteQuery} from '@vueuse/router'
import TaskCollectionService, {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
@ -66,7 +66,6 @@ export function useTaskList(
const params = ref<TaskFilterParams>({...getDefaultTaskFilterParams()})
const search = ref('')
const page = useRouteQuery('page', '1', { transform: Number })
const sortBy = ref({ ...sortByDefault })
@ -74,10 +73,6 @@ export function useTaskList(
const allParams = computed(() => {
const loadParams = {...params.value}
if (search.value !== '') {
loadParams.s = search.value
}
return formatSortOrder(sortBy.value, loadParams)
})
@ -122,16 +117,38 @@ export function useTaskList(
const route = useRoute()
watch(() => route.query, (query) => {
const { page: pageQueryValue, search: searchQuery } = query
if (searchQuery !== undefined) {
search.value = searchQuery as string
const {
page: pageQueryValue,
s,
filter,
} = query
if (s !== undefined) {
params.value.s = s as string
}
if (pageQueryValue !== undefined) {
page.value = Number(pageQueryValue)
}
if (filter !== undefined) {
params.value.filter = filter
}
}, { immediate: true })
const router = useRouter()
watch(
() => [page.value, params.value.filter, params.value.s],
() => {
router.replace({
name: route.name,
params: route.params,
query: {
page: page.value,
filter: params.value.filter || undefined,
s: params.value.s || undefined,
},
})
},
{ deep: true },
)
// Only listen for query path changes
watch(() => JSON.stringify(getAllTasksParams.value), (newParams, oldParams) => {
@ -148,7 +165,6 @@ export function useTaskList(
totalPages,
currentPage: page,
loadTasks,
searchTerm: search,
params,
sortByParam: sortBy,
}

View File

@ -19,6 +19,13 @@ export function secondsToPeriod(seconds: number): { unit: PeriodUnit, amount: nu
}
}
if (seconds % SECONDS_A_MINUTE === 0) {
return {
unit: 'minutes',
amount: seconds / SECONDS_A_MINUTE,
}
}
return {
unit: 'hours',
amount: seconds / SECONDS_A_HOUR,

View File

@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import type { RouteLocation } from 'vue-router'
import {saveLastVisited} from '@/helpers/saveLastVisited'
import {saveProjectView, getProjectViewId} from '@/helpers/projectView'
import {getProjectViewId} from '@/helpers/projectView'
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
import {getNextWeekDate} from '@/helpers/time/getNextWeekDate'
import {LINK_SHARE_HASH_PREFIX} from '@/constants/linkShareHash'
@ -366,7 +366,6 @@ const router = createRouter({
path: '/projects/:projectId/:viewId',
name: 'project.view',
component: ProjectView,
beforeEnter: (to) => saveProjectView(parseInt(to.params.projectId as string), parseInt(to.params.viewId as string)),
props: route => ({
projectId: parseInt(route.params.projectId as string),
viewId: route.params.viewId ? parseInt(route.params.viewId as string): undefined,

View File

@ -207,13 +207,17 @@ export const useProjectStore = defineStore('project', () => {
}
function getAncestors(project: IProject): IProject[] {
if (typeof project === 'undefined') {
return []
}
if (!project?.parentProjectId) {
return [project]
}
const parentProject = projects.value[project.parentProjectId]
return [
...getAncestors(parentProject),
...(parentProject ? getAncestors(parentProject) : []),
project,
]
}

View File

@ -2,6 +2,7 @@
import {computed, watch} from 'vue'
import {useProjectStore} from '@/stores/projects'
import {useRoute, useRouter} from 'vue-router'
import {saveProjectView} from '@/helpers/projectView'
import ProjectList from '@/components/project/views/ProjectList.vue'
import ProjectGantt from '@/components/project/views/ProjectGantt.vue'
@ -53,6 +54,13 @@ watch(
redirectToFirstViewIfNecessary,
)
// using a watcher instead of beforeEnter because beforeEnter is not called when only the viewId changes
watch(
() => [projectId, viewId],
() => saveProjectView(projectId, viewId),
{immediate: true},
)
const route = useRoute()
</script>

View File

@ -17,11 +17,11 @@ import postcssEasings from 'postcss-easings'
import postcssEasingGradients from 'postcss-easing-gradients'
const pathSrc = fileURLToPath(new URL('./src', import.meta.url))
const pathSrc = fileURLToPath(new URL('./src', import.meta.url)).replaceAll('\\', '/')
// the @use rules have to be the first in the compiled stylesheets
const PREFIXED_SCSS_STYLES = `@use "sass:math";
@import "${pathSrc}/styles/common-imports";`
@import "${pathSrc}/styles/common-imports.scss";`
const isModernBuild = Boolean(process.env.BUILD_MODERN_ONLY)
const legacy = isModernBuild

5
go.mod
View File

@ -21,7 +21,7 @@ require (
dario.cat/mergo v1.0.0
github.com/ThreeDotsLabs/watermill v1.3.5
github.com/adlio/trello v1.11.0
github.com/arran4/golang-ical v0.2.7
github.com/arran4/golang-ical v0.2.8
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/bbrks/go-blurhash v1.1.1
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
@ -110,7 +110,6 @@ require (
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.6.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-openapi/jsonpointer v0.20.1 // indirect
github.com/go-openapi/jsonreference v0.20.3 // indirect
@ -191,6 +190,8 @@ require (
replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
replace github.com/adlio/trello v1.11.0 => github.com/kolaente/trello v1.8.1-0.20240410214605-9314fa638eab // https://github.com/adlio/trello/pull/95
go 1.21
toolchain go1.21.2

36
go.sum
View File

@ -24,8 +24,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK2WofkbXg=
github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY=
github.com/adlio/trello v1.11.0 h1:PGpwpRZcRiVhsG7VEHb2GWKw4R2ZxB9nc6cMI/7mLD8=
github.com/adlio/trello v1.11.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
@ -33,6 +31,8 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7D
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/arran4/golang-ical v0.2.7 h1:VO7YlVaGupZE15aj6NhUhte/MIfZuoIzkoI71VsG6Gg=
github.com/arran4/golang-ical v0.2.7/go.mod h1:RqMuPGmwRRwjkb07hmm+JBqcWa1vF1LvVmPtSZN2OhQ=
github.com/arran4/golang-ical v0.2.8 h1:8lsFcfQqzg0gBpIxq7fWr4RV+8SVENLMXpSic5xsFUs=
github.com/arran4/golang-ical v0.2.8/go.mod h1:RqMuPGmwRRwjkb07hmm+JBqcWa1vF1LvVmPtSZN2OhQ=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@ -70,8 +70,6 @@ github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4M
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@ -129,8 +127,6 @@ github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@ -158,8 +154,6 @@ github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2r
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@ -311,6 +305,8 @@ github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible h1:q7DbyV+sFjEoTuuUdRDNl2nlyfztkZgxVVCV7JhzIkY=
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
github.com/kolaente/trello v1.8.1-0.20240410214605-9314fa638eab h1:KAEuOtvjNohQzONw0NpOlR4TlWgsPawY/PLyOiMHlFw=
github.com/kolaente/trello v1.8.1-0.20240410214605-9314fa638eab/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -537,8 +533,6 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
@ -580,9 +574,6 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
@ -596,7 +587,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -618,9 +608,6 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
@ -631,7 +618,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -662,20 +648,12 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -686,7 +664,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -707,7 +684,6 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -802,8 +778,6 @@ nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYm
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
src.techknowlogick.com/xgo v1.7.1-0.20240305180710-770b8eae9cec h1:ICDp83UjJvLcOFWHAxr7vmziKIHJkE4jsIF1mbT9Bwk=
src.techknowlogick.com/xgo v1.7.1-0.20240305180710-770b8eae9cec/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20240403232151-e01c4fbef884 h1:Ttvt8FCpUXfC8r3+LgSPrBUIr/JkHmYQtvmOwEET8qE=
src.techknowlogick.com/xgo v1.7.1-0.20240403232151-e01c4fbef884/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xormigrate v1.7.1 h1:RKGLLUAqJ+zO8iZ7eOc7oLH7f0cs2gfXSZSvBRBHnlY=
@ -814,7 +788,5 @@ xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.0.5/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
xorm.io/xorm v1.3.3/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo=
xorm.io/xorm v1.3.8 h1:CJmplmWqfSRpLWSPMmqz+so8toBp3m7ehuRehIWedZo=
xorm.io/xorm v1.3.8/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=

View File

@ -32,9 +32,9 @@ The to-do app to organize your life.
Also one of the two wild South American camelids which live in the high
alpine areas of the Andes and a relative of the llama.
Vikunja is a self-hosted To-Do list application with a web app and mobile apps for all platforms. It is licensed under the GPLv3.
Vikunja is a self-hosted To-Do list application with a web app and mobile apps for all platforms. It is licensed under the AGPLv3.
Find more info at vikunja.io.`,
Find out more at vikunja.io.`,
PreRun: webCmd.PreRun,
Run: webCmd.Run,
}

View File

@ -82,7 +82,6 @@ func init() {
if config.DatabaseType.GetString() == "sqlite" {
_, err = tx.Exec(`
create table buckets_dg_tmp
(
id INTEGER not null
@ -107,7 +106,7 @@ drop table buckets;
alter table buckets_dg_tmp
rename to buckets;
create unique index UQE_buckets_id
create unique index if not exists UQE_buckets_id
on buckets (id);
`)
return err

View File

@ -155,6 +155,21 @@ func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (task
projectIDs = append(projectIDs, p.ID)
}
views := map[int64]*ProjectView{}
err = s.In("project_id", projectIDs).Find(&views)
if err != nil {
return
}
viewIDs := []int64{}
for _, v := range views {
if projectsMap[v.ProjectID].Views == nil {
projectsMap[v.ProjectID].Views = []*ProjectView{}
}
projectsMap[v.ProjectID].Views = append(projectsMap[v.ProjectID].Views, v)
viewIDs = append(viewIDs, v.ID)
}
tasks, _, _, err := getTasksForProjects(s, rawProjects, u, &taskSearchOptions{
page: 0,
perPage: -1,
@ -194,17 +209,75 @@ func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (task
}
buckets := []*Bucket{}
err = s.In("project_id", projectIDs).Find(&buckets)
err = s.In("project_view_id", viewIDs).Find(&buckets)
if err != nil {
return
}
bucketIDs := []int64{}
for _, b := range buckets {
if _, exists := projectsMap[b.ProjectID]; !exists {
log.Debugf("[User Data Export] Project %d does not exist for bucket %d, omitting", b.ProjectID, b.ID)
view, exists := views[b.ProjectViewID]
if !exists {
log.Debugf("[User Data Export] Project view %d does not exist for bucket %d, omitting", b.ProjectViewID, b.ID)
continue
}
projectsMap[b.ProjectID].Buckets = append(projectsMap[b.ProjectID].Buckets, b)
_, exists = projectsMap[view.ProjectID]
if !exists {
log.Debugf("[User Data Export] Project %d does not exist for bucket %d, omitting", view.ProjectID, b.ID)
continue
}
projectsMap[view.ProjectID].Buckets = append(projectsMap[view.ProjectID].Buckets, b)
bucketIDs = append(bucketIDs, b.ID)
}
taskBuckets := []*TaskBucket{}
err = s.In("bucket_id", bucketIDs).Find(&taskBuckets)
if err != nil {
return
}
for _, tb := range taskBuckets {
view, exists := views[tb.ProjectViewID]
if !exists {
log.Debugf("[User Data Export] Project view %d does not exist, omitting", tb.ProjectViewID)
continue
}
_, exists = projectsMap[view.ProjectID]
if !exists {
log.Debugf("[User Data Export] Project %d does not exist, omitting", view.ProjectID)
continue
}
if projectsMap[view.ProjectID].TaskBuckets == nil {
projectsMap[view.ProjectID].TaskBuckets = []*TaskBucket{}
}
projectsMap[view.ProjectID].TaskBuckets = append(projectsMap[view.ProjectID].TaskBuckets, tb)
}
taskPositions := []*TaskPosition{}
err = s.In("project_view_id", viewIDs).Find(&taskPositions)
if err != nil {
return
}
for _, p := range taskPositions {
view, exists := views[p.ProjectViewID]
if !exists {
log.Debugf("[User Data Export] Project view %d does not exist, omitting", p.ProjectViewID)
continue
}
_, exists = projectsMap[view.ProjectID]
if !exists {
log.Debugf("[User Data Export] Project %d does not exist, omitting", view.ProjectID)
continue
}
if projectsMap[view.ProjectID].Positions == nil {
projectsMap[view.ProjectID].Positions = []*TaskPosition{}
}
projectsMap[view.ProjectID].Positions = append(projectsMap[view.ProjectID].Positions, p)
}
data, err := json.Marshal(projects)

View File

@ -529,7 +529,18 @@ func (l *AddTaskToTypesense) Handle(msg *message.Message) (err error) {
s := db.NewSession()
defer s.Close()
ttask, err := getTypesenseTaskForTask(s, event.Task, nil)
positionsMap, err := getPositionsForTask(s, event)
if err != nil {
return err
}
bucketsMap, err := getBucketsForTask(s, event)
if err != nil {
return err
}
ttask, err := getTypesenseTaskForTask(s, event.Task, nil, positionsMap, bucketsMap)
if err != nil {
return err
}
@ -540,6 +551,36 @@ func (l *AddTaskToTypesense) Handle(msg *message.Message) (err error) {
return
}
func getPositionsForTask(s *xorm.Session, event *TaskCreatedEvent) (positionsMap map[int64][]*TaskPositionWithView, err error) {
positions := []*TaskPositionWithView{}
err = s.
Table("project_views").
Where("project_views.project_id = ?", event.Task.ProjectID).
Join("LEFT", "task_positions", "project_views.id = task_positions.project_view_id AND task_positions.task_id = ?", event.Task.ID).
Find(&positions)
if err != nil {
return
}
positionsMap = make(map[int64][]*TaskPositionWithView, 1)
positionsMap[event.Task.ID] = positions
return
}
func getBucketsForTask(s *xorm.Session, event *TaskCreatedEvent) (bucketsMap map[int64][]*TaskBucket, err error) {
buckets := []*TaskBucket{}
err = s.
Where("task_id = ?", event.Task.ID).
Find(&buckets)
if err != nil {
return
}
bucketsMap = make(map[int64][]*TaskBucket, 1)
bucketsMap[event.Task.ID] = buckets
return
}
// UpdateTaskInTypesense represents a listener
type UpdateTaskInTypesense struct {
}

View File

@ -93,8 +93,10 @@ type ProjectWithTasksAndBuckets struct {
// An array of tasks which belong to the project.
Tasks []*TaskWithComments `xorm:"-" json:"tasks"`
// Only used for migration.
Buckets []*Bucket `xorm:"-" json:"buckets"`
BackgroundFileID int64 `xorm:"null" json:"background_file_id"`
Buckets []*Bucket `xorm:"-" json:"buckets"`
TaskBuckets []*TaskBucket `xorm:"-" json:"task_buckets"`
Positions []*TaskPosition `xorm:"-" json:"positions"`
BackgroundFileID int64 `xorm:"null" json:"background_file_id"`
}
// TableName returns a better name for the projects table
@ -110,15 +112,43 @@ type ProjectBackgroundType struct {
// ProjectBackgroundUpload represents the project upload background type
const ProjectBackgroundUpload string = "upload"
const FavoritesPseudoProjectID = -1
// FavoritesPseudoProject holds all tasks marked as favorites
var FavoritesPseudoProject = Project{
ID: -1,
ID: FavoritesPseudoProjectID,
Title: "Favorites",
Description: "This project has all tasks marked as favorites.",
IsFavorite: true,
Position: -1,
Created: time.Now(),
Updated: time.Now(),
Views: []*ProjectView{
{
ID: -1,
ProjectID: FavoritesPseudoProjectID,
Title: "List",
ViewKind: ProjectViewKindList,
Position: 100,
Filter: "done = false",
},
{
ID: -2,
ProjectID: FavoritesPseudoProjectID,
Title: "Gantt",
ViewKind: ProjectViewKindGantt,
Position: 200,
},
{
ID: -3,
ProjectID: FavoritesPseudoProjectID,
Title: "Table",
ViewKind: ProjectViewKindTable,
Position: 300,
},
},
Created: time.Now(),
Updated: time.Now(),
}
// ReadAll gets all projects a user has access to
@ -146,6 +176,9 @@ func (p *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
}
projects := []*Project{project}
err = addProjectDetails(s, projects, a)
if err == nil && len(projects) > 0 {
projects[0].ParentProjectID = 0
}
return projects, 0, 0, err
}
@ -207,6 +240,7 @@ func (p *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
func (p *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
if p.ID == FavoritesPseudoProject.ID {
p.Views = FavoritesPseudoProject.Views
// Already "built" the project in CanRead
return nil
}
@ -226,6 +260,11 @@ func (p *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
p.OwnerID = sf.OwnerID
}
_, isShareAuth := a.(*LinkSharing)
if isShareAuth {
p.ParentProjectID = 0
}
// Get project owner
p.Owner, err = user.GetUserByID(s, p.OwnerID)
if err != nil {
@ -1073,6 +1112,46 @@ func (p *Project) Delete(s *xorm.Session, a web.Auth) (err error) {
}
}
// Delete related project entities
views, err := getViewsForProject(s, p.ID)
if err != nil {
return
}
viewIDs := []int64{}
for _, v := range views {
viewIDs = append(viewIDs, v.ID)
}
_, err = s.In("project_view_id", viewIDs).Delete(&Bucket{})
if err != nil {
return
}
_, err = s.In("id", viewIDs).Delete(&ProjectView{})
if err != nil {
return
}
err = removeFromFavorite(s, p.ID, a, FavoriteKindProject)
if err != nil {
return
}
_, err = s.Where("project_id = ?", p.ID).Delete(&LinkSharing{})
if err != nil {
return
}
_, err = s.Where("project_id = ?", p.ID).Delete(&ProjectUser{})
if err != nil {
return
}
_, err = s.Where("project_id = ?", p.ID).Delete(&TeamProject{})
if err != nil {
return
}
// Delete the project
_, err = s.ID(p.ID).Delete(&Project{})
if err != nil {

View File

@ -159,6 +159,7 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
log.Debugf("Duplicated all link shares from project %d into %d", pd.ProjectID, pd.Project.ID)
err = pd.Project.ReadOne(s, doer)
return
}
@ -226,9 +227,11 @@ func duplicateViews(s *xorm.Session, pd *ProjectDuplicate, doer web.Auth, taskMa
})
}
_, err = s.Insert(&taskBuckets)
if err != nil {
return err
if len(taskBuckets) > 0 {
_, err = s.Insert(&taskBuckets)
if err != nil {
return err
}
}
oldTaskPositions := []*TaskPosition{}
@ -246,7 +249,9 @@ func duplicateViews(s *xorm.Session, pd *ProjectDuplicate, doer web.Auth, taskMa
})
}
_, err = s.Insert(&taskPositions)
if len(taskPositions) > 0 {
_, err = s.Insert(&taskPositions)
}
return
}

View File

@ -347,10 +347,22 @@ func (p *ProjectView) Update(s *xorm.Session, _ web.Auth) (err error) {
return
}
func GetProjectViewByIDAndProject(s *xorm.Session, id, projectID int64) (view *ProjectView, err error) {
func GetProjectViewByIDAndProject(s *xorm.Session, viewID, projectID int64) (view *ProjectView, err error) {
if projectID == FavoritesPseudoProjectID && viewID < 0 {
for _, v := range FavoritesPseudoProject.Views {
if v.ID == viewID {
return v, nil
}
}
return nil, &ErrProjectViewDoesNotExist{
ProjectViewID: viewID,
}
}
view = &ProjectView{}
exists, err := s.
Where("id = ? AND project_id = ?", id, projectID).
Where("id = ? AND project_id = ?", viewID, projectID).
NoAutoCondition().
Get(view)
if err != nil {
@ -359,7 +371,7 @@ func GetProjectViewByIDAndProject(s *xorm.Session, id, projectID int64) (view *P
if !exists {
return nil, &ErrProjectViewDoesNotExist{
ProjectViewID: id,
ProjectViewID: viewID,
}
}

View File

@ -100,6 +100,10 @@ func getTaskFilterOptsFromCollection(tf *TaskCollection, projectView *ProjectVie
param.orderBy = getSortOrderFromString(tf.OrderBy[i])
}
if s == taskPropertyPosition && projectView != nil && projectView.ID < 0 {
continue
}
if s == taskPropertyPosition && projectView != nil {
param.projectViewID = projectView.ID
}
@ -249,11 +253,20 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
opts.perPage = perPage
if view != nil {
opts.sortby = append(opts.sortby, &sortParam{
projectViewID: view.ID,
sortBy: taskPropertyPosition,
orderBy: orderAscending,
})
var hasOrderByPosition bool
for _, param := range opts.sortby {
if param.sortBy == taskPropertyPosition {
hasOrderByPosition = true
break
}
}
if !hasOrderByPosition {
opts.sortby = append(opts.sortby, &sortParam{
projectViewID: view.ID,
sortBy: taskPropertyPosition,
orderBy: orderAscending,
})
}
}
shareAuth, is := a.(*LinkSharing)

View File

@ -65,6 +65,14 @@ func (tc *TaskComment) TableName() string {
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{taskID}/comments [put]
func (tc *TaskComment) Create(s *xorm.Session, a web.Auth) (err error) {
tc.Created = time.Time{}
tc.Updated = time.Time{}
return tc.CreateWithTimestamps(s, a)
}
func (tc *TaskComment) CreateWithTimestamps(s *xorm.Session, a web.Auth) (err error) {
// Check if the task exists
task, err := GetTaskSimple(s, &Task{ID: tc.TaskID})
if err != nil {
@ -77,9 +85,16 @@ func (tc *TaskComment) Create(s *xorm.Session, a web.Auth) (err error) {
}
tc.AuthorID = tc.Author.ID
_, err = s.Insert(tc)
if err != nil {
return
if !tc.Created.IsZero() && !tc.Updated.IsZero() {
_, err = s.NoAutoTime().Insert(tc)
if err != nil {
return
}
} else {
_, err = s.Insert(tc)
if err != nil {
return
}
}
return events.Dispatch(&TaskCommentCreatedEvent{
@ -255,7 +270,7 @@ func (tc *TaskComment) ReadAll(s *xorm.Session, auth web.Auth, search string, pa
query := s.
Where(builder.And(where...)).
Join("LEFT", "users", "users.id = task_comments.author_id").
OrderBy("task_comments.id asc")
OrderBy("task_comments.created asc")
if limit > 0 {
query = query.Limit(limit, start)
}

View File

@ -297,6 +297,7 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
queryCount = queryCount.Join("LEFT", "task_buckets", "task_buckets.task_id = tasks.id")
}
totalCount, err = queryCount.
Select("count(DISTINCT tasks.id)").
Count(&Task{})
return
}
@ -379,6 +380,10 @@ func convertParsedFilterToTypesense(rawFilters []*taskFilter) (filterBy string,
f.field = "project_id"
}
if f.field == "bucket_id" {
f.field = "buckets"
}
filter := f.field
switch f.comparator {
@ -450,6 +455,7 @@ func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task,
"(" + filter + ")",
}
var projectViewIDForPosition int64
var sortbyFields []string
for i, param := range opts.sortby {
// Validate the params
@ -457,17 +463,19 @@ func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task,
return nil, totalCount, err
}
sortBy := param.sortBy
// Typesense does not allow sorting by ID, so we sort by created timestamp instead
if param.sortBy == taskPropertyID {
param.sortBy = taskPropertyCreated
sortBy = taskPropertyCreated
}
if param.sortBy == taskPropertyPosition {
param.sortBy = "positions.view_" + strconv.FormatInt(param.projectViewID, 10)
continue
sortBy = "positions.view_" + strconv.FormatInt(param.projectViewID, 10)
projectViewIDForPosition = param.projectViewID
}
sortbyFields = append(sortbyFields, param.sortBy+"(missing_values:last):"+param.orderBy.String())
sortbyFields = append(sortbyFields, sortBy+"(missing_values:last):"+param.orderBy.String())
if i == 2 {
// Typesense supports up to 3 sorting parameters
@ -525,9 +533,14 @@ func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task,
return nil, 0, err
}
err = t.s.
query := t.s.
In("id", taskIDs).
OrderBy(orderby).
Find(&tasks)
OrderBy(orderby)
if projectViewIDForPosition != 0 {
query = query.Join("LEFT", "task_positions", "task_positions.task_id = tasks.id AND task_positions.project_view_id = ?", projectViewIDForPosition)
}
err = query.Find(&tasks)
return tasks, int64(*result.Found), err
}

View File

@ -1486,6 +1486,18 @@ func (t *Task) Delete(s *xorm.Session, a web.Auth) (err error) {
return
}
// Delete all positions
_, err = s.Where("task_id = ?", t.ID).Delete(&TaskPosition{})
if err != nil {
return
}
// Delete all bucket relations
_, err = s.Where("task_id = ?", t.ID).Delete(&TaskBucket{})
if err != nil {
return
}
// Actually delete the task
_, err = s.ID(t.ID).Delete(Task{})
if err != nil {

View File

@ -146,22 +146,10 @@ func CreateTypesenseCollections() error {
Name: "updated",
Type: "int64", // unix timestamp
},
{
Name: "bucket_id",
Type: "int64",
},
{
Name: "position",
Type: "float",
},
{
Name: "created_by_id",
Type: "int64",
},
{
Name: "project_view_id",
Type: "int64",
},
{
Name: "reminders",
Type: "object[]", // TODO
@ -192,6 +180,14 @@ func CreateTypesenseCollections() error {
Type: "object[]", // TODO
Optional: pointer.True(),
},
{
Name: "positions",
Type: "object",
},
{
Name: "buckets",
Type: "int64[]",
},
},
}
@ -248,14 +244,8 @@ func ReindexAllTasks() (err error) {
return
}
func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int64]*Project) (ttask *typesenseTask, err error) {
positions := []*TaskPosition{}
err = s.Where("task_id = ?", task.ID).Find(&positions)
if err != nil {
return
}
ttask = convertTaskToTypesenseTask(task, positions)
func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int64]*Project, taskPositionCache map[int64][]*TaskPositionWithView, taskBucketCache map[int64][]*TaskBucket) (ttask *typesenseTask, err error) {
ttask = convertTaskToTypesenseTask(task, taskPositionCache[task.ID], taskBucketCache[task.ID])
var p *Project
if projectsCache == nil {
@ -299,9 +289,19 @@ func reindexTasksInTypesense(s *xorm.Session, tasks map[int64]*Task) (err error)
projects := make(map[int64]*Project)
typesenseTasks := []interface{}{}
positionsByTask, err := getPositionsByTask(s)
if err != nil {
return err
}
bucketsByTask, err := getBucketsByTask(s)
if err != nil {
return err
}
for _, task := range tasks {
ttask, err := getTypesenseTaskForTask(s, task, projects)
ttask, err := getTypesenseTaskForTask(s, task, projects, positionsByTask, bucketsByTask)
if err != nil {
return err
}
@ -320,11 +320,55 @@ func reindexTasksInTypesense(s *xorm.Session, tasks map[int64]*Task) (err error)
return err
}
log.Debugf("Indexed tasks %v into Typesense", tasks)
log.Debugf("Indexed %d tasks into Typesense", len(tasks))
return nil
}
type TaskPositionWithView struct {
ProjectView `xorm:"extends"`
TaskPosition `xorm:"extends"`
}
func getPositionsByTask(s *xorm.Session) (positionsByTask map[int64][]*TaskPositionWithView, err error) {
rawPositions := []*TaskPositionWithView{}
err = s.
Table("project_views").
Join("LEFT", "task_positions", "project_views.id = task_positions.project_view_id").
Find(&rawPositions)
if err != nil {
return
}
positionsByTask = make(map[int64][]*TaskPositionWithView, len(rawPositions))
for _, p := range rawPositions {
_, has := positionsByTask[p.TaskID]
if !has {
positionsByTask[p.TaskID] = []*TaskPositionWithView{}
}
positionsByTask[p.TaskID] = append(positionsByTask[p.TaskID], p)
}
return positionsByTask, nil
}
func getBucketsByTask(s *xorm.Session) (positionsByTask map[int64][]*TaskBucket, err error) {
rawBuckets := []*TaskBucket{}
err = s.Find(&rawBuckets)
if err != nil {
return
}
positionsByTask = make(map[int64][]*TaskBucket, len(rawBuckets))
for _, p := range rawBuckets {
_, has := positionsByTask[p.TaskID]
if !has {
positionsByTask[p.TaskID] = []*TaskBucket{}
}
positionsByTask[p.TaskID] = append(positionsByTask[p.TaskID], p)
}
return positionsByTask, nil
}
func indexDummyTask() (err error) {
// The initial sync should contain one dummy task with all related fields populated so that typesense
// creates the indexes properly. A little hacky, but gets the job done.
@ -386,6 +430,13 @@ func indexDummyTask() (err error) {
},
},
},
Positions: map[string]float64{
"view_1": 10,
"view_2": 30,
"view_3": 5450,
"view_4": 42,
},
Buckets: []int64{42},
}
_, err = typesenseClient.Collection("tasks").
@ -430,9 +481,10 @@ type typesenseTask struct {
Attachments interface{} `json:"attachments"`
Comments interface{} `json:"comments"`
Positions map[string]float64 `json:"positions"`
Buckets []int64 `json:"buckets"`
}
func convertTaskToTypesenseTask(task *Task, positions []*TaskPosition) *typesenseTask {
func convertTaskToTypesenseTask(task *Task, positions []*TaskPositionWithView, buckets []*TaskBucket) *typesenseTask {
tt := &typesenseTask{
ID: fmt.Sprintf("%d", task.ID),
@ -461,6 +513,8 @@ func convertTaskToTypesenseTask(task *Task, positions []*TaskPosition) *typesens
Labels: task.Labels,
//RelatedTasks: task.RelatedTasks,
Attachments: task.Attachments,
Positions: make(map[string]float64, len(positions)),
Buckets: make([]int64, 0, len(buckets)),
}
if task.DoneAt.IsZero() {
@ -477,7 +531,15 @@ func convertTaskToTypesenseTask(task *Task, positions []*TaskPosition) *typesens
}
for _, position := range positions {
tt.Positions["view_"+strconv.FormatInt(position.ProjectViewID, 10)] = position.Position
pos := position.TaskPosition.Position
if pos == 0 {
pos = float64(task.ID)
}
tt.Positions["view_"+strconv.FormatInt(position.ProjectView.ID, 10)] = pos
}
for _, bucket := range buckets {
tt.Buckets = append(tt.Buckets, bucket.BucketID)
}
return tt

View File

@ -64,6 +64,11 @@ func insertFromStructure(s *xorm.Session, str []*models.ProjectWithTasksAndBucke
}
p.ID = 0
for _, view := range p.Views {
view.ProjectID = 0
}
err = createProject(s, p, &archivedProjects, labels, user)
if err != nil {
return err
@ -167,7 +172,7 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
}
// Create all buckets
buckets := make(map[int64]*models.Bucket) // old bucket id is the key
bucketsByOldID := make(map[int64]*models.Bucket) // old bucket id is the key
if len(project.Buckets) > 0 {
log.Debugf("[creating structure] Creating %d buckets", len(project.Buckets))
}
@ -179,40 +184,45 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
if err != nil {
return
}
buckets[oldID] = bucket
bucketsByOldID[oldID] = bucket
log.Debugf("[creating structure] Created bucket %d, old ID was %d", bucket.ID, oldID)
}
// Create all views, create default views if we don't have any
viewsByOldIDs := make(map[int64]*models.ProjectView, len(oldViews))
if len(oldViews) > 0 {
for _, view := range oldViews {
oldID := view.ID
view.ID = 0
if view.DefaultBucketID != 0 {
bucket, has := buckets[view.DefaultBucketID]
bucket, has := bucketsByOldID[view.DefaultBucketID]
if has {
view.DefaultBucketID = bucket.ID
}
}
if view.DoneBucketID != 0 {
bucket, has := buckets[view.DoneBucketID]
bucket, has := bucketsByOldID[view.DoneBucketID]
if has {
view.DoneBucketID = bucket.ID
}
}
view.ProjectID = project.ID
err = view.Create(s, user)
if err != nil {
return
}
viewsByOldIDs[oldID] = view
}
} else {
// Only using the default views
// Add all buckets to the default kanban view
for _, view := range project.Views {
if view.ViewKind == models.ProjectViewKindKanban {
for _, b := range buckets {
for _, b := range bucketsByOldID {
b.ProjectViewID = view.ID
err = b.Update(s, user)
if err != nil {
@ -227,7 +237,7 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
log.Debugf("[creating structure] Creating %d tasks", len(tasks))
setBucketOrDefault := func(task *models.Task) {
bucket, exists := buckets[task.BucketID]
bucket, exists := bucketsByOldID[task.BucketID]
if exists {
task.BucketID = bucket.ID
} else if task.BucketID > 0 {
@ -240,6 +250,7 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
}
tasksByOldID := make(map[int64]*models.TaskWithComments, len(tasks))
newTaskIDs := []int64{}
// Create all tasks
for i, t := range tasks {
setBucketOrDefault(&tasks[i].Task)
@ -251,6 +262,8 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
continue
}
newTaskIDs = append(newTaskIDs, t.ID)
if err != nil {
return
}
@ -359,10 +372,11 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
log.Debugf("[creating structure] Associated task %d with label %d", t.ID, lb.ID)
}
// Comments
for _, comment := range t.Comments {
comment.TaskID = t.ID
comment.ID = 0
err = comment.Create(s, user)
err = comment.CreateWithTimestamps(s, user)
if err != nil {
return
}
@ -400,6 +414,58 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
}
}
if len(viewsByOldIDs) > 0 {
newPositions := []*models.TaskPosition{}
for _, pos := range project.Positions {
_, hasTask := tasksByOldID[pos.TaskID]
_, hasView := viewsByOldIDs[pos.ProjectViewID]
if !hasTask || !hasView {
continue
}
newPositions = append(newPositions, &models.TaskPosition{
TaskID: tasksByOldID[pos.TaskID].ID,
ProjectViewID: viewsByOldIDs[pos.ProjectViewID].ID,
Position: pos.Position,
})
}
if len(newPositions) > 0 {
_, err = s.In("task_id", newTaskIDs).Delete(&models.TaskPosition{})
if err != nil {
return
}
_, err = s.Insert(newPositions)
if err != nil {
return
}
}
newTaskBuckets := make([]*models.TaskBucket, 0, len(project.TaskBuckets))
for _, tb := range project.TaskBuckets {
_, hasTask := tasksByOldID[tb.TaskID]
_, hasBucket := bucketsByOldID[tb.BucketID]
if !hasTask || !hasBucket {
continue
}
newTaskBuckets = append(newTaskBuckets, &models.TaskBucket{
TaskID: tasksByOldID[tb.TaskID].ID,
BucketID: bucketsByOldID[tb.BucketID].ID,
ProjectViewID: bucketsByOldID[tb.BucketID].ProjectViewID,
})
}
if len(newTaskBuckets) > 0 {
_, err = s.In("task_id", newTaskIDs).Delete(&models.TaskBucket{})
if err != nil {
return
}
_, err = s.Insert(newTaskBuckets)
if err != nil {
return
}
}
}
project.Tasks = tasks
project.Buckets = originalBuckets

View File

@ -336,37 +336,39 @@ func convertTrelloDataToVikunja(organizationName string, trelloData []*trello.Bo
log.Debugf("[Trello Migration] Downloading %d card attachments from card %s", len(card.Attachments), card.ID)
}
for _, attachment := range card.Attachments {
if !attachment.IsUpload { // There are other types of attachments which are not files. We can only handle files.
log.Debugf("[Trello Migration] Attachment %s does not have a mime type, not downloading", attachment.ID)
if attachment.IsUpload {
// Download file and add it as attachment
log.Debugf("[Trello Migration] Downloading card attachment %s", attachment.ID)
buf, err := migration.DownloadFileWithHeaders(attachment.URL, map[string][]string{
"Authorization": {`OAuth oauth_consumer_key="` + config.MigrationTrelloKey.GetString() + `", oauth_token="` + token + `"`},
})
if err != nil {
return nil, err
}
vikunjaAttachment := &models.TaskAttachment{
File: &files.File{
Name: attachment.Name,
Mime: attachment.MimeType,
Size: uint64(buf.Len()),
FileContent: buf.Bytes(),
},
}
if card.IDAttachmentCover != "" && card.IDAttachmentCover == attachment.ID {
vikunjaAttachment.ID = 42
task.CoverImageAttachmentID = 42
}
task.Attachments = append(task.Attachments, vikunjaAttachment)
log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID)
continue
}
log.Debugf("[Trello Migration] Downloading card attachment %s", attachment.ID)
buf, err := migration.DownloadFileWithHeaders(attachment.URL, map[string][]string{
"Authorization": {`OAuth oauth_consumer_key="` + config.MigrationTrelloKey.GetString() + `", oauth_token="` + token + `"`},
})
if err != nil {
return nil, err
}
vikunjaAttachment := &models.TaskAttachment{
File: &files.File{
Name: attachment.Name,
Mime: attachment.MimeType,
Size: uint64(buf.Len()),
FileContent: buf.Bytes(),
},
}
if card.IDAttachmentCover != "" && card.IDAttachmentCover == attachment.ID {
vikunjaAttachment.ID = 42
task.CoverImageAttachmentID = 42
}
task.Attachments = append(task.Attachments, vikunjaAttachment)
log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID)
// Other links are not attachments in Vikunja, but we can add them to the description
task.Description += `<p><a href="` + attachment.URL + `">` + attachment.Name + "</a></p>\n"
}
// When the cover image was set manually, we need to add it as an attachment

View File

@ -78,6 +78,13 @@ func getTestBoard(t *testing.T) ([]*trello.Board, time.Time) {
MimeType: "image/jpg",
URL: "https://vikunja.io/testimage.jpg",
},
{
ID: "7cc71b16f0c7a57bed3c94e9",
Name: "Website",
IsUpload: false,
MimeType: "",
URL: "https://vikunja.io",
},
},
},
{
@ -265,10 +272,11 @@ func TestConvertTrelloToVikunja(t *testing.T) {
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Test Card 1",
Description: "<p>Card Description <strong>bold</strong></p>\n",
BucketID: 1,
DueDate: time1,
Title: "Test Card 1",
Description: "<p>Card Description <strong>bold</strong></p>\n" +
"<p><a href=\"https://vikunja.io\">Website</a></p>\n",
BucketID: 1,
DueDate: time1,
Labels: []*models.Label{
{
Title: "Label 1",
@ -481,6 +489,6 @@ func TestCreateOrganizationMap(t *testing.T) {
},
}
if diff, equal := messagediff.PrettyDiff(organizationMap, expectedMap); !equal {
t.Errorf("converted trello data = %v,\nwant %v,\ndiff: %v", organizationMap, expectedMap, diff)
t.Errorf("converted organization map = %v,\nwant %v,\ndiff: %v", organizationMap, expectedMap, diff)
}
}

View File

@ -156,10 +156,18 @@ func setupSentry(e *echo.Echo) {
if hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("url", c.Request().URL)
hub.CaptureException(herr.Internal)
if herr.Internal == nil {
hub.CaptureException(err)
} else {
hub.CaptureException(herr.Internal)
}
})
} else {
sentry.CaptureException(herr.Internal)
if herr.Internal == nil {
sentry.CaptureException(err)
} else {
sentry.CaptureException(herr.Internal)
}
log.Debugf("Could not add context for sending error '%s' to sentry", err.Error())
}
log.Debugf("Error '%s' sent to sentry", err.Error())