diff --git a/cypress/e2e/page-list.spec.js b/cypress/e2e/page-list.spec.js
index 6333ba0b1..6c5718a08 100644
--- a/cypress/e2e/page-list.spec.js
+++ b/cypress/e2e/page-list.spec.js
@@ -201,6 +201,32 @@ describe('Page list', function() {
})
})
+ describe('Page favorites', function() {
+ it('Allows to add and remove pages from favorites', function() {
+ cy.get('.page-list .page-list-favorites')
+ .should('not.exist')
+
+ cy.openPageMenu('Day 1')
+ cy.clickMenuButton('Add to favorites')
+ cy.get('.page-list .page-list-favorites')
+ .should('contain', 'Day 1')
+
+ cy.get('.page-list-favorites-list')
+ .should('be.visible')
+ cy.get('.page-list-favorites .toggle-favorites-button')
+ .click()
+ cy.get('.page-list-favorites-list')
+ .should('not.be.visible')
+ cy.get('.page-list-favorites .toggle-favorites-button')
+ .click()
+
+ cy.openPageMenu('Day 1')
+ cy.clickMenuButton('Remove from favorites')
+ cy.get('.page-list .page-list-favorites')
+ .should('not.exist')
+ })
+ })
+
describe('Print view', function() {
it('renders all the pages', function() {
let printStub
diff --git a/src/apis/collectives/userSettings.js b/src/apis/collectives/userSettings.js
index d153abcf7..dee8b57e2 100644
--- a/src/apis/collectives/userSettings.js
+++ b/src/apis/collectives/userSettings.js
@@ -9,7 +9,7 @@ import { collectivesUrl } from './urls.js'
/**
* Set the page order for the current user
*
- * @param {number} collectiveId ID of the colletive to be updated
+ * @param {number} collectiveId ID of the collective to be updated
* @param {number} pageOrder the desired page order for the current user
*/
export function setCollectiveUserSettingPageOrder(collectiveId, pageOrder) {
@@ -20,9 +20,9 @@ export function setCollectiveUserSettingPageOrder(collectiveId, pageOrder) {
}
/**
- * Set the the `show recent pages` toggle for the current user
+ * Set the `show recent pages` toggle for the current user
*
- * @param {number} collectiveId ID of the colletive to be updated
+ * @param {number} collectiveId ID of the collective to be updated
* @param {boolean} showRecentPages the desired value
*/
export function setCollectiveUserSettingShowRecentPages(collectiveId, showRecentPages) {
@@ -31,3 +31,16 @@ export function setCollectiveUserSettingShowRecentPages(collectiveId, showRecent
{ showRecentPages },
)
}
+
+/**
+ * Set favorite pages for the current user
+ *
+ * @param {number} collectiveId ID of the collective to be updated
+ * @param {Array} favoritePages the desired value
+ */
+export function setCollectiveUserSettingFavoritePages(collectiveId, favoritePages) {
+ return axios.put(
+ collectivesUrl(collectiveId, '_userSettings', 'favoritePages'),
+ { favoritePages: JSON.stringify(favoritePages) },
+ )
+}
diff --git a/src/components/Page/PageActionMenu.vue b/src/components/Page/PageActionMenu.vue
index 43e99e024..55f1f036c 100644
--- a/src/components/Page/PageActionMenu.vue
+++ b/src/components/Page/PageActionMenu.vue
@@ -65,6 +65,17 @@
{{ t('collectives', 'Show in Files') }}
+
+
+
+
+
+
+ {{ toggleFavoriteString }}
+
+
+
+
+
{{ sortedBy('byTitle') ? t('collectives', 'Sorted by title') : t('collectives', 'Sorted by recently changed') }}
@@ -94,12 +97,19 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -165,6 +179,7 @@ import CloseIcon from 'vue-material-design-icons/Close.vue'
import Draggable from './PageList/Draggable.vue'
import SubpageList from './PageList/SubpageList.vue'
import Item from './PageList/Item.vue'
+import PageFavorites from './PageList/PageFavorites.vue'
import PageTrash from './PageList/PageTrash.vue'
import SortAlphabeticalAscendingIcon from 'vue-material-design-icons/SortAlphabeticalAscending.vue'
import SortAscendingIcon from 'vue-material-design-icons/SortAscending.vue'
@@ -193,6 +208,7 @@ export default {
Draggable,
Item,
PagesTemplateIcon,
+ PageFavorites,
PageTrash,
SubpageList,
SortAlphabeticalAscendingIcon,
@@ -229,6 +245,7 @@ export default {
'rootPage',
'templatePage',
'currentPage',
+ 'hasFavoritePages',
'keptSortable',
'visibleSubpages',
'sortByOrder',
@@ -276,6 +293,10 @@ export default {
return (sortOrder) => this.sortByOrder === sortOrder
},
+ showFavorites() {
+ return !this.isFilteredView && this.hasFavoritePages
+ },
+
isFilteredView() {
return this.filterString !== ''
},
diff --git a/src/components/PageList/Item.vue b/src/components/PageList/Item.vue
index ad2ceef00..61a43a3d7 100644
--- a/src/components/PageList/Item.vue
+++ b/src/components/PageList/Item.vue
@@ -50,6 +50,13 @@
class="item-icon-badge"
:class="isCollapsed(pageId) ? 'collapsed' : 'expanded'" />
+
+
+
import { generateUrl } from '@nextcloud/router'
import { mapActions, mapState } from 'pinia'
+import { useCollectivesStore } from '../../stores/collectives.js'
import { usePagesStore } from '../../stores/pages.js'
import { TEMPLATE_PAGE } from '../../constants.js'
import isMobile from '@nextcloud/vue/dist/Mixins/isMobile.js'
@@ -99,6 +107,7 @@ import pageMixin from '../../mixins/pageMixin.js'
import PageIcon from '../Icon/PageIcon.vue'
import PageActionMenu from '../Page/PageActionMenu.vue'
import PageTemplateIcon from '../Icon/PageTemplateIcon.vue'
+import StarIcon from 'vue-material-design-icons/Star.vue'
import { scrollToPage } from '../../util/scrollToElement.js'
export default {
@@ -113,6 +122,7 @@ export default {
PageActionMenu,
PageTemplateIcon,
PlusIcon,
+ StarIcon,
},
mixins: [
@@ -165,6 +175,10 @@ export default {
type: Boolean,
default: false,
},
+ inFavoriteList: {
+ type: Boolean,
+ default: false,
+ },
hasVisibleSubpages: {
type: Boolean,
default: false,
@@ -191,6 +205,10 @@ export default {
},
computed: {
+ ...mapState(useCollectivesStore, [
+ 'currentCollective',
+ 'isFavoritePage',
+ ]),
...mapState(usePagesStore, [
'isCollapsed',
'currentPage',
@@ -208,14 +226,13 @@ export default {
&& this.currentPage.id === this.pageId
},
- indent() {
- // Start indention at level 2. And limit to 5 to prevent nasty subtrees
- return Math.min(Math.max(0, this.level - 1), 4)
+ isCollapsible() {
+ // root page and favorites are not collapsible
+ return this.level > 0 && !this.inFavoriteList && this.hasVisibleSubpages
},
- isCollapsible() {
- // root page is not collapsible
- return (this.level > 0 && this.hasVisibleSubpages)
+ showFavoriteStar() {
+ return !this.inFavoriteList && this.isFavoritePage(this.currentCollective.id, this.pageId)
},
pageTitleString() {
@@ -366,6 +383,10 @@ export default {
span.item-icon-badge {
background-color: var(--color-primary-element-light);
}
+
+ span.item-icon-favorite {
+ background-color: var(--color-primary-element-light);
+ }
}
&:hover, &:focus, &:active, &.highlight {
@@ -374,6 +395,10 @@ export default {
span.item-icon-badge {
background-color: var(--color-background-hover);
}
+
+ span.item-icon-favorite {
+ background-color: var(--color-background-hover);
+ }
}
&.highlight-animation {
@@ -442,6 +467,17 @@ export default {
transform: rotate(90deg);
}
}
+
+ // Configure favorite icon
+ .item-icon-favorite {
+ position: absolute;
+ top: 0;
+ right: -1px;
+ cursor: pointer;
+ border: 0;
+ border-radius: 50%;
+ background-color: var(--color-main-background);
+ }
}
.app-content-list-item-line-one {
diff --git a/src/components/PageList/PageFavorites.vue b/src/components/PageList/PageFavorites.vue
new file mode 100644
index 000000000..3dc0a9930
--- /dev/null
+++ b/src/components/PageList/PageFavorites.vue
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+ {{ t('collectives', 'Favorites') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/stores/collectives.js b/src/stores/collectives.js
index 29ad49b1c..59f77132d 100644
--- a/src/stores/collectives.js
+++ b/src/stores/collectives.js
@@ -137,6 +137,10 @@ export const useCollectivesStore = defineStore('collectives', {
? state.collectives.find(c => c.id === state.settingsCollectiveId)
: null
},
+
+ isFavoritePage: (state) => (id, pageId) => {
+ return state.collectives.find(c => c.id === id).userFavoritePages.includes(pageId)
+ },
},
actions: {
@@ -306,6 +310,26 @@ export const useCollectivesStore = defineStore('collectives', {
this._addOrUpdateCollectiveState(response.data.data)
},
+ /**
+ * @param {object} data the data object
+ * @param {number} data.id ID of the collective to be updated
+ * @param {number} data.pageId pageId to toggle in favoritePages
+ */
+ async toggleFavoritePage({ id, pageId }) {
+ const favoritePages = this.collectives
+ .find(c => c.id === id)
+ .userFavoritePages
+ // Only unique entries, filter out duplicates
+ .filter((value, i, a) => a.indexOf(value) === i)
+
+ if (favoritePages.indexOf(pageId) === -1) {
+ favoritePages.push(pageId)
+ } else {
+ favoritePages.splice(favoritePages.findIndex(id => id === pageId), 1)
+ }
+ await this.setCollectiveUserSettingFavoritePages({ id, favoritePages })
+ },
+
/**
* Set the page order for the current user
*
@@ -322,5 +346,10 @@ export const useCollectivesStore = defineStore('collectives', {
this.patchCollectiveWithProperty({ id, property: 'userShowRecentPages', value: showRecentPages })
await api.setCollectiveUserSettingShowRecentPages(id, showRecentPages)
},
+
+ async setCollectiveUserSettingFavoritePages({ id, favoritePages }) {
+ this.patchCollectiveWithProperty({ id, property: 'userFavoritePages', value: favoritePages })
+ await api.setCollectiveUserSettingFavoritePages(id, favoritePages)
+ },
},
})
diff --git a/src/stores/pages.js b/src/stores/pages.js
index 2d0f01f30..7cd403c2d 100644
--- a/src/stores/pages.js
+++ b/src/stores/pages.js
@@ -175,6 +175,16 @@ export const usePagesStore = defineStore('pages', {
}
},
+ favoritePages(state) {
+ const collectivesStore = useCollectivesStore()
+ const favoritePages = collectivesStore.currentCollective.userFavoritePages
+ return state.pages.filter(p => favoritePages.includes(p.id))
+ },
+
+ hasFavoritePages(state) {
+ return state.favoritePages.length > 0
+ },
+
sortedSubpages,
allPagesSorted(state) {