Merge branch 'navigation-update' into 'develop'

Navigation update + preferences storage (and some minor fixes)

See merge request pleroma/pleroma-fe!1592
shigusegubu-vue3
tusooa 1 year ago
commit 8b25febe36
  1. 4
      src/App.js
  2. 26
      src/App.scss
  3. 2
      src/App.vue
  4. 5
      src/boot/routes.js
  5. 4
      src/components/account_actions/account_actions.js
  6. 1
      src/components/account_actions/account_actions.vue
  7. 4
      src/components/desktop_nav/desktop_nav.scss
  8. 1
      src/components/desktop_nav/desktop_nav.vue
  9. 7
      src/components/lists/lists.js
  10. 24
      src/components/lists/lists.vue
  11. 110
      src/components/lists_edit/lists_edit.js
  12. 232
      src/components/lists_edit/lists_edit.vue
  13. 29
      src/components/lists_menu/lists_menu_content.js
  14. 17
      src/components/lists_menu/lists_menu_content.vue
  15. 79
      src/components/lists_new/lists_new.js
  16. 95
      src/components/lists_new/lists_new.vue
  17. 4
      src/components/lists_timeline/lists_timeline.js
  18. 7
      src/components/lists_user_search/lists_user_search.js
  19. 20
      src/components/lists_user_search/lists_user_search.vue
  20. 9
      src/components/mobile_nav/mobile_nav.js
  21. 29
      src/components/mobile_nav/mobile_nav.vue
  22. 3
      src/components/mobile_post_status_button/mobile_post_status_button.js
  23. 75
      src/components/nav_panel/nav_panel.js
  24. 262
      src/components/nav_panel/nav_panel.vue
  25. 18
      src/components/navigation/filter.js
  26. 75
      src/components/navigation/navigation.js
  27. 47
      src/components/navigation/navigation_entry.js
  28. 120
      src/components/navigation/navigation_entry.vue
  29. 88
      src/components/navigation/navigation_pins.js
  30. 76
      src/components/navigation/navigation_pins.vue
  31. 25
      src/components/popover/popover.js
  32. 7
      src/components/popover/popover.vue
  33. 2
      src/components/search_bar/search_bar.vue
  34. 5
      src/components/settings_modal/tabs/general_tab.vue
  35. 13
      src/components/side_drawer/side_drawer.js
  36. 14
      src/components/side_drawer/side_drawer.vue
  37. 1
      src/components/tab_switcher/tab_switcher.scss
  38. 5
      src/components/timeline/timeline.vue
  39. 16
      src/components/timeline_menu/timeline_menu.js
  40. 17
      src/components/timeline_menu/timeline_menu.vue
  41. 29
      src/components/timeline_menu/timeline_menu_content.js
  42. 66
      src/components/timeline_menu/timeline_menu_content.vue
  43. 4
      src/components/update_notification/update_notification.js
  44. 2
      src/components/update_notification/update_notification.vue
  45. 93
      src/components/user_list_menu/user_list_menu.js
  46. 38
      src/components/user_list_menu/user_list_menu.vue
  47. 23
      src/i18n/en.json
  48. 3
      src/modules/api.js
  49. 1
      src/modules/config.js
  50. 100
      src/modules/lists.js
  51. 223
      src/modules/serverSideStorage.js
  52. 17
      src/modules/users.js
  53. 13
      src/panel.scss
  54. 35
      src/services/api/api.service.js
  55. 2
      src/services/entity_normalizer/entity_normalizer.service.js
  56. 16
      test/unit/specs/modules/lists.spec.js
  57. 148
      test/unit/specs/modules/serverSideStorage.spec.js

