Skip to content

Commit

Permalink
Merge pull request #812 from nextcloud/enh/landing_page
Browse files Browse the repository at this point in the history
List recent pages and members on landing page
  • Loading branch information
mejo- authored Aug 2, 2023
2 parents 93526a6 + 331a903 commit 93630cd
Show file tree
Hide file tree
Showing 17 changed files with 736 additions and 33 deletions.
79 changes: 79 additions & 0 deletions cypress/e2e/page-landingpage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* @copyright Copyright (c) 2023 Jonas <jonas@nextcloud.com>
*
* @author Jonas <jonas@nextcloud.com>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

/**
* Tests for page details.
*/

const collective = 'Landingpage Collective'

describe('Page landing page', function() {
before(function() {
cy.login('bob', { route: '/apps/collectives' })
cy.deleteAndSeedCollective(collective)
cy.seedCircleMember(collective, 'alice')
cy.seedCircleMember(collective, 'jane')
cy.seedCircleMember(collective, 'john')
cy.seedPage('Page 1', '', 'Readme.md')
cy.seedPage('Page 2', '', 'Readme.md')
cy.seedPage('Page 3', '', 'Readme.md')
})

beforeEach(function() {
cy.login('bob', { route: `/apps/collectives/${collective}` })
// make sure the page list loaded properly
cy.contains('.app-content-list-item a', 'Page 1')
})

describe('Displays recent pages', function() {
it('Allows to display/close TOC and switch page modes in between', function() {
cy.get('.recent-pages-widget .recent-page-tile')
.contains('Page 2')
.click()

cy.url().should('include', `/apps/collectives/${encodeURIComponent(collective)}/${encodeURIComponent('Page 2')}`)
})
})

describe('Displays recent members', function() {
it('Allows to open members modal as admin', function() {
cy.get('.members-widget .avatardiv[title="alice"]')
cy.get('.members-widget .button-vue[title="Show members"]')
.click()

cy.get('.current-members').contains('.member-row', 'alice')
.find('.member-row__actions')
.should('exist')
})

it('Allows to open members modal as member', function() {
cy.login('alice', { route: `/apps/collectives/${collective}` })
cy.get('.members-widget .avatardiv[title="bob"]')
cy.get('.members-widget .button-vue[title="Show members"]')
.click()

cy.get('.current-members').contains('.member-row', 'bob')
.find('.member-row__actions')
.should('not.exist')
})
})
})
11 changes: 10 additions & 1 deletion src/components/LastUserBubble.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<template>
<div>
<template v-if="showPrefixString">
{{ t('collectives', 'Last changed by') }}
</template>
<NcUserBubble :display-name="lastUserDisplayName"
:user="lastUserId"
:show-user-status="false">
{{ lastEditedUserMessage }}
</NcUserBubble>
{{ lastUpdate }}
<span class="timestamp">
{{ lastUpdate }}
</span>
</div>
</template>

