Compare commits

..

12 Commits

Author SHA1 Message Date
renovate c2c563c636 fix(deps): update sentry-javascript monorepo to v7.73.0
continuous-integration/drone/pr Build is failing Details
2023-10-02 12:08:43 +00:00
kolaente 66c7a05cdb
fix(project): correctly show project color next to project title in list view
continuous-integration/drone/push Build is passing Details
Resolves https://community.vikunja.io/t/color-bubbles-not-showing-after-import/1648
2023-09-29 20:46:02 +02:00
kolaente 287daf9125
fix(auth): silently discard invalid auth tokens and log the user out
continuous-integration/drone/push Build is passing Details
2023-09-29 10:38:00 +02:00
kolaente 8507808058
fix(tasks): ignore empty lines when adding multiple tasks at once
continuous-integration/drone/push Build is passing Details
Resolves #3732 (comment)
2023-09-29 10:06:38 +02:00
kolaente 93c155dd2f
fix(quick actions): always open quick actions with hotkey, even if other inputs are focused
continuous-integration/drone/push Build is passing Details
Resolves #3743
2023-09-29 09:48:06 +02:00
kolaente b1c4748969
fix(background): unsplash author credit in dark mode
continuous-integration/drone/push Build is passing Details
2023-09-29 09:33:30 +02:00
kolaente 0887860b2a
fix(task): priority label spacing
continuous-integration/drone/push Build is passing Details
2023-09-29 09:32:00 +02:00
kolaente 0b1c8ed4dd
fix(attachments): layout and coloring in dark mode
continuous-integration/drone/push Build is passing Details
2023-09-29 09:28:51 +02:00
kolaente 3988a3f9f8
fix(task): correct spacing to task and project title
continuous-integration/drone/push Build is passing Details
2023-09-29 09:19:17 +02:00
Frederick [Bot] 11b65e844c [skip ci] Updated translations via Crowdin 2023-09-28 00:08:46 +00:00
renovate 5c23343172 chore(deps): update dev-dependencies (#3747)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3747
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-09-27 07:34:49 +00:00
renovate 01a4335c7c chore(deps): update node.js to v20.7 (#3736)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3736
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-09-27 07:34:22 +00:00
13 changed files with 656 additions and 556 deletions

View File

@ -42,7 +42,7 @@ steps:
# - .cache
- name: dependencies
image: node:20.5-alpine
image: node:20.7-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -55,7 +55,7 @@ steps:
# - restore-cache
- name: lint
image: node:20.5-alpine
image: node:20.7-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -66,7 +66,7 @@ steps:
- dependencies
- name: build-prod
image: node:20.5-alpine
image: node:20.7-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -77,7 +77,7 @@ steps:
- dependencies
- name: test-unit
image: node:20.5-alpine
image: node:20.7-alpine
pull: always
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
@ -87,7 +87,7 @@ steps:
- name: typecheck
failure: ignore
image: node:20.5-alpine
image: node:20.7-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -202,7 +202,7 @@ steps:
# - .cache
- name: build
image: node:20.5-alpine
image: node:20.7-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -285,7 +285,7 @@ steps:
# - .cache
- name: build
image: node:20.5-alpine
image: node:20.7-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -532,6 +532,6 @@ steps:
src/i18n/lang/en.json: en.json
---
kind: signature
hmac: 8df53c07dc07c8a22c4aca9effd0ef8335cbed6ed55c00caeb335e6bc15458a6
hmac: 53db9cca5f819e82969d6161c5e921d9988193f60bceacdf1a677dfea71ce6f3
...

View File

@ -3,7 +3,7 @@
# │─││ │││ │ │
# ┘─┘┘─┘┘┘─┘┘─┘
FROM --platform=$BUILDPLATFORM node:20.5-alpine AS builder
FROM --platform=$BUILDPLATFORM node:20.7-alpine AS builder
WORKDIR /build

View File

@ -53,8 +53,8 @@
"@infectoone/vue-ganttastic": "2.1.4",
"@intlify/unplugin-vue-i18n": "0.13.0",
"@kyvg/vue3-notification": "2.9.1",
"@sentry/tracing": "7.72.0",
"@sentry/vue": "7.72.0",
"@sentry/tracing": "7.73.0",
"@sentry/vue": "7.73.0",
"@vueuse/core": "10.4.1",
"@vueuse/router": "10.4.1",
"axios": "1.5.0",
@ -92,55 +92,55 @@
"@4tw/cypress-drag-drop": "2.2.5",
"@cypress/vite-dev-server": "5.0.6",
"@cypress/vue": "5.0.5",
"@faker-js/faker": "8.0.2",
"@faker-js/faker": "8.1.0",
"@histoire/plugin-screenshot": "0.17.0",
"@histoire/plugin-vue": "0.17.1",
"@rushstack/eslint-patch": "1.4.0",
"@rushstack/eslint-patch": "1.5.0",
"@tsconfig/node18": "18.2.2",
"@types/codemirror": "5.60.10",
"@types/dompurify": "3.0.2",
"@types/dompurify": "3.0.3",
"@types/flexsearch": "0.7.4",
"@types/is-touch-device": "1.0.0",
"@types/lodash.debounce": "4.0.7",
"@types/marked": "5.0.1",
"@types/node": "18.17.16",
"@types/node": "18.18.0",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.2",
"@typescript-eslint/eslint-plugin": "6.7.0",
"@typescript-eslint/parser": "6.7.0",
"@types/sortablejs": "1.15.3",
"@typescript-eslint/eslint-plugin": "6.7.3",
"@typescript-eslint/parser": "6.7.3",
"@vitejs/plugin-legacy": "4.1.1",
"@vitejs/plugin-vue": "4.3.4",
"@vue/eslint-config-typescript": "11.0.3",
"@vue/test-utils": "2.4.1",
"@vue/tsconfig": "0.4.0",
"autoprefixer": "10.4.15",
"browserslist": "4.21.10",
"caniuse-lite": "1.0.30001534",
"autoprefixer": "10.4.16",
"browserslist": "4.22.0",
"caniuse-lite": "1.0.30001540",
"css-has-pseudo": "6.0.0",
"csstype": "3.1.2",
"cypress": "12.17.4",
"esbuild": "0.19.3",
"eslint": "8.49.0",
"eslint": "8.50.0",
"eslint-plugin-vue": "9.17.0",
"happy-dom": "10.11.2",
"histoire": "0.17.0",
"postcss": "8.4.29",
"histoire": "0.17.2",
"postcss": "8.4.30",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.0",
"postcss-preset-env": "9.1.3",
"rollup": "3.29.1",
"postcss-preset-env": "9.1.4",
"rollup": "3.29.3",
"rollup-plugin-visualizer": "5.9.2",
"sass": "1.67.0",
"start-server-and-test": "2.0.0",
"sass": "1.68.0",
"start-server-and-test": "2.0.1",
"typescript": "5.2.2",
"vite": "4.4.9",
"vite-plugin-inject-preload": "1.3.3",
"vite-plugin-pwa": "0.16.5",
"vite-plugin-sentry": "1.3.0",
"vite-svg-loader": "4.0.0",
"vitest": "0.34.4",
"vue-tsc": "1.8.11",
"vitest": "0.34.5",
"vue-tsc": "1.8.15",
"wait-on": "7.0.1",
"workbox-cli": "7.0.0"
},

File diff suppressed because it is too large Load Diff

View File

@ -27,10 +27,7 @@
</div>
<div class="navbar-end">
<BaseButton @click="openQuickActions" class="trigger-button" v-shortcut="'Control+k'"
:title="$t('keyboardShortcuts.quickSearch')">
<icon icon="search" />
</BaseButton>
<OpenQuickActions/>
<Notifications />
<dropdown>
<template #trigger="{ toggleOpen, open }">
@ -80,6 +77,7 @@ import Notifications from '@/components/notifications/notifications.vue'
import Logo from '@/components/home/Logo.vue'
import BaseButton from '@/components/base/BaseButton.vue'
import MenuButton from '@/components/home/MenuButton.vue'
import OpenQuickActions from '@/components/misc/OpenQuickActions.vue'
import { getProjectTitle } from '@/helpers/getProjectTitle'
@ -98,10 +96,6 @@ const authStore = useAuthStore()
const configStore = useConfigStore()
const imprintUrl = computed(() => configStore.legal.imprintUrl)
const privacyPolicyUrl = computed(() => configStore.legal.privacyPolicyUrl)
function openQuickActions() {
baseStore.setQuickActionsActive(true)
}
</script>
<style lang="scss" scoped>

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
import BaseButton from '@/components/base/BaseButton.vue'
import {useBaseStore} from '@/stores/base'
import {onBeforeUnmount, onMounted} from 'vue'
import {eventToHotkeyString} from '@github/hotkey'
const baseStore = useBaseStore()
const GLOBAL_HOTKEY = 'Control+k'
// See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660
function openQuickActionsViaHotkey(event) {
const hotkeyString = eventToHotkeyString(event)
if (!hotkeyString) return
if (hotkeyString !== GLOBAL_HOTKEY) return
event.preventDefault()
openQuickActions()
}
onMounted(() => {
document.addEventListener('keydown', openQuickActionsViaHotkey)
})
onBeforeUnmount(() => {
document.removeEventListener('keydown', openQuickActionsViaHotkey)
})
function openQuickActions() {
baseStore.setQuickActionsActive(true)
}
</script>
<template>
<BaseButton
@click="openQuickActions"
class="trigger-button"
:title="$t('keyboardShortcuts.quickSearch')"
>
<icon icon="search"/>
</BaseButton>
</template>

View File

@ -37,8 +37,8 @@
>
<div class="filename">
{{ a.file.name }}
<span
v-if="task.coverImageAttachmentId === a.id"
<span
v-if="task.coverImageAttachmentId === a.id"
class="is-task-cover"
>
{{ $t('task.attachment.usedAsCover') }}
@ -115,18 +115,20 @@
</x-button>
<!-- Dropzone -->
<div
:class="{ hidden: !isOverDropZone }"
class="dropzone"
v-if="editEnabled"
>
<div class="drop-hint">
<div class="icon">
<icon icon="cloud-upload-alt"/>
<Teleport to="body">
<div
:class="{ hidden: !isOverDropZone }"
class="dropzone"
v-if="editEnabled"
>
<div class="drop-hint">
<div class="icon">
<icon icon="cloud-upload-alt"/>
</div>
<div class="hint">{{ $t('task.attachment.drop') }}</div>
</div>
<div class="hint">{{ $t('task.attachment.drop') }}</div>
</div>
</div>
</Teleport>
<!-- Delete modal -->
<modal
@ -323,7 +325,7 @@ async function setCoverImage(attachment: IAttachment | null) {
.dropzone {
position: fixed;
background: rgba(250, 250, 250, 0.8);
background: hsla(var(--grey-100-hsl), 0.8);
top: 0;
left: 0;
bottom: 0;
@ -356,11 +358,11 @@ async function setCoverImage(attachment: IAttachment | null) {
.hint {
margin: .5rem auto 2rem;
border-radius: 2px;
border-radius: $radius;
box-shadow: var(--shadow-md);
background: var(--primary);
padding: 1rem;
color: var(--white);
color: $white; // Should always be white because of the background, regardless of the theme
width: 100%;
max-width: 300px;
}

View File

@ -14,7 +14,7 @@
<template v-if="priority === priorities.URGENT">{{ $t('task.priority.urgent') }}</template>
<template v-if="priority === priorities.DO_NOW">{{ $t('task.priority.doNow') }}</template>
</span>
<span class="icon" v-if="priority === priorities.DO_NOW">
<span class="icon pr-0" v-if="priority === priorities.DO_NOW">
<icon icon="exclamation"/>
</span>
</span>

View File

@ -12,7 +12,7 @@
/>
<ColorBubble
v-if="showProjectColor && projectColor !== '' && currentProject?.id !== task.projectId"
v-if="!showProjectSeparately && projectColor !== '' && currentProject?.id !== task.projectId"
:color="projectColor"
class="mr-1"
/>
@ -25,7 +25,7 @@
<router-link
v-if="showProject && typeof project !== 'undefined'"
:to="{ name: 'project.list', params: { projectId: task.projectId } }"
class="task-project"
class="task-project mr-1"
:class="{'mr-2': task.hexColor !== ''}"
v-tooltip="$t('task.detail.belongsToProject', {project: project.title})"
>
@ -38,7 +38,7 @@
class="mr-1"
/>
<priority-label :priority="task.priority" :done="task.done"/>
<priority-label :priority="task.priority" :done="task.done" class="pr-2"/>
<router-link
:to="taskDetailRoute"
@ -107,8 +107,14 @@
{{ task.percentDone * 100 }}%
</progress>
<ColorBubble
v-if="showProjectSeparately && projectColor !== '' && currentProject?.id !== task.projectId"
:color="projectColor"
class="mr-1"
/>
<router-link
v-if="!showProject && currentProject?.id !== task.projectId && project"
v-if="showProjectSeparately"
:to="{ name: 'project.list', params: { projectId: task.projectId } }"
class="task-project"
v-tooltip="$t('task.detail.belongsToProject', {project: project.title})"
@ -132,7 +138,6 @@
<single-task-in-project
:key="subtask.id"
:the-task="getTaskById(subtask.id)"
:show-project-color="showProjectColor"
:disabled="disabled"
:can-mark-as-done="canMarkAsDone"
:all-tasks="allTasks"
@ -180,7 +185,6 @@ const {
isArchived = false,
showProject = false,
disabled = false,
showProjectColor = false,
canMarkAsDone = true,
allTasks = [],
} = defineProps<{
@ -188,7 +192,6 @@ const {
isArchived?: boolean,
showProject?: boolean,
disabled?: boolean,
showProjectColor?: boolean,
canMarkAsDone?: boolean,
allTasks?: ITask[],
}>()
@ -232,6 +235,8 @@ const taskStore = useTaskStore()
const project = computed(() => projectStore.projects[task.value.projectId])
const projectColor = computed(() => project.value ? project.value?.hexColor : '')
const showProjectSeparately = computed(() => !showProject && currentProject.value?.id !== task.value.projectId && project.value)
const currentProject = computed(() => {
return typeof baseStore.currentProject === 'undefined' ? {
id: 0,

View File

@ -17,7 +17,9 @@ const spaceRegex = /^ */
* relation between each other.
*/
export function parseSubtasksViaIndention(taskTitles: string, prefixMode: PrefixMode): TaskWithParent[] {
let titles = taskTitles.split(/[\r\n]+/)
let titles = taskTitles
.split(/[\r\n]+/)
.filter(t => t.replace(/\s/g, '').length > 0) // Remove titles which are empty or only contain spaces / tabs
if (titles.length == 0) {
return []

View File

@ -146,26 +146,26 @@
}
},
"apiTokens": {
"title": "API Tokens",
"title": "APIトークン",
"general": "API tokens allow you to use Vikunja's API without user credentials.",
"apiDocs": "Check out the api docs",
"createAToken": "Create a token",
"createToken": "Create token",
"30d": "30 Days",
"60d": "60 Days",
"90d": "90 Days",
"30d": "30",
"60d": "60",
"90d": "90",
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
"titleRequired": "The title is required",
"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!",
"tokenCreatedNotSeeAgain": "このトークンは二度と表示されません。安全な場所に保管してください。",
"delete": {
"header": "Delete this token",
"text1": "Are you sure you want to delete the token \"{token}\"?",
"text2": "This will revoke access to all applications or integrations using it. You cannot undo this."
},
"attributes": {
"title": "Title",
"title": "トークン名",
"titlePlaceholder": "Enter a title you will recognize later",
"expiresAt": "Expires at",
"permissions": "Permissions"
@ -332,21 +332,21 @@
"title": "カンバン",
"limit": "Limit: {limit}",
"noLimit": "未設定",
"doneBucket": "Done bucket",
"doneBucket": "バケットを完了",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"defaultBucket": "Default bucket",
"defaultBucket": "デフォルトのバケット",
"defaultBucketHint": "When creating tasks without specifying a bucket, they will be added to this bucket.",
"defaultBucketSavedSuccess": "The default bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "タスクの追加",
"addAnotherTask": "他のタスクを追加",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"addBucket": "新しいバケットの作成",
"addBucketPlaceholder": "新しいバケット名を入力…",
"deleteHeaderBucket": "バケットの削除",
"deleteBucketText1": "このバケットを削除して本当によろしいですか?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
@ -609,7 +609,7 @@
"closePopup": "閉じる",
"organization": "Organization",
"management": "Management",
"dateAndTime": "Date and time",
"dateAndTime": "日付と時刻",
"delete": {
"header": "タスクの削除",
"text1": "このタスクを削除して本当によろしいですか?",
@ -770,7 +770,7 @@
"repeat": {
"everyDay": "毎日",
"everyWeek": "毎週",
"every30d": "Every 30 Days",
"every30d": "30日ごと",
"mode": "繰り返しモード",
"monthly": "毎月",
"fromCurrentDate": "現在時刻からの間隔",

View File

@ -226,15 +226,20 @@ export const useAuthStore = defineStore('auth', () => {
const jwt = getToken()
let isAuthenticated = false
if (jwt) {
const base64 = jwt
.split('.')[1]
.replace('-', '+')
.replace('_', '/')
const info = new UserModel(JSON.parse(atob(base64)))
const ts = Math.round((new Date()).getTime() / MILLISECONDS_A_SECOND)
isAuthenticated = info.exp >= ts
// Settings should only be loaded from the api request, not via the jwt
setUser(info, false)
try {
const base64 = jwt
.split('.')[1]
.replace('-', '+')
.replace('_', '/')
const info = new UserModel(JSON.parse(atob(base64)))
const ts = Math.round((new Date()).getTime() / MILLISECONDS_A_SECOND)
isAuthenticated = info.exp >= ts
// Settings should only be loaded from the api request, not via the jwt
setUser(info, false)
} catch (e) {
logout()
}
if (isAuthenticated) {
await refreshUserInfo()
@ -292,11 +297,14 @@ export const useAuthStore = defineStore('auth', () => {
return newUser
} catch (e) {
if(e?.response?.data?.message === 'invalid or expired jwt') {
logout()
if(e?.response?.status === 401 ||
e?.response?.data?.message === 'missing, malformed, expired or otherwise invalid token provided') {
await logout()
return
}
console.log('continuerd')
const cause = {e}
if (typeof e?.response?.data?.message !== 'undefined') {

View File

@ -278,7 +278,7 @@ async function removeBackground() {
background: rgba(0, 0, 0, 0.5);
font-size: .75rem;
font-weight: bold;
color: var(--white);
color: $white;
transition: opacity $transition;
}
.image-search__result-item:hover .image-search__info {