diff options
| author | Henry Jameson <me@hjkos.com> | 2020-10-17 19:24:07 +0300 |
|---|---|---|
| committer | Henry Jameson <me@hjkos.com> | 2020-10-17 19:24:07 +0300 |
| commit | 29ff0be92cfd87ba95d1f78323794dfb1223b514 (patch) | |
| tree | 79836b7fb5b3e325274a833c34fd4c5f6155ba2d /src/components | |
| parent | a463959a365a5d618a79c96a26f6506e700d6ea3 (diff) | |
| parent | 3ca729d09880813420a263221cdc68bcca13a1fb (diff) | |
Merge remote-tracking branch 'origin/develop' into settings-changed
* origin/develop: (48 commits)
fix/leftover-emoji-checkboxes-in-settings
Apply 1 suggestion(s) to 1 file(s)
Translated using Weblate (Spanish)
Translated using Weblate (Persian)
Translated using Weblate (Persian)
Translated using Weblate (Polish)
update changelog
Stop click propagation when unhiding nsfw
Fix Follow Requests title style
Translated using Weblate (Persian)
Translated using Weblate (Persian)
Translated using Weblate (French)
Added translation using Weblate (Persian)
Translated using Weblate (Chinese (Traditional))
Translated using Weblate (Chinese (Simplified))
Translated using Weblate (Italian)
Translated using Weblate (English)
Translated using Weblate (English)
Translated using Weblate (Basque)
Translated using Weblate (Spanish)
...
Diffstat (limited to 'src/components')
23 files changed, 249 insertions, 61 deletions
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 63e0ceba..19c713d5 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -28,7 +28,7 @@ :href="attachment.url" :alt="attachment.description" :title="attachment.description" - @click.prevent="toggleHidden" + @click.prevent.stop="toggleHidden" > <img :key="nsfwImage" @@ -80,6 +80,8 @@ class="video" :attachment="attachment" :controls="allowPlay" + @play="$emit('play')" + @pause="$emit('pause')" /> <i v-if="!allowPlay" @@ -93,6 +95,8 @@ :alt="attachment.description" :title="attachment.description" controls + @play="$emit('play')" + @pause="$emit('pause')" /> <div diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 803abf69..34e723d0 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -5,6 +5,7 @@ import ChatMessage from '../chat_message/chat_message.vue' import PostStatusForm from '../post_status_form/post_status_form.vue' import ChatTitle from '../chat_title/chat_title.vue' import chatService from '../../services/chat_service/chat_service.js' +import { promiseInterval } from '../../services/promise_interval/promise_interval.js' import { getScrollPosition, getNewTopPosition, isBottomedOut, scrollableContainerHeight } from './chat_layout_utils.js' const BOTTOMED_OUT_OFFSET = 10 @@ -246,7 +247,7 @@ const Chat = { const fetchOlderMessages = !!maxId const sinceId = fetchLatest && chatMessageService.maxId - this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId }) + return this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId }) .then((messages) => { // Clear the current chat in case we're recovering from a ws connection loss. if (isFirstFetch) { @@ -287,7 +288,7 @@ const Chat = { }, doStartFetching () { this.$store.dispatch('startFetchingCurrentChat', { - fetcher: () => setInterval(() => this.fetchChat({ fetchLatest: true }), 5000) + fetcher: () => promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000) }) this.fetchChat({ isFirstFetch: true }) }, diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 45fb2bf6..069c0b40 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -44,7 +44,8 @@ const conversation = { 'isPage', 'pinnedStatusIdsObject', 'inProfile', - 'profileUserId' + 'profileUserId', + 'virtualHidden' ], created () { if (this.isPage) { @@ -52,6 +53,13 @@ const conversation = { } }, computed: { + hideStatus () { + if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { + return this.virtualHidden && this.$refs.statusComponent[0].suspendable + } else { + return this.virtualHidden + } + }, status () { return this.$store.state.statuses.allStatusesObject[this.statusId] }, @@ -102,6 +110,10 @@ const conversation = { }, isExpanded () { return this.expanded || this.isPage + }, + hiddenStyle () { + const height = (this.status && this.status.virtualHeight) || '120px' + return this.virtualHidden ? { height } : {} } }, components: { @@ -121,6 +133,12 @@ const conversation = { if (value) { this.fetchConversation() } + }, + virtualHidden (value) { + this.$store.dispatch( + 'setVirtualHeight', + { statusId: this.statusId, height: `${this.$el.clientHeight}px` } + ) } }, methods: { diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 997a4d10..e0b9fcc5 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -1,5 +1,7 @@ <template> <div + v-if="!hideStatus" + :style="hiddenStyle" class="Conversation" :class="{ '-expanded' : isExpanded, 'panel' : isExpanded }" > @@ -18,6 +20,7 @@ <status v-for="status in conversation" :key="status.id" + ref="statusComponent" :inline-expanded="collapsable && isExpanded" :statusoid="status" :expandable="!isExpanded" @@ -33,6 +36,10 @@ @toggleExpanded="toggleExpanded" /> </div> + <div + v-else + :style="hiddenStyle" + /> </template> <script src="./conversation.js"></script> @@ -53,8 +60,8 @@ .conversation-status { border-color: $fallback--border; border-color: var(--border, $fallback--border); - border-left: 4px solid $fallback--cRed; - border-left: 4px solid var(--cRed, $fallback--cRed); + border-left-color: $fallback--cRed; + border-left-color: var(--cRed, $fallback--cRed); } .conversation-status:last-child { diff --git a/src/components/follow_requests/follow_requests.vue b/src/components/follow_requests/follow_requests.vue index 5fa4cf39..41f19db8 100644 --- a/src/components/follow_requests/follow_requests.vue +++ b/src/components/follow_requests/follow_requests.vue @@ -1,7 +1,9 @@ <template> <div class="settings panel panel-default"> <div class="panel-heading"> - {{ $t('nav.friend_requests') }} + <div class="title"> + {{ $t('nav.friend_requests') }} + </div> </div> <div class="panel-body"> <FollowRequestCard diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue index b2d5acc5..60fa6ceb 100644 --- a/src/components/moderation_tools/moderation_tools.vue +++ b/src/components/moderation_tools/moderation_tools.vue @@ -178,7 +178,7 @@ box-shadow: var(--inputShadow); &.menu-checkbox-checked::after { - content: '✔'; + content: '✓'; } } diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index f8459fd1..4f944c95 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -7,12 +7,12 @@ :to="{ name: timelinesRoute }" :class="onTimelineRoute && 'router-link-active'" > - <i class="button-icon icon-home-2" /> {{ $t("nav.timelines") }} + <i class="button-icon icon-home-2" />{{ $t("nav.timelines") }} </router-link> </li> <li v-if="currentUser"> <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"> - <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }} + <i class="button-icon icon-bell-alt" />{{ $t("nav.interactions") }} </router-link> </li> <li v-if="currentUser && pleromaChatMessagesAvailable"> @@ -23,12 +23,12 @@ > {{ unreadChatCount }} </div> - <i class="button-icon icon-chat" /> {{ $t("nav.chats") }} + <i class="button-icon icon-chat" />{{ $t("nav.chats") }} </router-link> </li> <li v-if="currentUser && currentUser.locked"> <router-link :to="{ name: 'friend-requests' }"> - <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }} + <i class="button-icon icon-user-plus" />{{ $t("nav.friend_requests") }} <span v-if="followRequestCount > 0" class="badge follow-request-count" @@ -39,7 +39,7 @@ </li> <li> <router-link :to="{ name: 'about' }"> - <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }} + <i class="button-icon icon-info-circled" />{{ $t("nav.about") }} </router-link> </li> </ul> @@ -125,6 +125,10 @@ } } +.nav-panel .button-icon { + margin-right: 0.5em; +} + .nav-panel .button-icon:before { width: 1.1em; } diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js index abcf0455..11627e9c 100644 --- a/src/components/react_button/react_button.js +++ b/src/components/react_button/react_button.js @@ -1,5 +1,4 @@ import Popover from '../popover/popover.vue' -import { mapGetters } from 'vuex' const ReactButton = { props: ['status'], @@ -35,7 +34,9 @@ const ReactButton = { } return this.$store.state.instance.emoji || [] }, - ...mapGetters(['mergedConfig']) + mergedConfig () { + return this.$store.getters.mergedConfig + } } } diff --git a/src/components/retweet_button/retweet_button.js b/src/components/retweet_button/retweet_button.js index d9a0f92e..5a41f22d 100644 --- a/src/components/retweet_button/retweet_button.js +++ b/src/components/retweet_button/retweet_button.js @@ -1,4 +1,3 @@ -import { mapGetters } from 'vuex' const RetweetButton = { props: ['status', 'loggedIn', 'visibility'], @@ -28,7 +27,9 @@ const RetweetButton = { 'animate-spin': this.animated } }, - ...mapGetters(['mergedConfig']) + mergedConfig () { + return this.$store.getters.mergedConfig + } } } diff --git a/src/components/settings_modal/tabs/data_import_export_tab.js b/src/components/settings_modal/tabs/data_import_export_tab.js index 168f89e1..f4b736d2 100644 --- a/src/components/settings_modal/tabs/data_import_export_tab.js +++ b/src/components/settings_modal/tabs/data_import_export_tab.js @@ -1,6 +1,7 @@ import Importer from 'src/components/importer/importer.vue' import Exporter from 'src/components/exporter/exporter.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' +import { mapState } from 'vuex' const DataImportExportTab = { data () { @@ -18,21 +19,26 @@ const DataImportExportTab = { Checkbox }, computed: { - user () { - return this.$store.state.users.currentUser - } + ...mapState({ + backendInteractor: (state) => state.api.backendInteractor, + user: (state) => state.users.currentUser + }) }, methods: { getFollowsContent () { - return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id }) + return this.backendInteractor.exportFriends({ id: this.user.id }) .then(this.generateExportableUsersContent) }, getBlocksContent () { - return this.$store.state.api.backendInteractor.fetchBlocks() + return this.backendInteractor.fetchBlocks() + .then(this.generateExportableUsersContent) + }, + getMutesContent () { + return this.backendInteractor.fetchMutes() .then(this.generateExportableUsersContent) }, importFollows (file) { - return this.$store.state.api.backendInteractor.importFollows({ file }) + return this.backendInteractor.importFollows({ file }) .then((status) => { if (!status) { throw new Error('failed') @@ -40,7 +46,15 @@ const DataImportExportTab = { }) }, importBlocks (file) { - return this.$store.state.api.backendInteractor.importBlocks({ file }) + return this.backendInteractor.importBlocks({ file }) + .then((status) => { + if (!status) { + throw new Error('failed') + } + }) + }, + importMutes (file) { + return this.backendInteractor.importMutes({ file }) .then((status) => { if (!status) { throw new Error('failed') diff --git a/src/components/settings_modal/tabs/data_import_export_tab.vue b/src/components/settings_modal/tabs/data_import_export_tab.vue index b5d0f5ed..a406077d 100644 --- a/src/components/settings_modal/tabs/data_import_export_tab.vue +++ b/src/components/settings_modal/tabs/data_import_export_tab.vue @@ -36,6 +36,23 @@ :export-button-label="$t('settings.block_export_button')" /> </div> + <div class="setting-item"> + <h2>{{ $t('settings.mute_import') }}</h2> + <p>{{ $t('settings.import_mutes_from_a_csv_file') }}</p> + <Importer + :submit-handler="importMutes" + :success-message="$t('settings.mutes_imported')" + :error-message="$t('settings.mute_import_error')" + /> + </div> + <div class="setting-item"> + <h2>{{ $t('settings.mute_export') }}</h2> + <Exporter + :get-content="getMutesContent" + filename="mutes.csv" + :export-button-label="$t('settings.mute_export_button')" + /> + </div> </div> </template> diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index e03e731b..f794ad6b 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -58,6 +58,11 @@ {{ $t('settings.emoji_reactions_on_timeline') }} </BooleanSetting> </li> + <li> + <Checkbox v-model="virtualScrolling"> + {{ $t('settings.virtual_scrolling') }} + </Checkbox> + </li> </ul> </div> diff --git a/src/components/status/status.js b/src/components/status/status.js index d263da68..cd6e2f72 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -15,7 +15,6 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { muteWordHits } from '../../services/status_parser/status_parser.js' import { unescape, uniqBy } from 'lodash' -import { mapGetters, mapState } from 'vuex' const Status = { name: 'Status', @@ -54,6 +53,8 @@ const Status = { replying: false, unmuted: false, userExpanded: false, + mediaPlaying: [], + suspendable: true, error: null } }, @@ -157,7 +158,7 @@ const Status = { return this.mergedConfig.hideFilteredStatuses }, hideStatus () { - return this.deleted || (this.muted && this.hideFilteredStatuses) + return this.deleted || (this.muted && this.hideFilteredStatuses) || this.virtualHidden }, isFocused () { // retweet or root of an expanded conversation @@ -207,11 +208,18 @@ const Status = { hidePostStats () { return this.mergedConfig.hidePostStats }, - ...mapGetters(['mergedConfig']), - ...mapState({ - betterShadow: state => state.interface.browserSupport.cssFilter, - currentUser: state => state.users.currentUser - }) + currentUser () { + return this.$store.state.users.currentUser + }, + betterShadow () { + return this.$store.state.interface.browserSupport.cssFilter + }, + mergedConfig () { + return this.$store.getters.mergedConfig + }, + isSuspendable () { + return !this.replying && this.mediaPlaying.length === 0 + } }, methods: { visibilityIcon (visibility) { @@ -251,6 +259,12 @@ const Status = { }, generateUserProfileLink (id, name) { return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames) + }, + addMediaPlaying (id) { + this.mediaPlaying.push(id) + }, + removeMediaPlaying (id) { + this.mediaPlaying = this.mediaPlaying.filter(mediaId => mediaId !== id) } }, watch: { @@ -280,6 +294,9 @@ const Status = { if (this.isFocused && this.statusFromGlobalRepository.favoritedBy && this.statusFromGlobalRepository.favoritedBy.length !== num) { this.$store.dispatch('fetchFavs', this.status.id) } + }, + 'isSuspendable': function (val) { + this.suspendable = val } }, filters: { diff --git a/src/components/status/status.scss b/src/components/status/status.scss index 8d292d3f..c92d870b 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -25,6 +25,11 @@ $status-margin: 0.75em; --icon: var(--selectedPostIcon, $fallback--icon); } + &.-conversation { + border-left-width: 4px; + border-left-style: solid; + } + .status-container { display: flex; padding: $status-margin; diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 282ad37d..aa67e433 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -16,7 +16,7 @@ /> </div> <template v-if="muted && !isPreview"> - <div class="status-csontainer muted"> + <div class="status-container muted"> <small class="status-username"> <i v-if="muted && retweet" @@ -227,6 +227,7 @@ </span> </a> </StatusPopover> + <span v-else class="reply-to-no-popover" @@ -272,6 +273,8 @@ :no-heading="noHeading" :highlight="highlight" :focused="isFocused" + @mediaplay="addMediaPlaying($event)" + @mediapause="removeMediaPlaying($event)" /> <transition name="fade"> @@ -354,6 +357,7 @@ @onSuccess="clearError" /> </div> + </div> </div> <div @@ -376,4 +380,5 @@ </template> <script src="./status.js" ></script> + <style src="./status.scss" lang="scss"></style> diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue index 76fe3278..f7fb5ee2 100644 --- a/src/components/status_content/status_content.vue +++ b/src/components/status_content/status_content.vue @@ -107,6 +107,8 @@ :attachment="attachment" :allow-play="true" :set-media="setMedia()" + @play="$emit('mediaplay', attachment.id)" + @pause="$emit('mediapause', attachment.id)" /> <gallery v-if="galleryAttachments.length > 0" diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js index ab40bbd7..8044e994 100644 --- a/src/components/still-image/still-image.js +++ b/src/components/still-image/still-image.js @@ -19,14 +19,16 @@ const StillImage = { }, methods: { onLoad () { - this.imageLoadHandler && this.imageLoadHandler(this.$refs.src) + const image = this.$refs.src + if (!image) return + this.imageLoadHandler && this.imageLoadHandler(image) const canvas = this.$refs.canvas if (!canvas) return - const width = this.$refs.src.naturalWidth - const height = this.$refs.src.naturalHeight + const width = image.naturalWidth + const height = image.naturalHeight canvas.width = width canvas.height = height - canvas.getContext('2d').drawImage(this.$refs.src, 0, 0, width, height) + canvas.getContext('2d').drawImage(image, 0, 0, width, height) }, onError () { this.imageLoadError && this.imageLoadError() diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 5a7f7a78..17680542 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -33,7 +33,8 @@ const Timeline = { return { paused: false, unfocused: false, - bottomedOut: false + bottomedOut: false, + virtualScrollIndex: 0 } }, components: { @@ -78,6 +79,16 @@ const Timeline = { }, pinnedStatusIdsObject () { return keyBy(this.pinnedStatusIds) + }, + statusesToDisplay () { + const amount = this.timeline.visibleStatuses.length + const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80)) + const min = Math.max(0, this.virtualScrollIndex - statusesPerSide) + const max = Math.min(amount, this.virtualScrollIndex + statusesPerSide) + return this.timeline.visibleStatuses.slice(min, max).map(_ => _.id) + }, + virtualScrollingEnabled () { + return this.$store.getters.mergedConfig.virtualScrolling } }, created () { @@ -85,7 +96,7 @@ const Timeline = { const credentials = store.state.users.currentUser.credentials const showImmediately = this.timeline.visibleStatuses.length === 0 - window.addEventListener('scroll', this.scrollLoad) + window.addEventListener('scroll', this.handleScroll) if (store.state.api.fetchers[this.timelineName]) { return false } @@ -104,9 +115,10 @@ const Timeline = { this.unfocused = document.hidden } window.addEventListener('keydown', this.handleShortKey) + setTimeout(this.determineVisibleStatuses, 250) }, destroyed () { - window.removeEventListener('scroll', this.scrollLoad) + window.removeEventListener('scroll', this.handleScroll) window.removeEventListener('keydown', this.handleShortKey) if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false) this.$store.commit('setLoading', { timeline: this.timelineName, value: false }) @@ -146,6 +158,48 @@ const Timeline = { } }) }, 1000, this), + determineVisibleStatuses () { + if (!this.$refs.timeline) return + if (!this.virtualScrollingEnabled) return + + const statuses = this.$refs.timeline.children + const cappedScrollIndex = Math.max(0, Math.min(this.virtualScrollIndex, statuses.length - 1)) + + if (statuses.length === 0) return + + const height = Math.max(document.body.offsetHeight, window.pageYOffset) + + const centerOfScreen = window.pageYOffset + (window.innerHeight * 0.5) + + // Start from approximating the index of some visible status by using the + // the center of the screen on the timeline. + let approxIndex = Math.floor(statuses.length * (centerOfScreen / height)) + let err = statuses[approxIndex].getBoundingClientRect().y + + // if we have a previous scroll index that can be used, test if it's + // closer than the previous approximation, use it if so + + const virtualScrollIndexY = statuses[cappedScrollIndex].getBoundingClientRect().y + if (Math.abs(err) > virtualScrollIndexY) { + approxIndex = cappedScrollIndex + err = virtualScrollIndexY + } + + // if the status is too far from viewport, check the next/previous ones if + // they happen to be better + while (err < -20 && approxIndex < statuses.length - 1) { + err += statuses[approxIndex].offsetHeight + approxIndex++ + } + while (err > window.innerHeight + 100 && approxIndex > 0) { + approxIndex-- + err -= statuses[approxIndex].offsetHeight + } + + // this status is now the center point for virtual scrolling and visible + // statuses will be nearby statuses before and after it + this.virtualScrollIndex = approxIndex + }, scrollLoad (e) { const bodyBRect = document.body.getBoundingClientRect() const height = Math.max(bodyBRect.height, -(bodyBRect.y)) @@ -155,6 +209,10 @@ const Timeline = { this.fetchOlderStatuses() } }, + handleScroll: throttle(function (e) { + this.determineVisibleStatuses() + this.scrollLoad(e) + }, 200), handleVisibilityChange () { this.unfocused = document.hidden } diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 2ff933e9..c1e2f44b 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -32,7 +32,10 @@ </div> </div> <div :class="classes.body"> - <div class="timeline"> + <div + ref="timeline" + class="timeline" + > <template v-for="statusId in pinnedStatusIds"> <conversation v-if="timeline.statusesObject[statusId]" @@ -54,6 +57,7 @@ :collapsable="true" :in-profile="inProfile" :profile-user-id="userId" + :virtual-hidden="virtualScrollingEnabled && !statusesToDisplay.includes(status.id)" /> </template> </div> diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue index be512d60..b7e5f2da 100644 --- a/src/components/timeline_menu/timeline_menu.vue +++ b/src/components/timeline_menu/timeline_menu.vue @@ -138,15 +138,11 @@ &:last-child { border: none; } - - i { - margin: 0 0.5em; - } } a { display: block; - padding: 0.6em 0; + padding: 0.6em 0.65em; &:hover { background-color: $fallback--lightBg; @@ -174,6 +170,10 @@ text-decoration: underline; } } + + i { + margin-right: 0.5em; + } } } diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index c7c67c0a..b26499b4 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -156,8 +156,7 @@ .user-profile-field { display: flex; - margin: 0.25em auto; - max-width: 32em; + margin: 0.25em; border: 1px solid var(--border, $fallback--border); border-radius: $fallback--inputRadius; border-radius: var(--inputRadius, $fallback--inputRadius); diff --git a/src/components/video_attachment/video_attachment.js b/src/components/video_attachment/video_attachment.js index f0ca7e89..107b8985 100644 --- a/src/components/video_attachment/video_attachment.js +++ b/src/components/video_attachment/video_attachment.js @@ -3,27 +3,48 @@ const VideoAttachment = { props: ['attachment', 'controls'], data () { return { - loopVideo: this.$store.getters.mergedConfig.loopVideo + blocksSuspend: false, + // Start from true because removing "loop" property seems buggy in Vue + hasAudio: true + } + }, + computed: { + loopVideo () { + if (this.$store.getters.mergedConfig.loopVideoSilentOnly) { + return !this.hasAudio + } + return this.$store.getters.mergedConfig.loopVideo } }, methods: { - onVideoDataLoad (e) { + onPlaying (e) { + this.setHasAudio(e) + if (this.loopVideo) { + this.$emit('play', { looping: true }) + return + } + this.$emit('play') + }, + onPaused (e) { + this.$emit('pause') + }, + setHasAudio (e) { const target = e.srcElement || e.target + // If hasAudio is false, we've already marked this video to not have audio, + // a video can't gain audio out of nowhere so don't bother checking again. + if (!this.hasAudio) return if (typeof target.webkitAudioDecodedByteCount !== 'undefined') { // non-zero if video has audio track - if (target.webkitAudioDecodedByteCount > 0) { - this.loopVideo = this.loopVideo && !this.$store.getters.mergedConfig.loopVideoSilentOnly - } - } else if (typeof target.mozHasAudio !== 'undefined') { + if (target.webkitAudioDecodedByteCount > 0) return + } + if (typeof target.mozHasAudio !== 'undefined') { // true if video has audio track - if (target.mozHasAudio) { - this.loopVideo = this.loopVideo && !this.$store.getters.mergedConfig.loopVideoSilentOnly - } - } else if (typeof target.audioTracks !== 'undefined') { - if (target.audioTracks.length > 0) { - this.loopVideo = this.loopVideo && !this.$store.getters.mergedConfig.loopVideoSilentOnly - } + if (target.mozHasAudio) return + } + if (typeof target.audioTracks !== 'undefined') { + if (target.audioTracks.length > 0) return } + this.hasAudio = false } } } diff --git a/src/components/video_attachment/video_attachment.vue b/src/components/video_attachment/video_attachment.vue index 1ffed4e0..a4bf01e8 100644 --- a/src/components/video_attachment/video_attachment.vue +++ b/src/components/video_attachment/video_attachment.vue @@ -7,7 +7,8 @@ :alt="attachment.description" :title="attachment.description" playsinline - @loadeddata="onVideoDataLoad" + @playing="onPlaying" + @pause="onPaused" /> </template> |