Expand Down Expand Up @@ -33,6 +38,10 @@ export default {
type: Number,
required: true,
},
showPrefixString: {
type: Boolean,
default: false,
},
},
computed: {
Expand Down
30 changes: 8 additions & 22 deletions src/components/Member/CurrentMembers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<Member v-for="item in searchedMembers"
:key="item.singleId"
:circle-id="circleId"
:current-user-is-admin="currentUserIsAdmin"
:member-id="item.id"
:user-id="item.userId"
:display-name="item.displayName"
Expand Down Expand Up @@ -46,6 +47,10 @@ export default {
type: String,
default: '',
},
currentUserIsAdmin: {
type: Boolean,
default: true,
},
},
computed: {
Expand All @@ -60,7 +65,7 @@ export default {
sortedMembers() {
return this.currentMembers
.slice()
.sort(this.sortMembers)
.sort(this.sortCurrentUserFirst)
},
searchedMembers() {
Expand All @@ -86,37 +91,18 @@ export default {
methods: {
/**
*
* @param {object} m1 First member
* @param {string} m1.userId First member user ID
* @param {string} m1.displayName First member display name
* @param {number} m1.level First member level
* @param {number} m1.userType First member user type
* @param {object} m2 Second member
* @param {string} m2.userId Second member user ID
* @param {string} m2.displayName Second member display name
* @param {number} m2.level Second member level
* @param {number} m2.userType Second member user type
*/
sortMembers(m1, m2) {
sortCurrentUserFirst(m1, m2) {
if (m1.userId === this.currentUser) {
return -1
} else if (m2.userId === this.currentUser) {
return 1
}
// Sort by level (admin > moderator > member)
if (m1.level !== m2.level) {
return m1.level < m2.level
}
// Sort by user type (user > group > circle)
if (this.circleMemberType(m1) !== this.circleMemberType(m2)) {
return this.circleMemberType(m1) > this.circleMemberType(m2)
}
// Sort by display name
return m1.displayName.localeCompare(m2.displayName)
return 0
},
},
}
Expand Down
8 changes: 6 additions & 2 deletions src/components/Member/Member.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
</div>

<!-- Checkmark icon for selected -->
<div v-if="isSearched && isSelected" class="member-row__checkmark">
<div v-if="currentUserIsAdmin && isSearched && isSelected" class="member-row__checkmark">
<CheckIcon :size="20" />
</div>

<!-- Action menu -->
<NcActions v-else-if="!isSearched && !isCurrentUser"
<NcActions v-else-if="currentUserIsAdmin && !isSearched && !isCurrentUser"
:force-menu="true"
class="member-row__actions">
<NcActionButton v-if="!isAdmin"
Expand Down Expand Up @@ -110,6 +110,10 @@ export default {
type: String,
default: null,
},
currentUserIsAdmin: {
type: Boolean,
default: true,
},
memberId: {
type: String,
default: null,
Expand Down
18 changes: 14 additions & 4 deletions src/components/Member/MemberPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,23 @@
<CurrentMembers v-else-if="showCurrent"
:circle-id="circleId"
:current-members="currentMembers"
:search-query="searchQuery" />
:search-query="searchQuery"
:current-user-is-admin="currentUserIsAdmin" />

<!-- Selected members (optional) -->
<SelectedMembers v-if="!showCurrentSkeleton && showSelection"
<SelectedMembers v-if="currentUserIsAdmin && !showCurrentSkeleton && showSelection"
:selected-members="selectedMembers"
@delete-from-selection="deleteFromSelection" />

<!-- Searched and picked members -->
<MemberSearchResults v-if="!showCurrentSkeleton && hasSearchResults"
<MemberSearchResults v-if="currentUserIsAdmin && !showCurrentSkeleton && hasSearchResults"
:circle-id="circleId"
:search-results="filteredSearchResults"
:selection-set="selectedMembers"
:on-click-searched="onClickSearched" />

<!-- No search results -->
<template v-else-if="!showCurrentSkeleton">
<template v-else-if="currentUserIsAdmin && !showCurrentSkeleton">
<NcAppNavigationCaption class="member-picker-caption" :title="t('collectives', 'Add users, groups or circles…')" />
<Hint v-if="!isSearching" :hint="t('collectives', 'Search for members to add')" />
<Hint v-else-if="isSearchLoading" :hint="t('collectives', 'Loading…')" />
Expand Down Expand Up @@ -76,6 +77,10 @@ export default {
type: String,
default: null,
},
currentUserIsAdmin: {
type: Boolean,
default: true,
},
showCurrent: {
type: Boolean,
default: false,
Expand Down Expand Up @@ -189,6 +194,11 @@ export default {
},
onSearch() {
// Don't search for new members if not admin
if (!this.currentUserIsAdmin) {
return
}
this.searchResults = []
this.isSearchLoading = true
this.debounceFetchSearchResults()
Expand Down
14 changes: 12 additions & 2 deletions src/components/Nav/CollectiveMembersModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
<div class="modal-collective-members">
<MemberPicker :show-current="true"
:circle-id="collective.circleId"
:current-members="circleMembers(collective.circleId)"
:current-user-is-admin="currentUserIsAdmin"
:current-members="circleMembersSorted(collective.circleId)"
:on-click-searched="onClickSearched" />
</div>
</div>
Expand Down Expand Up @@ -47,8 +48,13 @@ export default {
computed: {
...mapGetters([
'circleMembers',
'circleMembersSorted',
'isCollectiveAdmin',
]),
currentUserIsAdmin() {
return this.isCollectiveAdmin(this.collective)
},
},
beforeMount() {
Expand All @@ -70,6 +76,10 @@ export default {
},
async onClickSearched(member) {
if (!this.currentUserIsAdmin) {
return
}
await this.dispatchAddMemberToCircle({
circleId: this.collective.circleId,
userId: member.id,
Expand Down
3 changes: 3 additions & 0 deletions src/components/Page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
@click="toggle('sidebar')" />
</NcActions>
</h1>
<LandingPageWidgets v-if="isLandingPage" />
<TextEditor :key="`text-editor-${currentPage.id}`"
ref="texteditor" />
</div>
Expand All @@ -113,6 +114,7 @@ import NcEmojiPicker from '@nextcloud/vue/dist/Components/NcEmojiPicker.js'
import CollectivesIcon from './Icon/CollectivesIcon.vue'
import EmoticonOutlineIcon from 'vue-material-design-icons/EmoticonOutline.vue'
import EditButton from './Page/EditButton.vue'
import LandingPageWidgets from './Page/LandingPageWidgets.vue'
import PageActionMenu from './Page/PageActionMenu.vue'
import PageTemplateIcon from './Icon/PageTemplateIcon.vue'
import TextEditor from './Page/TextEditor.vue'
Expand All @@ -128,6 +130,7 @@ export default {
CollectivesIcon,
EditButton,
EmoticonOutlineIcon,
LandingPageWidgets,
NcActionButton,
NcActions,
NcButton,
Expand Down
35 changes: 35 additions & 0 deletions src/components/Page/LandingPageWidgets.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<div class="landing-page-widgets">
<RecentPagesWidget />
<MembersWidget v-if="!isPublic" />
</div>
</template>

<script>
import { mapGetters } from 'vuex'
import MembersWidget from './LandingPageWidgets/MembersWidget.vue'
import RecentPagesWidget from './LandingPageWidgets/RecentPagesWidget.vue'
export default {
name: 'LandingPageWidgets',
components: {
MembersWidget,
RecentPagesWidget,
},
computed: {
...mapGetters([
'isPublic',
]),
},
}
</script>

<style scoped>
.landing-page-widgets {
margin: auto;
padding-left: 12px;
max-width: 670px;
}
</style>
Loading

0 comments on commit 93630cd

Please sign in to comment.