diff options
Diffstat (limited to 'src')
26 files changed, 137 insertions, 37 deletions
diff --git a/src/App.scss b/src/App.scss index ba8770e2..f3e9e557 100644 --- a/src/App.scss +++ b/src/App.scss @@ -425,6 +425,12 @@ main-router { border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + + .faint { + color: $fallback--faint; + color: var(--panelFaint, $fallback--faint); + } + a { color: $fallback--link; color: var(--panelLink, $fallback--link) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 50500582..5693dcc6 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -89,6 +89,8 @@ const afterStoreSetup = ({ store, i18n }) => { if ((config.chatDisabled)) { store.dispatch('disableChat') + } else { + store.dispatch('initializeSocket') } const router = new VueRouter({ 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 558125df..2e418f0c 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -9,6 +9,7 @@ import LinkPreview from '../link-preview/link-preview.vue' import { filter, find } from 'lodash' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import { mentionMatchesUrl } from 'src/services/mention_matcher/mention_matcher.js' const Status = { name: 'Status', @@ -79,7 +80,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 }, @@ -179,7 +180,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) { @@ -237,11 +238,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') } }, @@ -303,11 +316,14 @@ const Status = { 'highlight': function (id) { if (this.status.id === id) { let rect = this.$el.getBoundingClientRect() - if (rect.top < 140) { - window.scrollBy(0, rect.top - 200) - } else if (rect.top < window.innerHeight && rect.height >= (window.innerHeight - 50)) { - window.scrollBy(0, rect.top - 50) + if (rect.top < 100) { + // 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 d88428c7..45100a46 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"> 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> diff --git a/src/i18n/de.json b/src/i18n/de.json index c87371e6..82860e9e 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -155,7 +155,8 @@ "notification_visibility_mentions": "Erwähnungen", "notification_visibility_repeats": "Wiederholungen", "no_rich_text_description": "Rich-Text Formatierungen von allen Beiträgen entfernen", - "hide_network_description": "Zeige nicht, wem ich folge und wer mir folgt", + "hide_followings_description": "Zeige nicht, wem ich folge", + "hide_followers_description": "Zeige nicht, wer mir folgt", "nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind", "panelRadius": "Panel", "pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist", diff --git a/src/i18n/en.json b/src/i18n/en.json index 3ff98ab0..3078bade 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -49,7 +49,8 @@ "load_older": "Load older notifications", "notifications": "Notifications", "read": "Read!", - "repeated_you": "repeated your status" + "repeated_you": "repeated your status", + "no_more_notifications": "No more notifications" }, "post_status": { "new_status": "Post new status", @@ -157,7 +158,8 @@ "notification_visibility_mentions": "Mentions", "notification_visibility_repeats": "Repeats", "no_rich_text_description": "Strip rich text formatting from all posts", - "hide_network_description": "Don't show who I'm following and who's following me", + "hide_followings_description": "Don't show who I'm following", + "hide_followers_description": "Don't show who's following me", "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", "panelRadius": "Panels", "pause_on_unfocused": "Pause streaming when tab is not focused", @@ -317,7 +319,8 @@ "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated", "repeated": "repeated", "show_new": "Show new", - "up_to_date": "Up-to-date" + "up_to_date": "Up-to-date", + "no_more_statuses": "No more statuses" }, "user_card": { "approve": "Approve", diff --git a/src/i18n/fi.json b/src/i18n/fi.json index 08cfb617..ee7cd4d2 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -26,7 +26,8 @@ "followed_you": "seuraa sinua", "notifications": "Ilmoitukset", "read": "Lue!", - "repeated_you": "toisti viestisi" + "repeated_you": "toisti viestisi", + "no_more_notifications": "Ei enempää ilmoituksia" }, "post_status": { "default": "Tulin juuri saunasta.", @@ -77,7 +78,8 @@ "load_older": "Lataa vanhempia viestejä", "repeated": "toisti", "show_new": "Näytä uudet", - "up_to_date": "Ajantasalla" + "up_to_date": "Ajantasalla", + "no_more_statuses": "Ei enempää viestejä" }, "user_card": { "follow": "Seuraa", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 161856f0..37e96f6c 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -157,7 +157,8 @@ "notification_visibility_mentions": "メンション", "notification_visibility_repeats": "リピート", "no_rich_text_description": "リッチテキストをつかわない", - "hide_network_description": "わたしがフォローしているひとと、わたしをフォローしているひとを、みせない", + "hide_followings_description": "フォローしている人を表示しない", + "hide_followers_description": "フォローしている人を表示しない", "nsfw_clickthrough": "NSFWなファイルをかくす", "panelRadius": "パネル", "pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 4b69df07..9f40be51 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -156,7 +156,8 @@ "notification_visibility_mentions": "멘션", "notification_visibility_repeats": "반복", "no_rich_text_description": "모든 게시물의 서식을 지우기", - "hide_network_description": "내 팔로우와 팔로워를 숨기기", + "hide_followings_description": "내가 팔로우하는 사람을 표시하지 않음", + "hide_followers_description": "나를 따르는 사람을 보여주지 마라.", "nsfw_clickthrough": "NSFW 이미지 \"클릭해서 보이기\"를 활성화", "panelRadius": "패널", "pause_on_unfocused": "탭이 활성 상태가 아닐 때 스트리밍 멈추기", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 0887bb59..bf1e319f 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -127,7 +127,8 @@ "notification_visibility_mentions": "Упоминания", "notification_visibility_repeats": "Повторы", "no_rich_text_description": "Убрать форматирование из всех постов", - "hide_network_description": "Не показывать кого я читаю и кто меня читает", + "hide_followings_description": "Не показывать кого я читаю", + "hide_followers_description": "Не показывать кто читает меня", "nsfw_clickthrough": "Включить скрытие NSFW вложений", "panelRadius": "Панели", "pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе", diff --git a/src/modules/api.js b/src/modules/api.js index a61340c2..7bda13e7 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -20,6 +20,9 @@ const api = { removeFetcher (state, {timeline}) { delete state.fetchers[timeline] }, + setWsToken (state, token) { + state.wsToken = token + }, setSocket (state, socket) { state.socket = socket }, @@ -51,10 +54,14 @@ const api = { window.clearInterval(fetcher) store.commit('removeFetcher', {timeline}) }, - initializeSocket (store, token) { + setWsToken (store, token) { + store.commit('setWsToken', token) + }, + initializeSocket (store) { // Set up websocket connection if (!store.state.chatDisabled) { - let socket = new Socket('/socket', {params: {token: token}}) + const token = store.state.wsToken + const socket = new Socket('/socket', {params: {token}}) socket.connect() store.dispatch('initializeChat', socket) } diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 04ec4dbc..56619455 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -28,6 +28,7 @@ export const defaultState = { minId: Number.POSITIVE_INFINITY, data: [], idStore: {}, + loading: false, error: false }, favorites: new Set(), @@ -348,6 +349,9 @@ export const mutations = { setError (state, { value }) { state.error = value }, + setNotificationsLoading (state, { value }) { + state.notifications.loading = value + }, setNotificationsError (state, { value }) { state.notifications.error = value }, @@ -376,6 +380,9 @@ const statuses = { setError ({ rootState, commit }, { value }) { commit('setError', { value }) }, + setNotificationsLoading ({ rootState, commit }, { value }) { + commit('setNotificationsLoading', { value }) + }, setNotificationsError ({ rootState, commit }, { value }) { commit('setNotificationsError', { value }) }, diff --git a/src/modules/users.js b/src/modules/users.js index 181946b4..d6ab47ea 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -91,7 +91,9 @@ export const getters = { userById: state => id => state.users.find(user => user.id === id), userByName: state => name => - state.users.find(user => user.screen_name.toLowerCase() === name.toLowerCase()) + state.users.find(user => user.screen_name && + (user.screen_name.toLowerCase() === name.toLowerCase()) + ) } export const defaultState = { @@ -222,10 +224,10 @@ const users = { commit('setBackendInteractor', backendInteractorService(accessToken)) if (user.token) { - store.dispatch('initializeSocket', user.token) + store.dispatch('setWsToken', user.token) } - // Start getting fresh tweets. + // Start getting fresh posts. store.dispatch('startFetching', 'friends') // Get user mutes and follower info diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 5b0d8650..31b48cb6 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -130,7 +130,7 @@ const updateBanner = ({credentials, params}) => { // description const updateProfile = ({credentials, params}) => { // Always include these fields, because they might be empty or false - const fields = ['description', 'locked', 'no_rich_text', 'hide_network'] + const fields = ['description', 'locked', 'no_rich_text', 'hide_followings', 'hide_followers'] let url = PROFILE_UPDATE_URL const form = new FormData() diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index deffa537..fa955ad6 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -100,7 +100,8 @@ export const parseUser = (data) => { output.rights = data.rights output.no_rich_text = data.no_rich_text output.default_scope = data.default_scope - output.hide_network = data.hide_network + output.hide_followings = data.hide_followings + output.hide_followers = data.hide_followers output.background_image = data.background_image // on mastoapi this info is contained in a "relationship" output.following = data.following diff --git a/src/services/mention_matcher/mention_matcher.js b/src/services/mention_matcher/mention_matcher.js new file mode 100644 index 00000000..2c1ed970 --- /dev/null +++ b/src/services/mention_matcher/mention_matcher.js @@ -0,0 +1,9 @@ + +export const mentionMatchesUrl = (attention, url) => { + if (url === attention.statusnet_profile_url) { + return true + } + const [namepart, instancepart] = attention.screen_name.split('@') + const matchstring = new RegExp('://' + instancepart + '/.*' + namepart + '$', 'g') + return !!url.match(matchstring) +} diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 1480cded..b69ec643 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -24,6 +24,7 @@ const fetchAndUpdate = ({store, credentials, older = false}) => { return apiService.fetchTimeline(args) .then((notifications) => { update({store, notifications, older}) + return notifications }, () => store.dispatch('setNotificationsError', { value: true })) .catch(() => store.dispatch('setNotificationsError', { value: true })) } diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 9c4a4820..64f8f468 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -37,6 +37,7 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId }) } update({store, statuses, timeline, showImmediately, userId}) + return statuses }, () => store.dispatch('setError', { value: true })) } |
