diff --git a/src/App.vue b/src/App.vue
index 4104cd4d..7ca2290c 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -207,16 +207,26 @@
are nested inside of the namespaces makes it a lot harder.-->
@@ -508,6 +518,15 @@ export default {
// Notify the service worker to actually do the update
this.registration.waiting.postMessage('skipWaiting')
},
+ toggleFavoriteList(list) {
+ // The favorites pseudo list is always favorite
+ // Archived lists cannot be marked favorite
+ if (list.id === -1 || list.isArchived) {
+ return
+ }
+ this.$store.dispatch('lists/toggleListFavorite', list)
+ .catch(e => this.error(e, this))
+ },
},
}
diff --git a/src/models/list.js b/src/models/list.js
index 469e53ea..8e395e87 100644
--- a/src/models/list.js
+++ b/src/models/list.js
@@ -35,6 +35,7 @@ export default class ListModel extends AbstractModel {
hexColor: '',
identifier: '',
backgroundInformation: null,
+ isFavorite: false,
created: null,
updated: null,
diff --git a/src/store/modules/lists.js b/src/store/modules/lists.js
index 1f72f5d6..21d6d076 100644
--- a/src/store/modules/lists.js
+++ b/src/store/modules/lists.js
@@ -1,4 +1,7 @@
import Vue from 'vue'
+import ListService from '@/services/list'
+
+const FavoriteListsNamespace = -2
export default {
namespaced: true,
@@ -22,4 +25,32 @@ export default {
return null
},
},
+ actions: {
+ toggleListFavorite(ctx, list) {
+ list.isFavorite = !list.isFavorite
+ const listService = new ListService()
+
+ return listService.update(list)
+ .then(r => {
+ if (r.isFavorite) {
+ ctx.commit('addList', r)
+ r.namespaceId = FavoriteListsNamespace
+ ctx.commit('namespaces/addListToNamespace', r, {root: true})
+ } else {
+ ctx.commit('namespaces/setListInNamespaceById', r, {root: true})
+ r.namespaceId = FavoriteListsNamespace
+ ctx.commit('namespaces/removeListFromNamespaceById', r, {root: true})
+ }
+ ctx.dispatch('namespaces/loadNamespacesIfFavoritesDontExist', null, {root: true})
+ ctx.dispatch('namespaces/removeFavoritesNamespaceIfEmpty', null, {root: true})
+ return Promise.resolve(r)
+ })
+ .catch(e => {
+ // Reset the list state to the initial one to avoid confusion for the user
+ list.isFavorite = !list.isFavorite
+ ctx.commit('addList', list)
+ return Promise.reject(e)
+ })
+ },
+ },
}
\ No newline at end of file
diff --git a/src/store/modules/namespaces.js b/src/store/modules/namespaces.js
index 2248c8d3..85a46919 100644
--- a/src/store/modules/namespaces.js
+++ b/src/store/modules/namespaces.js
@@ -24,12 +24,14 @@ export default {
for (const n in state.namespaces) {
// We don't have the namespace id on the list which means we need to loop over all lists until we find it.
// FIXME: Not ideal at all - we should fix that at the api level.
- for (const l in state.namespaces[n].lists) {
- if (state.namespaces[n].lists[l].id === list.id) {
- const namespace = state.namespaces[n]
- namespace.lists[l] = list
- Vue.set(state.namespaces, n, namespace)
- return
+ if (state.namespaces[n].id === list.namespaceId) {
+ for (const l in state.namespaces[n].lists) {
+ if (state.namespaces[n].lists[l].id === list.id) {
+ const namespace = state.namespaces[n]
+ namespace.lists[l] = list
+ Vue.set(state.namespaces, n, namespace)
+ return
+ }
}
}
}
@@ -45,6 +47,20 @@ export default {
}
}
},
+ removeListFromNamespaceById(state, list) {
+ for (const n in state.namespaces) {
+ // We don't have the namespace id on the list which means we need to loop over all lists until we find it.
+ // FIXME: Not ideal at all - we should fix that at the api level.
+ if (state.namespaces[n].id === list.namespaceId) {
+ for (const l in state.namespaces[n].lists) {
+ if (state.namespaces[n].lists[l].id === list.id) {
+ state.namespaces[n].lists.splice(l, 1)
+ return
+ }
+ }
+ }
+ }
+ },
},
getters: {
getListAndNamespaceById: state => listId => {
@@ -99,5 +115,11 @@ export default {
return ctx.dispatch('loadNamespaces')
}
},
+ removeFavoritesNamespaceIfEmpty(ctx) {
+ if (ctx.state.namespaces[0].id === -2 && ctx.state.namespaces[0].lists.length === 0) {
+ ctx.state.namespaces.splice(0, 1)
+ return Promise.resolve()
+ }
+ },
},
}
\ No newline at end of file
diff --git a/src/styles/components/namespaces.scss b/src/styles/components/namespaces.scss
index 85815f62..1c68d3bf 100644
--- a/src/styles/components/namespaces.scss
+++ b/src/styles/components/namespaces.scss
@@ -30,7 +30,6 @@ $lists-per-row: 5;
border: 1px solid $grey;
color: $grey !important;
padding: 2px 4px;
- margin-left: .5rem;
border-radius: 3px;
font-family: $vikunja-font;
background: rgba($white, 0.75);
@@ -41,6 +40,7 @@ $lists-per-row: 5;
flex-flow: row wrap;
.list {
+ cursor: pointer;
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
height: 150px;
background: $white;
@@ -100,6 +100,7 @@ $lists-per-row: 5;
.is-archived {
font-size: .75em;
+ float: left;
}
}
@@ -127,6 +128,29 @@ $lists-per-row: 5;
color: $white;
}
}
+
+ .favorite {
+ transition: opacity $transition, color $transition;
+ opacity: 0;
+
+ &:hover {
+ color: $orange;
+ }
+
+ &.is-archived {
+ display: none;
+ }
+
+ &.is-favorite {
+ display: inline-block;
+ opacity: 1;
+ color: $orange;
+ }
+ }
+
+ &:hover .favorite {
+ opacity: 1;
+ }
}
}
}
diff --git a/src/styles/theme/navigation.scss b/src/styles/theme/navigation.scss
index e03b0a52..bc29bb82 100644
--- a/src/styles/theme/navigation.scss
+++ b/src/styles/theme/navigation.scss
@@ -164,25 +164,44 @@
overflow: hidden;
}
- .menu-label, .menu-list a {
+ .menu-label, .menu-list span.list-menu-link, .menu-list a {
display: flex;
align-items: center;
justify-content: space-between;
+ cursor: pointer;
- span.name:not(.icon) {
+ .list-menu-title {
overflow: hidden;
text-overflow: ellipsis;
+ width: 100%;
+ }
- .color-bubble {
- display: inline-block;
- vertical-align: initial;
- width: 12px;
- height: 12px;
- border-radius: 100%;
- margin-right: 2px;
+ .color-bubble {
+ display: inline-block;
+ width: 14px; // Without this, the bubble is only 10.2342357612px wide and seems squashed.
+ height: 12px;
+ border-radius: 100%;
+ margin-right: 4px;
+ }
+
+ .favorite {
+ margin-left: .25rem;
+ transition: opacity $transition, color $transition;
+ opacity: 0;
+
+ &:hover {
+ color: $orange;
+ }
+
+ &.is-favorite {
+ opacity: 1;
+ color: $orange;
}
}
+ &:hover .favorite {
+ opacity: 1;
+ }
}
.menu-label {
@@ -201,7 +220,7 @@
padding: 10px 0.3em 0;
}
- .menu-label, .nsettings, .menu-list a {
+ .menu-label, .nsettings, .menu-list span.list-menu-link, .menu-list a {
color: $vikunja-nav-color;
}
@@ -243,7 +262,7 @@
height: 44px;
}
- a {
+ span.list-menu-link, a {
padding: 0.75em .5em 0.75em $navbar-padding * 1.5;
transition: all 0.2s ease;
@@ -299,7 +318,7 @@
font-family: $vikunja-font;
}
- a {
+ span.list-menu-link, a {
padding-left: 2em;
display: inline-block;
}
diff --git a/src/views/namespaces/ListNamespaces.vue b/src/views/namespaces/ListNamespaces.vue
index 8b275bac..b691dbae 100644
--- a/src/views/namespaces/ListNamespaces.vue
+++ b/src/views/namespaces/ListNamespaces.vue
@@ -33,12 +33,20 @@
}"
:to="{ name: 'list.index', params: { listId: l.id} }"
class="list"
+ tag="span"
v-if="showArchived ? true : !l.isArchived"
>
-
- Archived
-
+
+ Archived
+
+
+
+
+
{{ l.title }}
@@ -93,6 +101,15 @@ export default {
})
})
},
+ toggleFavoriteList(list) {
+ // The favorites pseudo list is always favorite
+ // Archived lists cannot be marked favorite
+ if (list.id === -1 || list.isArchived) {
+ return
+ }
+ this.$store.dispatch('lists/toggleListFavorite', list)
+ .catch(e => this.error(e, this))
+ },
},
}