diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/block_card/block_card.js | 2 | ||||
| -rw-r--r-- | src/components/media_modal/media_modal.vue | 15 | ||||
| -rw-r--r-- | src/components/mobile_post_status_modal/mobile_post_status_modal.js | 91 | ||||
| -rw-r--r-- | src/components/mobile_post_status_modal/mobile_post_status_modal.vue | 76 | ||||
| -rw-r--r-- | src/components/mute_card/mute_card.js | 2 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.js | 3 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.vue | 1 | ||||
| -rw-r--r-- | src/components/side_drawer/side_drawer.vue | 15 | ||||
| -rw-r--r-- | src/components/status/status.js | 8 | ||||
| -rw-r--r-- | src/components/user_avatar/user_avatar.js | 5 | ||||
| -rw-r--r-- | src/components/user_card/user_card.js | 3 | ||||
| -rw-r--r-- | src/components/user_card/user_card.vue | 14 | ||||
| -rw-r--r-- | src/components/user_profile/user_profile.js | 94 | ||||
| -rw-r--r-- | src/components/user_profile/user_profile.vue | 4 |
14 files changed, 258 insertions, 75 deletions
diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js index 11fa27b4..c459ff1b 100644 --- a/src/components/block_card/block_card.js +++ b/src/components/block_card/block_card.js @@ -9,7 +9,7 @@ const BlockCard = { }, computed: { user () { - return this.$store.getters.userById(this.userId) + return this.$store.getters.findUser(this.userId) }, blocked () { return this.user.statusnet_blocking diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 427bf12b..7f666603 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -1,5 +1,5 @@ <template> - <div class="modal-view" v-if="showing" @click.prevent="hide"> + <div class="modal-view media-modal-view" v-if="showing" @click.prevent="hide"> <img class="modal-image" v-if="type === 'image'" :src="currentMedia.url"></img> <VideoAttachment class="modal-image" @@ -32,18 +32,7 @@ <style lang="scss"> @import '../../_variables.scss'; -.modal-view { - z-index: 1000; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - background-color: rgba(0, 0, 0, 0.5); - +.media-modal-view { &:hover { .modal-view-button-arrow { opacity: 0.75; diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.js b/src/components/mobile_post_status_modal/mobile_post_status_modal.js new file mode 100644 index 00000000..2f24dd08 --- /dev/null +++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.js @@ -0,0 +1,91 @@ +import PostStatusForm from '../post_status_form/post_status_form.vue' +import { throttle } from 'lodash' + +const MobilePostStatusModal = { + components: { + PostStatusForm + }, + data () { + return { + hidden: false, + postFormOpen: false, + scrollingDown: false, + inputActive: false, + oldScrollPos: 0, + amountScrolled: 0 + } + }, + created () { + window.addEventListener('scroll', this.handleScroll) + window.addEventListener('resize', this.handleOSK) + }, + destroyed () { + window.removeEventListener('scroll', this.handleScroll) + window.removeEventListener('resize', this.handleOSK) + }, + computed: { + currentUser () { + return this.$store.state.users.currentUser + }, + isHidden () { + return this.hidden || this.inputActive + } + }, + methods: { + openPostForm () { + this.postFormOpen = true + this.hidden = true + + const el = this.$el.querySelector('textarea') + this.$nextTick(function () { + el.focus() + }) + }, + closePostForm () { + this.postFormOpen = false + this.hidden = false + }, + handleOSK () { + // This is a big hack: we're guessing from changed window sizes if the + // on-screen keyboard is active or not. This is only really important + // for phones in portrait mode and it's more important to show the button + // in normal scenarios on all phones, than it is to hide it when the + // keyboard is active. + // Guesswork based on https://www.mydevice.io/#compare-devices + + // for example, iphone 4 and android phones from the same time period + const smallPhone = window.innerWidth < 350 + const smallPhoneKbOpen = smallPhone && window.innerHeight < 345 + + const biggerPhone = !smallPhone && window.innerWidth < 450 + const biggerPhoneKbOpen = biggerPhone && window.innerHeight < 560 + if (smallPhoneKbOpen || biggerPhoneKbOpen) { + this.inputActive = true + } else { + this.inputActive = false + } + }, + handleScroll: throttle(function () { + const scrollAmount = window.scrollY - this.oldScrollPos + const scrollingDown = scrollAmount > 0 + + if (scrollingDown !== this.scrollingDown) { + this.amountScrolled = 0 + this.scrollingDown = scrollingDown + if (!scrollingDown) { + this.hidden = false + } + } else if (scrollingDown) { + this.amountScrolled += scrollAmount + if (this.amountScrolled > 100 && !this.hidden) { + this.hidden = true + } + } + + this.oldScrollPos = window.scrollY + this.scrollingDown = scrollingDown + }, 100) + } +} + +export default MobilePostStatusModal diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue new file mode 100644 index 00000000..0a451c28 --- /dev/null +++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue @@ -0,0 +1,76 @@ +<template> +<div v-if="currentUser"> + <div + class="post-form-modal-view modal-view" + v-show="postFormOpen" + @click="closePostForm" + > + <div class="post-form-modal-panel panel" @click.stop=""> + <div class="panel-heading">{{$t('post_status.new_status')}}</div> + <PostStatusForm class="panel-body" @posted="closePostForm"/> + </div> + </div> + <button + class="new-status-button" + :class="{ 'hidden': isHidden }" + @click="openPostForm" + > + <i class="icon-edit" /> + </button> +</div> +</template> + +<script src="./mobile_post_status_modal.js"></script> + +<style lang="scss"> +@import '../../_variables.scss'; + +.post-form-modal-view { + max-height: 100%; + display: block; +} + +.post-form-modal-panel { + flex-shrink: 0; + margin: 25% 0 4em 0; + width: 100%; +} + +.new-status-button { + width: 5em; + height: 5em; + border-radius: 100%; + position: fixed; + bottom: 1.5em; + right: 1.5em; + // TODO: this needs its own color, it has to stand out enough and link color + // is not very optimal for this particular use. + background-color: $fallback--fg; + background-color: var(--btn, $fallback--fg); + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3); + z-index: 10; + + transition: 0.35s transform; + transition-timing-function: cubic-bezier(0, 1, 0.5, 1); + + &.hidden { + transform: translateY(150%); + } + + i { + font-size: 1.5em; + color: $fallback--text; + color: var(--text, $fallback--text); + } +} + +@media all and (min-width: 801px) { + .new-status-button { + display: none; + } +} + +</style> diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js index 5dd0a9e5..65c9cfb5 100644 --- a/src/components/mute_card/mute_card.js +++ b/src/components/mute_card/mute_card.js @@ -9,7 +9,7 @@ const MuteCard = { }, computed: { user () { - return this.$store.getters.userById(this.userId) + return this.$store.getters.findUser(this.userId) }, muted () { return this.user.muted diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 23a2c7e2..1f0df35a 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -222,6 +222,9 @@ const PostStatusForm = { this.highlighted = 0 } }, + onKeydown (e) { + e.stopPropagation() + }, setCaret ({target: {selectionStart}}) { this.caret = selectionStart }, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 0ddde4ea..3d1df91b 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -20,6 +20,7 @@ ref="textarea" @click="setCaret" @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" + @keydown="onKeydown" @keydown.down="cycleForward" @keydown.up="cycleBackward" @keydown.shift.tab="cycleBackward" diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index b608b008..95ee21b4 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -15,12 +15,7 @@ </div> </div> <ul> - <li v-if="currentUser" @click="toggleDrawer"> - <router-link :to="{ name: 'new-status', params: { username: currentUser.screen_name } }"> - {{ $t("post_status.new_status") }} - </router-link> - </li> - <li v-else @click="toggleDrawer"> + <li v-if="!currentUser" @click="toggleDrawer"> <router-link :to="{ name: 'login' }"> {{ $t("login.login") }} </router-link> @@ -119,14 +114,14 @@ } .side-drawer-container-open { - transition-delay: 0.0s; - transition-property: left; + transition: 0.35s; + transition-property: background-color; + background-color: rgba(0, 0, 0, 0.5); } .side-drawer-container-closed { left: -100%; - transition-delay: 0.5s; - transition-property: left; + background-color: rgba(0, 0, 0, 0); } .side-drawer-click-outside { diff --git a/src/components/status/status.js b/src/components/status/status.js index 9e18fe15..c90da6d4 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -145,11 +145,11 @@ const Status = { return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id) }, replyToName () { - const user = this.$store.state.users.usersObject[this.status.in_reply_to_user_id] - if (user) { - return user.screen_name - } else { + if (this.status.in_reply_to_screen_name) { return this.status.in_reply_to_screen_name + } else { + const user = this.$store.getters.findUser(this.status.in_reply_to_user_id) + return user && user.screen_name } }, hideReply () { diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js index e513b993..e6fed3b5 100644 --- a/src/components/user_avatar/user_avatar.js +++ b/src/components/user_avatar/user_avatar.js @@ -23,6 +23,11 @@ const UserAvatar = { imageLoadError () { this.showPlaceholder = true } + }, + watch: { + src () { + this.showPlaceholder = false + } } } diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 80d15a27..43a77f45 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -15,6 +15,9 @@ export default { betterShadow: this.$store.state.interface.browserSupport.cssFilter } }, + created () { + this.$store.dispatch('fetchUserRelationship', this.user.id) + }, computed: { classes () { return [{ diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index cc2ce6b8..690e1bde 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -11,7 +11,7 @@ <div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div> <div :title="user.name" class='user-name' v-else>{{user.name}}</div> <router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser"> - <i class="button-icon icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i> + <i class="button-icon icon-pencil usersettings" :title="$t('tool_tip.user_settings')"></i> </router-link> <a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser && !user.is_local"> <i class="icon-link-ext usersettings"></i> @@ -159,6 +159,18 @@ &-bio { text-align: center; + + img { + object-fit: contain; + vertical-align: middle; + max-width: 100%; + max-height: 400px; + + .emoji { + width: 32px; + height: 32px; + } + } } // Modifiers diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 54126514..82df4510 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -9,7 +9,7 @@ import withList from '../../hocs/with_list/with_list' const FollowerList = compose( withLoadMore({ fetch: (props, $store) => $store.dispatch('addFollowers', props.userId), - select: (props, $store) => get($store.getters.userById(props.userId), 'followers', []), + select: (props, $store) => get($store.getters.findUser(props.userId), 'followers', []), destory: (props, $store) => $store.dispatch('clearFollowers', props.userId), childPropName: 'entries', additionalPropNames: ['userId'] @@ -20,7 +20,7 @@ const FollowerList = compose( const FriendList = compose( withLoadMore({ fetch: (props, $store) => $store.dispatch('addFriends', props.userId), - select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []), + select: (props, $store) => get($store.getters.findUser(props.userId), 'friends', []), destory: (props, $store) => $store.dispatch('clearFriends', props.userId), childPropName: 'entries', additionalPropNames: ['userId'] @@ -31,28 +31,16 @@ const FriendList = compose( const UserProfile = { data () { return { - error: false + error: false, + fetchedUserId: null } }, created () { - this.$store.commit('clearTimeline', { timeline: 'user' }) - this.$store.commit('clearTimeline', { timeline: 'favorites' }) - this.$store.commit('clearTimeline', { timeline: 'media' }) - this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy }) - this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy }) - this.startFetchFavorites() if (!this.user.id) { - this.$store.dispatch('fetchUser', this.fetchBy) - .catch((reason) => { - const errorMessage = get(reason, 'error.error') - if (errorMessage === 'No user with such user_id') { // Known error - this.error = this.$t('user_profile.profile_does_not_exist') - } else if (errorMessage) { - this.error = errorMessage - } else { - this.error = this.$t('user_profile.profile_loading_error') - } - }) + this.fetchUserId() + .then(() => this.startUp()) + } else { + this.startUp() } }, destroyed () { @@ -69,7 +57,7 @@ const UserProfile = { return this.$store.state.statuses.timelines.media }, userId () { - return this.$route.params.id || this.user.id + return this.$route.params.id || this.user.id || this.fetchedUserId }, userName () { return this.$route.params.name || this.user.screen_name @@ -79,10 +67,9 @@ const UserProfile = { this.userId === this.$store.state.users.currentUser.id }, userInStore () { - if (this.isExternal) { - return this.$store.getters.userById(this.userId) - } - return this.$store.getters.userByName(this.userName) + const routeParams = this.$route.params + // This needs fetchedUserId so that computed will be refreshed when user is fetched + return this.$store.getters.findUser(this.fetchedUserId || routeParams.name || routeParams.id) }, user () { if (this.timeline.statuses[0]) { @@ -93,9 +80,6 @@ const UserProfile = { } return {} }, - fetchBy () { - return this.isExternal ? this.userId : this.userName - }, isExternal () { return this.$route.name === 'external-user-profile' }, @@ -109,14 +93,38 @@ const UserProfile = { methods: { startFetchFavorites () { if (this.isUs) { - this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.fetchBy }) + this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.userId }) + } + }, + fetchUserId () { + let fetchPromise + if (this.userId && !this.$route.params.name) { + fetchPromise = this.$store.dispatch('fetchUser', this.userId) + } else { + fetchPromise = this.$store.dispatch('fetchUser', this.userName) + .then(({ id }) => { + this.fetchedUserId = id + }) } + return fetchPromise + .catch((reason) => { + const errorMessage = get(reason, 'error.error') + if (errorMessage === 'No user with such user_id') { // Known error + this.error = this.$t('user_profile.profile_does_not_exist') + } else if (errorMessage) { + this.error = errorMessage + } else { + this.error = this.$t('user_profile.profile_loading_error') + } + }) + .then(() => this.startUp()) }, startUp () { - this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy }) - this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy }) - - this.startFetchFavorites() + if (this.userId) { + this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId }) + this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId }) + this.startFetchFavorites() + } }, cleanUp () { this.$store.dispatch('stopFetching', 'user') @@ -128,19 +136,19 @@ const UserProfile = { } }, watch: { - userName () { - if (this.isExternal) { - return + // userId can be undefined if we don't know it yet + userId (newVal) { + if (newVal) { + this.cleanUp() + this.startUp() } - this.cleanUp() - this.startUp() }, - userId () { - if (!this.isExternal) { - return + userName () { + if (this.$route.params.name) { + this.fetchUserId() + this.cleanUp() + this.startUp() } - this.cleanUp() - this.startUp() }, $route () { this.$refs.tabSwitcher.activateTab(0)() diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 7d4a8b1f..d449eb85 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -11,7 +11,7 @@ :title="$t('user_profile.timeline_title')" :timeline="timeline" :timeline-name="'user'" - :user-id="fetchBy" + :user-id="userId" /> <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count"> <FriendList :userId="userId" /> @@ -25,7 +25,7 @@ :embedded="true" :title="$t('user_card.media')" timeline-name="media" :timeline="media" - :user-id="fetchBy" + :user-id="userId" /> <Timeline v-if="isUs" |
