diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.scss | 8 | ||||
| -rw-r--r-- | src/components/attachment/attachment.js | 38 | ||||
| -rw-r--r-- | src/components/attachment/attachment.vue | 9 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.vue | 49 | ||||
| -rw-r--r-- | src/components/settings/settings.js | 22 | ||||
| -rw-r--r-- | src/components/settings/settings.vue | 118 | ||||
| -rw-r--r-- | src/components/status/status.vue | 59 | ||||
| -rw-r--r-- | src/components/timeline/timeline.js | 5 | ||||
| -rw-r--r-- | src/i18n/messages.js | 10 | ||||
| -rw-r--r-- | src/main.js | 4 | ||||
| -rw-r--r-- | src/modules/config.js | 4 |
11 files changed, 252 insertions, 74 deletions
diff --git a/src/App.scss b/src/App.scss index 43786e9d..3b8b3224 100644 --- a/src/App.scss +++ b/src/App.scss @@ -142,6 +142,14 @@ input, textarea, .select { color: $fallback--fg; color: var(--fg, $fallback--fg); } + &:disabled, + { + &, + & + label, + & + label::before { + opacity: .5; + } + } + label::before { display: inline-block; content: '✔'; diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index d9bc4477..cc19714d 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -13,9 +13,10 @@ const Attachment = { return { nsfwImage, hideNsfwLocal: this.$store.state.config.hideNsfw, + loopVideo: this.$store.state.config.loopVideo, showHidden: false, loading: false, - img: document.createElement('img') + img: this.type === 'image' && document.createElement('img') } }, components: { @@ -45,14 +46,35 @@ const Attachment = { } }, toggleHidden () { - if (this.img.onload) { - this.img.onload() + if (this.img) { + if (this.img.onload) { + this.img.onload() + } else { + this.loading = true + this.img.src = this.attachment.url + this.img.onload = () => { + this.loading = false + this.showHidden = !this.showHidden + } + } } else { - this.loading = true - this.img.src = this.attachment.url - this.img.onload = () => { - this.loading = false - this.showHidden = !this.showHidden + this.showHidden = !this.showHidden + } + }, + onVideoDataLoad (e) { + if (typeof e.srcElement.webkitAudioDecodedByteCount !== 'undefined') { + // non-zero if video has audio track + if (e.srcElement.webkitAudioDecodedByteCount > 0) { + this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly + } + } else if (typeof e.srcElement.mozHasAudio !== 'undefined') { + // true if video has audio track + if (e.srcElement.mozHasAudio) { + this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly + } + } else if (typeof e.srcElement.audioTracks !== 'undefined') { + if (e.srcElement.audioTracks.length > 0) { + this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly } } } diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index c48fb16b..d01c8566 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -2,7 +2,7 @@ <div v-if="size==='hide'"> <a class="placeholder" v-if="type !== 'html'" target="_blank" :href="attachment.url">[{{nsfw ? "NSFW/" : ""}}{{type.toUpperCase()}}]</a> </div> - <div v-else class="attachment" :class="{[type]: true, loading, 'small-attachment': isSmall, 'fullwidth': fullwidth}" v-show="!isEmpty"> + <div v-else class="attachment" :class="{[type]: true, loading, 'small-attachment': isSmall, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}" v-show="!isEmpty"> <a class="image-attachment" v-if="hidden" @click.prevent="toggleHidden()"> <img :key="nsfwImage" :src="nsfwImage"/> </a> @@ -14,7 +14,7 @@ <StillImage :class="{'small': isSmall}" referrerpolicy="no-referrer" :mimetype="attachment.mimetype" :src="attachment.large_thumb_url || attachment.url"/> </a> - <video :class="{'small': isSmall}" v-if="type === 'video' && !hidden" :src="attachment.url" controls loop></video> + <video :class="{'small': isSmall}" v-if="type === 'video' && !hidden" @loadeddata="onVideoDataLoad" :src="attachment.url" controls :loop="loopVideo"></video> <audio v-if="type === 'audio'" :src="attachment.url" controls></audio> @@ -38,7 +38,6 @@ .attachments { display: flex; flex-wrap: wrap; - margin-right: -0.7em; .attachment.media-upload-container { flex: 0 0 auto; @@ -50,6 +49,10 @@ margin-right: 0.5em; } + .nsfw-placeholder { + cursor: pointer; + } + .small-attachment { &.image, &.video { max-width: 35%; diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 3749271d..2b84758b 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -65,12 +65,14 @@ <i class="icon-cancel" @click="clearError"></i> </div> <div class="attachments"> - <div class="media-upload-container attachment" v-for="file in newStatus.files"> + <div class="media-upload-wrapper" v-for="file in newStatus.files"> <i class="fa icon-cancel" @click="removeMediaFile(file)"></i> - <img class="thumbnail media-upload" :src="file.image" v-if="type(file) === 'image'"></img> - <video v-if="type(file) === 'video'" :src="file.image" controls></video> - <audio v-if="type(file) === 'audio'" :src="file.image" controls></audio> - <a v-if="type(file) === 'unknown'" :href="file.image">{{file.url}}</a> + <div class="media-upload-container attachment"> + <img class="thumbnail media-upload" :src="file.image" v-if="type(file) === 'image'"></img> + <video v-if="type(file) === 'video'" :src="file.image" controls></video> + <audio v-if="type(file) === 'audio'" :src="file.image" controls></audio> + <a v-if="type(file) === 'unknown'" :href="file.image">{{file.url}}</a> + </div> </div> </div> </form> @@ -120,14 +122,49 @@ text-align: center; } + .media-upload-wrapper { + flex: 0 0 auto; + max-width: 100%; + min-width: 50px; + margin-right: .2em; + margin-bottom: .5em; + + .icon-cancel { + display: inline-block; + position: static; + margin: 0; + padding-bottom: 0; + margin-left: $fallback--attachmentRadius; + margin-left: var(--attachmentRadius, $fallback--attachmentRadius); + background-color: $fallback--btn; + background-color: var(--btn, $fallback--btn); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + } + .attachments { padding: 0 0.5em; .attachment { + margin: 0; position: relative; + flex: 0 0 auto; border: 1px solid $fallback--border; border: 1px solid var(--border, $fallback--border); - margin: 0.5em 0.8em 0.2em 0; + text-align: center; + + audio { + min-width: 300px; + flex: 1 0 auto; + } + + a { + display: block; + text-align: left; + line-height: 1.2; + padding: .5em; + } } i { diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index a26111d6..169b9080 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -1,3 +1,4 @@ +/* eslint-env browser */ import StyleSwitcher from '../style_switcher/style_switcher.vue' import { filter, trim } from 'lodash' @@ -7,11 +8,21 @@ const settings = { hideAttachmentsLocal: this.$store.state.config.hideAttachments, hideAttachmentsInConvLocal: this.$store.state.config.hideAttachmentsInConv, hideNsfwLocal: this.$store.state.config.hideNsfw, + loopVideoLocal: this.$store.state.config.loopVideo, + loopVideoSilentOnlyLocal: this.$store.state.config.loopVideoSilentOnly, muteWordsString: this.$store.state.config.muteWords.join('\n'), autoLoadLocal: this.$store.state.config.autoLoad, streamingLocal: this.$store.state.config.streaming, + pauseOnUnfocusedLocal: this.$store.state.config.pauseOnUnfocused, hoverPreviewLocal: this.$store.state.config.hoverPreview, - stopGifs: this.$store.state.config.stopGifs + stopGifs: this.$store.state.config.stopGifs, + loopSilentAvailable: + // Firefox + Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || + // Chrome-likes + Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') || + // Future spec, still not supported in Nightly 63 as of 08/2018 + Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks') } }, components: { @@ -32,12 +43,21 @@ const settings = { hideNsfwLocal (value) { this.$store.dispatch('setOption', { name: 'hideNsfw', value }) }, + loopVideoLocal (value) { + this.$store.dispatch('setOption', { name: 'loopVideo', value }) + }, + loopVideoSilentOnlyLocal (value) { + this.$store.dispatch('setOption', { name: 'loopVideoSilentOnly', value }) + }, autoLoadLocal (value) { this.$store.dispatch('setOption', { name: 'autoLoad', value }) }, streamingLocal (value) { this.$store.dispatch('setOption', { name: 'streaming', value }) }, + pauseOnUnfocusedLocal (value) { + this.$store.dispatch('setOption', { name: 'pauseOnUnfocused', value }) + }, hoverPreviewLocal (value) { this.$store.dispatch('setOption', { name: 'hoverPreview', value }) }, diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index 6245e758..d6aec2ff 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -1,53 +1,77 @@ <template> - <div class="settings panel panel-default"> - <div class="panel-heading"> - {{$t('settings.settings')}} +<div class="settings panel panel-default"> + <div class="panel-heading"> + {{$t('settings.settings')}} + </div> + <div class="panel-body"> + <div class="setting-item"> + <h2>{{$t('settings.theme')}}</h2> + <style-switcher></style-switcher> </div> - <div class="panel-body"> - <div class="setting-item"> - <h2>{{$t('settings.theme')}}</h2> - <style-switcher></style-switcher> - </div> - <div class="setting-item"> - <h2>{{$t('settings.filtering')}}</h2> - <p>{{$t('settings.filtering_explanation')}}</p> - <textarea id="muteWords" v-model="muteWordsString"></textarea> - </div> - <div class="setting-item"> - <h2>{{$t('settings.attachments')}}</h2> - <ul class="setting-list"> - <li> - <input type="checkbox" id="hideAttachments" v-model="hideAttachmentsLocal"> - <label for="hideAttachments">{{$t('settings.hide_attachments_in_tl')}}</label> - </li> - <li> - <input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal"> - <label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label> - </li> - <li> - <input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal"> - <label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label> - </li> - <li> - <input type="checkbox" id="autoload" v-model="autoLoadLocal"> - <label for="autoload">{{$t('settings.autoload')}}</label> - </li> - <li> - <input type="checkbox" id="streaming" v-model="streamingLocal"> - <label for="streaming">{{$t('settings.streaming')}}</label> - </li> + <div class="setting-item"> + <h2>{{$t('settings.filtering')}}</h2> + <p>{{$t('settings.filtering_explanation')}}</p> + <textarea id="muteWords" v-model="muteWordsString"></textarea> + </div> + <div class="setting-item"> + <h2>{{$t('nav.timeline')}}</h2> + <ul class="setting-list"> + <li> + <input type="checkbox" id="streaming" v-model="streamingLocal"> + <label for="streaming">{{$t('settings.streaming')}}</label> + <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]"> <li> - <input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal"> - <label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label> + <input :disabled="!streamingLocal" type="checkbox" id="pauseOnUnfocused" v-model="pauseOnUnfocusedLocal"> + <label for="pauseOnUnfocused">{{$t('settings.pause_on_unfocused')}}</label> </li> + </ul> + </li> + <li> + <input type="checkbox" id="autoload" v-model="autoLoadLocal"> + <label for="autoload">{{$t('settings.autoload')}}</label> + </li> + <li> + <input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal"> + <label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label> + </li> + </ul> + </div> + <div class="setting-item"> + <h2>{{$t('settings.attachments')}}</h2> + <ul class="setting-list"> + <li> + <input type="checkbox" id="hideAttachments" v-model="hideAttachmentsLocal"> + <label for="hideAttachments">{{$t('settings.hide_attachments_in_tl')}}</label> + </li> + <li> + <input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal"> + <label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label> + </li> + <li> + <input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal"> + <label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label> + </li> + <li> + <input type="checkbox" id="stopGifs" v-model="stopGifs"> + <label for="stopGifs">{{$t('settings.stop_gifs')}}</label> + </li> + <li> + <input type="checkbox" id="loopVideo" v-model="loopVideoLocal"> + <label for="loopVideo">{{$t('settings.loop_video')}}</label> + <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]"> <li> - <input type="checkbox" id="stopGifs" v-model="stopGifs"> - <label for="stopGifs">{{$t('settings.stop_gifs')}}</label> + <input :disabled="!loopVideoLocal || !loopSilentAvailable" type="checkbox" id="loopVideoSilentOnly" v-model="loopVideoSilentOnlyLocal"> + <label for="loopVideoSilentOnly">{{$t('settings.loop_video_silent_only')}}</label> + <div v-if="!loopSilentAvailable" class="unavailable"> + <i class="icon-globe"/>! {{$t('settings.limited_availability')}} + </div> </li> - </ul> - </div> + </ul> + </li> + </ul> </div> </div> +</div> </template> <script src="./settings.js"> @@ -67,6 +91,12 @@ height: 100px; } + .unavailable, + .unavailable i { + color: var(--cRed, $fallback--cRed); + color: $fallback--cRed; + } + .old-avatar { width: 128px; border-radius: $fallback--avatarRadius; @@ -89,8 +119,12 @@ } .setting-list { list-style-type: none; + padding-left: 2em; li { margin-bottom: 0.5em; } + .suboptions { + margin-top: 0.3em + } } </style> diff --git a/src/components/status/status.vue b/src/components/status/status.vue index e2fb5d36..b8993b0d 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -11,8 +11,8 @@ <div v-if="retweet && !noHeading" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info"> <StillImage v-if="retweet" class='avatar' :src="statusoid.user.profile_image_url_original"/> <div class="media-body faint"> - <a v-if="retweeterHtml" :href="statusoid.user.statusnet_profile_url" style="font-weight: bold;" :title="'@'+statusoid.user.screen_name" v-html="retweeterHtml"></a> - <a v-else :href="statusoid.user.statusnet_profile_url" style="font-weight: bold;" :title="'@'+statusoid.user.screen_name">{{retweeter}}</a> + <a v-if="retweeterHtml" :href="statusoid.user.statusnet_profile_url" class="user-name" :title="'@'+statusoid.user.screen_name" v-html="retweeterHtml"></a> + <a v-else :href="statusoid.user.statusnet_profile_url" class="user-name" :title="'@'+statusoid.user.screen_name">{{retweeter}}</a> <i class='fa icon-retweet retweeted'></i> {{$t('timeline.repeated')}} </div> @@ -57,8 +57,10 @@ <router-link class="timeago" :to="{ name: 'conversation', params: { id: status.id } }"> <timeago :since="status.created_at" :auto-update="60"></timeago> </router-link> - <span v-if="status.visibility"><i :class="visibilityIcon(status.visibility)"></i> </span> - <a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="icon-link-ext"></i></a> + <div class="visibility-icon" v-if="status.visibility"> + <i :class="visibilityIcon(status.visibility)"></i> + </div> + <a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="icon-link-ext-alt"></i></a> <template v-if="expandable"> <a href="#" @click.prevent="toggleExpanded"><i class="icon-plus-squared"></i></a> </template> @@ -141,6 +143,7 @@ margin-top: 0.25em; margin-left: 0.5em; z-index: 50; + .status { flex: 1; border: 0; @@ -155,6 +158,7 @@ text-align: center; border-width: 1px; border-style: solid; + i { font-size: 2em; } @@ -196,6 +200,7 @@ .media-heading { flex-wrap: nowrap; + line-height: 18px; } .media-heading-left { @@ -218,12 +223,22 @@ flex: 1 0; display: flex; flex-wrap: wrap; - align-content: center; + align-items: baseline; + + .user-name { + margin-right: .45em; + + img { + width: 14px; + height: 14px; + vertical-align: middle; + object-fit: contain + } + } } + .links { display: flex; - padding-top: 1px; - margin-left: 0.2em; font-size: 12px; color: $fallback--link; color: var(--link, $fallback--link); @@ -247,19 +262,25 @@ } .media-heading-right { + display: inline-flex; flex-shrink: 0; - display: flex; flex-wrap: nowrap; - max-height: 1.5em; - margin-left: 0.25em; + margin-left: .25em; + align-self: baseline; + .timeago { margin-right: 0.2em; font-size: 12px; - padding-top: 1px; + align-self: last baseline; } - i { + + > * { margin-left: 0.2em; } + a:hover i { + color: $fallback--fg; + color: var(--fg, $fallback--fg); + } } a { @@ -318,6 +339,7 @@ .retweet-info { padding: 0.4em 0.6em 0 0.6em; margin: 0; + .avatar { border-radius: $fallback--avatarAltRadius; border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); @@ -333,9 +355,22 @@ display: flex; align-content: center; flex-wrap: wrap; + + .user-name { + font-weight: bold; + + img { + width: 14px; + height: 14px; + vertical-align: middle; + object-fit: contain + } + } + i { padding: 0 0.2em; } + a { max-width: 100%; overflow: hidden; diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 5c179567..a651f619 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -133,7 +133,10 @@ const Timeline = { } if (count > 0) { // only 'stream' them when you're scrolled to the top - if (window.pageYOffset < 15 && !this.paused && !this.unfocused) { + if (window.pageYOffset < 15 && + !this.paused && + !(this.unfocused && this.$store.state.config.pauseOnUnfocused) + ) { this.showNewStatuses() } else { this.paused = true diff --git a/src/i18n/messages.js b/src/i18n/messages.js index 38e58c4c..99382364 100644 --- a/src/i18n/messages.js +++ b/src/i18n/messages.js @@ -314,6 +314,9 @@ const en = { stop_gifs: 'Play-on-hover GIFs', autoload: 'Enable automatic loading when scrolled to the bottom', streaming: 'Enable automatic streaming of new posts when scrolled to the top', + pause_on_unfocused: 'Pause streaming when tab is not focused', + loop_video: 'Loop videos', + loop_video_silent_only: 'Loop only videos without sound (i.e. Mastodon\'s "gifs")', reply_link_preview: 'Enable reply-link preview on mouse hover', follow_import: 'Follow import', import_followers_from_a_csv_file: 'Import follows from a csv file', @@ -333,6 +336,7 @@ const en = { changed_password: 'Password changed successfully!', change_password_error: 'There was an issue changing your password.', lock_account_description: 'Restrict your account to approved followers only', + limited_availability: 'Unavailable in your browser', default_vis: 'Default visibility scope', profile_tab: 'Profile', security_tab: 'Security', @@ -1609,6 +1613,9 @@ const ru = { nsfw_clickthrough: 'Включить скрытие NSFW вложений', autoload: 'Включить автоматическую загрузку при прокрутке вниз', streaming: 'Включить автоматическую загрузку новых сообщений при прокрутке вверх', + pause_on_unfocused: 'Приостановить загрузку когда вкладка не в фокусе', + loop_video: 'Зациливать видео', + loop_video_silent_only: 'Зацикливать только беззвучные видео (т.е. "гифки" с Mastodon)', reply_link_preview: 'Включить предварительный просмотр ответа при наведении мыши', follow_import: 'Импортировать читаемых', import_followers_from_a_csv_file: 'Импортировать читаемых из файла .csv', @@ -1626,7 +1633,8 @@ const ru = { new_password: 'Новый пароль', confirm_new_password: 'Подтверждение нового пароля', changed_password: 'Пароль изменён успешно.', - change_password_error: 'Произошла ошибка при попытке изменить пароль.' + change_password_error: 'Произошла ошибка при попытке изменить пароль.', + limited_availability: 'Не доступно в вашем браузере' }, notifications: { notifications: 'Уведомления', diff --git a/src/main.js b/src/main.js index cb53edd3..df271ce3 100644 --- a/src/main.js +++ b/src/main.js @@ -54,6 +54,10 @@ const persistedStateOptions = { 'config.muteWords', 'config.customTheme', 'config.highlight', + 'config.loopVideo', + 'config.loopVideoSilentOnly', + 'config.pauseOnUnfocused', + 'config.stopGifs', 'users.lastLoginName' ] } diff --git a/src/modules/config.js b/src/modules/config.js index 2b50655b..fe31ab01 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -7,9 +7,13 @@ const defaultState = { hideAttachments: false, hideAttachmentsInConv: false, hideNsfw: true, + loopVideo: true, + loopVideoSilentOnly: true, autoLoad: true, streaming: false, hoverPreview: true, + pauseOnUnfocused: true, + stopGifs: false, muteWords: [], highlight: {} } |