@ -92,8 +92,12 @@ export default {
isChats () {
return this.$route.name === 'chat' || this.$route.name === 'chats'
},
isListEdit () {
return this.$route.name === 'lists-edit'
},
newPostButtonShown () {
if (this.isChats) return false
if (this.isListEdit) return false
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },

@ -117,12 +117,28 @@ h4 {
margin: 0;
}
.iconLetter {
display: inline-block;
text-align: center;
font-weight: 1000;
}
i[class*=icon-],
.svg-inline--fa {
.svg-inline--fa,
.iconLetter {
color: $fallback--icon;
color: var(--icon, $fallback--icon);
}
.button-unstyled:hover,
a:hover {
> i[class*=icon-],
> .svg-inline--fa,
> .iconLetter {
color: var(--text);
}
}
nav {
z-index: var(--ZI_navbar);
color: var(--topBarText);
@ -765,17 +781,23 @@ option {
}
.fa-scale-110 {
&.svg-inline--fa {
&.svg-inline--fa,
&.iconLetter {
font-size: 1.1em;
}
}
.fa-old-padding {
&.iconLetter,
&.svg-inline--fa, &-layer {
padding: 0 0.3em;
}
}
.veryfaint {
opacity: 0.25;
}
.login-hint {
text-align: center;

@ -36,7 +36,7 @@
<div
id="main-scroller"
class="column main"
:class="{ '-full-height': isChats }"
:class="{ '-full-height': isChats || isListEdit }"
>
<div
v-if="!currentUser"

@ -23,6 +23,7 @@ import RemoteUserResolver from 'components/remote_user_resolver/remote_user_reso
import Lists from 'components/lists/lists.vue'
import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
import ListsEdit from 'components/lists_edit/lists_edit.vue'
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@ -79,7 +80,9 @@ export default (store) => {
{ name: 'legacy-user-profile', path: '/:name', component: UserProfile },
{ name: 'lists', path: '/lists', component: Lists },
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit }
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
{ name: 'lists-new', path: '/lists/new', component: ListsEdit },
{ name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute }
]
if (store.state.instance.pleromaChatMessagesAvailable) {

@ -1,6 +1,7 @@
import { mapState } from 'vuex'
import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisV
@ -19,7 +20,8 @@ const AccountActions = {
},
components: {
ProgressButton,
Popover
Popover,
UserListMenu
},
methods: {
showRepeats () {

@ -28,6 +28,7 @@
class="dropdown-divider"
/>
</template>
<UserListMenu :user="user" />
<button
v-if="relationship.blocking"
class="btn button-default btn-block dropdown-item"

@ -137,4 +137,8 @@
text-align: right;
}
}
.spacer {
width: 1em;
}
}

@ -61,6 +61,7 @@
:title="$t('nav.administration')"
/>
</a>
<span class="spacer" />
<button
v-if="currentUser"
class="button-unstyled nav-icon"

@ -1,5 +1,4 @@
import ListsCard from '../lists_card/lists_card.vue'
import ListsNew from '../lists_new/lists_new.vue'
const Lists = {
data () {
@ -8,11 +7,7 @@ const Lists = {
}
},
components: {
ListsCard,
ListsNew
},
created () {
this.$store.dispatch('startFetchingLists')
ListsCard
},
computed: {
lists () {

@ -1,21 +1,15 @@
<template>
<div v-if="isNew">
<ListsNew @cancel="cancelNewList" />
</div>
<div
v-else
class="settings panel panel-default"
>
<div class="Lists panel panel-default">
<div class="panel-heading">
<div class="title">
{{ $t('lists.lists') }}
</div>
<button
class="button-default"
@click="newList"
<router-link
:to="{ name: 'lists-new' }"
class="button-default btn new-list-button"
>
{{ $t("lists.new") }}
</button>
</router-link>
</div>
<div class="panel-body">
<ListsCard
@ -29,3 +23,11 @@
</template>
<script src="./lists.js"></script>
<style lang="scss">
.Lists {
.new-list-button {
padding: 0 0.5em;
}
}
</style>

@ -1,7 +1,9 @@
import { mapState, mapGetters } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import ListsUserSearch from '../lists_user_search/lists_user_search.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSearch,
@ -17,22 +19,33 @@ const ListsNew = {
components: {
BasicUserCard,
UserAvatar,
ListsUserSearch
ListsUserSearch,
TabSwitcher,
PanelLoading
},
data () {
return {
title: '',
userIds: [],
selectedUserIds: []
titleDraft: '',
membersUserIds: [],
removedUserIds: new Set([]), // users we added for members, to undo
searchUserIds: [],
addedUserIds: new Set([]), // users we added from search, to undo
searchLoading: false,
reallyDelete: false
}
},
created () {
this.$store.dispatch('fetchList', { id: this.id })
.then(() => { this.title = this.findListTitle(this.id) })
this.$store.dispatch('fetchListAccounts', { id: this.id })
if (!this.id) return
this.$store.dispatch('fetchList', { listId: this.id })
.then(() => {
this.selectedUserIds = this.findListAccounts(this.id)
this.selectedUserIds.forEach(userId => {
this.title = this.findListTitle(this.id)
this.titleDraft = this.title
})
this.$store.dispatch('fetchListAccounts', { listId: this.id })
.then(() => {
this.membersUserIds = this.findListAccounts(this.id)
this.membersUserIds.forEach(userId => {
this.$store.dispatch('fetchUserIfMissing', userId)
})
})
@ -41,11 +54,12 @@ const ListsNew = {
id () {
return this.$route.params.id
},
users () {
return this.userIds.map(userId => this.findUser(userId))
membersUsers () {
return [...this.membersUserIds, ...this.addedUserIds]
.map(userId => this.findUser(userId)).filter(user => user)
},
selectedUsers () {
return this.selectedUserIds.map(userId => this.findUser(userId)).filter(user => user)
searchUsers () {
return this.searchUserIds.map(userId => this.findUser(userId)).filter(user => user)
},
...mapState({
currentUser: state => state.users.currentUser
@ -56,33 +70,73 @@ const ListsNew = {
onInput () {
this.search(this.query)
},
selectUser (user) {
if (this.selectedUserIds.includes(user.id)) {
this.removeUser(user.id)
toggleRemoveMember (user) {
if (this.removedUserIds.has(user.id)) {
this.id && this.addUser(user)
this.removedUserIds.delete(user.id)
} else {
this.addUser(user)
this.id && this.removeUser(user.id)
this.removedUserIds.add(user.id)
}
},
isSelected (user) {
return this.selectedUserIds.includes(user.id)
toggleAddFromSearch (user) {
if (this.addedUserIds.has(user.id)) {
this.id && this.removeUser(user.id)
this.addedUserIds.delete(user.id)
} else {
this.id && this.addUser(user)
this.addedUserIds.add(user.id)
}
},
isRemoved (user) {
return this.removedUserIds.has(user.id)
},
isAdded (user) {
return this.addedUserIds.has(user.id)
},
addUser (user) {
this.selectedUserIds.push(user.id)
this.$store.dispatch('addListAccount', { accountId: this.user.id, listId: this.id })
},
removeUser (userId) {
this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId: this.id })
},
onResults (results) {
this.userIds = results
onSearchLoading (results) {
this.searchLoading = true
},
updateList () {
this.$store.dispatch('setList', { id: this.id, title: this.title })
this.$store.dispatch('setListAccounts', { id: this.id, accountIds: this.selectedUserIds })
this.$router.push({ name: 'lists-timeline', params: { id: this.id } })
onSearchLoadingDone (results) {
this.searchLoading = false
},
onSearchResults (results) {
this.searchLoading = false
this.searchUserIds = results
},
updateListTitle () {
this.$store.dispatch('setList', { listId: this.id, title: this.titleDraft })
.then(() => {
this.title = this.findListTitle(this.id)
})
},
createList () {
this.$store.dispatch('createList', { title: this.titleDraft })
.then((list) => {
return this
.$store
.dispatch('setListAccounts', { listId: list.id, accountIds: [...this.addedUserIds] })
.then(() => list.id)
})
.then((listId) => {
this.$router.push({ name: 'lists-timeline', params: { id: listId } })
})
.catch((e) => {
this.$store.dispatch('pushGlobalNotice', {
messageKey: 'lists.error',
messageArgs: [e.message],
level: 'error'
})
})
},
deleteList () {
this.$store.dispatch('deleteList', { id: this.id })
this.$store.dispatch('deleteList', { listId: this.id })
this.$router.push({ name: 'lists' })
}
}

@ -1,8 +1,8 @@
<template>
<div class="panel-default panel list-edit">
<div class="panel-default panel ListEdit">
<div
ref="header"
class="panel-heading"
class="panel-heading list-edit-heading"
>
<button
class="button-unstyled go-back-button"
@ -13,54 +13,151 @@
icon="chevron-left"
/>
</button>
<div class="title">
<i18n-t
v-if="id"
keypath="lists.editing_list"
>
<template #listTitle>
{{ title }}
</template>
</i18n-t>
<i18n-t
v-else
keypath="lists.creating_list"
/>
</div>
</div>
<div class="input-wrap">
<input
ref="title"
v-model="title"
:placeholder="$t('lists.title')"
<div class="panel-body">
<div class="input-wrap">
<label for="list-edit-title">{{ $t('lists.title') }}</label>
{{ ' ' }}
<input
id="list-edit-title"
ref="title"
v-model="titleDraft"
>
<button
v-if="id"
class="btn button-default follow-button"
@click="updateListTitle"
>
{{ $t('lists.update_title') }}
</button>
</div>
<tab-switcher
class="list-member-management"
:scrollable-tabs="true"
>
<div
v-if="id || addedUserIds.size > 0"
:label="$t('lists.manage_members')"
class="members-list"
>
<div class="users-list">
<div
v-for="user in membersUsers"
:key="user.id"
class="member"
>
<BasicUserCard
:user="user"
>
<button
class="btn button-default follow-button"
@click="toggleRemoveMember(user)"
>
{{ isRemoved(user) ? $t('general.undo') : $t('lists.remove_from_list') }}
</button>
</BasicUserCard>
</div>
</div>
</div>
<div
class="search-list"
:label="$t('lists.add_members')"
>
<ListsUserSearch
@results="onSearchResults"
@loading="onSearchLoading"
@loadingDone="onSearchLoadingDone"
/>
<div
v-if="searchLoading"
class="loading"
>
<PanelLoading />
</div>
<div
v-else
class="users-list"
>
<div
v-for="user in searchUsers"
:key="user.id"
class="member"
>
<BasicUserCard
:user="user"
>
<span
v-if="membersUserIds.includes(user.id)"
>
{{ $t('lists.is_in_list') }}
</span>
<button
v-if="!membersUserIds.includes(user.id)"
class="btn button-default follow-button"
@click="toggleAddFromSearch(user)"
>
{{ isAdded(user) ? $t('general.undo') : $t('lists.add_to_list') }}
</button>
<button
v-else
class="btn button-default follow-button"
@click="toggleRemoveMember(user)"
>
{{ isRemoved(user) ? $t('general.undo') : $t('lists.remove_from_list') }}
</button>
</BasicUserCard>
</div>
</div>
</div>
</tab-switcher>
</div>
<div class="member-list">
<div
v-for="user in selectedUsers"
:key="user.id"
class="member"
<div class="panel-footer">
<span class="spacer" />
<button
v-if="!id"
class="btn button-default footer-button"
@click="createList"
>
<BasicUserCard
:user="user"
:class="isSelected(user) ? 'selected' : ''"
@click.capture.prevent="selectUser(user)"
/>
</div>
</div>
<ListsUserSearch @results="onResults" />
<div class="member-list">
<div
v-for="user in users"
:key="user.id"
class="member"
{{ $t('lists.create') }}
</button>
<button
v-else-if="!reallyDelete"
class="btn button-default footer-button"
@click="reallyDelete = true"
>
<BasicUserCard
:user="user"
:class="isSelected(user) ? 'selected' : ''"
@click.capture.prevent="selectUser(user)"
/>
</div>
{{ $t('lists.delete') }}
</button>
<template v-else>
{{ $t('lists.really_delete') }}
<button
class="btn button-default footer-button"
@click="deleteList"
>
{{ $t('general.yes') }}
</button>
<button
class="btn button-default footer-button"
@click="reallyDelete = false"
>
{{ $t('general.no') }}
</button>
</template>
</div>
<button
:disabled="title && title.length === 0"
class="btn button-default"
@click="updateList"
>
{{ $t('lists.save') }}
</button>
<button
class="btn button-default"
@click="deleteList"
>
{{ $t('lists.delete') }}
</button>
</div>
</template>
@ -69,28 +166,43 @@
<style lang="scss">
@import '../../_variables.scss';
.list-edit {
.input-wrap {
.ListEdit {
--panel-body-padding: 0.5em;
height: calc(100vh - var(--navbar-height));
overflow: hidden;
display: flex;
flex-direction: column;
.list-edit-heading {
grid-template-columns: auto minmax(50%, 1fr);
}
.panel-body {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
flex: 1;
flex-direction: column;
overflow: hidden;
}
input {
width: 100%;
}
.list-member-management {
flex: 1 0 auto;
}
.search-icon {
margin-right: 0.3em;
}
.member-list {
.users-list {
padding-bottom: 0.7rem;
overflow-y: auto;
}
.basic-user-card:hover,
.basic-user-card.selected {
cursor: pointer;
background-color: var(--selectedPost, $fallback--lightBg);
& .search-list,
& .members-list {
overflow: hidden;
flex-direction: column;
min-height: 0;
}
.go-back-button {
@ -102,7 +214,15 @@
}
.btn {
margin: 0.5em;
margin: 0 0.5em;
}
.panel-footer {
grid-template-columns: minmax(10%, 1fr);
.footer-button {
min-width: 9em;
}
}
}
</style>

@ -1,28 +1,17 @@
import { mapState } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faUsers,
faGlobe,
faBookmark,
faEnvelope,
faHome
} from '@fortawesome/free-solid-svg-icons'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { getListEntries } from 'src/components/navigation/filter.js'
library.add(
faUsers,
faGlobe,
faBookmark,
faEnvelope,
faHome
)
const ListsMenuContent = {
created () {
this.$store.dispatch('startFetchingLists')
export const ListsMenuContent = {
props: [
'showPin'
],
components: {
NavigationEntry
},
computed: {
...mapState({
lists: state => state.lists.allLists,
lists: getListEntries,
currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private,
federating: state => state.instance.federating

@ -1,16 +1,11 @@
<template>
<ul>
<li
v-for="list in lists.slice().reverse()"
:key="list.id"
>
<router-link
class="menu-item"
:to="{ name: 'lists-timeline', params: { id: list.id } }"
>
{{ list.title }}
</router-link>
</li>
<NavigationEntry
v-for="item in lists"
:key="item.name"
:show-pin="showPin"
:item="item"
/>
</ul>
</template>

@ -1,79 +0,0 @@
import { mapState, mapGetters } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import ListsUserSearch from '../lists_user_search/lists_user_search.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSearch,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
library.add(
faSearch,
faChevronLeft
)
const ListsNew = {
components: {
BasicUserCard,
UserAvatar,
ListsUserSearch
},
data () {
return {
title: '',
userIds: [],
selectedUserIds: []
}
},
computed: {
users () {
return this.userIds.map(userId => this.findUser(userId))
},
selectedUsers () {
return this.selectedUserIds.map(userId => this.findUser(userId))
},
...mapState({
currentUser: state => state.users.currentUser
}),
...mapGetters(['findUser'])
},
methods: {
goBack () {
this.$emit('cancel')
},
onInput () {
this.search(this.query)
},
selectUser (user) {
if (this.selectedUserIds.includes(user.id)) {
this.removeUser(user.id)
} else {
this.addUser(user)
}
},
isSelected (user) {
return this.selectedUserIds.includes(user.id)
},
addUser (user) {
this.selectedUserIds.push(user.id)
},
removeUser (userId) {
this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
},
onResults (results) {
this.userIds = results
},
createList () {
// the API has two different endpoints for "creating a list with a name"
// and "updating the accounts on the list".
this.$store.dispatch('createList', { title: this.title })
.then((list) => {
this.$store.dispatch('setListAccounts', { id: list.id, accountIds: this.selectedUserIds })
this.$router.push({ name: 'lists-timeline', params: { id: list.id } })
})
}
}
}
export default ListsNew

@ -1,95 +0,0 @@
<template>
<div class="panel-default panel list-new">
<div
ref="header"
class="panel-heading"
>
<button
class="button-unstyled go-back-button"
@click="goBack"
>
<FAIcon
size="lg"
icon="chevron-left"
/>
</button>
</div>
<div class="input-wrap">
<input
ref="title"
v-model="title"
:placeholder="$t('lists.title')"
>
</div>
<div class="member-list">
<div
v-for="user in selectedUsers"
:key="user.id"
class="member"
>
<BasicUserCard
:user="user"
:class="isSelected(user) ? 'selected' : ''"
@click.capture.prevent="selectUser(user)"
/>
</div>
</div>
<ListsUserSearch
@results="onResults"
/>
<div
v-for="user in users"
:key="user.id"
class="member"
>
<BasicUserCard
:user="user"
:class="isSelected(user) ? 'selected' : ''"
@click.capture.prevent="selectUser(user)"
/>
</div>
<button
:disabled="title && title.length === 0"
class="btn button-default"
@click="createList"
>
{{ $t('lists.create') }}
</button>
</div>
</template>
<script src="./lists_new.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.list-new {
.search-icon {
margin-right: 0.3em;
}
.member-list {
padding-bottom: 0.7rem;
}
.basic-user-card:hover,
.basic-user-card.selected {
cursor: pointer;
background-color: var(--selectedPost, $fallback--lightBg);
}
.go-back-button {
text-align: center;
line-height: 1;
height: 100%;
align-self: start;
width: var(--__panel-heading-height-inner);
}
.btn {
margin: 0.5em;
}
}
</style>

@ -17,14 +17,14 @@ const ListsTimeline = {
this.listId = route.params.id
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
this.$store.dispatch('fetchList', { id: this.listId })
this.$store.dispatch('fetchList', { listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
}
}
},
created () {
this.listId = this.$route.params.id
this.$store.dispatch('fetchList', { id: this.listId })
this.$store.dispatch('fetchList', { listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
},
unmounted () {

@ -15,6 +15,7 @@ const ListsUserSearch = {
components: {
Checkbox
},
emits: ['loading', 'loadingDone', 'results'],
data () {
return {
loading: false,
@ -33,12 +34,16 @@ const ListsUserSearch = {
}
this.loading = true
this.$emit('loading')
this.userIds = []
this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts', following: this.followingOnly })
.then(data => {
this.loading = false
this.$emit('results', data.accounts.map(a => a.id))
})
.finally(() => {
this.loading = false
this.$emit('loadingDone')
})
}
}
}

@ -1,5 +1,5 @@
<template>
<div>
<div class="ListsUserSearch">
<div class="input-wrap">
<div class="input-search">
<FAIcon
@ -29,17 +29,19 @@
<style lang="scss">
@import '../../_variables.scss';
.input-wrap {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
.ListsUserSearch {
.input-wrap {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
input {
width: 100%;
input {
width: 100%;
}
}
}
.search-icon {
margin-right: 0.3em;
.search-icon {
margin-right: 0.3em;
}
}
</style>

@ -2,6 +2,7 @@ import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -19,7 +20,8 @@ library.add(
const MobileNav = {
components: {
SideDrawer,
Notifications
Notifications,
NavigationPins
},
data: () => ({
notificationsCloseGesture: undefined,
@ -47,7 +49,10 @@ const MobileNav = {
isChat () {
return this.$route.name === 'chat'
},
...mapGetters(['unreadChatCount'])
...mapGetters(['unreadChatCount']),
chatsPinned () {
return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
}
},
methods: {
toggleMobileSidebar () {

@ -17,20 +17,12 @@
icon="bars"
/>
<div
v-if="unreadChatCount"
v-if="unreadChatCount && !chatsPinned"
class="alert-dot"
/>
</button>
<router-link
v-if="!hideSitename"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
>
{{ sitename }}
</router-link>
</div>
<div class="item right">
<NavigationPins class="pins" />
</div> <div class="item right">
<button
v-if="currentUser"
class="button-unstyled mobile-nav-button"
@ -94,6 +86,7 @@
grid-template-columns: 2fr auto;
width: 100%;
box-sizing: border-box;
a {
color: var(--topBarLink, $fallback--link);
}
@ -178,13 +171,20 @@
}
}
.pins {
flex: 1;
.pinned-item {
flex-grow: 1;
}
}
.mobile-notifications {
margin-top: 50px;
width: 100vw;
height: calc(100vh - var(--navbar-height));
overflow-x: hidden;
overflow-y: scroll;
color: $fallback--text;
color: var(--text, $fallback--text);
background-color: $fallback--bg;
@ -194,14 +194,17 @@
padding: 0;
border-radius: 0;
box-shadow: none;
.panel {
border-radius: 0;
margin: 0;
box-shadow: none;
}
.panel:after {
.panel::after {
border-radius: 0;
}
.panel .panel-heading {
border-radius: 0;
box-shadow: none;

@ -10,7 +10,8 @@ library.add(
const HIDDEN_FOR_PAGES = new Set([
'chats',
'chat'
'chat',
'lists-edit'
])
const MobilePostStatusButton = {

@ -1,6 +1,10 @@
import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue'
import ListsMenuContent from '../lists_menu/lists_menu_content.vue'
import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue'
import { mapState, mapGetters } from 'vuex'
import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js'
import { filterNavigation } from 'src/components/navigation/filter.js'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -30,21 +34,23 @@ library.add(
faStream,
faList
)
const NavPanel = {
props: ['forceExpand', 'forceEditMode'],
created () {
if (this.currentUser && this.currentUser.locked) {
this.$store.dispatch('startFetchingFollowRequests')
}
},
components: {
TimelineMenuContent,
ListsMenuContent
ListsMenuContent,
NavigationEntry,
NavigationPins,
Checkbox
},
data () {
return {
editMode: false,
showTimelines: false,
showLists: false
showLists: false,
timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })),
rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k }))
}
},
methods: {
@ -53,19 +59,62 @@ const NavPanel = {
},
toggleLists () {
this.showLists = !this.showLists
},
toggleEditMode () {
this.editMode = !this.editMode
},
toggleCollapse () {
this.$store.commit('setPreference', { path: 'simple.collapseNav', value: !this.collapsed })
this.$store.dispatch('pushServerSideStorage')
},
isPinned (item) {
return this.pinnedItems.has(item)
},
togglePin (item) {
if (this.isPinned(item)) {
this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
} else {
this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
}
this.$store.dispatch('pushServerSideStorage')
}
},
computed: {
listsNavigation () {
return this.$store.getters.mergedConfig.listsNavigation
},
...mapState({
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems),
collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav
}),
timelinesItems () {
return filterNavigation(
Object
.entries({ ...TIMELINES })
.map(([k, v]) => ({ ...v, name: k })),
{
hasChats: this.pleromaChatMessagesAvailable,
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser
}
)
},
rootItems () {
return filterNavigation(
Object
.entries({ ...ROOT_ITEMS })
.map(([k, v]) => ({ ...v, name: k })),
{
hasChats: this.pleromaChatMessagesAvailable,
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser
}
)
},
...mapGetters(['unreadChatCount'])
}
}

@ -1,135 +1,99 @@
<template>
<div class="NavPanel">
<div class="panel panel-default">
<ul>
<li v-if="currentUser || !privateMode">
<button
class="button-unstyled menu-item"
@click="toggleTimelines"
>
<FAIcon
fixed-width
class="fa-scale-110"
icon="stream"
/>{{ $t("nav.timelines") }}
<FAIcon
class="timelines-chevron"
fixed-width
:icon="showTimelines ? 'chevron-up' : 'chevron-down'"
/>
</button>
<div
v-show="showTimelines"
class="timelines-background"
>
<TimelineMenuContent class="timelines" />
</div>
</li>
<li v-if="currentUser && listsNavigation">
<button
class="button-unstyled menu-item"
@click="toggleLists"
>
<router-link
:to="{ name: 'lists' }"
@click.stop
>
<FAIcon
fixed-width
class="fa-scale-110"
icon="list"
/>{{ $t("nav.lists") }}
</router-link>
<FAIcon
class="timelines-chevron"
fixed-width
:icon="showLists ? 'chevron-up' : 'chevron-down'"
<div
v-if="!forceExpand"
class="panel-heading nav-panel-heading"
>
<NavigationPins :limit="6" />
<div class="spacer" />
<button
class="button-unstyled"
@click="toggleCollapse"
>
<FAIcon
class="timelines-chevron"
fixed-width
:icon="collapsed ? 'chevron-down' : 'chevron-up'"
/>
</button>
</div>
<ul
v-if="!collapsed || forceExpand"