diff --git a/package.json b/package.json index 14937673..fcdea2c1 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "v-click-outside": "^2.1.1", "vue": "^2.5.13", "vue-chat-scroll": "^1.2.1", - "vue-compose": "^0.7.1", "vue-i18n": "^7.3.2", "vue-popperjs": "^2.0.3", "vue-router": "^3.0.1", diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 8afe8b44..48de6678 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -24,19 +24,11 @@ diff --git a/src/components/follow_requests/follow_requests.vue b/src/components/follow_requests/follow_requests.vue index b83c2d68..36901fb4 100644 --- a/src/components/follow_requests/follow_requests.vue +++ b/src/components/follow_requests/follow_requests.vue @@ -4,7 +4,7 @@ {{$t('nav.friend_requests')}}
- +
diff --git a/src/components/list/list.vue b/src/components/list/list.vue new file mode 100644 index 00000000..7136915b --- /dev/null +++ b/src/components/list/list.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/components/progress_button/progress_button.vue b/src/components/progress_button/progress_button.vue new file mode 100644 index 00000000..737360bb --- /dev/null +++ b/src/components/progress_button/progress_button.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/components/selectable_list/selectable_list.js b/src/components/selectable_list/selectable_list.js new file mode 100644 index 00000000..10980d46 --- /dev/null +++ b/src/components/selectable_list/selectable_list.js @@ -0,0 +1,66 @@ +import List from '../list/list.vue' +import Checkbox from '../checkbox/checkbox.vue' + +const SelectableList = { + components: { + List, + Checkbox + }, + props: { + items: { + type: Array, + default: () => [] + }, + getKey: { + type: Function, + default: item => item.id + } + }, + data () { + return { + selected: [] + } + }, + computed: { + allKeys () { + return this.items.map(this.getKey) + }, + filteredSelected () { + return this.allKeys.filter(key => this.selected.indexOf(key) !== -1) + }, + allSelected () { + return this.filteredSelected.length === this.items.length + }, + noneSelected () { + return this.filteredSelected.length === 0 + }, + someSelected () { + return !this.allSelected && !this.noneSelected + } + }, + methods: { + isSelected (item) { + return this.filteredSelected.indexOf(this.getKey(item)) !== -1 + }, + toggle (checked, item) { + const key = this.getKey(item) + const oldChecked = this.isSelected(key) + if (checked !== oldChecked) { + if (checked) { + this.selected.push(key) + } else { + this.selected.splice(this.selected.indexOf(key), 1) + } + } + }, + toggleAll (value) { + if (value) { + this.selected = this.allKeys.slice(0) + } else { + this.selected = [] + } + } + } +} + +export default SelectableList diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue new file mode 100644 index 00000000..ba1e5266 --- /dev/null +++ b/src/components/selectable_list/selectable_list.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index cb465d66..4eddb8b1 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -1,33 +1,26 @@ -import { compose } from 'vue-compose' import get from 'lodash/get' import UserCard from '../user_card/user_card.vue' import FollowCard from '../follow_card/follow_card.vue' import Timeline from '../timeline/timeline.vue' import ModerationTools from '../moderation_tools/moderation_tools.vue' +import List from '../list/list.vue' import withLoadMore from '../../hocs/with_load_more/with_load_more' -import withList from '../../hocs/with_list/with_list' -const FollowerList = compose( - withLoadMore({ - fetch: (props, $store) => $store.dispatch('fetchFollowers', props.userId), - select: (props, $store) => get($store.getters.findUser(props.userId), 'followerIds', []).map(id => $store.getters.findUser(id)), - destory: (props, $store) => $store.dispatch('clearFollowers', props.userId), - childPropName: 'entries', - additionalPropNames: ['userId'] - }), - withList({ getEntryProps: user => ({ user }) }) -)(FollowCard) +const FollowerList = withLoadMore({ + fetch: (props, $store) => $store.dispatch('fetchFollowers', props.userId), + select: (props, $store) => get($store.getters.findUser(props.userId), 'followerIds', []).map(id => $store.getters.findUser(id)), + destroy: (props, $store) => $store.dispatch('clearFollowers', props.userId), + childPropName: 'items', + additionalPropNames: ['userId'] +})(List) -const FriendList = compose( - withLoadMore({ - fetch: (props, $store) => $store.dispatch('fetchFriends', props.userId), - select: (props, $store) => get($store.getters.findUser(props.userId), 'friendIds', []).map(id => $store.getters.findUser(id)), - destory: (props, $store) => $store.dispatch('clearFriends', props.userId), - childPropName: 'entries', - additionalPropNames: ['userId'] - }), - withList({ getEntryProps: user => ({ user }) }) -)(FollowCard) +const FriendList = withLoadMore({ + fetch: (props, $store) => $store.dispatch('fetchFriends', props.userId), + select: (props, $store) => get($store.getters.findUser(props.userId), 'friendIds', []).map(id => $store.getters.findUser(id)), + destroy: (props, $store) => $store.dispatch('clearFriends', props.userId), + childPropName: 'items', + additionalPropNames: ['userId'] +})(List) const UserProfile = { data () { @@ -134,7 +127,8 @@ const UserProfile = { Timeline, FollowerList, FriendList, - ModerationTools + ModerationTools, + FollowCard } } diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 89900f60..71c625b7 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -14,10 +14,18 @@ :user-id="userId" />
- + + +
- + + +
- +
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index dd931e67..e88ee612 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -1,4 +1,3 @@ -import { compose } from 'vue-compose' import unescape from 'lodash/unescape' import get from 'lodash/get' import map from 'lodash/map' @@ -10,29 +9,24 @@ import ScopeSelector from '../scope_selector/scope_selector.vue' import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' import BlockCard from '../block_card/block_card.vue' import MuteCard from '../mute_card/mute_card.vue' +import SelectableList from '../selectable_list/selectable_list.vue' +import ProgressButton from '../progress_button/progress_button.vue' import EmojiInput from '../emoji-input/emoji-input.vue' import Autosuggest from '../autosuggest/autosuggest.vue' import withSubscription from '../../hocs/with_subscription/with_subscription' -import withList from '../../hocs/with_list/with_list' import userSearchApi from '../../services/new_api/user_search.js' -const BlockList = compose( - withSubscription({ - fetch: (props, $store) => $store.dispatch('fetchBlocks'), - select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []), - childPropName: 'entries' - }), - withList({ getEntryProps: userId => ({ userId }) }) -)(BlockCard) +const BlockList = withSubscription({ + fetch: (props, $store) => $store.dispatch('fetchBlocks'), + select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []), + childPropName: 'items' +})(SelectableList) -const MuteList = compose( - withSubscription({ - fetch: (props, $store) => $store.dispatch('fetchMutes'), - select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []), - childPropName: 'entries' - }), - withList({ getEntryProps: userId => ({ userId }) }) -)(MuteCard) +const MuteList = withSubscription({ + fetch: (props, $store) => $store.dispatch('fetchMutes'), + select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []), + childPropName: 'items' +})(SelectableList) const UserSettings = { data () { @@ -80,7 +74,8 @@ const UserSettings = { EmojiInput, Autosuggest, BlockCard, - MuteCard + MuteCard, + ProgressButton }, computed: { user () { @@ -360,6 +355,21 @@ const UserSettings = { this.$store.dispatch('addNewUsers', users) return map(users, 'id') }) + }, + blockUsers (ids) { + return this.$store.dispatch('blockUsers', ids) + }, + unblockUsers (ids) { + return this.$store.dispatch('unblockUsers', ids) + }, + muteUsers (ids) { + return this.$store.dispatch('muteUsers', ids) + }, + unmuteUsers (ids) { + return this.$store.dispatch('unmuteUsers', ids) + }, + identity (value) { + return value } } } diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index 9d37ffc4..d68e68fa 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -200,9 +200,22 @@ - + + + - +
@@ -211,9 +224,22 @@
- + + + - + @@ -276,5 +302,15 @@ &-usersearch-wrapper { padding: 1em; } + + &-bulk-actions { + text-align: right; + padding: 0 1em; + min-height: 28px; + + button { + width: 10em; + } + } } diff --git a/src/components/who_to_follow/who_to_follow.vue b/src/components/who_to_follow/who_to_follow.vue index 1630f5ac..8bc9a728 100644 --- a/src/components/who_to_follow/who_to_follow.vue +++ b/src/components/who_to_follow/who_to_follow.vue @@ -4,7 +4,7 @@ {{$t('who_to_follow.who_to_follow')}}
- +
diff --git a/src/hocs/with_list/with_list.js b/src/hocs/with_list/with_list.js deleted file mode 100644 index 896f8fc8..00000000 --- a/src/hocs/with_list/with_list.js +++ /dev/null @@ -1,40 +0,0 @@ -import Vue from 'vue' -import map from 'lodash/map' -import isEmpty from 'lodash/isEmpty' -import './with_list.scss' - -const defaultEntryPropsGetter = entry => ({ entry }) -const defaultKeyGetter = entry => entry.id - -const withList = ({ - getEntryProps = defaultEntryPropsGetter, // function to accept entry and index values and return props to be passed into the item component - getKey = defaultKeyGetter // funciton to accept entry and index values and return key prop value -}) => (ItemComponent) => ( - Vue.component('withList', { - props: [ - 'entries', // array of entry - 'entryProps', // additional props to be passed into each entry - 'entryListeners' // additional event listeners to be passed into each entry - ], - render (createElement) { - return ( -
- {map(this.entries, (entry, index) => { - const props = { - key: getKey(entry, index), - props: { - ...this.$props.entryProps, - ...getEntryProps(entry, index) - }, - on: this.$props.entryListeners - } - return - })} - {isEmpty(this.entries) && this.$slots.empty &&
{this.$slots.empty}
} -
- ) - } - }) -) - -export default withList diff --git a/src/hocs/with_list/with_list.scss b/src/hocs/with_list/with_list.scss deleted file mode 100644 index c6e13d5b..00000000 --- a/src/hocs/with_list/with_list.scss +++ /dev/null @@ -1,6 +0,0 @@ -.with-list { - &-empty-content { - text-align: center; - padding: 10px; - } -} \ No newline at end of file diff --git a/src/hocs/with_load_more/with_load_more.scss b/src/hocs/with_load_more/with_load_more.scss index 1a0a9c40..4cefe2be 100644 --- a/src/hocs/with_load_more/with_load_more.scss +++ b/src/hocs/with_load_more/with_load_more.scss @@ -1,10 +1,16 @@ + +@import '../../_variables.scss'; + .with-load-more { &-footer { padding: 10px; text-align: center; + border-top: 1px solid; + border-top-color: $fallback--border; + border-top-color: var(--border, $fallback--border); .error { font-size: 14px; } } -} \ No newline at end of file +} diff --git a/src/i18n/en.json b/src/i18n/en.json index c71c9036..7dca05e3 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -112,6 +112,9 @@ "password_confirmation_match": "should be the same as password" } }, + "selectable_list": { + "select_all": "Select all" + }, "settings": { "app_name": "App name", "attachmentRadius": "Attachments", diff --git a/src/modules/users.js b/src/modules/users.js index 58db8b71..a5dec794 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -32,6 +32,35 @@ const getNotificationPermission = () => { return Promise.resolve(Notification.permission) } +const blockUser = (store, id) => { + return store.rootState.api.backendInteractor.blockUser(id) + .then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + store.commit('addBlockId', id) + store.commit('removeStatus', { timeline: 'friends', userId: id }) + store.commit('removeStatus', { timeline: 'public', userId: id }) + store.commit('removeStatus', { timeline: 'publicAndExternal', userId: id }) + }) +} + +const unblockUser = (store, id) => { + return store.rootState.api.backendInteractor.unblockUser(id) + .then((relationship) => store.commit('updateUserRelationship', [relationship])) +} + +const muteUser = (store, id) => { + return store.rootState.api.backendInteractor.muteUser(id) + .then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + store.commit('addMuteId', id) + }) +} + +const unmuteUser = (store, id) => { + return store.rootState.api.backendInteractor.unmuteUser(id) + .then((relationship) => store.commit('updateUserRelationship', [relationship])) +} + export const mutations = { setMuted (state, { user: { id }, muted }) { const user = state.usersObject[id] @@ -206,19 +235,17 @@ const users = { return blocks }) }, - blockUser (store, userId) { - return store.rootState.api.backendInteractor.blockUser(userId) - .then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - store.commit('addBlockId', userId) - store.commit('removeStatus', { timeline: 'friends', userId }) - store.commit('removeStatus', { timeline: 'public', userId }) - store.commit('removeStatus', { timeline: 'publicAndExternal', userId }) - }) + blockUser (store, id) { + return blockUser(store, id) }, unblockUser (store, id) { - return store.rootState.api.backendInteractor.unblockUser(id) - .then((relationship) => store.commit('updateUserRelationship', [relationship])) + return unblockUser(store, id) + }, + blockUsers (store, ids = []) { + return Promise.all(ids.map(id => blockUser(store, id))) + }, + unblockUsers (store, ids = []) { + return Promise.all(ids.map(id => unblockUser(store, id))) }, fetchMutes (store) { return store.rootState.api.backendInteractor.fetchMutes() @@ -229,15 +256,16 @@ const users = { }) }, muteUser (store, id) { - return store.rootState.api.backendInteractor.muteUser(id) - .then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - store.commit('addMuteId', id) - }) + return muteUser(store, id) }, unmuteUser (store, id) { - return store.rootState.api.backendInteractor.unmuteUser(id) - .then((relationship) => store.commit('updateUserRelationship', [relationship])) + return unmuteUser(store, id) + }, + muteUsers (store, ids = []) { + return Promise.all(ids.map(id => muteUser(store, id))) + }, + unmuteUsers (store, ids = []) { + return Promise.all(ids.map(id => unmuteUser(store, id))) }, fetchFriends ({ rootState, commit }, id) { const user = rootState.users.usersObject[id] diff --git a/yarn.lock b/yarn.lock index e463c0d5..69951563 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6430,16 +6430,6 @@ vue-chat-scroll@^1.2.1: version "1.3.5" resolved "https://registry.yarnpkg.com/vue-chat-scroll/-/vue-chat-scroll-1.3.5.tgz#a5ee5bae5058f614818a96eac5ee3be4394a2f68" -vue-compose@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/vue-compose/-/vue-compose-0.7.1.tgz#1c11c4cd5e2c8f2743b03fce8ab43d78aabc20b3" - dependencies: - vue-hoc "0.x.x" - -vue-hoc@0.x.x: - version "0.4.7" - resolved "https://registry.yarnpkg.com/vue-hoc/-/vue-hoc-0.4.7.tgz#4d3322ba89b8b0e42b19045ef536c21d948a4fac" - vue-hot-reload-api@^2.0.11: version "2.3.1" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"