From 171f6f08943dd1d87120df3e4894ddcfd5e1d246 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Sat, 6 Aug 2022 17:26:43 +0300 Subject: Lists implementation --- src/components/lists/lists.js | 32 ++++++ src/components/lists/lists.vue | 31 ++++++ src/components/lists_card/lists_card.js | 16 +++ src/components/lists_card/lists_card.vue | 51 ++++++++++ src/components/lists_edit/lists_edit.js | 91 +++++++++++++++++ src/components/lists_edit/lists_edit.vue | 108 +++++++++++++++++++++ src/components/lists_menu/lists_menu_content.js | 33 +++++++ src/components/lists_menu/lists_menu_content.vue | 17 ++++ src/components/lists_new/lists_new.js | 79 +++++++++++++++ src/components/lists_new/lists_new.vue | 95 ++++++++++++++++++ src/components/lists_timeline/lists_timeline.js | 36 +++++++ src/components/lists_timeline/lists_timeline.vue | 10 ++ .../lists_user_search/lists_user_search.js | 46 +++++++++ .../lists_user_search/lists_user_search.vue | 45 +++++++++ src/components/nav_panel/nav_panel.js | 19 +++- src/components/nav_panel/nav_panel.vue | 45 +++++++++ src/components/settings_modal/tabs/general_tab.vue | 47 +++++++++ src/components/side_drawer/side_drawer.js | 6 +- src/components/side_drawer/side_drawer.vue | 12 +++ src/components/timeline/timeline.js | 3 + src/components/timeline_menu/timeline_menu.js | 3 + 21 files changed, 819 insertions(+), 6 deletions(-) create mode 100644 src/components/lists/lists.js create mode 100644 src/components/lists/lists.vue create mode 100644 src/components/lists_card/lists_card.js create mode 100644 src/components/lists_card/lists_card.vue create mode 100644 src/components/lists_edit/lists_edit.js create mode 100644 src/components/lists_edit/lists_edit.vue create mode 100644 src/components/lists_menu/lists_menu_content.js create mode 100644 src/components/lists_menu/lists_menu_content.vue create mode 100644 src/components/lists_new/lists_new.js create mode 100644 src/components/lists_new/lists_new.vue create mode 100644 src/components/lists_timeline/lists_timeline.js create mode 100644 src/components/lists_timeline/lists_timeline.vue create mode 100644 src/components/lists_user_search/lists_user_search.js create mode 100644 src/components/lists_user_search/lists_user_search.vue (limited to 'src/components') diff --git a/src/components/lists/lists.js b/src/components/lists/lists.js new file mode 100644 index 00000000..791b99b2 --- /dev/null +++ b/src/components/lists/lists.js @@ -0,0 +1,32 @@ +import ListsCard from '../lists_card/lists_card.vue' +import ListsNew from '../lists_new/lists_new.vue' + +const Lists = { + data () { + return { + isNew: false + } + }, + components: { + ListsCard, + ListsNew + }, + created () { + this.$store.dispatch('startFetchingLists') + }, + computed: { + lists () { + return this.$store.state.lists.allLists + } + }, + methods: { + cancelNewList () { + this.isNew = false + }, + newList () { + this.isNew = true + } + } +} + +export default Lists diff --git a/src/components/lists/lists.vue b/src/components/lists/lists.vue new file mode 100644 index 00000000..fcc56447 --- /dev/null +++ b/src/components/lists/lists.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/components/lists_card/lists_card.js b/src/components/lists_card/lists_card.js new file mode 100644 index 00000000..b503caec --- /dev/null +++ b/src/components/lists_card/lists_card.js @@ -0,0 +1,16 @@ +import { library } from '@fortawesome/fontawesome-svg-core' +import { + faEllipsisH +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faEllipsisH +) + +const ListsCard = { + props: [ + 'list' + ] +} + +export default ListsCard diff --git a/src/components/lists_card/lists_card.vue b/src/components/lists_card/lists_card.vue new file mode 100644 index 00000000..c0d58f18 --- /dev/null +++ b/src/components/lists_card/lists_card.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js new file mode 100644 index 00000000..7b642929 --- /dev/null +++ b/src/components/lists_edit/lists_edit.js @@ -0,0 +1,91 @@ +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 UserAvatar from '../user_avatar/user_avatar.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: [] + } + }, + created () { + this.$store.dispatch('fetchList', { id: this.id }) + .then(() => { this.title = this.findListTitle(this.id) }) + this.$store.dispatch('fetchListAccounts', { id: this.id }) + .then(() => { + this.selectedUserIds = this.findListAccounts(this.id) + this.selectedUserIds.forEach(userId => { + this.$store.dispatch('fetchUserIfMissing', userId) + }) + }) + }, + computed: { + id () { + return this.$route.params.id + }, + users () { + return this.userIds.map(userId => this.findUser(userId)) + }, + selectedUsers () { + return this.selectedUserIds.map(userId => this.findUser(userId)).filter(user => user) + }, + ...mapState({ + currentUser: state => state.users.currentUser + }), + ...mapGetters(['findUser', 'findListTitle', 'findListAccounts']) + }, + methods: { + 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 + }, + 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: 'list-timeline', params: { id: this.id } }) + }, + deleteList () { + this.$store.dispatch('deleteList', { id: this.id }) + this.$router.push({ name: 'lists' }) + } + } +} + +export default ListsNew diff --git a/src/components/lists_edit/lists_edit.vue b/src/components/lists_edit/lists_edit.vue new file mode 100644 index 00000000..69007b02 --- /dev/null +++ b/src/components/lists_edit/lists_edit.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js new file mode 100644 index 00000000..37e7868c --- /dev/null +++ b/src/components/lists_menu/lists_menu_content.js @@ -0,0 +1,33 @@ +import { mapState } from 'vuex' +import { library } from '@fortawesome/fontawesome-svg-core' +import { + faUsers, + faGlobe, + faBookmark, + faEnvelope, + faHome +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faUsers, + faGlobe, + faBookmark, + faEnvelope, + faHome +) + +const ListsMenuContent = { + created () { + this.$store.dispatch('startFetchingLists') + }, + computed: { + ...mapState({ + lists: state => state.lists.allLists, + currentUser: state => state.users.currentUser, + privateMode: state => state.instance.private, + federating: state => state.instance.federating + }) + } +} + +export default ListsMenuContent diff --git a/src/components/lists_menu/lists_menu_content.vue b/src/components/lists_menu/lists_menu_content.vue new file mode 100644 index 00000000..5606b74f --- /dev/null +++ b/src/components/lists_menu/lists_menu_content.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/components/lists_new/lists_new.js b/src/components/lists_new/lists_new.js new file mode 100644 index 00000000..7e3470fa --- /dev/null +++ b/src/components/lists_new/lists_new.js @@ -0,0 +1,79 @@ +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: 'list-timeline', params: { id: list.id } }) + }) + } + } +} + +export default ListsNew diff --git a/src/components/lists_new/lists_new.vue b/src/components/lists_new/lists_new.vue new file mode 100644 index 00000000..4733bdde --- /dev/null +++ b/src/components/lists_new/lists_new.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/components/lists_timeline/lists_timeline.js b/src/components/lists_timeline/lists_timeline.js new file mode 100644 index 00000000..4611a736 --- /dev/null +++ b/src/components/lists_timeline/lists_timeline.js @@ -0,0 +1,36 @@ +import Timeline from '../timeline/timeline.vue' +const ListsTimeline = { + data () { + return { + listId: null + } + }, + components: { + Timeline + }, + computed: { + timeline () { return this.$store.state.statuses.timelines.list } + }, + watch: { + $route: function (route) { + if (route.name === 'list-timeline' && route.params.id !== this.listId) { + 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('startFetchingTimeline', { timeline: 'list', listId: this.listId }) + } + } + }, + created () { + this.listId = this.$route.params.id + this.$store.dispatch('fetchList', { id: this.listId }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId }) + }, + unmounted () { + this.$store.dispatch('stopFetchingTimeline', 'list') + this.$store.commit('clearTimeline', { timeline: 'list' }) + } +} + +export default ListsTimeline diff --git a/src/components/lists_timeline/lists_timeline.vue b/src/components/lists_timeline/lists_timeline.vue new file mode 100644 index 00000000..18156b81 --- /dev/null +++ b/src/components/lists_timeline/lists_timeline.vue @@ -0,0 +1,10 @@ + + + diff --git a/src/components/lists_user_search/lists_user_search.js b/src/components/lists_user_search/lists_user_search.js new file mode 100644 index 00000000..5798841a --- /dev/null +++ b/src/components/lists_user_search/lists_user_search.js @@ -0,0 +1,46 @@ +import { library } from '@fortawesome/fontawesome-svg-core' +import { + faSearch, + faChevronLeft +} from '@fortawesome/free-solid-svg-icons' +import { debounce } from 'lodash' +import Checkbox from '../checkbox/checkbox.vue' + +library.add( + faSearch, + faChevronLeft +) + +const ListsUserSearch = { + components: { + Checkbox + }, + data () { + return { + loading: false, + query: '', + followingOnly: true + } + }, + methods: { + onInput: debounce(function () { + this.search(this.query) + }, 2000), + search (query) { + if (!query) { + this.loading = false + return + } + + this.loading = true + 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)) + }) + } + } +} + +export default ListsUserSearch diff --git a/src/components/lists_user_search/lists_user_search.vue b/src/components/lists_user_search/lists_user_search.vue new file mode 100644 index 00000000..03fb3ba6 --- /dev/null +++ b/src/components/lists_user_search/lists_user_search.vue @@ -0,0 +1,45 @@ + + + + diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index 37bcb409..abeff6bf 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,4 +1,5 @@ import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue' +import ListsMenuContent from '../lists_menu/lists_menu_content.vue' import { mapState, mapGetters } from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' @@ -12,7 +13,8 @@ import { faComments, faBell, faInfoCircle, - faStream + faStream, + faList } from '@fortawesome/free-solid-svg-icons' library.add( @@ -25,7 +27,8 @@ library.add( faComments, faBell, faInfoCircle, - faStream + faStream, + faList ) const NavPanel = { @@ -35,19 +38,27 @@ const NavPanel = { } }, components: { - TimelineMenuContent + TimelineMenuContent, + ListsMenuContent }, data () { return { - showTimelines: false + showTimelines: false, + showLists: false } }, methods: { toggleTimelines () { this.showTimelines = !this.showTimelines + }, + toggleLists () { + this.showLists = !this.showLists } }, computed: { + listsNavigation () { + return this.$store.getters.mergedConfig.listsNavigation + }, ...mapState({ currentUser: state => state.users.currentUser, followRequestCount: state => state.api.followRequests.length, diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 3fd27d89..9322e233 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -25,6 +25,51 @@ +
  • + +
    + +
    +
  • +
  • + + + +
  • +
  • + + {{ $t('settings.lists_navigation') }} + +
  • +
  • +

    {{ $t('settings.columns') }}

    +
  • +
  • + + {{ $t('settings.disable_sticky_headers') }} + +
  • +
  • + + {{ $t('settings.show_scrollbars') }} + +
  • +
  • + + {{ $t('settings.right_sidebar') }} + +
  • +
  • + + {{ $t('settings.third_column_mode') }} + +
  • +
  • + {{ $t('settings.column_sizes') }} +
    + + {{ $t('settings.column_sizes_' + column) }} + +
    +
  • diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index f45f8def..913fa695 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -14,7 +14,8 @@ import { faSearch, faTachometerAlt, faCog, - faInfoCircle + faInfoCircle, + faList } from '@fortawesome/free-solid-svg-icons' library.add( @@ -28,7 +29,8 @@ library.add( faSearch, faTachometerAlt, faCog, - faInfoCircle + faInfoCircle, + faList ) const SideDrawer = { diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index 7547fb08..7d9d36d7 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -55,6 +55,18 @@ /> {{ $t("nav.timelines") }} +
  • + + {{ $t("nav.lists") }} + +
  • { if (statuses && statuses.length === 0) { diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js index a11e7b7e..0792dc13 100644 --- a/src/components/timeline_menu/timeline_menu.js +++ b/src/components/timeline_menu/timeline_menu.js @@ -58,6 +58,9 @@ const TimelineMenu = { if (route === 'tag-timeline') { return '#' + this.$route.params.tag } + if (route === 'list-timeline') { + return this.$store.getters.findListTitle(this.$route.params.id) + } const i18nkey = timelineNames()[this.$route.name] return i18nkey ? this.$t(i18nkey) : route } -- cgit v1.2.3-70-g09d2 From 891fbf996d7ddd415d99ea9065d54b0f493b5422 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Sat, 6 Aug 2022 21:39:08 +0300 Subject: Lists implementation: make route naming consistent --- src/boot/routes.js | 4 ++-- src/components/lists_card/lists_card.vue | 4 ++-- src/components/lists_edit/lists_edit.js | 2 +- src/components/lists_menu/lists_menu_content.vue | 2 +- src/components/lists_new/lists_new.js | 2 +- src/components/lists_timeline/lists_timeline.js | 2 +- src/components/timeline_menu/timeline_menu.js | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/components') diff --git a/src/boot/routes.js b/src/boot/routes.js index 8bb7d196..95e47199 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -77,8 +77,8 @@ export default (store) => { { name: 'about', path: '/about', component: About }, { name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile }, { name: 'lists', path: '/lists', component: Lists }, - { name: 'list-timeline', path: '/lists/:id', component: ListsTimeline }, - { name: 'list-edit', path: '/lists/:id/edit', component: ListsEdit } + { name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline }, + { name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit } ] if (store.state.instance.pleromaChatMessagesAvailable) { diff --git a/src/components/lists_card/lists_card.vue b/src/components/lists_card/lists_card.vue index c0d58f18..13866d8c 100644 --- a/src/components/lists_card/lists_card.vue +++ b/src/components/lists_card/lists_card.vue @@ -1,13 +1,13 @@