1
0
forked from vikunja/frontend

Compare commits

..

1 Commits

Author SHA1 Message Date
67d770b95b chore(deps): update pnpm to v8.6.12 2023-08-06 12:05:32 +00:00
22 changed files with 875 additions and 3164 deletions

2
.nvmrc
View File

@ -1 +1 @@
18.17.1
18.17.0

View File

@ -57,7 +57,6 @@ ENV VIKUNJA_SENTRY_ENABLED false
ENV VIKUNJA_SENTRY_DSN https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480
ENV VIKUNJA_PROJECT_INFINITE_NESTING_ENABLED false
ENV VIKUNJA_ALLOW_ICON_CHANGES true
ENV VIKUNJA_CUSTOM_LOGO_URL ""
COPY docker/injector.sh /docker-entrypoint.d/50-injector.sh
COPY docker/ipv6-disable.sh /docker-entrypoint.d/60-ipv6-disable.sh

View File

@ -1,5 +1,4 @@
import {UserFactory} from '../../factories/user'
import {ProjectFactory} from '../../factories/project'
const testAndAssertFailed = fixture => {
cy.intercept(Cypress.env('API_URL') + '/login*').as('login')
@ -14,28 +13,26 @@ const testAndAssertFailed = fixture => {
cy.get('div.message.danger').contains('Wrong username or password.')
}
const credentials = {
username: 'test',
password: '1234',
}
function login() {
cy.get('input[id=username]').type(credentials.username)
cy.get('input[id=password]').type(credentials.password)
cy.get('.button').contains('Login').click()
cy.url().should('include', '/')
}
const username = 'test'
context('Login', () => {
beforeEach(() => {
UserFactory.create(1, {username: credentials.username})
UserFactory.create(1, {username})
})
it('Should log in with the right credentials', () => {
const fixture = {
username: 'test',
password: '1234',
}
cy.visit('/login')
login()
cy.get('input[id=username]').type(fixture.username)
cy.get('input[id=password]').type(fixture.password)
cy.get('.button').contains('Login').click()
cy.url().should('include', '/')
cy.clock(1625656161057) // 13:00
cy.get('h2').should('contain', `Hi ${credentials.username}!`)
cy.get('h2').should('contain', `Hi ${fixture.username}!`)
})
it('Should fail with a bad password', () => {
@ -60,15 +57,4 @@ context('Login', () => {
cy.visit('/')
cy.url().should('include', '/login')
})
it('Should redirect to the previous route after logging in', () => {
const projects = ProjectFactory.create(1)
cy.visit(`/projects/${projects[0].id}/list`)
cy.url().should('include', '/login')
login()
cy.url().should('include', `/projects/${projects[0].id}/list`)
})
})

View File

@ -13,6 +13,5 @@ sed -ri "s:^(\s*window.SENTRY_ENABLED\s*=)\s*.+:\1 ${VIKUNJA_SENTRY_ENABLED}:g"
sed -ri "s:^(\s*window.SENTRY_DSN\s*=)\s*.+:\1 '${VIKUNJA_SENTRY_DSN}':g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.PROJECT_INFINITE_NESTING_ENABLED\s*=)\s*.+:\1 '${VIKUNJA_PROJECT_INFINITE_NESTING_ENABLED}':g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.ALLOW_ICON_CHANGES\s*=)\s*.+:\1 ${VIKUNJA_ALLOW_ICON_CHANGES}:g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.CUSTOM_LOGO_URL\s*=)\s*.+:\1 ${VIKUNJA_CUSTOM_LOGO_URL}:g" /usr/share/nginx/html/index.html
date -uIseconds | xargs echo 'info: started at'

View File

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1692494774,
"narHash": "sha256-noGVoOTyZ2Kr5OFglzKYOX48cx3hggdCPbXrYMG2FDw=",
"lastModified": 1685498995,
"narHash": "sha256-rdyjnkq87tJp+T2Bm1OD/9NXKSsh/vLlPeqCc/mm7qs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3476a10478587dec90acb14ec6bde0966c545cc0",
"rev": "9cfaa8a1a00830d17487cb60a19bb86f96f09b27",
"type": "github"
},
"original": {

View File

@ -32,8 +32,6 @@
window.PROJECT_INFINITE_NESTING_ENABLED = false
// Allow changing the logo and other icons based on various occasions throughout the year.
window.ALLOW_ICON_CHANGES = true
// Allow using a custom logo via external URL.
window.CUSTOM_LOGO_URL = ''
</script>
</body>
</html>

View File

@ -45,9 +45,9 @@
"story:preview": "histoire preview"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-regular-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/vue-fontawesome": "3.0.3",
"@github/hotkey": "2.0.1",
"@infectoone/vue-ganttastic": "2.1.4",
@ -55,7 +55,7 @@
"@kyvg/vue3-notification": "2.9.1",
"@sentry/tracing": "7.60.0",
"@sentry/vue": "7.60.0",
"@vueuse/core": "10.3.0",
"@vueuse/core": "10.2.1",
"axios": "1.4.0",
"blurhash": "2.0.5",
"bulma-css-variables": "0.9.33",
@ -74,11 +74,11 @@
"klona": "2.0.6",
"lodash.debounce": "4.0.8",
"marked": "5.1.1",
"pinia": "2.1.6",
"pinia": "2.1.4",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",
"sortablejs": "1.15.0",
"ufo": "1.2.0",
"ufo": "1.1.2",
"vue": "3.3.4",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.3",
@ -92,54 +92,54 @@
"@cypress/vite-dev-server": "5.0.5",
"@cypress/vue": "5.0.5",
"@faker-js/faker": "8.0.2",
"@histoire/plugin-screenshot": "0.17.0",
"@histoire/plugin-vue": "0.17.0",
"@rushstack/eslint-patch": "1.3.3",
"@tsconfig/node18": "18.2.1",
"@types/codemirror": "5.60.9",
"@histoire/plugin-screenshot": "0.16.1",
"@histoire/plugin-vue": "0.16.1",
"@rushstack/eslint-patch": "1.3.2",
"@tsconfig/node18": "18.2.0",
"@types/codemirror": "5.60.8",
"@types/dompurify": "3.0.2",
"@types/flexsearch": "0.7.3",
"@types/is-touch-device": "1.0.0",
"@types/lodash.debounce": "4.0.7",
"@types/marked": "5.0.1",
"@types/node": "18.17.8",
"@types/node": "18.17.0",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.1",
"@typescript-eslint/eslint-plugin": "6.4.1",
"@typescript-eslint/parser": "6.4.1",
"@typescript-eslint/eslint-plugin": "6.1.0",
"@typescript-eslint/parser": "6.1.0",
"@vitejs/plugin-legacy": "4.1.1",
"@vitejs/plugin-vue": "4.3.3",
"@vitejs/plugin-vue": "4.2.3",
"@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.30001522",
"autoprefixer": "10.4.14",
"browserslist": "4.21.9",
"caniuse-lite": "1.0.30001517",
"css-has-pseudo": "6.0.0",
"csstype": "3.1.2",
"cypress": "12.17.4",
"esbuild": "0.19.2",
"eslint": "8.47.0",
"eslint-plugin-vue": "9.17.0",
"happy-dom": "10.11.0",
"histoire": "0.17.0",
"postcss": "8.4.28",
"cypress": "12.17.2",
"esbuild": "0.18.15",
"eslint": "8.45.0",
"eslint-plugin-vue": "9.15.1",
"happy-dom": "10.5.2",
"histoire": "0.16.2",
"postcss": "8.4.27",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.0",
"postcss-preset-env": "9.1.1",
"rollup": "3.28.1",
"postcss-preset-env": "9.0.0",
"rollup": "3.26.3",
"rollup-plugin-visualizer": "5.9.2",
"sass": "1.66.1",
"sass": "1.64.1",
"start-server-and-test": "2.0.0",
"typescript": "5.1.6",
"vite": "4.4.9",
"vite-plugin-inject-preload": "1.3.2",
"vite": "4.4.6",
"vite-plugin-inject-preload": "1.3.1",
"vite-plugin-pwa": "0.16.4",
"vite-plugin-sentry": "1.3.0",
"vite-svg-loader": "4.0.0",
"vitest": "0.34.2",
"vue-tsc": "1.8.8",
"vitest": "0.33.0",
"vue-tsc": "1.8.6",
"wait-on": "7.0.1",
"workbox-cli": "7.0.0"
},

File diff suppressed because it is too large Load Diff

View File

@ -9,21 +9,15 @@ import {MILLISECONDS_A_HOUR} from '@/constants/date'
const now = useNow({
interval: MILLISECONDS_A_HOUR,
})
const Logo = computed(() => window.ALLOW_ICON_CHANGES && now.value.getMonth() === 6 ? LogoFullPride : LogoFull)
const CustomLogo = computed(() => window.CUSTOM_LOGO_URL)
const Logo = computed(() => window.ALLOW_ICON_CHANGES && now.value.getMonth() === 5 ? LogoFullPride : LogoFull)
</script>
<template>
<div>
<Logo v-if="!CustomLogo" alt="Vikunja" class="logo" />
<img v-show="CustomLogo" :src="CustomLogo" alt="Vikunja" class="logo" />
</div>
<Logo alt="Vikunja" class="logo" />
</template>
<style lang="scss" scoped>
.logo {
color: var(--logo-text-color);
max-width: 168px;
max-height: 48px;
}
</style>

View File

@ -60,14 +60,6 @@
:can-collapse="false"
/>
</nav>
<nav class="menu" v-if="savedFilterProjects">
<ProjectsNavigation
:model-value="savedFilterProjects"
:can-edit-order="false"
:can-collapse="false"
/>
</nav>
<nav class="menu">
<ProjectsNavigation
@ -99,7 +91,6 @@ const projectStore = useProjectStore()
const projects = computed(() => projectStore.notArchivedRootProjects)
const favoriteProjects = computed(() => projectStore.favoriteProjects)
const savedFilterProjects = computed(() => projectStore.savedFilterProjects)
</script>
<style lang="scss" scoped>

View File

@ -33,7 +33,7 @@
}"
/>
<BaseButton
v-if="!project.isArchived && project.id > -1"
v-if="!project.isArchived"
class="favorite"
:class="{'is-favorite': project.isFavorite}"
@click.prevent.stop="projectStore.toggleProjectFavorite(project)"

View File

@ -157,7 +157,7 @@
<template
v-if="['filters.create', 'project.edit', 'filter.settings.edit'].includes($route.name as string)">
<div class="field">
<label class="label">{{ $t('project.projects') }}</label>
<label class="label">{{ $t('project.lists') }}</label>
<div class="control">
<SelectProject
v-model="entities.projects"

View File

@ -1,10 +1,6 @@
const LAST_VISITED_KEY = 'lastVisited'
export const saveLastVisited = (name: string | undefined, params: object, query: object) => {
if (typeof name === 'undefined') {
return
}
export const saveLastVisited = (name: string, params: object, query: object) => {
localStorage.setItem(LAST_VISITED_KEY, JSON.stringify({name, params, query}))
}
@ -13,7 +9,7 @@ export const getLastVisited = () => {
if (lastVisited === null) {
return null
}
return JSON.parse(lastVisited)
}

View File

@ -47,13 +47,8 @@ export async function setLanguage(lang: SupportedLocale): Promise<SupportedLocal
// If the language hasn't been loaded yet
if (!i18n.global.availableLocales.includes(lang)) {
try {
const messages = await import(`./lang/${lang}.json`)
i18n.global.setLocaleMessage(lang, messages.default)
} catch (e) {
console.error(`Failed to load language ${lang}:`, e)
return setLanguage(getBrowserLanguage())
}
const messages = await import(`./lang/${lang}.json`)
i18n.global.setLocaleMessage(lang, messages.default)
}
i18n.global.locale.value = lang

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,7 @@
"passwordRequired": "Vänligen ange ett lösenord.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Har du inget konto än?",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Har du redan ett konto?",
"remember": "Stay logged in"
},
@ -64,7 +64,7 @@
"newPassword": "Nytt lösenord",
"newPasswordConfirm": "New Password Confirmation",
"currentPassword": "Nuvarande lösenord",
"currentPasswordPlaceholder": "Ditt nuvarande lösenord",
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Uppdatera din e-postadress",
@ -78,7 +78,7 @@
"emailReminders": "Send me reminders for tasks via Email",
"overdueReminders": "Send me a summary of my undone overdue tasks every day",
"discoverableByName": "Allow other users to add me as a member to teams or projects when they search for my name",
"discoverableByEmail": "Tillåt andra användare att lägga till mig som medlem i team eller projekt när de söker efter min fullständiga e-postadress",
"discoverableByEmail": "Allow other users to add me as a member to teams or projects when they search for my full email",
"playSoundWhenDone": "Play a sound when marking tasks as done",
"weekStart": "Week starts on",
"weekStartSunday": "Söndag",
@ -196,7 +196,7 @@
"title": "Set project background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Sök efter en bakgrund…",
"searchPlaceholder": "Search for a background…",
"poweredByUnsplash": "Powered by Unsplash",
"loadMore": "Load more photos",
"success": "The background has been set successfully!",
@ -242,7 +242,7 @@
"namePlaceholder": "t. ex. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
"password": "Lösenord (valfritt)",
"passwordExplanation": "Vid inloggning kommer användaren att bli ombedd att ange detta lösenord.",
"passwordExplanation": "When signing in, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this project with this link share. This cannot be undone!",
@ -255,7 +255,7 @@
"typeUser": "användare | användare",
"typeTeam": "team | teams",
"shared": "Shared with these {type}",
"you": "Du",
"you": "You",
"notShared": "Not shared with any {type} yet.",
"removeHeader": "Remove a {type} from the {sharable}",
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
@ -380,7 +380,7 @@
"manage": "Hantera etiketter",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose project you have access.",
"newCTA": "Du har för närvarande inga etiketter.",
"search": "Skriv för att söka efter en etikett…",
"search": "Type to search for a label…",
"create": {
"header": "Ny etikett",
"title": "Skapa ny etikett",
@ -450,8 +450,8 @@
"input": {
"resetColor": "Återställ färg",
"datepicker": {
"today": "I dag",
"tomorrow": "I morgon",
"today": "Idag",
"tomorrow": "Imorgon",
"nextMonday": "Nästa måndag",
"thisWeekend": "This Weekend",
"laterThisWeek": "Later This Week",
@ -490,13 +490,13 @@
"from": "Från",
"fromto": "{from} till {to}",
"ranges": {
"today": "I dag",
"today": "Idag",
"thisWeek": "Denna vecka",
"restOfThisWeek": "The Rest of This Week",
"nextWeek": "Nästa vecka",
"next7Days": "Next 7 Days",
"lastWeek": "Förra veckan",
"thisMonth": "Denna månad",
"lastWeek": "Last Week",
"thisMonth": "This Month",
"restOfThisMonth": "The Rest of This Month",
"nextMonth": "Nästa månad",
"next30Days": "Next 30 Days",
@ -529,7 +529,7 @@
"examples": {
"now": "Just nu",
"in24h": "In 24h",
"today": "I dag kl. 00:00",
"today": "Idag kl. 00:00",
"beginningOfThisWeek": "The beginning of this week at 00:00",
"endOfThisWeek": "The end of this week",
"in30Days": "Om 30 dagar",
@ -638,7 +638,7 @@
"delete": "Radera bilaga",
"deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Kopiera URL",
"copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text",
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
@ -650,10 +650,10 @@
"loading": "Laddar kommentarer…",
"edited": "edited {date}",
"creating": "Creating comment…",
"placeholder": "Skriv din kommentar…",
"placeholder": "Add your comment…",
"comment": "Comment",
"delete": "Delete this comment",
"deleteText1": "Är du säker på att du vill radera denna kommentar?",
"deleteText1": "Are you sure you want to delete this comment?",
"deleteSuccess": "The comment was deleted successfully.",
"addedSuccess": "The comment was added successfully."
},
@ -661,10 +661,10 @@
"title": "Defer due date",
"1day": "1 dag",
"3days": "3 dagar",
"1week": "1 vecka"
"1week": "1 week"
},
"description": {
"placeholder": "Klicka här för att ange en beskrivning…",
"placeholder": "Click here to enter a description…",
"empty": "No description available yet."
},
"assignee": {
@ -674,8 +674,8 @@
"unassignSuccess": "The user has been unassigned successfully."
},
"label": {
"placeholder": "Skriv för att lägga till en ny etikett…",
"createPlaceholder": "Lägg till som ny etikett",
"placeholder": "Type to add a new label…",
"createPlaceholder": "Add this as new label",
"addSuccess": "The label has been added successfully.",
"createSuccess": "The label has been created successfully.",
"removeSuccess": "The label has been removed successfully.",
@ -722,18 +722,18 @@
"reminder": {
"before": "{amount} {unit} before {type}",
"after": "{amount} {unit} after {type}",
"beforeShort": "re",
"afterShort": "efter",
"beforeShort": "before",
"afterShort": "after",
"onDueDate": "On the due date",
"onStartDate": "On the start date",
"onEndDate": "On the end date",
"custom": "Custom",
"dateAndTime": "Datum och tid"
"dateAndTime": "Date and time"
},
"repeat": {
"everyDay": "Varje dag",
"everyWeek": "Varje vecka",
"everyMonth": "Varje månad",
"everyWeek": "Every Week",
"everyMonth": "Every Month",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
@ -762,7 +762,7 @@
"project2": "This will return an error if the project does not exist.",
"project3": "To use spaces, simply add a \" or ' around the project name.",
"project4": "For example: {prefix}\"Project with spaces\".",
"dateAndTime": "Datum och tid",
"dateAndTime": "Date and time",
"date": "Any date will be used as the due date of the new task. You can use dates in any of these formats:",
"dateWeekday": "any weekday, will use the next date with that date",
"dateCurrentYear": "will use the current year",
@ -782,7 +782,7 @@
"edit": {
"title": "Edit Team \"{team}\"",
"members": "Team Members",
"search": "Skriv för att söka efter en användare…",
"search": "Type to search a user…",
"addUser": "Add to team",
"makeMember": "Make Member",
"makeAdmin": "Make Admin",
@ -790,7 +790,7 @@
"userAddedSuccess": "The team member was successfully added.",
"madeMember": "The team member was successfully made member.",
"madeAdmin": "The team member was successfully made admin.",
"mustSelectUser": "Vänligen välj en användare.",
"mustSelectUser": "Please select a user.",
"delete": {
"header": "Delete the team",
"text1": "Are you sure you want to delete this team and all of its members?",
@ -841,7 +841,7 @@
"move": "Move this task to another project",
"reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description",
"delete": "Radera denna uppgift",
"delete": "Delete this task",
"priority": "Change the priority of this task",
"favorite": "Mark this task as favorite / unfavorite"
},
@ -888,7 +888,7 @@
"loadingError": {
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
"tryAgain": "försök igen",
"contact": "kontakta oss"
"contact": "contact us"
},
"notification": {
"title": "Notifications",
@ -996,13 +996,13 @@
},
"time": {
"units": {
"seconds": "sekund|sekunder",
"minutes": "minut|minuter",
"hours": "timme|timmar",
"days": "dag|dagar",
"weeks": "vecka|veckor",
"months": "månad|månader",
"years": "år|år"
"seconds": "second|seconds",
"minutes": "minute|minutes",
"hours": "hour|hours",
"days": "day|days",
"weeks": "week|weeks",
"months": "month|months",
"years": "year|years"
}
}
}

View File

@ -23,7 +23,6 @@ declare global {
SENTRY_DSN: string;
PROJECT_INFINITE_NESTING_ENABLED: boolean;
ALLOW_ICON_CHANGES: boolean;
CUSTOM_LOGO_URL?: string;
}
}
@ -36,8 +35,8 @@ if (apiUrlFromStorage !== null) {
}
// Make sure the api url does not contain a / at the end
if (window.API_URL.endsWith('/')) {
window.API_URL = window.API_URL.slice(0, -1)
if (window.API_URL.slice(window.API_URL.length - 1, window.API_URL.length) === '/') {
window.API_URL = window.API_URL.slice(0, window.API_URL.length - 1)
}
// directives

View File

@ -85,6 +85,7 @@ export default class TaskModel extends AbstractModel<ITask> implements ITask {
index = 0
isFavorite = false
subscription: ISubscription = null
coverImageAttachmentId: IAttachment['id'] = null
position = 0
kanbanPosition = 0

View File

@ -448,9 +448,16 @@ export async function getAuthForRoute(to: RouteLocation, authStore) {
return
}
// Check if the route the user wants to go to is a route which needs authentication. We use this to
// redirect the user after successful login.
const isValidUserAppRoute = ![
const baseStore = useBaseStore()
// When trying this before the current user was fully loaded we might get a flash of the login screen
// in the user shell. To make shure this does not happen we check if everything is ready before trying.
if (!baseStore.ready) {
return
}
// Check if the user is already logged in and redirect them to the home page if not
if (
![
'user.login',
'user.password-reset.request',
'user.password-reset.reset',
@ -461,19 +468,8 @@ export async function getAuthForRoute(to: RouteLocation, authStore) {
localStorage.getItem('passwordResetToken') === null &&
localStorage.getItem('emailConfirmToken') === null &&
!(to.name === 'home' && (typeof to.query.userPasswordReset !== 'undefined' || typeof to.query.userEmailConfirm !== 'undefined'))
if (isValidUserAppRoute) {
) {
saveLastVisited(to.name as string, to.params, to.query)
}
const baseStore = useBaseStore()
// When trying this before the current user was fully loaded we might get a flash of the login screen
// in the user shell. To make sure this does not happen we check if everything is ready before trying.
if (!baseStore.ready) {
return
}
if (isValidUserAppRoute) {
return {name: 'user.login'}
}

View File

@ -36,11 +36,9 @@ export const useProjectStore = defineStore('project', () => {
const projectsArray = computed(() => Object.values(projects.value)
.sort((a, b) => a.position - b.position))
const notArchivedRootProjects = computed(() => projectsArray.value
.filter(p => p.parentProjectId === 0 && !p.isArchived && p.id > 0))
.filter(p => p.parentProjectId === 0 && !p.isArchived))
const favoriteProjects = computed(() => projectsArray.value
.filter(p => !p.isArchived && p.isFavorite))
const savedFilterProjects = computed(() => projectsArray.value
.filter(p => !p.isArchived && p.id < -1))
const hasProjects = computed(() => projectsArray.value.length > 0)
const getChildProjects = computed(() => {
@ -200,7 +198,6 @@ export const useProjectStore = defineStore('project', () => {
notArchivedRootProjects: readonly(notArchivedRootProjects),
favoriteProjects: readonly(favoriteProjects),
hasProjects: readonly(hasProjects),
savedFilterProjects: readonly(savedFilterProjects),
getChildProjects,
findProjectByExactname,

View File

@ -63,7 +63,7 @@
<nothing v-if="ctaVisible && tasks.length === 0 && !loading">
{{ $t('project.list.empty') }}
<ButtonLink @click="focusNewTaskInput()" v-if="project.id > 0">
<ButtonLink @click="focusNewTaskInput()">
{{ $t('project.list.newTaskCta') }}
</ButtonLink>
</nothing>