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 @@ + + + + + + + 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) {