diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/basic_user_card/basic_user_card.vue | 10 | ||||
| -rw-r--r-- | src/components/checkbox/checkbox.vue | 75 | ||||
| -rw-r--r-- | src/components/follow_requests/follow_requests.vue | 2 | ||||
| -rw-r--r-- | src/components/list/list.vue | 42 | ||||
| -rw-r--r-- | src/components/progress_button/progress_button.vue | 35 | ||||
| -rw-r--r-- | src/components/selectable_list/selectable_list.js | 66 | ||||
| -rw-r--r-- | src/components/selectable_list/selectable_list.vue | 59 | ||||
| -rw-r--r-- | src/components/user_profile/user_profile.js | 40 | ||||
| -rw-r--r-- | src/components/user_profile/user_profile.vue | 12 | ||||
| -rw-r--r-- | src/components/user_search/user_search.vue | 2 | ||||
| -rw-r--r-- | src/components/user_settings/user_settings.js | 48 | ||||
| -rw-r--r-- | src/components/user_settings/user_settings.vue | 44 | ||||
| -rw-r--r-- | src/components/who_to_follow/who_to_follow.vue | 2 |
13 files changed, 377 insertions, 60 deletions
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 @@ <script src="./basic_user_card.js"></script> <style lang="scss"> -@import '../../_variables.scss'; - .basic-user-card { display: flex; flex: 1 0; margin: 0; - padding-top: 0.6em; - padding-right: 1em; - padding-bottom: 0.6em; - padding-left: 1em; - border-bottom: 1px solid; - border-bottom-color: $fallback--border; - border-bottom-color: var(--border, $fallback--border); + padding: 0.6em 1em; &-collapsed-content { margin-left: 0.7em; diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue new file mode 100644 index 00000000..4152b049 --- /dev/null +++ b/src/components/checkbox/checkbox.vue @@ -0,0 +1,75 @@ +<template> + <label class="checkbox"> + <input type="checkbox" :checked="checked" @change="$emit('change', $event.target.checked)" :indeterminate.prop="indeterminate"> + <i class="checkbox-indicator" /> + <span v-if="!!$slots.default"><slot></slot></span> + </label> +</template> + +<script> +export default { + model: { + prop: 'checked', + event: 'change' + }, + props: ['checked', 'indeterminate'] +} +</script> + +<style lang="scss"> +@import '../../_variables.scss'; + +.checkbox { + position: relative; + display: inline-block; + padding-left: 1.2em; + min-height: 1.2em; + + &-indicator::before { + position: absolute; + left: 0; + top: 0; + display: block; + content: '✔'; + transition: color 200ms; + width: 1.1em; + height: 1.1em; + border-radius: $fallback--checkboxRadius; + border-radius: var(--checkboxRadius, $fallback--checkboxRadius); + box-shadow: 0px 0px 2px black inset; + box-shadow: var(--inputShadow); + background-color: $fallback--fg; + background-color: var(--input, $fallback--fg); + vertical-align: top; + text-align: center; + line-height: 1.1em; + font-size: 1.1em; + color: transparent; + overflow: hidden; + box-sizing: border-box; + } + + input[type=checkbox] { + display: none; + + &:checked + .checkbox-indicator::before { + color: $fallback--text; + color: var(--text, $fallback--text); + } + + &:indeterminate + .checkbox-indicator::before { + content: '–'; + color: $fallback--text; + color: var(--text, $fallback--text); + } + + &:disabled + .checkbox-indicator::before { + opacity: .5; + } + } + + & > span { + margin-left: .5em; + } +} +</style> 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')}} </div> <div class="panel-body"> - <FollowRequestCard v-for="request in requests" :key="request.id" :user="request"/> + <FollowRequestCard v-for="request in requests" :key="request.id" :user="request" class="list-item"/> </div> </div> </template> 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 @@ +<template> + <div class="list"> + <div v-for="item in items" class="list-item" :key="getKey(item)"> + <slot name="item" :item="item" /> + </div> + <div class="list-empty-content faint" v-if="items.length === 0 && !!$slots.empty"> + <slot name="empty" /> + </div> + </div> +</template> + +<script> +export default { + props: { + items: { + type: Array, + default: () => [] + }, + getKey: { + type: Function, + default: item => item.id + } + } +} +</script> + +<style lang="scss"> +@import '../../_variables.scss'; + +.list { + &-item:not(:last-child) { + border-bottom: 1px solid; + border-bottom-color: $fallback--border; + border-bottom-color: var(--border, $fallback--border); + } + + &-empty-content { + text-align: center; + padding: 10px; + } +} +</style> 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 @@ +<template> + <button :disabled="progress || disabled" @click="onClick"> + <template v-if="progress"> + <slot name="progress" /> + </template> + <template v-else> + <slot /> + </template> + </button> +</template> + +<script> +export default { + props: { + disabled: { + type: Boolean + }, + click: { // click event handler. Must return a promise + type: Function, + default: () => Promise.resolve() + } + }, + data () { + return { + progress: false + } + }, + methods: { + onClick () { + this.progress = true + this.click().then(() => { this.progress = false }) + } + } +} +</script> 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 @@ +<template> + <div class="selectable-list"> + <div class="selectable-list-header" v-if="items.length > 0"> + <div class="selectable-list-checkbox-wrapper"> + <Checkbox :checked="allSelected" @change="toggleAll" :indeterminate="someSelected">{{ $t('selectable_list.select_all') }}</Checkbox> + </div> + <div class="selectable-list-header-actions"> + <slot name="header" :selected="filteredSelected" /> + </div> + </div> + <List :items="items" :getKey="getKey"> + <template slot="item" slot-scope="{item}"> + <div class="selectable-list-item-inner" :class="{ 'selectable-list-item-selected-inner': isSelected(item) }"> + <div class="selectable-list-checkbox-wrapper"> + <Checkbox :checked="isSelected(item)" @change="checked => toggle(checked, item)" /> + </div> + <slot name="item" :item="item" /> + </div> + </template> + <template slot="empty"><slot name="empty" /></template> + </List> + </div> +</template> + +<script src="./selectable_list.js"></script> + +<style lang="scss"> +@import '../../_variables.scss'; + +.selectable-list { + &-item-inner { + display: flex; + align-items: center; + } + + &-item-selected-inner { + background-color: $fallback--lightBg; + background-color: var(--lightBg, $fallback--lightBg); + } + + &-header { + display: flex; + align-items: center; + padding: 0.6em 0; + border-bottom: 2px solid; + border-bottom-color: $fallback--border; + border-bottom-color: var(--border, $fallback--border); + + &-actions { + flex: 1; + } + } + + &-checkbox-wrapper { + padding: 0 10px; + flex: none; + } +} +</style> 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" /> <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count"> - <FriendList :userId="userId" /> + <FriendList :userId="userId"> + <template slot="item" slot-scope="{item}"> + <FollowCard :user="item" /> + </template> + </FriendList> </div> <div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count"> - <FollowerList :userId="userId" :entryProps="{noFollowsYou: isUs}" /> + <FollowerList :userId="userId"> + <template slot="item" slot-scope="{item}"> + <FollowCard :user="item" :noFollowsYou="isUs" /> + </template> + </FollowerList> </div> <Timeline :label="$t('user_card.media')" diff --git a/src/components/user_search/user_search.vue b/src/components/user_search/user_search.vue index 1269eea6..890b3c13 100644 --- a/src/components/user_search/user_search.vue +++ b/src/components/user_search/user_search.vue @@ -13,7 +13,7 @@ <i class="icon-spin3 animate-spin"/> </div> <div v-else class="panel-body"> - <FollowCard v-for="user in users" :key="user.id" :user="user"/> + <FollowCard v-for="user in users" :key="user.id" :user="user" class="list-item"/> </div> </div> </template> 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 @@ <BlockCard slot-scope="row" :userId="row.item"/> </Autosuggest> </div> - <block-list :refresh="true"> + <BlockList :refresh="true" :getKey="identity"> + <template slot="header" slot-scope="{selected}"> + <div class="profile-edit-bulk-actions"> + <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => blockUsers(selected)"> + {{ $t('user_card.block') }} + <template slot="progress">{{ $t('user_card.block_progress') }}</template> + </ProgressButton> + <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => unblockUsers(selected)"> + {{ $t('user_card.unblock') }} + <template slot="progress">{{ $t('user_card.unblock_progress') }}</template> + </ProgressButton> + </div> + </template> + <template slot="item" slot-scope="{item}"><BlockCard :userId="item" /></template> <template slot="empty">{{$t('settings.no_blocks')}}</template> - </block-list> + </BlockList> </div> <div :label="$t('settings.mutes_tab')"> @@ -211,9 +224,22 @@ <MuteCard slot-scope="row" :userId="row.item"/> </Autosuggest> </div> - <mute-list :refresh="true"> + <MuteList :refresh="true" :getKey="identity"> + <template slot="header" slot-scope="{selected}"> + <div class="profile-edit-bulk-actions"> + <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => muteUsers(selected)"> + {{ $t('user_card.mute') }} + <template slot="progress">{{ $t('user_card.mute_progress') }}</template> + </ProgressButton> + <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => unmuteUsers(selected)"> + {{ $t('user_card.unmute') }} + <template slot="progress">{{ $t('user_card.unmute_progress') }}</template> + </ProgressButton> + </div> + </template> + <template slot="item" slot-scope="{item}"><MuteCard :userId="item" /></template> <template slot="empty">{{$t('settings.no_mutes')}}</template> - </mute-list> + </MuteList> </div> </tab-switcher> </div> @@ -276,5 +302,15 @@ &-usersearch-wrapper { padding: 1em; } + + &-bulk-actions { + text-align: right; + padding: 0 1em; + min-height: 28px; + + button { + width: 10em; + } + } } </style> 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')}} </div> <div class="panel-body"> - <FollowCard v-for="user in users" :key="user.id" :user="user"/> + <FollowCard v-for="user in users" :key="user.id" :user="user" class="list-item"/> </div> </div> </template> |
