forked from vikunja/frontend
Compare commits
2 Commits
main
...
renovate/v
Author | SHA1 | Date | |
---|---|---|---|
e053dab73f | |||
972fa83bd3 |
|
@ -6,6 +6,7 @@
|
|||
# (2) Comment in and adjust the values as needed.
|
||||
|
||||
# VITE_IS_ONLINE=true
|
||||
# VITE_WORKBOX_DEBUG=false
|
||||
# SENTRY_AUTH_TOKEN=YOUR_TOKEN
|
||||
# SENTRY_ORG=vikunja
|
||||
# SENTRY_PROJECT=frontend-oss
|
||||
|
|
386
CHANGELOG.md
386
CHANGELOG.md
|
@ -9,390 +9,6 @@ All releases can be found on https://code.vikunja.io/frontend/releases.
|
|||
|
||||
The releases aim at the api versions which is why there are missing versions.
|
||||
|
||||
## [0.22.0] - 2023-12-19
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(api tokens)* Expiry of tokens in a number of days
|
||||
* *(api tokens)* Lint
|
||||
* *(api tokens)* Make deletion of old tokens work
|
||||
* *(api tokens)* Show a token after it was created
|
||||
* *(attachments)* Layout and coloring in dark mode
|
||||
* *(auth)* Correctly redirect the user to the last visited page after login
|
||||
* *(auth)* Silently discard invalid auth tokens and log the user out
|
||||
* *(background)* Unsplash author credit in dark mode
|
||||
* *(build)* Don't download Puppeteer when building for prod
|
||||
* *(ci)* Pin used node version to 20.5 to avoid build issues
|
||||
* *(ci)* Use correct secret key to push
|
||||
* *(docker)* Set correct default value for custom logo url
|
||||
* *(editor)* Actions styling
|
||||
* *(editor)* Actually populate loaded data into the editor
|
||||
* *(editor)* Add icons for clearing marks and nodes
|
||||
* *(editor)* Add missing dependencies for commands
|
||||
* *(editor)* Add missing dependency
|
||||
* *(editor)* Add workaround for checklist tiptap bug
|
||||
* *(editor)* Alignment and focus states
|
||||
* *(editor)* Allow checking a checkbox even when the editor is set to read only
|
||||
* *(editor)* Always set mode to preview after save
|
||||
* *(editor)* Always show placeholder when empty
|
||||
* *(editor)* Change description when switching between tasks
|
||||
* *(editor)* Check for almost empty editor value
|
||||
* *(editor)* Check for empty content
|
||||
* *(editor)* Checklist button icon
|
||||
* *(editor)* Commands list in dark mode
|
||||
* *(editor)* Correctly resolve images in descriptions
|
||||
* *(editor)* Don't check parent checkbox when child label was clicked
|
||||
* *(editor)* Don't crash when the component isn't completely mounted
|
||||
* *(editor)* Don't create empty "blob" files when pasting images
|
||||
* *(editor)* Don't prevent typing editor focus shortcut when other instance of an editor is focused already
|
||||
* *(editor)* Don't use global shortcut when anything is focused
|
||||
* *(editor)* Duplicate name
|
||||
* *(editor)* Duplicate name for extension
|
||||
* *(editor)* Focus state
|
||||
* *(editor)* Image button icon
|
||||
* *(editor)* Image paste handling
|
||||
* *(editor)* Keep editor open when emptying content from the outside
|
||||
* *(editor)* Keep editor open when emptying content from the outside (#3852)
|
||||
* *(editor)* Lint
|
||||
* *(editor)* Lint
|
||||
* *(editor)* List styling
|
||||
* *(editor)* Make checklist indicator work again
|
||||
* *(editor)* Make initial editor mode (preview/edit) work
|
||||
* *(editor)* Make tests work with changed structure
|
||||
* *(editor)* Permission check for table editing
|
||||
* *(editor)* Placeholder showing or not showing
|
||||
* *(editor)* Reset on empty
|
||||
* *(editor)* Show editor if there is no content initially
|
||||
* *(editor)* Use edit enable
|
||||
* *(editor)* Use modelValue directly to update values in the editor
|
||||
* *(filter)* Don't immediately re-trigger prepareFilter
|
||||
* *(filter)* Don't prevent entering date math strings
|
||||
* *(filter)* Don't show other filters in project selection in saved filter
|
||||
* *(filter)* Make other filters are not available for project selection
|
||||
* *(filters)* Don't allow marking a filter as favorite
|
||||
* *(filters)* Incorrect translation string
|
||||
* *(filters)* Infinite loop when creating filters with dates (#3061)
|
||||
* *(gantt)* Open task with double click from the gantt chart
|
||||
* *(gantt)* Update the gantt view when switching between projects
|
||||
* *(i18n)* Add upload files config
|
||||
* *(i18n)* Fall back to browser language if the configured user language is invalid
|
||||
* *(i18n)* Hungarian translation
|
||||
* *(kanban)* Check if doneBucketId is set
|
||||
* *(kanban)* Make sure kanban cards always have text color matching their background
|
||||
* *(kanban)* Opening a task from the kanban board and then reloading the page should not crash everything when then navigating back
|
||||
* *(list view)* Align nested subtasks with the parent text
|
||||
* *(menu)* Separate favorite and saved filter projects from other projects
|
||||
* *(navigation)* Don't hide color bubble in navigation on touch devices
|
||||
* *(navigation)* Show filter settings dropdown
|
||||
* *(project)* Correctly show project color next to project title in list view
|
||||
* *(projects)* Don't suggest to create a new task in an empty filter
|
||||
* *(quick actions)* Always open quick actions with hotkey, even if other inputs are focused
|
||||
* *(quick actions)* Always search for projects
|
||||
* *(quick actions)* Don't show projects when searching for labels or tasks
|
||||
* *(quick actions)* Invalid class prop
|
||||
* *(quick actions)* Project filter
|
||||
* *(quick actions)* Project search
|
||||
* *(quick actions)* Search for tasks within a project when specifying a project with quick add magic
|
||||
* *(quick add magic)* Annually and variants spelling
|
||||
* *(quick add magic)* Headline
|
||||
* *(quick add magic)* Ignore common task indention when adding multiple tasks at once
|
||||
* *(quick add magic)* Repeating intervals in words
|
||||
* *(settings)* Allow removing the default project via settings
|
||||
* *(settings)* Move overdue remindeer time below
|
||||
* *(sw)* Remove debug option via env as it would not be replaced correctly in prod builds
|
||||
* *(task)* Correct spacing to task and project title
|
||||
* *(task)* Correctly build task identifier
|
||||
* *(task)* Don't reload the kanban board when opening a task
|
||||
* *(task)* Don't reload the kanban board when opening a task
|
||||
* *(task)* Duplicate attribute
|
||||
* *(task)* Make sure the modal close button is not overlapped with the title field (#3256)
|
||||
* *(task)* Priority label sizing and positioning in different environments
|
||||
* *(task)* Priority label spacing
|
||||
* *(task)* Remove wrong repeat types
|
||||
* *(task)* Show related tasks form with shortcut even when there are already other related tasks
|
||||
* *(task)* Use editor as preview first, then check for edit
|
||||
* *(task)* Use empty description helper everywhere
|
||||
* *(tasks)* Don't use the filter for upcoming when one is set for the home page
|
||||
* *(tasks)* Favorited sub tasks are not shown in favorites pseudo list
|
||||
* *(tasks)* Ignore empty lines when adding multiple tasks at once
|
||||
* *(tasks)* Make sure tasks are fully clickable
|
||||
* *(tasks)* Play pop sound directly and not from store
|
||||
* *(tasks)* Prevent endless references
|
||||
* *(tasks)* Reset page number when applying filters
|
||||
* *(tasks)* Update api route
|
||||
* *(tasks)* Update sub task relations in list view after they were created
|
||||
* *(tasks)* Use mousedown event instead of click to close the task popup
|
||||
* *(test)* Use correct file input
|
||||
* *(user)* Allow openid users to request their deletion
|
||||
* *(webhooks)* Styling* Correctly resolve kanban board in the background when moving a task ([8902c15](8902c15f7e9590da075e860f3d35939169ee246a))
|
||||
* Don't render route modal when no properties are defined ([b1fe3fe](b1fe3fe29b3f7c8e3f1fa279b74f674bc63db232))
|
||||
* Don't try to load buckets for project id 0 ([15ecafd](15ecafdf04391139da27f38dac9ed915d6220a9a))
|
||||
* Lint ([218d724](218d72494a088b612e720ca2e9b566c0d3446579))
|
||||
* Lint ([337c3e5](337c3e5e3e06a9e4928bebffda2e2f223fef398b))
|
||||
* Lint ([7f2d921](7f2d92138e302188d6000632b4bc9bf053194dee))
|
||||
* Lint ([99e2161](99e2161c09b1e2b08f3a907bd2e3ad2c71da87d3))
|
||||
* Lint ([c01957a](c01957aae24696812c80b18c77137b5030fc757a))
|
||||
* Tests ([f6d1db3](f6d1db35957c4c2fda7a58539a0a39db1b683ccb))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Remove unused dependencies
|
||||
* *(deps)* Update dependencies
|
||||
* *(deps)* Update dependencies
|
||||
* *(deps)* Update dependency @fortawesome/vue-fontawesome to v3.0.5 (#3815)
|
||||
* *(deps)* Update dependency @github/hotkey to v2.1.0 (#3766)
|
||||
* *(deps)* Update dependency @github/hotkey to v2.1.1 (#3770)
|
||||
* *(deps)* Update dependency @github/hotkey to v2.2.0 (#3809)
|
||||
* *(deps)* Update dependency @github/hotkey to v2.3.0 (#3810)
|
||||
* *(deps)* Update dependency @github/hotkey to v2.3.1 (#3845)
|
||||
* *(deps)* Update dependency @github/hotkey to v3
|
||||
* *(deps)* Update dependency @infectoone/vue-ganttastic to v2.2.0
|
||||
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v0.12.2
|
||||
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v1
|
||||
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v1.5.0 (#3812)
|
||||
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v1.6.0
|
||||
* *(deps)* Update dependency @kyvg/vue3-notification to v3
|
||||
* *(deps)* Update dependency @kyvg/vue3-notification to v3.1.2
|
||||
* *(deps)* Update dependency @types/is-touch-device to v1.0.1 (#3786)
|
||||
* *(deps)* Update dependency @types/is-touch-device to v1.0.2 (#3816)
|
||||
* *(deps)* Update dependency @types/lodash.clonedeep to v4.5.8 (#3787)
|
||||
* *(deps)* Update dependency @types/lodash.clonedeep to v4.5.9 (#3817)
|
||||
* *(deps)* Update dependency @types/node to v18.17.0
|
||||
* *(deps)* Update dependency @types/node to v20 (#3796)
|
||||
* *(deps)* Update dependency @types/sortablejs to v1.15.4 (#3788)
|
||||
* *(deps)* Update dependency @types/sortablejs to v1.15.5 (#3818)
|
||||
* *(deps)* Update dependency @vueuse/core to v10.3.0
|
||||
* *(deps)* Update dependency @vueuse/core to v10.4.0 (#3723)
|
||||
* *(deps)* Update dependency axios to v1.5.1
|
||||
* *(deps)* Update dependency axios to v1.6.0 (#3801)
|
||||
* *(deps)* Update dependency axios to v1.6.2 (#3820)
|
||||
* *(deps)* Update dependency caniuse-lite to v1.0.30001514
|
||||
* *(deps)* Update dependency codemirror to v5.65.14
|
||||
* *(deps)* Update dependency dayjs to v1.11.10 (#3753)
|
||||
* *(deps)* Update dependency dompurify to v3.0.5
|
||||
* *(deps)* Update dependency dompurify to v3.0.6 (#3754)
|
||||
* *(deps)* Update dependency eslint to v8.52.0 (#3785)
|
||||
* *(deps)* Update dependency highlight.js to v11.9.0 (#3763)
|
||||
* *(deps)* Update dependency lowlight to v2.9.0 (#3789)
|
||||
* *(deps)* Update dependency marked to v5.1.1
|
||||
* *(deps)* Update dependency marked to v9
|
||||
* *(deps)* Update dependency marked to v9.1.0 (#3760)
|
||||
* *(deps)* Update dependency marked to v9.1.1 (#3768)
|
||||
* *(deps)* Update dependency marked to v9.1.2 (#3774)
|
||||
* *(deps)* Update dependency node (#3797)
|
||||
* *(deps)* Update dependency node (#3834)
|
||||
* *(deps)* Update dependency node to v18.18.0
|
||||
* *(deps)* Update dependency node to v18.18.1
|
||||
* *(deps)* Update dependency node to v18.18.2
|
||||
* *(deps)* Update dependency pinia to v2.1.6
|
||||
* *(deps)* Update dependency pinia to v2.1.7 (#3771)
|
||||
* *(deps)* Update dependency sass to v1.69.2 (#3767)
|
||||
* *(deps)* Update dependency sortablejs to v1.15.1 (#3841)
|
||||
* *(deps)* Update dependency ufo to v1.2.0
|
||||
* *(deps)* Update dependency ufo to v1.3.1
|
||||
* *(deps)* Update dependency ufo to v1.3.2 (#3824)
|
||||
* *(deps)* Update dependency vite to v4.4.2
|
||||
* *(deps)* Update dependency vite to v4.4.3
|
||||
* *(deps)* Update dependency vue to v3.3.10 (#3843)
|
||||
* *(deps)* Update dependency vue to v3.3.13
|
||||
* *(deps)* Update dependency vue to v3.3.5 (#3782)
|
||||
* *(deps)* Update dependency vue to v3.3.6 (#3784)
|
||||
* *(deps)* Update dependency vue to v3.3.7 (#3799)
|
||||
* *(deps)* Update dependency vue to v3.3.8 (#3814)
|
||||
* *(deps)* Update dependency vue to v3.3.9 (#3837)
|
||||
* *(deps)* Update dependency vue-i18n to v9.5.0
|
||||
* *(deps)* Update dependency vue-i18n to v9.6.0 (#3800)
|
||||
* *(deps)* Update dependency vue-i18n to v9.6.1 (#3803)
|
||||
* *(deps)* Update dependency vue-i18n to v9.6.5 (#3807)
|
||||
* *(deps)* Update dependency vue-i18n to v9.7.0 (#3825)
|
||||
* *(deps)* Update dependency vue-i18n to v9.8.0 (#3833)
|
||||
* *(deps)* Update dependency vue-router to v4.2.5 (#3755)
|
||||
* *(deps)* Update dessant/repo-lockdown action to v4
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies
|
||||
* *(deps)* Update dev-dependencies (#3721)
|
||||
* *(deps)* Update dev-dependencies (#3726)
|
||||
* *(deps)* Update dev-dependencies (#3740)
|
||||
* *(deps)* Update dev-dependencies (#3746)
|
||||
* *(deps)* Update dev-dependencies (#3747)
|
||||
* *(deps)* Update dev-dependencies (#3757)
|
||||
* *(deps)* Update dev-dependencies (#3761)
|
||||
* *(deps)* Update dev-dependencies (#3769)
|
||||
* *(deps)* Update dev-dependencies (#3776)
|
||||
* *(deps)* Update dev-dependencies (#3780)
|
||||
* *(deps)* Update dev-dependencies (#3793)
|
||||
* *(deps)* Update dev-dependencies (#3802)
|
||||
* *(deps)* Update dev-dependencies (#3806)
|
||||
* *(deps)* Update dev-dependencies (#3811)
|
||||
* *(deps)* Update dev-dependencies (#3813)
|
||||
* *(deps)* Update dev-dependencies (#3821)
|
||||
* *(deps)* Update dev-dependencies (#3826)
|
||||
* *(deps)* Update dev-dependencies (#3828)
|
||||
* *(deps)* Update dev-dependencies (#3829)
|
||||
* *(deps)* Update dev-dependencies (#3835)
|
||||
* *(deps)* Update dev-dependencies (#3842)
|
||||
* *(deps)* Update dev-dependencies (#3846)
|
||||
* *(deps)* Update dev-dependencies (#3856)
|
||||
* *(deps)* Update dev-dependencies (major) (#3741)
|
||||
* *(deps)* Update dev-dependencies (major) (#3827)
|
||||
* *(deps)* Update dev-dependencies to v6
|
||||
* *(deps)* Update flake
|
||||
* *(deps)* Update font awesome to v6.4.2
|
||||
* *(deps)* Update font awesome to v6.5.1 (#3839)
|
||||
* *(deps)* Update lockfile
|
||||
* *(deps)* Update lockfile
|
||||
* *(deps)* Update lockfile
|
||||
* *(deps)* Update lockfile
|
||||
* *(deps)* Update lockfile
|
||||
* *(deps)* Update lockfile
|
||||
* *(deps)* Update node.js to v18.17.0
|
||||
* *(deps)* Update node.js to v18.17.1
|
||||
* *(deps)* Update node.js to v20.7 (#3736)
|
||||
* *(deps)* Update node.js to v20.8 (#3756)
|
||||
* *(deps)* Update pnpm to v8.10.2
|
||||
* *(deps)* Update pnpm to v8.10.5
|
||||
* *(deps)* Update pnpm to v8.11.0
|
||||
* *(deps)* Update pnpm to v8.12.1
|
||||
* *(deps)* Update pnpm to v8.6.12
|
||||
* *(deps)* Update pnpm to v8.6.7
|
||||
* *(deps)* Update pnpm to v8.6.8
|
||||
* *(deps)* Update pnpm to v8.6.9
|
||||
* *(deps)* Update pnpm to v8.7.0
|
||||
* *(deps)* Update pnpm to v8.8.0
|
||||
* *(deps)* Update pnpm to v8.9.0
|
||||
* *(deps)* Update pnpm to v8.9.2
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.58.0
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.58.1
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.59.1
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.59.2
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.59.3
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.60.0
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.73.0
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.74.0 (#3772)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.74.1 (#3778)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.75.1 (#3798)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.77.0 (#3805)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.80.1 (#3819)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.85.0 (#3831)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.88.0
|
||||
* *(deps)* Update sub-dependencies
|
||||
* *(deps)* Update tiptap to v2.1.12 (#3790)
|
||||
* *(deps)* Update tiptap to v2.1.13 (#3840)
|
||||
* *(deps)* Update vueuse to v10.5.0 (#3762)
|
||||
* *(deps)* Update vueuse to v10.6.1 (#3822)
|
||||
* *(deps)* Update vueuse to v10.7.0 (#3844)
|
||||
|
||||
### Features
|
||||
|
||||
* *(api tokens)* Add basic api token overview
|
||||
* *(api tokens)* Add deleting api tokens
|
||||
* *(api tokens)* Add token creation form
|
||||
* *(api tokens)* Allow custom selection of expiry dates
|
||||
* *(api tokens)* Allow selecting all permissions
|
||||
* *(api tokens)* Format permissions and groups human-readable
|
||||
* *(api tokens)* Show warning if token has expired
|
||||
* *(api tokens)* Validate title field when creating a new token
|
||||
* *(assignees)* Improve avatar list consistency
|
||||
* *(editor)* Add all slash commands
|
||||
* *(editor)* Add bubble menu
|
||||
* *(editor)* Add code highlighting
|
||||
* *(editor)* Add command list example
|
||||
* *(editor)* Add comment when pressing ctrl enter
|
||||
* *(editor)* Add placeholder
|
||||
* *(editor)* Add proper description for all buttons
|
||||
* *(editor)* Add tests to check rendering of task description
|
||||
* *(editor)* Add tooltips for everything
|
||||
* *(editor)* Add uploading an image on save
|
||||
* *(editor)* Allow passing placeholder down
|
||||
* *(editor)* Edit mode
|
||||
* *(editor)* Edit shortcut to set focus into the editor
|
||||
* *(editor)* Enable table
|
||||
* *(editor)* Image upload
|
||||
* *(editor)* Improve overall styling
|
||||
* *(editor)* Make image upload work via slash command
|
||||
* *(editor)* Make task list work
|
||||
* *(editor)* Mark a checkbox item as done when clicking on its text
|
||||
* *(editor)* Move all editor related components into one folder
|
||||
* *(editor)* Only load attachment images when rendering is done
|
||||
* *(editor)* Open links when clicking on them
|
||||
* *(editor)* Properly bubble changes when they are made
|
||||
* *(editor)* Resolve and load attachment images from content
|
||||
* *(editor)* Save when pressing ctrl enter
|
||||
* *(gantt)* Implement dynamic sizing on small date ranges (#3750)
|
||||
* *(i18n)* Add Slovene language for selection in the ui
|
||||
* *(i18n)* Add arabic to list of selectable languages
|
||||
* *(i18n)* Add hungarian translation for selection
|
||||
* *(i18n)* Run translation update directly
|
||||
* *(i18n)* Update crowdin sync to use v2 api
|
||||
* *(i18n)* Update translations only once a day
|
||||
* *(kanban)* Add icon for bucket collapse
|
||||
* *(kanban)* Add setting for default bucket
|
||||
* *(kanban)* Save done bucket with project instead of bucket
|
||||
* *(labels)* Assign random color when creating labels
|
||||
* *(list view)* Show subtasks nested
|
||||
* *(migration)* Proper wording for async migration
|
||||
* *(notifications)* Add option to mark all as read
|
||||
* *(quick actions)* Show done tasks last
|
||||
* *(quick actions)* Show labels as labels and tasks with all of their details
|
||||
* *(quick actions)* Show task identifier
|
||||
* *(quick actions)* Show tasks for a label when selecting it
|
||||
* *(quick add magic)* Allow using the project identifier via quick add magic
|
||||
* *(task)* Add more tests
|
||||
* *(task)* Group related task action buttons
|
||||
* *(task)* Immediately set focus on the task search input when opening the related tasks menu
|
||||
* *(task)* Move task priority to the front when showing tasks inline
|
||||
* *(task)* Save currently opened task with control/meta + s
|
||||
* *(tasks)* Make the whole task in list view clickable
|
||||
* *(tasks)* Update due date text every minute
|
||||
* *(webhooks)* Add form validation* Allow custom logo via environment variable (#3685) ([cade3df](cade3df3e9a7eca8e0aa9d1553dd5597f0f5a8a2))
|
||||
* *(webhooks)* Add webhook management form
|
||||
* Add demo mode warning message ([ed8fb71](ed8fb71ff0b05860f320e2a1fe6c3cb29ed2889a))
|
||||
* Add setting for default bucket ([04ba101](04ba1011cc3042f657ddb40ee727caf455db8b64))
|
||||
* Api tokens ([28f2551](28f2551d87b99c59055a4909195e435dbd9794b6))
|
||||
* Improve error message for invalid API url ([725fd1a](725fd1ad467fb988810cb23f12d372af236bd21d))
|
||||
* Move from easymde to tiptap editor (#2222) ([26fc9b4](26fc9b4e4f8b96616385f4ca0a77a0ff7ee5eee5))
|
||||
* Quick actions improvments ([47d5890](47d589002ccef5047a25ea3ad8ebe582c3b0bbc6))
|
||||
* Webhooks (#3783) ([5d991e5](5d991e539bb3a249447847c13c92ee35d356b902))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* *(ci)* Sign drone config
|
||||
* *(editor)* Add break icon
|
||||
* *(editor)* Add horizontal line icon
|
||||
* *(editor)* Cleanup
|
||||
* *(editor)* Cleanup unused options
|
||||
* *(editor)* Format
|
||||
* *(editor)* Make sure all tiptap dependencies are updated as one
|
||||
* *(editor)* Move checklist to the other lists
|
||||
* *(editor)* Remove converting markdown
|
||||
* *(editor)* Remove marked usages
|
||||
* *(editor)* Remove old editor component
|
||||
* *(editor)* Remove unused components
|
||||
* *(editor)* Use typed props definition
|
||||
* *(filter)* Remove debug log
|
||||
* *(quick actions)* Format* Provide better error messages when refreshing user info fails ([d535879](d5358793de7fc53795329382222e5f3bafc7fba1))
|
||||
* Add pr lockdown ([07b1e9a](07b1e9a6b76eb7d92640e00a1dec4294efd2947b))
|
||||
* Cleanup ([a4a2b95](a4a2b95dc7eaad5fe313884eec0d22d7ae5f85c1))
|
||||
* Debug ([3cb1e7d](3cb1e7dede659acd19410e0611346e0f582f2ff3))
|
||||
* Format ([c3f85fc](c3f85fcb1988603a58104552b35101b13e93b06e))
|
||||
* Improve checking for API url '/' suffix (#121) ([311b1d7](311b1d7594cfd03be4d998f4aead041a8ca63f8c))
|
||||
* Include version json string in release zip ([c4adcf4](c4adcf4655550214ae795d941eb51878f34cedeb))
|
||||
* Update flake ([64c90c7](64c90c7fe8a77ded21778a798f18862fe966bd1a))
|
||||
* Update lockfile ([9f82ec4](9f82ec4162151ba32f329cb8e335eff6b21cebd4))
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* [skip ci] Updated translations via Crowdin
|
||||
|
||||
## [0.21.0] - 2023-07-07
|
||||
|
||||
### Bug Fixes
|
||||
|
@ -5910,4 +5526,4 @@ Co-committed-by: renovate <renovatebot@kolaente.de>
|
|||
* Fixed trying to verify an email when there was none
|
||||
* Fixed loading tasks when the user was not authenticated
|
||||
|
||||
## [0.1] - 2018-09-20
|
||||
## [0.1] - 2018-09-20
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
|
||||
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
|
||||
[![Download](https://img.shields.io/badge/download-v0.22.0-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Download](https://img.shields.io/badge/download-v0.21.0-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
|
||||
|
||||
This is the web frontend for Vikunja, written in Vue.js.
|
||||
|
@ -49,4 +49,4 @@ pnpm run build
|
|||
|
||||
```shell
|
||||
pnpm run lint
|
||||
```
|
||||
```
|
||||
|
|
|
@ -5,19 +5,6 @@ import {ProjectFactory} from '../../factories/project'
|
|||
import {TaskFactory} from '../../factories/task'
|
||||
import {prepareProjects} from './prepareProjects'
|
||||
|
||||
function createSingleTaskInBucket(count = 1, attrs = {}) {
|
||||
const projects = ProjectFactory.create(1)
|
||||
const buckets = BucketFactory.create(2, {
|
||||
project_id: projects[0].id,
|
||||
})
|
||||
const tasks = TaskFactory.create(count, {
|
||||
project_id: projects[0].id,
|
||||
bucket_id: buckets[0].id,
|
||||
...attrs,
|
||||
})
|
||||
return tasks[0]
|
||||
}
|
||||
|
||||
describe('Project View Kanban', () => {
|
||||
createFakeUserAndLogin()
|
||||
prepareProjects()
|
||||
|
@ -220,7 +207,15 @@ describe('Project View Kanban', () => {
|
|||
})
|
||||
|
||||
it('Should remove a task from the board when deleting it', () => {
|
||||
const task = createSingleTaskInBucket(5)
|
||||
const projects = ProjectFactory.create(1)
|
||||
const buckets = BucketFactory.create(2, {
|
||||
project_id: projects[0].id,
|
||||
})
|
||||
const tasks = TaskFactory.create(5, {
|
||||
project_id: 1,
|
||||
bucket_id: buckets[0].id,
|
||||
})
|
||||
const task = tasks[0]
|
||||
cy.visit('/projects/1/kanban')
|
||||
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
|
@ -243,43 +238,4 @@ describe('Project View Kanban', () => {
|
|||
cy.get('.kanban .bucket .tasks')
|
||||
.should('not.contain', task.title)
|
||||
})
|
||||
|
||||
it('Should show a task description icon if the task has a description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
|
||||
const task = createSingleTaskInBucket(1, {
|
||||
description: 'Lorem Ipsum',
|
||||
})
|
||||
|
||||
cy.visit(`/projects/${task.project_id}/kanban`)
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.bucket .tasks .task .footer .icon svg')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Should not show a task description icon if the task has an empty description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
|
||||
const task = createSingleTaskInBucket(1, {
|
||||
description: '',
|
||||
})
|
||||
|
||||
cy.visit(`/projects/${task.project_id}/kanban`)
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.bucket .tasks .task .footer .icon svg')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
|
||||
const task = createSingleTaskInBucket(1, {
|
||||
description: '<p></p>',
|
||||
})
|
||||
|
||||
cy.visit(`/projects/${task.project_id}/kanban`)
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.bucket .tasks .task .footer .icon svg')
|
||||
.should('not.exist')
|
||||
})
|
||||
})
|
|
@ -36,7 +36,7 @@ function uploadAttachmentAndVerify(taskId: number) {
|
|||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Add Attachments')
|
||||
.click()
|
||||
cy.get('input[type=file]#files', {timeout: 1000})
|
||||
cy.get('input[type=file]', {timeout: 1000})
|
||||
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
|
||||
cy.wait('@uploadAttachment')
|
||||
|
||||
|
@ -112,50 +112,10 @@ describe('Task', () => {
|
|||
.should('contain', 'Favorites')
|
||||
})
|
||||
|
||||
it('Should show a task description icon if the task has a description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||
TaskFactory.create(1, {
|
||||
description: 'Lorem Ipsum',
|
||||
})
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.tasks .task .project-task-icon')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Should not show a task description icon if the task has an empty description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||
TaskFactory.create(1, {
|
||||
description: '',
|
||||
})
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.tasks .task .project-task-icon')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||
TaskFactory.create(1, {
|
||||
description: '<p></p>',
|
||||
})
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.tasks .task .project-task-icon')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
describe('Task Detail View', () => {
|
||||
beforeEach(() => {
|
||||
TaskCommentFactory.truncate()
|
||||
LabelTaskFactory.truncate()
|
||||
TaskAttachmentFactory.truncate()
|
||||
})
|
||||
|
||||
it('Shows all task details', () => {
|
||||
|
@ -253,45 +213,6 @@ describe('Task', () => {
|
|||
.should('exist')
|
||||
})
|
||||
|
||||
it('Shows an empty editor when the description of a task is empty', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: '',
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
|
||||
.should('have.attr', 'data-placeholder')
|
||||
cy.get('.task-view .details.content.description .tiptap button.done-edit')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Shows a preview editor when the description of a task is not empty', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: 'Lorem Ipsum dolor sit amet',
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
|
||||
.should('not.have.attr', 'data-placeholder')
|
||||
cy.get('.task-view .details.content.description .tiptap button.done-edit')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Shows a preview editor when the description of a task contains html', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: '<p>Lorem Ipsum dolor sit amet</p>',
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
|
||||
.should('not.have.attr', 'data-placeholder')
|
||||
cy.get('.task-view .details.content.description .tiptap button.done-edit')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Can add a new comment', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
|
@ -771,7 +692,7 @@ describe('Task', () => {
|
|||
.should('exist')
|
||||
})
|
||||
|
||||
it.only('Can check items off a checklist', () => {
|
||||
it('Can check items off a checklist', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: `
|
||||
|
@ -840,7 +761,7 @@ describe('Task', () => {
|
|||
.should('exist')
|
||||
})
|
||||
|
||||
it('Should render an image from attachment', async () => {
|
||||
it.only('Should render an image from attachment', async () => {
|
||||
|
||||
TaskAttachmentFactory.truncate()
|
||||
|
||||
|
|
1
env.d.ts
vendored
1
env.d.ts
vendored
|
@ -25,6 +25,7 @@ interface ImportMetaEnv {
|
|||
readonly SENTRY_ORG?: string
|
||||
readonly SENTRY_PROJECT?: string
|
||||
|
||||
readonly VITE_WORKBOX_DEBUG?: boolean
|
||||
readonly VITE_IS_ONLINE: boolean
|
||||
}
|
||||
|
||||
|
|
68
package.json
68
package.json
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"homepage": "https://vikunja.io/",
|
||||
"funding": "https://opencollective.com/vikunja",
|
||||
"packageManager": "pnpm@8.12.1",
|
||||
"packageManager": "pnpm@8.11.0",
|
||||
"keywords": [
|
||||
"todo",
|
||||
"productivity",
|
||||
|
@ -49,12 +49,12 @@
|
|||
"@fortawesome/free-regular-svg-icons": "6.5.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.1",
|
||||
"@fortawesome/vue-fontawesome": "3.0.5",
|
||||
"@github/hotkey": "3.1.0",
|
||||
"@github/hotkey": "2.3.0",
|
||||
"@infectoone/vue-ganttastic": "2.2.0",
|
||||
"@intlify/unplugin-vue-i18n": "2.0.0",
|
||||
"@kyvg/vue3-notification": "3.1.2",
|
||||
"@sentry/tracing": "7.88.0",
|
||||
"@sentry/vue": "7.88.0",
|
||||
"@intlify/unplugin-vue-i18n": "1.5.0",
|
||||
"@kyvg/vue3-notification": "3.0.2",
|
||||
"@sentry/tracing": "7.80.1",
|
||||
"@sentry/vue": "7.80.1",
|
||||
"@tiptap/core": "2.1.13",
|
||||
"@tiptap/extension-blockquote": "2.1.13",
|
||||
"@tiptap/extension-bold": "2.1.13",
|
||||
|
@ -90,8 +90,8 @@
|
|||
"@tiptap/vue-3": "2.1.13",
|
||||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.clonedeep": "4.5.9",
|
||||
"@vueuse/core": "10.7.0",
|
||||
"@vueuse/router": "10.7.0",
|
||||
"@vueuse/core": "10.6.1",
|
||||
"@vueuse/router": "10.6.1",
|
||||
"axios": "1.6.2",
|
||||
"blurhash": "2.0.5",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
|
@ -110,10 +110,10 @@
|
|||
"pinia": "2.1.7",
|
||||
"register-service-worker": "1.7.2",
|
||||
"snake-case": "3.0.4",
|
||||
"sortablejs": "1.15.1",
|
||||
"sortablejs": "1.15.0",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.3.2",
|
||||
"vue": "3.3.13",
|
||||
"vue": "3.3.9",
|
||||
"vue-advanced-cropper": "2.8.8",
|
||||
"vue-flatpickr-component": "11.0.3",
|
||||
"vue-i18n": "9.8.0",
|
||||
|
@ -128,7 +128,7 @@
|
|||
"@faker-js/faker": "8.3.1",
|
||||
"@histoire/plugin-screenshot": "0.17.6",
|
||||
"@histoire/plugin-vue": "0.17.6",
|
||||
"@rushstack/eslint-patch": "1.6.1",
|
||||
"@rushstack/eslint-patch": "1.6.0",
|
||||
"@tsconfig/node18": "18.2.2",
|
||||
"@types/codemirror": "5.60.15",
|
||||
"@types/dompurify": "3.0.5",
|
||||
|
@ -136,44 +136,44 @@
|
|||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.debounce": "4.0.9",
|
||||
"@types/marked": "5.0.2",
|
||||
"@types/node": "20.10.5",
|
||||
"@types/node": "20.10.1",
|
||||
"@types/postcss-preset-env": "7.7.0",
|
||||
"@types/sortablejs": "1.15.7",
|
||||
"@typescript-eslint/eslint-plugin": "6.15.0",
|
||||
"@typescript-eslint/parser": "6.15.0",
|
||||
"@vitejs/plugin-legacy": "5.2.0",
|
||||
"@vitejs/plugin-vue": "4.5.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.13.1",
|
||||
"@typescript-eslint/parser": "6.13.1",
|
||||
"@vitejs/plugin-legacy": "4.1.1",
|
||||
"@vitejs/plugin-vue": "4.5.1",
|
||||
"@vue/eslint-config-typescript": "12.0.0",
|
||||
"@vue/test-utils": "2.4.3",
|
||||
"@vue/tsconfig": "0.5.1",
|
||||
"@vue/tsconfig": "0.4.0",
|
||||
"autoprefixer": "10.4.16",
|
||||
"browserslist": "4.22.2",
|
||||
"caniuse-lite": "1.0.30001570",
|
||||
"css-has-pseudo": "6.0.1",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.6.1",
|
||||
"esbuild": "0.19.10",
|
||||
"eslint": "8.56.0",
|
||||
"browserslist": "4.22.1",
|
||||
"caniuse-lite": "1.0.30001565",
|
||||
"css-has-pseudo": "6.0.0",
|
||||
"csstype": "3.1.2",
|
||||
"cypress": "13.6.0",
|
||||
"esbuild": "0.19.8",
|
||||
"eslint": "8.54.0",
|
||||
"eslint-plugin-vue": "9.19.2",
|
||||
"happy-dom": "12.10.3",
|
||||
"histoire": "0.17.6",
|
||||
"postcss": "8.4.32",
|
||||
"postcss": "8.4.31",
|
||||
"postcss-easing-gradients": "3.0.1",
|
||||
"postcss-easings": "4.0.0",
|
||||
"postcss-focus-within": "8.0.1",
|
||||
"postcss-focus-within": "8.0.0",
|
||||
"postcss-preset-env": "9.3.0",
|
||||
"rollup": "4.9.1",
|
||||
"rollup-plugin-visualizer": "5.11.0",
|
||||
"rollup": "4.6.1",
|
||||
"rollup-plugin-visualizer": "5.9.3",
|
||||
"sass": "1.69.5",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"typescript": "5.3.3",
|
||||
"vite": "5.0.10",
|
||||
"typescript": "5.3.2",
|
||||
"vite": "4.5.0",
|
||||
"vite-plugin-inject-preload": "1.3.3",
|
||||
"vite-plugin-pwa": "0.17.4",
|
||||
"vite-plugin-pwa": "0.17.2",
|
||||
"vite-plugin-sentry": "1.3.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "1.0.4",
|
||||
"vue-tsc": "1.8.25",
|
||||
"vite-svg-loader": "4.0.0",
|
||||
"vitest": "0.34.6",
|
||||
"vue-tsc": "1.8.24",
|
||||
"wait-on": "7.2.0",
|
||||
"workbox-cli": "7.0.0"
|
||||
},
|
||||
|
|
3175
pnpm-lock.yaml
3175
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -47,6 +47,7 @@
|
|||
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']"/>
|
||||
</BaseButton>
|
||||
<ProjectSettingsDropdown
|
||||
v-if="project.id > 0"
|
||||
class="menu-list-dropdown"
|
||||
:project="project"
|
||||
:level="level"
|
||||
|
@ -57,6 +58,7 @@
|
|||
</BaseButton>
|
||||
</template>
|
||||
</ProjectSettingsDropdown>
|
||||
<span class="list-setting-spacer" v-else></span>
|
||||
</div>
|
||||
<ProjectsNavigation
|
||||
v-if="canNestDeeper && childProjectsOpen && canCollapse"
|
||||
|
|
|
@ -118,6 +118,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch} from 'vue'
|
||||
import {refDebounced} from '@vueuse/core'
|
||||
|
||||
import EditorToolbar from './EditorToolbar.vue'
|
||||
|
||||
|
@ -172,7 +173,6 @@ import {Placeholder} from '@tiptap/extension-placeholder'
|
|||
import {eventToHotkeyString} from '@github/hotkey'
|
||||
import {mergeAttributes} from '@tiptap/core'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||
|
||||
const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
|
@ -200,9 +200,7 @@ const CustomTableCell = TableCell.extend({
|
|||
})
|
||||
|
||||
type CacheKey = `${ITask['id']}-${IAttachment['id']}`
|
||||
const loadedAttachments = ref<{
|
||||
[key: CacheKey]: string
|
||||
}>({})
|
||||
const loadedAttachments = ref<{ [key: CacheKey]: string }>({})
|
||||
|
||||
const CustomImage = Image.extend({
|
||||
addAttributes() {
|
||||
|
@ -274,6 +272,7 @@ const {
|
|||
showSave = false,
|
||||
placeholder = '',
|
||||
editShortcut = '',
|
||||
initialMode = 'edit',
|
||||
} = defineProps<{
|
||||
modelValue: string,
|
||||
uploadCallback?: UploadCallback,
|
||||
|
@ -282,11 +281,13 @@ const {
|
|||
showSave?: boolean,
|
||||
placeholder?: string,
|
||||
editShortcut?: string,
|
||||
initialMode?: Mode,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'save'])
|
||||
|
||||
const internalMode = ref<Mode>('edit')
|
||||
const inputHTML = ref('')
|
||||
const internalMode = ref<Mode>(initialMode)
|
||||
const isEditing = computed(() => internalMode.value === 'edit' && isEditEnabled)
|
||||
|
||||
const editor = useEditor({
|
||||
|
@ -358,28 +359,14 @@ const editor = useEditor({
|
|||
TaskItem.configure({
|
||||
nested: true,
|
||||
onReadOnlyChecked: (node: Node, checked: boolean): boolean => {
|
||||
if (!isEditEnabled) {
|
||||
return false
|
||||
if (isEditEnabled) {
|
||||
node.attrs.checked = checked
|
||||
inputHTML.value = editor.value?.getHTML()
|
||||
bubbleSave()
|
||||
return true
|
||||
}
|
||||
|
||||
// The following is a workaround for this bug:
|
||||
// https://github.com/ueberdosis/tiptap/issues/4521
|
||||
// https://github.com/ueberdosis/tiptap/issues/3676
|
||||
|
||||
editor.value!.state.doc.descendants((subnode, pos) => {
|
||||
if (node.eq(subnode)) {
|
||||
const {tr} = editor.value!.state
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
...node.attrs,
|
||||
checked,
|
||||
})
|
||||
editor.value!.view.dispatch(tr)
|
||||
bubbleSave()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return true
|
||||
return false
|
||||
},
|
||||
}),
|
||||
|
||||
|
@ -389,55 +376,52 @@ const editor = useEditor({
|
|||
BubbleMenu,
|
||||
],
|
||||
onUpdate: () => {
|
||||
bubbleNow()
|
||||
inputHTML.value = editor.value!.getHTML()
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => isEditing.value,
|
||||
() => {
|
||||
editor.value?.setEditable(isEditing.value)
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => modelValue,
|
||||
value => {
|
||||
inputHTML.value = value
|
||||
|
||||
if (!editor?.value) return
|
||||
|
||||
if (editor.value.getHTML() === value) {
|
||||
return
|
||||
}
|
||||
|
||||
setModeAndValue(value)
|
||||
editor.value.commands.setContent(value, false)
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
function bubbleNow() {
|
||||
if (editor.value?.getHTML() === modelValue) {
|
||||
return
|
||||
}
|
||||
const debouncedInputHTML = refDebounced(inputHTML, 1000)
|
||||
watch(debouncedInputHTML, () => bubbleNow())
|
||||
|
||||
emit('update:modelValue', editor.value?.getHTML())
|
||||
function bubbleNow() {
|
||||
emit('update:modelValue', inputHTML.value)
|
||||
}
|
||||
|
||||
function bubbleSave() {
|
||||
bubbleNow()
|
||||
emit('save', editor.value?.getHTML())
|
||||
emit('save', inputHTML.value)
|
||||
if (isEditing.value) {
|
||||
internalMode.value = 'preview'
|
||||
}
|
||||
}
|
||||
|
||||
function setEdit(focus: boolean = true) {
|
||||
function setEdit() {
|
||||
internalMode.value = 'edit'
|
||||
if (focus) {
|
||||
editor.value?.commands.focus()
|
||||
}
|
||||
editor.value?.commands.focus()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => isEditing.value,
|
||||
() => {
|
||||
editor.value?.setEditable(isEditing.value)
|
||||
},
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => editor.value?.destroy())
|
||||
|
||||
const uploadInputRef = ref<HTMLInputElement | null>(null)
|
||||
|
@ -507,17 +491,15 @@ function setLink() {
|
|||
.run()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(() => {
|
||||
internalMode.value = initialMode
|
||||
nextTick(() => {
|
||||
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
||||
input?.addEventListener('paste', handleImagePaste)
|
||||
})
|
||||
if (editShortcut !== '') {
|
||||
document.addEventListener('keydown', setFocusToEditor)
|
||||
}
|
||||
|
||||
await nextTick()
|
||||
|
||||
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
||||
input?.addEventListener('paste', handleImagePaste)
|
||||
|
||||
setModeAndValue(modelValue)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
@ -530,11 +512,6 @@ onBeforeUnmount(() => {
|
|||
}
|
||||
})
|
||||
|
||||
function setModeAndValue(value: string) {
|
||||
internalMode.value = isEditorContentEmpty(value) ? 'edit' : 'preview'
|
||||
editor.value?.commands.setContent(value, false)
|
||||
}
|
||||
|
||||
function handleImagePaste(event) {
|
||||
if (event?.clipboardData?.items?.length === 0) {
|
||||
return
|
||||
|
@ -544,6 +521,7 @@ function handleImagePaste(event) {
|
|||
|
||||
const image = event.clipboardData.items[0]
|
||||
if (image.kind === 'file' && image.type.startsWith('image/')) {
|
||||
console.log('img', image.getAsFile())
|
||||
uploadAndInsertFiles([image.getAsFile()])
|
||||
}
|
||||
}
|
||||
|
@ -560,7 +538,7 @@ function setFocusToEditor(event) {
|
|||
}
|
||||
event.preventDefault()
|
||||
|
||||
if (!isEditing.value && isEditEnabled) {
|
||||
if (initialMode === 'preview' && isEditEnabled && !isEditing.value) {
|
||||
internalMode.value = 'edit'
|
||||
}
|
||||
|
||||
|
|
|
@ -149,15 +149,13 @@ async function addTask() {
|
|||
await Promise.all(newTasks)
|
||||
|
||||
const taskRelationService = new TaskRelationService()
|
||||
const allParentTasks = tasksToCreate.filter(t => t.parent !== null).map(t => t.parent)
|
||||
const relations = tasksToCreate.map(async t => {
|
||||
const createdTask = createdTasks[t.title]
|
||||
if (typeof createdTask === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
const isParent = allParentTasks.includes(t.title)
|
||||
if (t.parent === null && !isParent) {
|
||||
if (t.parent === null) {
|
||||
emit('taskAdded', createdTask)
|
||||
return
|
||||
}
|
||||
|
@ -173,19 +171,10 @@ async function addTask() {
|
|||
relationKind: RELATION_KIND.PARENTTASK,
|
||||
}))
|
||||
|
||||
createdTask.relatedTasks[RELATION_KIND.PARENTTASK] = [{
|
||||
...createdParentTask,
|
||||
relatedTasks: {}, // To avoid endless references
|
||||
}]
|
||||
createdTask.relatedTasks[RELATION_KIND.PARENTTASK] = [createdParentTask]
|
||||
// we're only emitting here so that the relation shows up in the project
|
||||
emit('taskAdded', createdTask)
|
||||
|
||||
createdParentTask.relatedTasks[RELATION_KIND.SUBTASK] = [{
|
||||
...createdTask,
|
||||
relatedTasks: {}, // To avoid endless references
|
||||
}]
|
||||
emit('taskAdded', createdParentTask)
|
||||
|
||||
return rel
|
||||
})
|
||||
await Promise.all(relations)
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
v-model="description"
|
||||
@update:model-value="saveWithDelay"
|
||||
@save="save"
|
||||
:initial-mode="isEditorContentEmpty(description) ? 'edit' : 'preview'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -38,6 +39,7 @@ import Editor from '@/components/input/AsyncEditor'
|
|||
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||
|
||||
type AttachmentUploadFunction = (file: File, onSuccess: (attachmentUrl: string) => void) => Promise<string>
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
'is-loading': loadingInternal || loading,
|
||||
'draggable': !(loadingInternal || loading),
|
||||
'has-light-text': color !== TASK_DEFAULT_COLOR && !colorIsDark(color),
|
||||
'has-custom-background-color': color !== TASK_DEFAULT_COLOR ? color : undefined,
|
||||
}"
|
||||
:style="{'background-color': color !== TASK_DEFAULT_COLOR ? color : undefined}"
|
||||
@click.exact="openTaskDetail()"
|
||||
|
@ -49,10 +48,7 @@
|
|||
</progress>
|
||||
<div class="footer">
|
||||
<labels :labels="task.labels"/>
|
||||
<priority-label
|
||||
:priority="task.priority"
|
||||
:done="task.done"
|
||||
class="is-inline-flex is-align-items-center"/>
|
||||
<priority-label :priority="task.priority" :done="task.done" class="is-inline-flex is-align-items-center"/>
|
||||
<assignee-list
|
||||
v-if="task.assignees.length > 0"
|
||||
:assignees="task.assignees"
|
||||
|
@ -64,7 +60,7 @@
|
|||
<span class="icon" v-if="task.attachments.length > 0">
|
||||
<icon icon="paperclip"/>
|
||||
</span>
|
||||
<span v-if="!isEditorContentEmpty(task.description)" class="icon">
|
||||
<span v-if="task.description" class="icon">
|
||||
<icon icon="align-left"/>
|
||||
</span>
|
||||
<span class="icon" v-if="task.repeatAfter.amount > 0">
|
||||
|
@ -95,7 +91,6 @@ import {useTaskStore} from '@/stores/tasks'
|
|||
import AssigneeList from '@/components/tasks/partials/assigneeList.vue'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {playPopSound} from '@/helpers/playPop'
|
||||
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -118,7 +113,7 @@ async function toggleTaskDone(task: ITask) {
|
|||
...task,
|
||||
done: !task.done,
|
||||
})
|
||||
|
||||
|
||||
if (updatedTask.done && useAuthStore().settings.frontendSettings.playSoundWhenDone) {
|
||||
playPopSound()
|
||||
}
|
||||
|
@ -284,16 +279,6 @@ $task-background: var(--white);
|
|||
width: auto;
|
||||
}
|
||||
|
||||
&.has-custom-background-color {
|
||||
color: hsl(215, 27.9%, 16.9%); // copied from grey-800 to avoid different values in dark mode
|
||||
|
||||
.footer .icon,
|
||||
.due-date,
|
||||
.priority-label {
|
||||
background: hsl(220, 13%, 91%);
|
||||
}
|
||||
}
|
||||
|
||||
&.has-light-text {
|
||||
color: var(--white);
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
<span class="project-task-icon" v-if="task.attachments.length > 0">
|
||||
<icon icon="paperclip"/>
|
||||
</span>
|
||||
<span class="project-task-icon" v-if="!isEditorContentEmpty(task.description)">
|
||||
<span class="project-task-icon" v-if="task.description">
|
||||
<icon icon="align-left"/>
|
||||
</span>
|
||||
<span class="project-task-icon" v-if="task.repeatAfter.amount > 0">
|
||||
|
@ -184,7 +184,6 @@ import AssigneeList from '@/components/tasks/partials/assigneeList.vue'
|
|||
import {useIntervalFn} from '@vueuse/core'
|
||||
import {playPopSound} from '@/helpers/playPop'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||
|
||||
const {
|
||||
theTask,
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import {parseURL} from 'ufo'
|
||||
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
import type {IProvider} from '@/types/IProvider'
|
||||
|
||||
export const redirectToProvider = (provider: IProvider) => {
|
||||
export const redirectToProvider = (provider: IProvider, redirectUrl = '') => {
|
||||
|
||||
// We're not using the redirect url provided by the server to allow redirects when using the electron app.
|
||||
// The implications are not quite clear yet hence the logic to pass in another redirect url still exists.
|
||||
const redirectUrl = `${window.location.href.replace('/login', '')}/auth/openid/`
|
||||
if (redirectUrl === '') {
|
||||
const {host, protocol} = parseURL(window.location.href)
|
||||
redirectUrl = `${protocol}//${host}/auth/openid/`
|
||||
}
|
||||
|
||||
const state = createRandomID(24)
|
||||
localStorage.setItem('state', state)
|
||||
|
@ -13,7 +18,7 @@ export const redirectToProvider = (provider: IProvider) => {
|
|||
window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUrl}${provider.key}&response_type=code&scope=openid email profile&state=${state}`
|
||||
}
|
||||
export const redirectToProviderOnLogout = (provider: IProvider) => {
|
||||
if (provider.logoutUrl.length > 0) {
|
||||
if (provider.logoutUrl.length > 0){
|
||||
window.location.href = `${provider.logoutUrl}`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,7 +160,6 @@
|
|||
"expired": "This token has expired {ago}.",
|
||||
"tokenCreatedSuccess": "Here is your new api token: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Store it in a secure location, you won't see it again!",
|
||||
"selectAll": "Select all",
|
||||
"delete": {
|
||||
"header": "Delete this token",
|
||||
"text1": "Are you sure you want to delete the token \"{token}\"?",
|
||||
|
|
|
@ -27,7 +27,7 @@ function redirectToProviderIfNothingElseIsEnabled() {
|
|||
(window.location.pathname.startsWith('/login') || window.location.pathname === '/') && // Kinda hacky, but prevents an endless loop.
|
||||
window.location.search.includes('redirectToProvider=true')
|
||||
) {
|
||||
redirectToProvider(auth.openidConnect.providers[0])
|
||||
redirectToProvider(auth.openidConnect.providers[0], auth.openidConnect.redirectUrl)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ const workboxVersion = 'v7.0.0'
|
|||
importScripts(`${fullBaseUrl}workbox-${workboxVersion}/workbox-sw.js`)
|
||||
workbox.setConfig({
|
||||
modulePathPrefix: `${fullBaseUrl}workbox-${workboxVersion}`,
|
||||
debug: Boolean(import.meta.env.VITE_WORKBOX_DEBUG),
|
||||
})
|
||||
|
||||
import { precacheAndRoute } from 'workbox-precaching'
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
>
|
||||
<div class="bucket-header" @click="() => unCollapseBucket(bucket)">
|
||||
<span
|
||||
v-if="project?.doneBucketId === bucket.id"
|
||||
v-if="project.doneBucketId === bucket.id"
|
||||
class="icon is-small has-text-success mr-2"
|
||||
v-tooltip="$t('project.kanban.doneBucketHint')"
|
||||
>
|
||||
|
@ -97,7 +97,7 @@
|
|||
<dropdown-item
|
||||
@click.stop="toggleDoneBucket(bucket)"
|
||||
v-tooltip="$t('project.kanban.doneBucketHintExtended')"
|
||||
:icon-class="{'has-text-success': bucket.id === project?.doneBucketId}"
|
||||
:icon-class="{'has-text-success': bucket.id === project.doneBucketId}"
|
||||
icon="check-double"
|
||||
>
|
||||
{{ $t('project.kanban.doneBucket') }}
|
||||
|
@ -436,7 +436,7 @@ async function updateTaskPosition(e) {
|
|||
oldBucket !== undefined && // This shouldn't actually be `undefined`, but let's play it safe.
|
||||
newBucket.id !== oldBucket.id
|
||||
) {
|
||||
newTask.done = project.value?.doneBucketId === newBucket.id
|
||||
newTask.done = project.value.doneBucketId === newBucket.id
|
||||
}
|
||||
if (
|
||||
oldBucket !== undefined && // This shouldn't actually be `undefined`, but let's play it safe.
|
||||
|
@ -620,7 +620,7 @@ async function toggleDefaultBucket(bucket: IBucket) {
|
|||
}
|
||||
|
||||
async function toggleDoneBucket(bucket: IBucket) {
|
||||
const doneBucketId = project.value?.doneBucketId === bucket.id
|
||||
const doneBucketId = project.value.doneBucketId === bucket.id
|
||||
? 0
|
||||
: bucket.id
|
||||
|
||||
|
|
|
@ -175,11 +175,7 @@ const tasks = ref<ITask[]>([])
|
|||
watch(
|
||||
allTasks,
|
||||
() => {
|
||||
tasks.value = [...allTasks.value]
|
||||
if (projectId < 0) {
|
||||
return
|
||||
}
|
||||
tasks.value = tasks.value.filter(t => typeof t.relatedTasks?.parenttask === 'undefined')
|
||||
tasks.value = [...allTasks.value].filter(t => typeof t.relatedTasks?.parenttask === 'undefined')
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -245,9 +241,9 @@ function updateTaskList(task: ITask) {
|
|||
loadTasks()
|
||||
}
|
||||
else {
|
||||
allTasks.value = [
|
||||
tasks.value = [
|
||||
task,
|
||||
...allTasks.value,
|
||||
...tasks.value,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
'is-modal': isModal,
|
||||
}"
|
||||
>
|
||||
<!-- Removing everything until the task is loaded to prevent empty initialization of other components -->
|
||||
<div class="task-view" v-if="visible">
|
||||
<div class="task-view">
|
||||
<Heading
|
||||
:task="task"
|
||||
@update:task="Object.assign(task, $event)"
|
||||
|
@ -606,8 +605,7 @@ watch(
|
|||
}
|
||||
|
||||
try {
|
||||
const loaded = await taskService.get({id})
|
||||
Object.assign(task.value, loaded)
|
||||
Object.assign(task.value, await taskService.get({id}))
|
||||
attachmentStore.set(task.value.attachments)
|
||||
taskColor.value = task.value.hexColor
|
||||
setActiveFields()
|
||||
|
|
|
@ -12,24 +12,22 @@ import 'flatpickr/dist/flatpickr.css'
|
|||
import {useI18n} from 'vue-i18n'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import Message from '@/components/misc/message.vue'
|
||||
import type {IApiToken} from '@/modelTypes/IApiToken'
|
||||
|
||||
const service = new ApiTokenService()
|
||||
const tokens = ref<IApiToken[]>([])
|
||||
const tokens = ref([])
|
||||
const apiDocsUrl = window.API_URL + '/docs'
|
||||
const showCreateForm = ref(false)
|
||||
const availableRoutes = ref(null)
|
||||
const newToken = ref<IApiToken>(new ApiTokenModel())
|
||||
const newToken = ref(new ApiTokenModel())
|
||||
const newTokenExpiry = ref<string | number>(30)
|
||||
const newTokenExpiryCustom = ref(new Date())
|
||||
const newTokenPermissions = ref({})
|
||||
const newTokenPermissionsGroup = ref({})
|
||||
const newTokenTitleValid = ref(true)
|
||||
const apiTokenTitle = ref()
|
||||
const tokenCreatedSuccessMessage = ref('')
|
||||
|
||||
const showDeleteModal = ref<boolean>(false)
|
||||
const tokenToDelete = ref<IApiToken>()
|
||||
const showDeleteModal = ref(false)
|
||||
const tokenToDelete = ref(null)
|
||||
|
||||
const {t} = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
|
@ -67,8 +65,8 @@ function resetPermissions() {
|
|||
async function deleteToken() {
|
||||
await service.delete(tokenToDelete.value)
|
||||
showDeleteModal.value = false
|
||||
const index = tokens.value.findIndex(el => el.id === tokenToDelete.value.id)
|
||||
tokenToDelete.value = null
|
||||
const index = tokens.value.findIndex(el => el.id === tokenToDelete.value.id)
|
||||
if (index === -1) {
|
||||
return
|
||||
}
|
||||
|
@ -113,32 +111,6 @@ async function createToken() {
|
|||
function formatPermissionTitle(title: string): string {
|
||||
return title.replaceAll('_', ' ')
|
||||
}
|
||||
|
||||
function selectPermissionGroup(group: string, checked: boolean) {
|
||||
Object.entries(availableRoutes.value[group]).forEach(entry => {
|
||||
const [key] = entry
|
||||
newTokenPermissions.value[group][key] = checked
|
||||
})
|
||||
}
|
||||
|
||||
function toggleGroupPermissionsFromChild(group: string, checked: boolean) {
|
||||
if (checked) {
|
||||
// Check if all permissions of that group are checked and check the "select all" checkbox in that case
|
||||
let allChecked = true
|
||||
Object.entries(availableRoutes.value[group]).forEach(entry => {
|
||||
const [key] = entry
|
||||
if (!newTokenPermissions.value[group][key]) {
|
||||
allChecked = false
|
||||
}
|
||||
})
|
||||
|
||||
if (allChecked) {
|
||||
newTokenPermissionsGroup.value[group] = true
|
||||
}
|
||||
} else {
|
||||
newTokenPermissionsGroup.value[group] = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -243,31 +215,15 @@ function toggleGroupPermissionsFromChild(group: string, checked: boolean) {
|
|||
<p>{{ $t('user.settings.apiTokens.permissionExplanation') }}</p>
|
||||
<div v-for="(routes, group) in availableRoutes" class="mb-2" :key="group">
|
||||
<strong class="is-capitalized">{{ formatPermissionTitle(group) }}</strong><br/>
|
||||
<template
|
||||
v-if="Object.keys(routes).length > 1"
|
||||
>
|
||||
<fancycheckbox
|
||||
class="mr-2 is-italic"
|
||||
v-model="newTokenPermissionsGroup[group]"
|
||||
@update:model-value="checked => selectPermissionGroup(group, checked)"
|
||||
>
|
||||
{{ $t('user.settings.apiTokens.selectAll') }}
|
||||
</fancycheckbox>
|
||||
<br/>
|
||||
</template>
|
||||
<template
|
||||
<fancycheckbox
|
||||
v-for="(paths, route) in routes"
|
||||
:key="group+'-'+route"
|
||||
class="mr-2 is-capitalized"
|
||||
v-model="newTokenPermissions[group][route]"
|
||||
>
|
||||
<fancycheckbox
|
||||
class="mr-2 is-capitalized"
|
||||
v-model="newTokenPermissions[group][route]"
|
||||
@update:model-value="checked => toggleGroupPermissionsFromChild(group, checked)"
|
||||
>
|
||||
{{ formatPermissionTitle(route) }}
|
||||
</fancycheckbox>
|
||||
<br/>
|
||||
</template>
|
||||
{{ formatPermissionTitle(route) }}
|
||||
</fancycheckbox>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user