diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/link-preview/link-preview.js | 21 | ||||
| -rw-r--r-- | src/components/link-preview/link-preview.vue | 79 | ||||
| -rw-r--r-- | src/components/nav_panel/nav_panel.vue | 1 | ||||
| -rw-r--r-- | src/components/notifications/notifications.js | 14 | ||||
| -rw-r--r-- | src/components/notifications/notifications.vue | 9 | ||||
| -rw-r--r-- | src/components/status/status.js | 40 | ||||
| -rw-r--r-- | src/components/status/status.vue | 22 | ||||
| -rw-r--r-- | src/components/timeline/timeline.js | 10 | ||||
| -rw-r--r-- | src/components/timeline/timeline.vue | 9 | ||||
| -rw-r--r-- | src/components/user_profile/user_profile.js | 3 | ||||
| -rw-r--r-- | src/components/user_settings/user_settings.js | 9 | ||||
| -rw-r--r-- | src/components/user_settings/user_settings.vue | 8 |
12 files changed, 196 insertions, 29 deletions
diff --git a/src/components/link-preview/link-preview.js b/src/components/link-preview/link-preview.js new file mode 100644 index 00000000..2f6da55e --- /dev/null +++ b/src/components/link-preview/link-preview.js @@ -0,0 +1,21 @@ +const LinkPreview = { + name: 'LinkPreview', + props: [ + 'card', + 'size', + 'nsfw' + ], + computed: { + useImage () { + // Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid + // as it makes sure to hide the image if somehow NSFW tagged preview can + // exist. + return this.card.image && !this.nsfw && this.size !== 'hide' + }, + useDescription () { + return this.card.description && /\S/.test(this.card.description) + } + } +} + +export default LinkPreview diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue new file mode 100644 index 00000000..9b3f2550 --- /dev/null +++ b/src/components/link-preview/link-preview.vue @@ -0,0 +1,79 @@ +<template> + <div> + <a class="link-preview-card" :href="card.url" target="_blank" rel="noopener"> + <div class="card-image" :class="{ 'small-image': size === 'small' }" v-if="useImage"> + <img :src="card.image"></img> + </div> + <div class="card-content"> + <span class="card-host faint">{{ card.provider_name }}</span> + <h4 class="card-title">{{ card.title }}</h4> + <p class="card-description" v-if="useDescription">{{ card.description }}</p> + </div> + </a> + </div> +</template> + +<script src="./link-preview.js"></script> + +<style lang="scss"> +@import '../../_variables.scss'; + +.link-preview-card { + display: flex; + flex-direction: row; + cursor: pointer; + overflow: hidden; + + // TODO: clean up the random margins in attachments, this makes preview line + // up with attachments... + margin-right: 0.7em; + + .card-image { + flex-shrink: 0; + width: 120px; + max-width: 25%; + img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: $fallback--attachmentRadius; + border-radius: var(--attachmentRadius, $fallback--attachmentRadius); + } + } + + .small-image { + width: 80px; + } + + .card-content { + max-height: 100%; + margin: 0.5em; + display: flex; + flex-direction: column; + } + + .card-host { + font-size: 12px; + } + + .card-description { + margin: 0.5em 0 0 0; + overflow: hidden; + text-overflow: ellipsis; + word-break: break-word; + line-height: 1.2em; + // cap description at 3 lines, the 1px is to clean up some stray pixels + // TODO: fancier fade-out at the bottom to show off that it's too long? + max-height: calc(1.2em * 3 - 1px); + } + + color: $fallback--text; + color: var(--text, $fallback--text); + border-style: solid; + border-width: 1px; + border-radius: $fallback--attachmentRadius; + border-radius: var(--attachmentRadius, $fallback--attachmentRadius); + border-color: $fallback--border; + border-color: var(--border, $fallback--border); +} +</style> diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index ad7c53f9..3aa0a793 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -44,6 +44,7 @@ .nav-panel .panel { overflow: hidden; + box-shadow: var(--panelShadow); } .nav-panel ul { list-style: none; diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index ea32bbd0..5e95631a 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -13,6 +13,11 @@ const Notifications = { notificationsFetcher.startFetching({ store, credentials }) }, + data () { + return { + bottomedOut: false + } + }, computed: { notifications () { return notificationsFromStore(this.$store) @@ -28,6 +33,9 @@ const Notifications = { }, unseenCount () { return this.unseenNotifications.length + }, + loading () { + return this.$store.state.statuses.notifications.loading } }, components: { @@ -49,10 +57,16 @@ const Notifications = { fetchOlderNotifications () { const store = this.$store const credentials = store.state.users.currentUser.credentials + store.commit('setNotificationsLoading', { value: true }) notificationsFetcher.fetchAndUpdate({ store, credentials, older: true + }).then(notifs => { + store.commit('setNotificationsLoading', { value: false }) + if (notifs.length === 0) { + this.bottomedOut = true + } }) } } diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index 64f18720..6f162b62 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -18,10 +18,15 @@ </div> </div> <div class="panel-footer"> - <a href="#" v-on:click.prevent='fetchOlderNotifications()' v-if="!notifications.loading"> + <div v-if="bottomedOut" class="new-status-notification text-center panel-footer faint"> + {{$t('notifications.no_more_notifications')}} + </div> + <a v-else-if="!loading" href="#" v-on:click.prevent="fetchOlderNotifications()"> <div class="new-status-notification text-center panel-footer">{{$t('notifications.load_older')}}</div> </a> - <div class="new-status-notification text-center panel-footer" v-else>...</div> + <div v-else class="new-status-notification text-center panel-footer"> + <i class="icon-spin3 animate-spin"/> + </div> </div> </div> </div> diff --git a/src/components/status/status.js b/src/components/status/status.js index 9730eded..ec4de516 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -6,10 +6,12 @@ import PostStatusForm from '../post_status_form/post_status_form.vue' import UserCardContent from '../user_card_content/user_card_content.vue' import StillImage from '../still-image/still-image.vue' import Gallery from '../gallery/gallery.vue' -import { filter, find } from 'lodash' -import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' +import LinkPreview from '../link-preview/link-preview.vue' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import fileType from 'src/services/file_type/file_type.service' +import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' +import { mentionMatchesUrl } from 'src/services/mention_matcher/mention_matcher.js' +import { filter, find } from 'lodash' const Status = { name: 'Status', @@ -33,7 +35,7 @@ const Status = { userExpanded: false, preview: null, showPreview: false, - showingTall: false, + showingTall: this.inConversation && this.focused, expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined' ? !this.$store.state.instance.collapseMessageWithSubject : !this.$store.state.config.collapseMessageWithSubject, @@ -81,7 +83,7 @@ const Status = { }, replyProfileLink () { if (this.isReply) { - return this.generateUserProfileLink(this.status.in_reply_to_status_id, this.replyToName) + return this.generateUserProfileLink(this.status.in_reply_to_user_id, this.replyToName) } }, retweet () { return !!this.statusoid.retweeted_status }, @@ -181,7 +183,7 @@ const Status = { return this.tallStatus }, showingMore () { - return this.showingTall || (this.status.summary && this.expandingSubject) + return (this.tallStatus && this.showingTall) || (this.status.summary && this.expandingSubject) }, nsfwClickthrough () { if (!this.status.nsfw) { @@ -243,7 +245,8 @@ const Status = { PostStatusForm, UserCardContent, StillImage, - Gallery + Gallery, + LinkPreview }, methods: { visibilityIcon (visibility) { @@ -258,11 +261,23 @@ const Status = { return 'icon-globe' } }, - linkClicked ({target}) { + linkClicked (event) { + let { target } = event if (target.tagName === 'SPAN') { target = target.parentNode } if (target.tagName === 'A') { + if (target.className.match(/mention/)) { + const href = target.getAttribute('href') + const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href)) + if (attn) { + event.stopPropagation() + event.preventDefault() + const link = this.generateUserProfileLink(attn.id, attn.screen_name) + this.$router.push(link) + return + } + } window.open(target.href, '_blank') } }, @@ -287,11 +302,11 @@ const Status = { toggleShowMore () { if (this.showingTall) { this.showingTall = false - } else if (this.expandingSubject) { + } else if (this.expandingSubject && this.status.summary) { this.expandingSubject = false } else if (this.hideTallStatus) { this.showingTall = true - } else if (this.hideSubjectStatus) { + } else if (this.hideSubjectStatus && this.status.summary) { this.expandingSubject = true } }, @@ -329,8 +344,13 @@ const Status = { if (this.status.id === id) { let rect = this.$el.getBoundingClientRect() if (rect.top < 100) { - window.scrollBy(0, rect.top - 200) + // Post is above screen, match its top to screen top + window.scrollBy(0, rect.top - 100) + } else if (rect.height >= (window.innerHeight - 50)) { + // Post we want to see is taller than screen so match its top to screen top + window.scrollBy(0, rect.top - 100) } else if (rect.bottom > window.innerHeight - 50) { + // Post is below screen, match its bottom to screen bottom window.scrollBy(0, rect.bottom - window.innerHeight + 50) } } diff --git a/src/components/status/status.vue b/src/components/status/status.vue index c1800d64..c6e73e4e 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -24,9 +24,9 @@ <div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet }]" :style="[ userStyle ]" class="media status"> <div v-if="!noHeading" class="media-left"> - <a :href="status.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded"> + <router-link :to="userProfileLink" @click.stop.prevent.capture.native="toggleUserExpanded"> <StillImage class='avatar' :class="{'avatar-compact': compact, 'better-shadow': betterShadow}" :src="status.user.profile_image_url_original"/> - </a> + </router-link> </div> <div class="status-body"> <div class="usercard media-body" v-if="userExpanded"> @@ -112,6 +112,10 @@ /> </div> + <div v-if="status.card && !hideSubjectStatus && !noHeading" class="link-preview media-body"> + <link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" /> + </div> + <div v-if="!noHeading && !noReplyLinks" class='status-actions media-body'> <div v-if="loggedIn"> <a href="#" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')"> @@ -235,6 +239,11 @@ vertical-align: bottom; flex-basis: 100%; + a { + display: inline-block; + word-break: break-all; + } + small { font-weight: lighter; } @@ -310,11 +319,6 @@ } } - a { - display: inline-block; - word-break: break-all; - } - .tall-status { position: relative; height: 220px; @@ -323,6 +327,8 @@ } .tall-status-hider { + display: inline-block; + word-break: break-all; position: absolute; height: 70px; margin-top: 150px; @@ -340,6 +346,8 @@ .status-unhider, .cw-status-hider { width: 100%; text-align: center; + display: inline-block; + word-break: break-all; } .status-content { diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 98da8660..85e0a055 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -16,7 +16,8 @@ const Timeline = { data () { return { paused: false, - unfocused: false + unfocused: false, + bottomedOut: false } }, computed: { @@ -95,7 +96,12 @@ const Timeline = { showImmediately: true, userId: this.userId, tag: this.tag - }).then(() => store.commit('setLoading', { timeline: this.timelineName, value: false })) + }).then(statuses => { + store.commit('setLoading', { timeline: this.timelineName, value: false }) + if (statuses.length === 0) { + this.bottomedOut = true + } + }) }, 1000, this), scrollLoad (e) { const bodyBRect = document.body.getBoundingClientRect() diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 6ba598c5..e3eea3bd 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -20,10 +20,15 @@ </div> </div> <div :class="classes.footer"> - <a href="#" v-on:click.prevent='fetchOlderStatuses()' v-if="!timeline.loading"> + <div v-if="bottomedOut" class="new-status-notification text-center panel-footer faint"> + {{$t('timeline.no_more_statuses')}} + </div> + <a v-else-if="!timeline.loading" href="#" v-on:click.prevent='fetchOlderStatuses()'> <div class="new-status-notification text-center panel-footer">{{$t('timeline.load_older')}}</div> </a> - <div class="new-status-notification text-center panel-footer" v-else>...</div> + <div v-else class="new-status-notification text-center panel-footer"> + <i class="icon-spin3 animate-spin"/> + </div> </div> </div> </template> diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 991062cd..27e138b0 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -36,7 +36,8 @@ const UserProfile = { return this.$route.params.name || this.user.screen_name }, isUs () { - return this.userId === this.$store.state.users.currentUser.id + return this.userId && this.$store.state.users.currentUser.id && + this.userId === this.$store.state.users.currentUser.id }, friends () { return this.user.friends diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index dcce275a..be799f5d 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -10,7 +10,8 @@ const UserSettings = { newLocked: this.$store.state.users.currentUser.locked, newNoRichText: this.$store.state.users.currentUser.no_rich_text, newDefaultScope: this.$store.state.users.currentUser.default_scope, - newHideNetwork: this.$store.state.users.currentUser.hide_network, + hideFollowings: this.$store.state.users.currentUser.hide_followings, + hideFollowers: this.$store.state.users.currentUser.hide_followers, followList: null, followImportError: false, followsImported: false, @@ -66,7 +67,8 @@ const UserSettings = { /* eslint-disable camelcase */ const default_scope = this.newDefaultScope const no_rich_text = this.newNoRichText - const hide_network = this.newHideNetwork + const hide_followings = this.hideFollowings + const hide_followers = this.hideFollowers /* eslint-enable camelcase */ this.$store.state.api.backendInteractor .updateProfile({ @@ -78,7 +80,8 @@ const UserSettings = { /* eslint-disable camelcase */ default_scope, no_rich_text, - hide_network + hide_followings, + hide_followers /* eslint-enable camelcase */ }}).then((user) => { if (!user.error) { diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index 4bc2eeec..253bbd34 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -30,8 +30,12 @@ <label for="account-no-rich-text">{{$t('settings.no_rich_text_description')}}</label> </p> <p> - <input type="checkbox" v-model="newHideNetwork" id="account-hide-network"> - <label for="account-hide-network">{{$t('settings.hide_network_description')}}</label> + <input type="checkbox" v-model="hideFollowings" id="account-hide-followings"> + <label for="account-hide-followings">{{$t('settings.hide_followings_description')}}</label> + </p> + <p> + <input type="checkbox" v-model="hideFollowers" id="account-hide-followers"> + <label for="account-hide-followers">{{$t('settings.hide_followers_description')}}</label> </p> <button :disabled='newName.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button> </div> |
