diff options
| author | Shpuld Shpuldson <shp@cock.li> | 2020-12-04 11:20:01 +0200 |
|---|---|---|
| committer | Shpuld Shpuldson <shp@cock.li> | 2020-12-04 11:20:01 +0200 |
| commit | 1fd1553a1c06f22ce5718b16814d0f03688fdc06 (patch) | |
| tree | 6416934d1cf657b4b7fe27f628add4940c356a74 /src | |
| parent | 15bed586dcd1d10a6a05c664cf5bab72cdbf2a46 (diff) | |
| parent | 42c747a342cd7d435dcbe411276ac4999ff92395 (diff) | |
Merge branch 'develop' into feat/report-notification
Diffstat (limited to 'src')
95 files changed, 963 insertions, 657 deletions
diff --git a/src/App.scss b/src/App.scss index ca7d33cd..cdc3209c 100644 --- a/src/App.scss +++ b/src/App.scss @@ -33,6 +33,7 @@ h4 { max-width: 980px; align-content: flex-start; } + .underlay { background-color: rgba(0,0,0,0.15); background-color: var(--underlay, rgba(0,0,0,0.15)); @@ -69,7 +70,7 @@ a { color: var(--link, $fallback--link); } -button { +.button-default { user-select: none; color: $fallback--text; color: var(--btnText, $fallback--text); @@ -85,7 +86,8 @@ button { font-family: sans-serif; font-family: var(--interfaceFont, sans-serif); - i[class*=icon-], .svg-inline--fa { + i[class*=icon-], + .svg-inline--fa { color: $fallback--text; color: var(--btnText, $fallback--text); } @@ -107,7 +109,8 @@ button { background-color: $fallback--fg; background-color: var(--btnPressed, $fallback--fg); - svg, i { + svg, + i { color: $fallback--text; color: var(--btnPressedText, $fallback--text); } @@ -120,7 +123,8 @@ button { background-color: $fallback--fg; background-color: var(--btnDisabled, $fallback--fg); - svg, i { + svg, + i { color: $fallback--text; color: var(--btnDisabledText, $fallback--text); } @@ -134,7 +138,8 @@ button { box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset; box-shadow: var(--buttonPressedShadow); - svg, i { + svg, + i { color: $fallback--text; color: var(--btnToggledText, $fallback--text); } @@ -149,6 +154,30 @@ button { } } +.button-unstyled { + background: none; + border: none; + outline: none; + display: inline; + text-align: initial; + font-size: 100%; + font-family: inherit; + padding: 0; + line-height: unset; + cursor: pointer; + box-sizing: content-box; + color: inherit; + + &.-link { + color: $fallback--link; + color: var(--link, $fallback--link); + } + + &.-fullwidth { + width: 100%; + } +} + input, textarea, .select, .input { &.unstyled { @@ -442,6 +471,7 @@ main-router { color: $fallback--faint; color: var(--panelFaint, $fallback--faint); } + .faint-link { color: $fallback--faint; color: var(--faintLink, $fallback--faint); @@ -453,11 +483,8 @@ main-router { overflow-x: hidden; } - button { - flex-shrink: 0; - } - - button, .alert { + .button-default, + .alert { // height: 100%; line-height: 21px; min-height: 0; @@ -468,8 +495,11 @@ main-router { align-self: stretch; } - button { - &, i[class*=icon-] { + .button-default { + flex-shrink: 0; + + &, + i[class*=icon-] { color: $fallback--text; color: var(--btnPanelText, $fallback--text); } @@ -492,7 +522,8 @@ main-router { } } - a { + a, + .-link { color: $fallback--link; color: var(--panelLink, $fallback--link) } @@ -507,15 +538,15 @@ 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 { + a, + .-link { color: $fallback--link; - color: var(--panelLink, $fallback--link) + color: var(--panelLink, $fallback--link); } } @@ -797,7 +828,7 @@ nav { } } -.btn.btn-default { +.btn.button-default { min-height: 28px; } diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 3cbbf020..b472fcf6 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -7,6 +7,7 @@ import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { applyTheme } from '../services/style_setter/style_setter.js' +import FaviconService from '../services/favicon_service/favicon_service.js' let staticInitialResults = null @@ -326,6 +327,8 @@ const afterStoreSetup = async ({ store, i18n }) => { const width = windowWidth() store.dispatch('setMobileLayout', width <= 800) + FaviconService.initFaviconService() + const overrides = window.___pleromafe_dev_overrides || {} const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin store.dispatch('setInstanceOption', { name: 'server', value: server }) diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue index e3ae376e..ab5d1d29 100644 --- a/src/components/account_actions/account_actions.vue +++ b/src/components/account_actions/account_actions.vue @@ -4,6 +4,7 @@ trigger="click" placement="bottom" :bound-to="{ x: 'container' }" + remove-padding > <div slot="content" @@ -13,14 +14,14 @@ <template v-if="relationship.following"> <button v-if="relationship.showing_reblogs" - class="btn btn-default dropdown-item" + class="btn button-default dropdown-item" @click="hideRepeats" > {{ $t('user_card.hide_repeats') }} </button> <button v-if="!relationship.showing_reblogs" - class="btn btn-default dropdown-item" + class="btn button-default dropdown-item" @click="showRepeats" > {{ $t('user_card.show_repeats') }} @@ -32,27 +33,27 @@ </template> <button v-if="relationship.blocking" - class="btn btn-default btn-block dropdown-item" + class="btn button-default btn-block dropdown-item" @click="unblockUser" > {{ $t('user_card.unblock') }} </button> <button v-else - class="btn btn-default btn-block dropdown-item" + class="btn button-default btn-block dropdown-item" @click="blockUser" > {{ $t('user_card.block') }} </button> <button - class="btn btn-default btn-block dropdown-item" + class="btn button-default btn-block dropdown-item" @click="reportUser" > {{ $t('user_card.report') }} </button> <button v-if="pleromaChatMessagesAvailable" - class="btn btn-default btn-block dropdown-item" + class="btn button-default btn-block dropdown-item" @click="openChat" > {{ $t('user_card.message') }} @@ -61,7 +62,7 @@ </div> <div slot="trigger" - class="btn btn-default ellipsis-button" + class="ellipsis-button" > <FAIcon class="icon" diff --git a/src/components/async_component_error/async_component_error.vue b/src/components/async_component_error/async_component_error.vue index b68b98f9..b1b59638 100644 --- a/src/components/async_component_error/async_component_error.vue +++ b/src/components/async_component_error/async_component_error.vue @@ -8,7 +8,7 @@ {{ $t('general.error_retry') }} </p> <button - class="btn" + class="btn button-default" @click="retry" > {{ $t('general.retry') }} diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index e23fcb1b..5f5779a0 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -8,14 +8,18 @@ import { faFile, faMusic, faImage, - faVideo + faVideo, + faPlayCircle, + faTimes } from '@fortawesome/free-solid-svg-icons' library.add( faFile, faMusic, faImage, - faVideo + faVideo, + faPlayCircle, + faTimes ) const Attachment = { diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index f1fac2c8..2c1c1682 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -42,15 +42,13 @@ icon="play-circle" /> </a> - <div + <button v-if="nsfw && hideNsfwLocal && !hidden" - class="hider" + class="button-unstyled hider" + @click.prevent="toggleHidden" > - <a - href="#" - @click.prevent="toggleHidden" - >Hide</a> - </div> + <FAIcon icon="times" /> + </button> <a v-if="type === 'image' && (!hidden || preloadImage)" @@ -234,15 +232,23 @@ .hider { position: absolute; right: 0; - white-space: nowrap; margin: 10px; - padding: 5px; - background: rgba(230,230,230,0.6); - font-weight: bold; + padding: 0; z-index: 4; - line-height: 1; border-radius: $fallback--tooltipRadius; border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + text-align: center; + width: 2em; + height: 2em; + font-size: 1.25em; + // TODO: theming? hard to theme with unknown background image color + background: rgba(230, 230, 230, 0.7); + .svg-inline--fa { + color: rgba(0, 0, 0, 0.6); + } + &:hover .svg-inline--fa { + color: rgba(0, 0, 0, 0.9); + } } video { diff --git a/src/components/block_card/block_card.vue b/src/components/block_card/block_card.vue index 5b00b738..2fe66d4c 100644 --- a/src/components/block_card/block_card.vue +++ b/src/components/block_card/block_card.vue @@ -3,7 +3,7 @@ <div class="block-card-content-container"> <button v-if="blocked" - class="btn btn-default" + class="btn button-default" :disabled="progress" @click="unblockUser" > @@ -16,7 +16,7 @@ </button> <button v-else - class="btn btn-default" + class="btn button-default" :disabled="progress" @click="blockUser" > diff --git a/src/components/chat_list/chat_list.vue b/src/components/chat_list/chat_list.vue index 17e2f795..e23eec13 100644 --- a/src/components/chat_list/chat_list.vue +++ b/src/components/chat_list/chat_list.vue @@ -10,7 +10,10 @@ <span class="title"> {{ $t("chats.chats") }} </span> - <button @click="newChat"> + <button + class="button-default" + @click="newChat" + > {{ $t("chats.new") }} </button> </div> diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss index 5af744a3..e4351d3b 100644 --- a/src/components/chat_message/chat_message.scss +++ b/src/components/chat_message/chat_message.scss @@ -31,9 +31,6 @@ color: $fallback--text; color: var(--text, $fallback--text); } - - border-radius: $fallback--chatMessageRadius; - border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius); } .popover { diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue index 3849ab6e..0777f880 100644 --- a/src/components/chat_message/chat_message.vue +++ b/src/components/chat_message/chat_message.vue @@ -53,7 +53,7 @@ <div slot="content"> <div class="dropdown-menu"> <button - class="dropdown-item dropdown-item-icon" + class="button-default dropdown-item dropdown-item-icon" @click="deleteMessage" > <FAIcon icon="times" /> {{ $t("chats.delete") }} @@ -62,7 +62,7 @@ </div> <button slot="trigger" - class="menu-icon" + class="button-default menu-icon" :title="$t('chats.more')" > <FAIcon icon="ellipsis-h" /> diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index e0b9fcc5..353859b8 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -10,12 +10,13 @@ class="panel-heading conversation-heading" > <span class="title"> {{ $t('timeline.conversation') }} </span> - <span v-if="collapsable"> - <a - href="#" - @click.prevent="toggleExpanded" - >{{ $t('timeline.collapse') }}</a> - </span> + <button + v-if="collapsable" + class="button-unstyled -link" + @click.prevent="toggleExpanded" + > + {{ $t('timeline.collapse') }} + </button> </div> <status v-for="status in conversation" @@ -57,13 +58,6 @@ } &.-expanded { - .conversation-status { - border-color: $fallback--border; - border-color: var(--border, $fallback--border); - border-left-color: $fallback--cRed; - border-left-color: var(--cRed, $fallback--cRed); - } - .conversation-status:last-child { border-bottom: none; border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss index 028692a9..8fd8e620 100644 --- a/src/components/desktop_nav/desktop_nav.scss +++ b/src/components/desktop_nav/desktop_nav.scss @@ -21,7 +21,7 @@ grid-template-areas: "logo sitename actions"; } - button { + .button-default { &, svg { color: $fallback--text; color: var(--btnTopBarText, $fallback--text); @@ -80,12 +80,13 @@ .nav-icon { margin-left: 0.2em; width: 2em; + height: 100%; text-align: center; - } - a, a svg { - color: $fallback--link; - color: var(--topBarLink, $fallback--link); + .svg-inline--fa { + color: $fallback--link; + color: var(--topBarLink, $fallback--link); + } } .sitename { diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue index 3a6e4033..762aa610 100644 --- a/src/components/desktop_nav/desktop_nav.vue +++ b/src/components/desktop_nav/desktop_nav.vue @@ -36,9 +36,8 @@ @toggled="onSearchBarToggled" @click.stop.native /> - <a - href="#" - class="nav-icon" + <button + class="button-unstyled nav-icon" @click.stop="openSettingsModal" > <FAIcon @@ -47,29 +46,32 @@ icon="cog" :title="$t('nav.preferences')" /> - </a> + </button> <a v-if="currentUser && currentUser.role === 'admin'" href="/pleroma/admin/#/login-pleroma" class="nav-icon" target="_blank" - ><FAIcon - fixed-width - class="fa-scale-110 fa-old-padding" - icon="tachometer-alt" - :title="$t('nav.administration')" - /></a> - <a + > + <FAIcon + fixed-width + class="fa-scale-110 fa-old-padding" + icon="tachometer-alt" + :title="$t('nav.administration')" + /> + </a> + <button v-if="currentUser" - href="#" - class="nav-icon" + class="button-unstyled nav-icon" @click.prevent="logout" - ><FAIcon - fixed-width - class="fa-scale-110 fa-old-padding" - icon="sign-out-alt" - :title="$t('login.logout')" - /></a> + > + <FAIcon + fixed-width + class="fa-scale-110 fa-old-padding" + icon="sign-out-alt" + :title="$t('login.logout')" + /> + </button> </div> </div> </nav> diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue index 97aee243..3b5aec14 100644 --- a/src/components/domain_mute_card/domain_mute_card.vue +++ b/src/components/domain_mute_card/domain_mute_card.vue @@ -6,7 +6,7 @@ <ProgressButton v-if="muted" :click="unmuteDomain" - class="btn btn-default" + class="btn button-default" > {{ $t('domain_mute_card.unmute') }} <template slot="progress"> @@ -16,7 +16,7 @@ <ProgressButton v-else :click="muteDomain" - class="btn btn-default" + class="btn button-default" > {{ $t('domain_mute_card.mute') }} <template slot="progress"> diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 87303d08..2068a598 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -114,7 +114,8 @@ const EmojiInput = { showPicker: false, temporarilyHideSuggestions: false, keepOpen: false, - disableClickOutside: false + disableClickOutside: false, + suggestions: [] } }, components: { @@ -124,21 +125,6 @@ const EmojiInput = { padEmoji () { return this.$store.getters.mergedConfig.padEmoji }, - suggestions () { - const firstchar = this.textAtCaret.charAt(0) - if (this.textAtCaret === firstchar) { return [] } - const matchedSuggestions = this.suggest(this.textAtCaret) - if (matchedSuggestions.length <= 0) { - return [] - } - return take(matchedSuggestions, 5) - .map(({ imageUrl, ...rest }, index) => ({ - ...rest, - // eslint-disable-next-line camelcase - img: imageUrl || '', - highlighted: index === this.highlighted - })) - }, showSuggestions () { return this.focused && this.suggestions && @@ -188,6 +174,23 @@ const EmojiInput = { watch: { showSuggestions: function (newValue) { this.$emit('shown', newValue) + }, + textAtCaret: async function (newWord) { + const firstchar = newWord.charAt(0) + this.suggestions = [] + if (newWord === firstchar) return + const matchedSuggestions = await this.suggest(newWord) + // Async: cancel if textAtCaret has changed during wait + if (this.textAtCaret !== newWord) return + if (matchedSuggestions.length <= 0) return + this.suggestions = take(matchedSuggestions, 5) + .map(({ imageUrl, ...rest }) => ({ + ...rest, + img: imageUrl || '' + })) + }, + suggestions (newValue) { + this.$nextTick(this.resize) } }, methods: { diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index 224e72cf..4becdc41 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -6,13 +6,13 @@ > <slot /> <template v-if="enableEmojiPicker"> - <div + <button v-if="!hideEmojiButton" - class="emoji-picker-icon" + class="button-unstyled emoji-picker-icon" @click.prevent="togglePicker" > <FAIcon :icon="['far', 'smile-beam']" /> - </div> + </button> <EmojiPicker v-if="enableEmojiPicker" ref="picker" @@ -37,7 +37,7 @@ v-for="(suggestion, index) in suggestions" :key="index" class="autocomplete-item" - :class="{ highlighted: suggestion.highlighted }" + :class="{ highlighted: index === highlighted }" @click.stop.prevent="onClick($event, suggestion)" > <span class="image"> diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index 8330345b..14a2b41e 100644 --- a/src/components/emoji_input/suggestor.js +++ b/src/components/emoji_input/suggestor.js @@ -1,4 +1,3 @@ -import { debounce } from 'lodash' /** * suggest - generates a suggestor function to be used by emoji-input * data: object providing source information for specific types of suggestions: @@ -11,19 +10,19 @@ import { debounce } from 'lodash' * doesn't support user linking you can just provide only emoji. */ -const debounceUserSearch = debounce((data, input) => { - data.updateUsersList(input) -}, 500) - -export default data => input => { - const firstChar = input[0] - if (firstChar === ':' && data.emoji) { - return suggestEmoji(data.emoji)(input) - } - if (firstChar === '@' && data.users) { - return suggestUsers(data)(input) +export default data => { + const emojiCurry = suggestEmoji(data.emoji) + const usersCurry = data.store && suggestUsers(data.store) + return input => { + const firstChar = input[0] + if (firstChar === ':' && data.emoji) { + return emojiCurry(input) + } + if (firstChar === '@' && usersCurry) { + return usersCurry(input) + } + return [] } - return [] } export const suggestEmoji = emojis => input => { @@ -57,50 +56,75 @@ export const suggestEmoji = emojis => input => { }) } -export const suggestUsers = data => input => { - const noPrefix = input.toLowerCase().substr(1) - const users = data.users - - const newUsers = users.filter( - user => - user.screen_name.toLowerCase().startsWith(noPrefix) || - user.name.toLowerCase().startsWith(noPrefix) - - /* taking only 20 results so that sorting is a bit cheaper, we display - * only 5 anyway. could be inaccurate, but we ideally we should query - * backend anyway - */ - ).slice(0, 20).sort((a, b) => { - let aScore = 0 - let bScore = 0 - - // Matches on screen name (i.e. user@instance) makes a priority - aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 - bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 - - // Matches on name takes second priority - aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 - bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 - - const diff = (bScore - aScore) * 10 - - // Then sort alphabetically - const nameAlphabetically = a.name > b.name ? 1 : -1 - const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1 - - return diff + nameAlphabetically + screenNameAlphabetically - /* eslint-disable camelcase */ - }).map(({ screen_name, name, profile_image_url_original }) => ({ - displayText: screen_name, - detailText: name, - imageUrl: profile_image_url_original, - replacement: '@' + screen_name + ' ' - })) - - // BE search users to get more comprehensive results - if (data.updateUsersList) { - debounceUserSearch(data, noPrefix) +export const suggestUsers = ({ dispatch, state }) => { + // Keep some persistent values in closure, most importantly for the + // custom debounce to work. Lodash debounce does not return a promise. + let suggestions = [] + let previousQuery = '' + let timeout = null + let cancelUserSearch = null + + const userSearch = (query) => dispatch('searchUsers', { query }) + const debounceUserSearch = (query) => { + cancelUserSearch && cancelUserSearch() + return new Promise((resolve, reject) => { + timeout = setTimeout(() => { + userSearch(query).then(resolve).catch(reject) + }, 300) + cancelUserSearch = () => { + clearTimeout(timeout) + resolve([]) + } + }) + } + + return async input => { + const noPrefix = input.toLowerCase().substr(1) + if (previousQuery === noPrefix) return suggestions + + suggestions = [] + previousQuery = noPrefix + // Fetch more and wait, don't fetch if there's the 2nd @ because + // the backend user search can't deal with it. + // Reference semantics make it so that we get the updated data after + // the await. + if (!noPrefix.includes('@')) { + await debounceUserSearch(noPrefix) + } + + const newSuggestions = state.users.users.filter( + user => + user.screen_name.toLowerCase().startsWith(noPrefix) || + user.name.toLowerCase().startsWith(noPrefix) + ).slice(0, 20).sort((a, b) => { + let aScore = 0 + let bScore = 0 + + // Matches on screen name (i.e. user@instance) makes a priority + aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 + bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 + + // Matches on name takes second priority + aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 + bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 + + const diff = (bScore - aScore) * 10 + + // Then sort alphabetically + const nameAlphabetically = a.name > b.name ? 1 : -1 + const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1 + + return diff + nameAlphabetically + screenNameAlphabetically + /* eslint-disable camelcase */ + }).map(({ screen_name, name, profile_image_url_original }) => ({ + displayText: screen_name, + detailText: name, + imageUrl: profile_image_url_original, + replacement: '@' + screen_name + ' ' + })) + /* eslint-enable camelcase */ + + suggestions = newSuggestions || [] + return suggestions } - return newUsers - /* eslint-enable camelcase */ } diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue index 2f14b5b2..51d50359 100644 --- a/src/components/emoji_reactions/emoji_reactions.vue +++ b/src/components/emoji_reactions/emoji_reactions.vue @@ -6,7 +6,7 @@ :users="accountsForEmoji[reaction.name]" > <button - class="emoji-reaction btn btn-default" + class="emoji-reaction btn button-default" :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }" @click="emojiOnClick(reaction.name, $event)" @mouseenter="fetchEmojiReactionsByIfMissing()" diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue index ae00487f..8ffe34f8 100644 --- a/src/components/export_import/export_import.vue +++ b/src/components/export_import/export_import.vue @@ -2,13 +2,13 @@ <div class="import-export-container"> <slot name="before" /> <button - class="btn" + class="btn button-default" @click="exportData" > {{ exportLabel }} </button> <button - class="btn" + class="btn button-default" @click="importData" > {{ importLabel }} diff --git a/src/components/exporter/exporter.vue b/src/components/exporter/exporter.vue index ecd71bf1..d6a03088 100644 --- a/src/components/exporter/exporter.vue +++ b/src/components/exporter/exporter.vue @@ -11,7 +11,7 @@ </div> <button v-else - class="btn btn-default" + class="btn button-default" @click="process" > {{ exportButtonLabel }} diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue index a33f6e87..e687d487 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -2,8 +2,9 @@ <Popover trigger="click" placement="top" - class="extra-button-popover" + :offset="{ y: 5 }" :bound-to="{ x: 'container' }" + remove-padding > <div slot="content" @@ -12,7 +13,7 @@ <div class="dropdown-menu"> <button v-if="canMute && !status.thread_muted" - class="dropdown-item dropdown-item-icon" + class="button-default dropdown-item dropdown-item-icon" @click.prevent="muteConversation" > <FAIcon @@ -22,7 +23,7 @@ </button> <button v-if="canMute && status.thread_muted" - class="dropdown-item dropdown-item-icon" + class="button-default dropdown-item dropdown-item-icon" @click.prevent="unmuteConversation" > <FAIcon @@ -32,7 +33,7 @@ </button> <button v-if="!status.pinned && canPin" - class="dropdown-item dropdown-item-icon" + class="button-default dropdown-item dropdown-item-icon" @click.prevent="pinStatus" @click="close" > @@ -43,7 +44,7 @@ </button> <button v-if="status.pinned && canPin" - class="dropdown-item dropdown-item-icon" + class="button-default dropdown-item dropdown-item-icon" @click.prevent="unpinStatus" @click="close" > @@ -54,7 +55,7 @@ </button> <button v-if="!status.bookmarked" - class="dropdown-item dropdown-item-icon" + class="button-default dropdown-item dropdown-item-icon" @click.prevent="bookmarkStatus" @click="close" > @@ -65,7 +66,7 @@ </button> <button v-if="status.bookmarked" - class="dropdown-item dropdown-item-icon" + class="button-default dropdown-item dropdown-item-icon" @click.prevent="unbookmarkStatus" @click="close" > @@ -76,7 +77,7 @@ </button> <button v-if="canDelete" - class="dropdown-item dropdown-item-icon" + class="button-default dropdown-item dropdown-item-icon" @click.prevent="deleteStatus" @click="close" > @@ -86,7 +87,7 @@ /><span>{{ $t("status.delete") }}</span> </button> <button - class="dropdown-item dropdown-item-icon" + class="button-default dropdown-item dropdown-item-icon" @click.prevent="copyLink" @click="close" > @@ -97,9 +98,12 @@ </button> </div> </div> - <span slot="trigger"> + <span + slot="trigger" + class="ExtraButtons" + > <FAIcon - class="ExtraButtons fa-scale-110 fa-old-padding" + class="fa-scale-110 fa-old-padding" icon="ellipsis-h" /> </span> @@ -112,11 +116,11 @@ @import '../../_variables.scss'; .ExtraButtons { - cursor: pointer; position: static; + padding: 10px; + margin: -10px; - &:hover, - .extra-button-popover.open & { + &:hover .svg-inline--fa { color: $fallback--text; color: var(--text, $fallback--text); } diff --git a/src/components/favorite_button/favorite_button.js b/src/components/favorite_button/favorite_button.js index 2a2ee84a..5cd05f73 100644 --- a/src/components/favorite_button/favorite_button.js +++ b/src/components/favorite_button/favorite_button.js @@ -31,11 +31,6 @@ const FavoriteButton = { } }, computed: { - classes () { - return { - '-favorited': this.status.favorited - } - }, ...mapGetters(['mergedConfig']) } } diff --git a/src/components/favorite_button/favorite_button.vue b/src/components/favorite_button/favorite_button.vue index dfe12f86..dce25e24 100644 --- a/src/components/favorite_button/favorite_button.vue +++ b/src/components/favorite_button/favorite_button.vue @@ -1,23 +1,31 @@ <template> - <div v-if="loggedIn"> - <FAIcon - :class="classes" - class="FavoriteButton fa-scale-110 fa-old-padding -interactive" + <div class="FavoriteButton"> + <button + v-if="loggedIn" + class="button-unstyled interactive" + :class="status.favorited && '-favorited'" :title="$t('tool_tip.favorite')" - :icon="[status.favorited ? 'fas' : 'far', 'star']" - :spin="animated" @click.prevent="favorite()" - /> - <span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span> - </div> - <div v-else> - <FAIcon - :class="classes" - class="FavoriteButton fa-scale-110 fa-old-padding" - :title="$t('tool_tip.favorite')" - :icon="['far', 'star']" - /> - <span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span> + > + <FAIcon + class="fa-scale-110 fa-old-padding" + :icon="[status.favorited ? 'fas' : 'far', 'star']" + :spin="animated" + /> + </button> + <span v-else> + <FAIcon + class="fa-scale-110 fa-old-padding" + :title="$t('tool_tip.favorite')" + :icon="['far', 'star']" + /> + </span> + <span + v-if="!mergedConfig.hidePostStats && status.fave_num > 0" + class="action-counter" + > + {{ status.fave_num }} + </span> </div> </template> @@ -27,19 +35,28 @@ @import '../../_variables.scss'; .FavoriteButton { - &.-interactive { - cursor: pointer; - animation-duration: 0.6s; + display: flex; + + > :first-child { + padding: 10px; + margin: -10px -8px -10px -10px; + } + + .action-counter { + pointer-events: none; + user-select: none; + } + + .interactive { + .svg-inline--fa { + animation-duration: 0.6s; + } - &:hover { + &:hover .svg-inline--fa, + &.-favorited .svg-inline--fa { color: $fallback--cOrange; color: var(--cOrange, $fallback--cOrange); } } - - &.-favorited { - color: $fallback--cOrange; - color: var(--cOrange, $fallback--cOrange); - } } </style> diff --git a/src/components/follow_button/follow_button.vue b/src/components/follow_button/follow_button.vue index bfdc137b..7f85f1d7 100644 --- a/src/components/follow_button/follow_button.vue +++ b/src/components/follow_button/follow_button.vue @@ -1,6 +1,6 @@ <template> <button - class="btn btn-default follow-button" + class="btn button-default follow-button" :class="{ toggled: isPressed }" :disabled="inProgress" :title="title" diff --git a/src/components/follow_request_card/follow_request_card.vue b/src/components/follow_request_card/follow_request_card.vue index b217b8ed..1b12ba4b 100644 --- a/src/components/follow_request_card/follow_request_card.vue +++ b/src/components/follow_request_card/follow_request_card.vue @@ -2,13 +2,13 @@ <basic-user-card :user="user"> <div class="follow-request-card-content-container"> <button - class="btn btn-default" + class="btn button-default" @click="approveUser" > {{ $t('user_card.approve') }} </button> <button - class="btn btn-default" + class="btn button-default" @click="denyUser" > {{ $t('user_card.deny') }} diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue index 75def612..448461b4 100644 --- a/src/components/image_cropper/image_cropper.vue +++ b/src/components/image_cropper/image_cropper.vue @@ -11,21 +11,21 @@ </div> <div class="image-cropper-buttons-wrapper"> <button - class="btn" + class="button-default btn" type="button" :disabled="submitting" @click="submit()" v-text="saveText" /> <button - class="btn" + class="button-default btn" type="button" :disabled="submitting" @click="destroy" v-text="cancelText" /> <button - class="btn" + class="button-default btn" type="button" :disabled="submitting" @click="submit(false)" diff --git a/src/components/importer/importer.vue b/src/components/importer/importer.vue index c4fe5b00..210823f5 100644 --- a/src/components/importer/importer.vue +++ b/src/components/importer/importer.vue @@ -15,7 +15,7 @@ /> <button v-else - class="btn btn-default" + class="btn button-default" @click="submit" > {{ submitButtonLabel }} diff --git a/src/components/link-preview/link-preview.js b/src/components/link-preview/link-preview.js index 444aafbe..add7c563 100644 --- a/src/components/link-preview/link-preview.js +++ b/src/components/link-preview/link-preview.js @@ -1,3 +1,5 @@ +import { mapGetters } from 'vuex' + const LinkPreview = { name: 'LinkPreview', props: [ @@ -15,11 +17,20 @@ const LinkPreview = { // 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' + return this.card.image && !this.censored && this.size !== 'hide' + }, + censored () { + return this.nsfw && this.hideNsfwConfig }, useDescription () { return this.card.description && /\S/.test(this.card.description) - } + }, + hideNsfwConfig () { + return this.mergedConfig.hideNsfw + }, + ...mapGetters([ + 'mergedConfig' + ]) }, created () { if (this.useImage) { diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue index 69171977..d3ca39b8 100644 --- a/src/components/link-preview/link-preview.vue +++ b/src/components/link-preview/link-preview.vue @@ -9,12 +9,17 @@ <div v-if="useImage && imageLoaded" class="card-image" - :class="{ 'small-image': size === 'small' }" > <img :src="card.image"> </div> <div class="card-content"> - <span class="card-host faint">{{ card.provider_name }}</span> + <span class="card-host faint"> + <span + v-if="censored" + class="nsfw-alert alert warning" + >{{ $t('status.nsfw') }}</span> + {{ card.provider_name }} + </span> <h4 class="card-title">{{ card.title }}</h4> <p v-if="useDescription" @@ -50,10 +55,6 @@ } } - .small-image { - width: 80px; - } - .card-content { max-height: 100%; margin: 0.5em; @@ -76,6 +77,10 @@ max-height: calc(1.2em * 3 - 1px); } + .nsfw-alert { + margin: 2em 0; + } + color: $fallback--text; color: var(--text, $fallback--text); border-style: solid; diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue index a1f77210..bfabb946 100644 --- a/src/components/login_form/login_form.vue +++ b/src/components/login_form/login_form.vue @@ -61,7 +61,7 @@ <button :disabled="loggingIn" type="submit" - class="btn btn-default" + class="btn button-default" > {{ $t('login.login') }} </button> diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue index 88251a26..e955aa72 100644 --- a/src/components/media_upload/media_upload.vue +++ b/src/components/media_upload/media_upload.vue @@ -1,33 +1,29 @@ <template> - <div + <label class="media-upload" :class="{ disabled: disabled }" + :title="$t('tool_tip.media_upload')" > - <label - class="label" - :title="$t('tool_tip.media_upload')" + <FAIcon + v-if="uploading" + class="progress-icon" + icon="circle-notch" + spin + /> + <FAIcon + v-if="!uploading" + class="new-icon" + icon="upload" + /> + <input + v-if="uploadReady" + :disabled="disabled" + type="file" + style="position: fixed; top: -100em" + multiple="true" + @change="change" > - <FAIcon - v-if="uploading" - class="progress-icon" - icon="circle-notch" - spin - /> - <FAIcon - v-if="!uploading" - class="new-icon" - icon="upload" - /> - <input - v-if="uploadReady" - :disabled="disabled" - type="file" - style="position: fixed; top: -100em" - multiple="true" - @change="change" - > - </label> - </div> + </label> </template> <script src="./media_upload.js" ></script> @@ -36,12 +32,6 @@ @import '../../_variables.scss'; .media-upload { - .label { - display: inline-block; - } - - .new-icon { - cursor: pointer; - } + cursor: pointer; } </style> diff --git a/src/components/mfa_form/recovery_form.vue b/src/components/mfa_form/recovery_form.vue index 78953649..0bf68e27 100644 --- a/src/components/mfa_form/recovery_form.vue +++ b/src/components/mfa_form/recovery_form.vue @@ -23,23 +23,23 @@ <div class="form-group"> <div class="login-bottom"> <div> - <a - href="#" + <button + class="button-unstyled -link" @click.prevent="requireTOTP" > {{ $t('login.enter_two_factor_code') }} - </a> + </button> <br> - <a - href="#" + <button + class="button-unstyled -link" @click.prevent="abortMFA" > {{ $t('general.cancel') }} - </a> + </button> </div> <button type="submit" - class="btn btn-default" + class="btn button-default" > {{ $t('general.verify') }} </button> diff --git a/src/components/mfa_form/totp_form.vue b/src/components/mfa_form/totp_form.vue index 9401cad5..79230148 100644 --- a/src/components/mfa_form/totp_form.vue +++ b/src/components/mfa_form/totp_form.vue @@ -25,23 +25,23 @@ <div class="form-group"> <div class="login-bottom"> <div> - <a - href="#" + <button + class="button-unstyled -link" @click.prevent="requireRecovery" > {{ $t('login.enter_recovery_code') }} - </a> + </button> <br> - <a - href="#" + <button + class="button-unstyled -link" @click.prevent="abortMFA" > {{ $t('general.cancel') }} - </a> + </button> </div> <button type="submit" - class="btn btn-default" + class="btn button-default" > {{ $t('general.verify') }} </button> diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index 5304a500..0f0ea457 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -9,9 +9,8 @@ @click="scrollToTop()" > <div class="item"> - <a - href="#" - class="mobile-nav-button" + <button + class="button-unstyled mobile-nav-button" @click.stop.prevent="toggleMobileSidebar()" > <FAIcon @@ -22,7 +21,7 @@ v-if="unreadChatCount" class="alert-dot" /> - </a> + </button> <router-link v-if="!hideSitename" class="site-name" @@ -33,10 +32,9 @@ </router-link> </div> <div class="item right"> - <a + <button v-if="currentUser" - class="mobile-nav-button" - href="#" + class="button-unstyled mobile-nav-button" @click.stop.prevent="openMobileNotifications()" > <FAIcon @@ -47,7 +45,7 @@ v-if="unseenNotificationsCount" class="alert-dot" /> - </a> + </button> </div> </nav> <div diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.vue b/src/components/mobile_post_status_button/mobile_post_status_button.vue index 50529878..767f8244 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.vue +++ b/src/components/mobile_post_status_button/mobile_post_status_button.vue @@ -1,7 +1,7 @@ <template> <div v-if="isLoggedIn"> <button - class="new-status-button" + class="button-default new-status-button" :class="{ 'hidden': isHidden }" @click="openPostForm" > diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue index 60fa6ceb..5c7b82ec 100644 --- a/src/components/moderation_tools/moderation_tools.vue +++ b/src/components/moderation_tools/moderation_tools.vue @@ -12,13 +12,13 @@ <div class="dropdown-menu"> <span v-if="user.is_local"> <button - class="dropdown-item" + class="button-default dropdown-item" @click="toggleRight("admin")" > {{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }} </button> <button - class="dropdown-item" + class="button-default dropdown-item" @click="toggleRight("moderator")" > {{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }} @@ -29,13 +29,13 @@ /> </span> <button - class="dropdown-item" + class="button-default dropdown-item" @click="toggleActivationStatus()" > {{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }} </button> <button - class="dropdown-item" + class="button-default dropdown-item" @click="deleteUserDialog(true)" > {{ $t('user_card.admin_menu.delete_account') }} @@ -47,7 +47,7 @@ /> <span v-if="hasTagPolicy"> <button - class="dropdown-item" + class="button-default dropdown-item" @click="toggleTag(tags.FORCE_NSFW)" > {{ $t('user_card.admin_menu.force_nsfw') }} @@ -57,7 +57,7 @@ /> </button> <button - class="dropdown-item" + class="button-default dropdown-item" @click="toggleTag(tags.STRIP_MEDIA)" > {{ $t('user_card.admin_menu.strip_media') }} @@ -67,7 +67,7 @@ /> </button> <button - class="dropdown-item" + class="button-default dropdown-item" @click="toggleTag(tags.FORCE_UNLISTED)" > {{ $t('user_card.admin_menu.force_unlisted') }} @@ -77,7 +77,7 @@ /> </button> <button - class="dropdown-item" + class="button-default dropdown-item" @click="toggleTag(tags.SANDBOX)" > {{ $t('user_card.admin_menu.sandbox') }} @@ -88,7 +88,7 @@ </button> <button v-if="user.is_local" - class="dropdown-item" + class="button-default dropdown-item" @click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)" > {{ $t('user_card.admin_menu.disable_remote_subscription') }} @@ -99,7 +99,7 @@ </button> <button v-if="user.is_local" - class="dropdown-item" + class="button-default dropdown-item" @click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)" > {{ $t('user_card.admin_menu.disable_any_subscription') }} @@ -110,7 +110,7 @@ </button> <button v-if="user.is_local" - class="dropdown-item" + class="button-default dropdown-item" @click="toggleTag(tags.QUARANTINE)" > {{ $t('user_card.admin_menu.quarantine') }} @@ -124,7 +124,7 @@ </div> <button slot="trigger" - class="btn btn-default btn-block" + class="btn button-default btn-block" :class="{ toggled }" > {{ $t('user_card.admin_menu.moderation') }} @@ -141,13 +141,13 @@ <p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p> <template slot="footer"> <button - class="btn btn-default" + class="btn button-default" @click="deleteUserDialog(false)" > {{ $t('general.cancel') }} </button> <button - class="btn btn-default danger" + class="btn button-default danger" @click="deleteUser()" > {{ $t('user_card.admin_menu.delete_user') }} diff --git a/src/components/mute_card/mute_card.vue b/src/components/mute_card/mute_card.vue index 9611fb82..ca33c6c5 100644 --- a/src/components/mute_card/mute_card.vue +++ b/src/components/mute_card/mute_card.vue @@ -3,7 +3,7 @@ <div class="mute-card-content-container"> <button v-if="muted" - class="btn btn-default" + class="btn button-default" :disabled="progress" @click="unmuteUser" > @@ -16,7 +16,7 @@ </button> <button v-else - class="btn btn-default" + class="btn button-default" :disabled="progress" @click="muteUser" > diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 2bbde108..f56aa977 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -14,14 +14,15 @@ {{ notification.from_profile.screen_name }} </router-link> </small> - <a - href="#" - class="unmute" + <button + class="button-unstyled unmute" @click.prevent="toggleMute" - ><FAIcon - class="fa-scale-110 fa-old-padding" - icon="eye-slash" - /></a> + > + <FAIcon + class="fa-scale-110 fa-old-padding" + icon="eye-slash" + /> + </button> </div> <div v-else @@ -132,14 +133,16 @@ /> </span> </div> - <a + <button v-if="needMute" - href="#" + class="button-unstyled" @click.prevent="toggleMute" - ><FAIcon - class="fa-scale-110 fa-old-padding" - icon="eye-slash" - /></a> + > + <FAIcon + class="fa-scale-110 fa-old-padding" + icon="eye-slash" + /> + </button> </span> <div v-if="notification.type === 'follow' || notification.type === 'follow_request'" diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 4b479e13..49258563 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -6,6 +6,7 @@ import { filteredNotificationsFromStore, unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils.js' +import FaviconService from '../../services/favicon_service/favicon_service.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' @@ -75,8 +76,10 @@ const Notifications = { watch: { unseenCountTitle (count) { if (count > 0) { + FaviconService.drawFaviconBadge() this.$store.dispatch('setPageTitle', `(${count})`) } else { + FaviconService.clearFaviconBadge() this.$store.dispatch('setPageTitle', '') } } diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index bd875cca..725d1ad4 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -15,16 +15,9 @@ class="badge badge-notification unseen-count" >{{ unseenCount }}</span> </div> - <div - v-if="error" - class="loadmore-error alert error" - @click.prevent - > - {{ $t('timeline.error_fetching') }} - </div> <button v-if="unseenCount" - class="read-button" + class="button-default read-button" @click.prevent="markAsSeen" > {{ $t('notifications.read') }} @@ -48,15 +41,15 @@ > {{ $t('notifications.no_more_notifications') }} </div> - <a + <button v-else-if="!loading" - href="#" + class="button-unstyled -link -fullwidth" @click.prevent="fetchOlderNotifications()" > <div class="new-status-notification text-center panel-footer"> {{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }} </div> - </a> + </button> <div v-else class="new-status-notification text-center panel-footer" diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue index 0deb9ccf..a931cb5a 100644 --- a/src/components/password_reset/password_reset.vue +++ b/src/components/password_reset/password_reset.vue @@ -51,7 +51,7 @@ <button :disabled="isPending" type="submit" - class="btn btn-default btn-block" + class="btn button-default btn-block" > {{ $t('general.submit') }} </button> diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue index 5f54b416..264a5f03 100644 --- a/src/components/poll/poll.vue +++ b/src/components/poll/poll.vue @@ -49,7 +49,7 @@ <div class="footer faint"> <button v-if="!showResults" - class="btn btn-default poll-vote-button" + class="btn button-default poll-vote-button" type="button" :disabled="isDisabled" @click="vote" diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js index 695f73b9..5e417fa0 100644 --- a/src/components/popover/popover.js +++ b/src/components/popover/popover.js @@ -21,7 +21,10 @@ const Popover = { // Replaces the classes you may want for the popover container. // Use 'popover-default' in addition to get the default popover // styles with your custom class. - popoverClass: String + popoverClass: String, + // If true, subtract padding when calculating position for the popover, + // use it when popover offset looks to be different on top vs bottom. + removePadding: Boolean }, data () { return { @@ -96,9 +99,15 @@ const Popover = { if (origin.y + content.offsetHeight > yBounds.max) usingTop = true if (origin.y - content.offsetHeight < yBounds.min) usingTop = false + let vPadding = 0 + if (this.removePadding && usingTop) { + const anchorStyle = getComputedStyle(anchorEl) + vPadding = parseFloat(anchorStyle.paddingTop) + parseFloat(anchorStyle.paddingBottom) + } + const yOffset = (this.offset && this.offset.y) || 0 const translateY = usingTop - ? -anchorEl.offsetHeight - yOffset - content.offsetHeight + ? -anchorEl.offsetHeight + vPadding - yOffset - content.offsetHeight : yOffset const xOffset = (this.offset && this.offset.x) || 0 diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue index 9b8680e5..020eab05 100644 --- a/src/components/popover/popover.vue +++ b/src/components/popover/popover.vue @@ -3,12 +3,13 @@ @mouseenter="onMouseenter" @mouseleave="onMouseleave" > - <div + <button ref="trigger" + class="button-unstyled -fullwidth popover-trigger-button" @click="onClick" > <slot name="trigger" /> - </div> + </button> <div v-if="!hidden" ref="content" @@ -30,6 +31,10 @@ <style lang="scss"> @import '../../_variables.scss'; +.popover-trigger-button { + display: block; +} + .popover { z-index: 8; position: absolute; diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 3ff4a3c8..4148381c 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -159,8 +159,7 @@ const PostStatusForm = { ...this.$store.state.instance.emoji, ...this.$store.state.instance.customEmoji ], - users: this.$store.state.users.users, - updateUsersList: (query) => this.$store.dispatch('searchUsers', { query }) + store: this.$store }) }, emojiSuggestor () { diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 42d3152b..ed830f57 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -24,12 +24,12 @@ tag="p" class="visibility-notice" > - <a - href="#" + <button + class="button-unstyled -link" @click="openProfileTab" > {{ $t('post_status.account_not_locked_warning_link') }} - </a> + </button> </i18n> <p v-if="!hideScopeNotice && newStatus.visibility === 'public'" @@ -243,38 +243,34 @@ @upload-failed="uploadFailed" @all-uploaded="finishedUploadingFiles" /> - <div - class="emoji-icon" + <button + class="emoji-icon button-unstyled" + :title="$t('emoji.add_emoji')" + @click="showEmojiPicker" > - <div - :title="$t('emoji.add_emoji')" - class="btn btn-default" - @click="showEmojiPicker" - > - <FAIcon icon="smile-beam" /> - </div> - </div> - <div + <FAIcon icon="smile-beam" /> + </button> + <button v-if="pollsAvailable" - class="poll-icon" + class="poll-icon button-unstyled" :class="{ selected: pollFormVisible }" :title="$t('polls.add_poll')" @click="togglePollForm" > <FAIcon icon="poll-h" /> - </div> + </button> </div> <button v-if="posting" disabled - class="btn btn-default" + class="btn button-default" > {{ $t('post_status.posting') }} </button> <button v-else-if="isOverLengthLimit" disabled - class="btn btn-default" + class="btn button-default" > {{ $t('general.submit') }} </button> @@ -282,7 +278,7 @@ <button v-else :disabled="uploadingFiles || disableSubmit" - class="btn btn-default" + class="btn button-default" @touchstart.stop.prevent="postStatus($event, newStatus)" @click.stop.prevent="postStatus($event, newStatus)" > diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js index de0df70c..5e7b7580 100644 --- a/src/components/react_button/react_button.js +++ b/src/components/react_button/react_button.js @@ -27,13 +27,21 @@ const ReactButton = { }, computed: { commonEmojis () { - return ['👍', '😠', '👀', '😂', '🔥'] + return [ + { displayText: 'thumbsup', replacement: '👍' }, + { displayText: 'angry', replacement: '😠' }, + { displayText: 'eyes', replacement: '👀' }, + { displayText: 'joy', replacement: '😂' }, + { displayText: 'fire', replacement: '🔥' } + ] }, emojis () { if (this.filterWord !== '') { const filterWordLowercase = this.filterWord.toLowerCase() let orderedEmojiList = [] for (const emoji of this.$store.state.instance.emoji) { + if (emoji.replacement === this.filterWord) return [emoji] + const indexOfFilterWord = emoji.displayText.toLowerCase().indexOf(filterWordLowercase) if (indexOfFilterWord > -1) { if (!Array.isArray(orderedEmojiList[indexOfFilterWord])) { diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue index e508a3e9..dde67d21 100644 --- a/src/components/react_button/react_button.vue +++ b/src/components/react_button/react_button.vue @@ -3,8 +3,8 @@ trigger="click" placement="top" :offset="{ y: 5 }" - class="react-button-popover" :bound-to="{ x: 'container' }" + remove-padding > <div slot="content" @@ -22,15 +22,17 @@ v-for="emoji in commonEmojis" :key="emoji" class="emoji-button" + :title="emoji.displayText" @click="addReaction($event, emoji, close)" > - {{ emoji }} + {{ emoji.replacement }} </span> <div class="reaction-picker-divider" /> <span v-for="(emoji, key) in emojis" :key="key" class="emoji-button" + :title="emoji.displayText" @click="addReaction($event, emoji.replacement, close)" > {{ emoji.replacement }} @@ -38,11 +40,14 @@ <div class="reaction-bottom-fader" /> </div> </div> - <span slot="trigger"> + <span + slot="trigger" + class="ReactButton" + :title="$t('tool_tip.add_reaction')" + > <FAIcon - class="fa-scale-110 fa-old-padding add-reaction-button" + class="fa-scale-110 fa-old-padding" :icon="['far', 'smile-beam']" - :title="$t('tool_tip.add_reaction')" /> </span> </Popover> @@ -102,10 +107,11 @@ } } -.add-reaction-button { - cursor: pointer; +.ReactButton { + padding: 10px; + margin: -10px; - &:hover { + &:hover .svg-inline--fa { color: $fallback--text; color: var(--text, $fallback--text); } diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index a83ca1e5..100df0d6 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -211,7 +211,7 @@ <button :disabled="isPending" type="submit" - class="btn btn-default" + class="btn button-default" > {{ $t('general.submit') }} </button> diff --git a/src/components/reply_button/reply_button.vue b/src/components/reply_button/reply_button.vue index a0ac8941..c17041da 100644 --- a/src/components/reply_button/reply_button.vue +++ b/src/components/reply_button/reply_button.vue @@ -1,20 +1,28 @@ <template> - <div> - <FAIcon + <div class="ReplyButton"> + <button v-if="loggedIn" - class="ReplyButton fa-scale-110 fa-old-padding -interactive" - icon="reply" - :title="$t('tool_tip.reply')" + class="button-unstyled interactive" :class="{'-active': replying}" - @click.prevent="$emit('toggle')" - /> - <FAIcon - v-else - icon="reply" - class="ReplyButton fa-scale-110 fa-old-padding" :title="$t('tool_tip.reply')" - /> - <span v-if="status.replies_count > 0"> + @click.prevent="$emit('toggle')" + > + <FAIcon + class="fa-scale-110 fa-old-padding" + icon="reply" + /> + </button> + <span v-else> + <FAIcon + icon="reply" + class="fa-scale-110 fa-old-padding" + :title="$t('tool_tip.reply')" + /> + </span> + <span + v-if="status.replies_count > 0" + class="action-counter" + > {{ status.replies_count }} </span> </div> @@ -26,14 +34,25 @@ @import '../../_variables.scss'; .ReplyButton { - &.-interactive { - cursor: pointer; + display: flex; - &:hover, - &.-active { + > :first-child { + padding: 10px; + margin: -10px -8px -10px -10px; + } + + .action-counter { + pointer-events: none; + user-select: none; + } + + .interactive { + &:hover .svg-inline--fa, + &.-active .svg-inline--fa { color: $fallback--cBlue; color: var(--cBlue, $fallback--cBlue); } } + } </style> diff --git a/src/components/retweet_button/retweet_button.js b/src/components/retweet_button/retweet_button.js index 5ee4179a..2103fd0b 100644 --- a/src/components/retweet_button/retweet_button.js +++ b/src/components/retweet_button/retweet_button.js @@ -24,11 +24,6 @@ const RetweetButton = { } }, computed: { - classes () { - return { - '-repeated': this.status.repeated - } - }, mergedConfig () { return this.$store.getters.mergedConfig } diff --git a/src/components/retweet_button/retweet_button.vue b/src/components/retweet_button/retweet_button.vue index b234f3d9..859ce499 100644 --- a/src/components/retweet_button/retweet_button.vue +++ b/src/components/retweet_button/retweet_button.vue @@ -1,33 +1,38 @@ <template> - <div v-if="loggedIn"> - <template v-if="visibility !== 'private' && visibility !== 'direct'"> + <div class="RetweetButton"> + <button + v-if="visibility !== 'private' && visibility !== 'direct' && loggedIn" + class="button-unstyled interactive" + :class="status.repeated && '-repeated'" + :title="$t('tool_tip.repeat')" + @click.prevent="retweet()" + > <FAIcon - :class="classes" - class="RetweetButton fa-scale-110 fa-old-padding -interactive" + class="fa-scale-110 fa-old-padding" icon="retweet" :spin="animated" - :title="$t('tool_tip.repeat')" - @click.prevent="retweet()" /> - <span v-if="!mergedConfig.hidePostStats && status.repeat_num > 0">{{ status.repeat_num }}</span> - </template> - <template v-else> + </button> + <span v-else-if="loggedIn"> <FAIcon - :class="classes" - class="RetweetButton fa-scale-110 fa-old-padding" + class="fa-scale-110 fa-old-padding" icon="lock" :title="$t('timeline.no_retweet_hint')" /> - </template> - </div> - <div v-else-if="!loggedIn"> - <FAIcon - :class="classes" - class="fa-scale-110 fa-old-padding" - icon="retweet" - :title="$t('tool_tip.repeat')" - /> - <span v-if="!mergedConfig.hidePostStats && status.repeat_num > 0">{{ status.repeat_num }}</span> + </span> + <span v-else> + <FAIcon + class="fa-scale-110 fa-old-padding" + icon="retweet" + :title="$t('tool_tip.repeat')" + /> + </span> + <span + v-if="!mergedConfig.hidePostStats && status.repeat_num > 0" + class="no-event" + > + {{ status.repeat_num }} + </span> </div> </template> @@ -37,19 +42,28 @@ @import '../../_variables.scss'; .RetweetButton { - &.-interactive { - cursor: pointer; - animation-duration: 0.6s; + display: flex; - &:hover { + > :first-child { + padding: 10px; + margin: -10px -8px -10px -10px; + } + + .action-counter { + pointer-events: none; + user-select: none; + } + + .interactive { + .svg-inline--fa { + animation-duration: 0.6s; + } + + &:hover .svg-inline--fa, + &.-repeated .svg-inline--fa { color: $fallback--cGreen; color: var(--cGreen, $fallback--cGreen); } } - - &.-repeated { - color: $fallback--cGreen; - color: var(--cGreen, $fallback--cGreen); - } } </style> diff --git a/src/components/scope_selector/scope_selector.vue b/src/components/scope_selector/scope_selector.vue index a22a4fda..66ac612e 100644 --- a/src/components/scope_selector/scope_selector.vue +++ b/src/components/scope_selector/scope_selector.vue @@ -3,9 +3,9 @@ v-if="!showNothing" class="ScopeSelector" > - <span + <button v-if="showDirect" - class="scope" + class="button-unstyled scope" :class="css.direct" :title="$t('post_status.scope.direct')" @click="changeVis('direct')" @@ -14,10 +14,10 @@ icon="envelope" class="fa-scale-110 fa-old-padding" /> - </span> - <span + </button> + <button v-if="showPrivate" - class="scope" + class="button-unstyled scope" :class="css.private" :title="$t('post_status.scope.private')" @click="changeVis('private')" @@ -26,10 +26,10 @@ icon="lock" class="fa-scale-110 fa-old-padding" /> - </span> - <span + </button> + <button v-if="showUnlisted" - class="scope" + class="button-unstyled scope" :class="css.unlisted" :title="$t('post_status.scope.unlisted')" @click="changeVis('unlisted')" @@ -38,10 +38,10 @@ icon="lock-open" class="fa-scale-110 fa-old-padding" /> - </span> - <span + </button> + <button v-if="showPublic" - class="scope" + class="button-unstyled scope" :class="css.public" :title="$t('post_status.scope.public')" @click="changeVis('public')" @@ -50,7 +50,7 @@ icon="globe" class="fa-scale-110 fa-old-padding" /> - </span> + </button> </div> </template> diff --git a/src/components/search/search.vue b/src/components/search/search.vue index 665390f9..a6503c9f 100644 --- a/src/components/search/search.vue +++ b/src/components/search/search.vue @@ -14,7 +14,7 @@ @keyup.enter="newQuery(searchTerm)" > <button - class="btn search-button" + class="btn button-default search-button" @click="newQuery(searchTerm)" > <FAIcon icon="search" /> diff --git a/src/components/search_bar/search_bar.vue b/src/components/search_bar/search_bar.vue index 89a601c8..6cf9179e 100644 --- a/src/components/search_bar/search_bar.vue +++ b/src/components/search_bar/search_bar.vue @@ -3,17 +3,18 @@ class="SearchBar" :class="{ '-expanded': !hidden }" > - <a + <button v-if="hidden" - href="#" - class="nav-icon" + class="button-unstyled nav-icon" :title="$t('nav.search')" - ><FAIcon - fixed-width - class="fa-scale-110 fa-old-padding" - icon="search" @click.prevent.stop="toggleHidden" - /></a> + > + <FAIcon + fixed-width + class="fa-scale-110 fa-old-padding" + icon="search" + /> + </button> <template v-else> <input id="search-bar-input" @@ -25,7 +26,7 @@ @keyup.enter="find(searchTerm)" > <button - class="btn search-button" + class="button-default search-button" @click="find(searchTerm)" > <FAIcon @@ -33,14 +34,16 @@ icon="search" /> </button> - <span> + <button + class="button-unstyled cancel-search" + @click.prevent.stop="toggleHidden" + > <FAIcon fixed-width icon="times" class="cancel-icon fa-scale-110 fa-old-padding" - @click.prevent.stop="toggleHidden" /> - </span> + </button> </template> </div> </template> @@ -69,8 +72,11 @@ flex: 1 0 auto; } + .cancel-search { + height: 50px; + } + .cancel-icon { - cursor: pointer; color: $fallback--text; color: var(--btnTopBarText, $fallback--text); } diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue index 6bc64ed0..552ca41f 100644 --- a/src/components/settings_modal/settings_modal.vue +++ b/src/components/settings_modal/settings_modal.vue @@ -30,13 +30,13 @@ </template> </transition> <button - class="btn" + class="btn button-default" @click="peekModal" > {{ $t('general.peek') }} </button> <button - class="btn" + class="btn button-default" @click="closeModal" > {{ $t('general.close') }} diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue index 5a1cf2c0..63d36bf9 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue @@ -27,7 +27,7 @@ <div class="bulk-actions"> <ProgressButton v-if="selected.length > 0" - class="btn btn-default bulk-action-button" + class="btn button-default bulk-action-button" :click="() => blockUsers(selected)" > {{ $t('user_card.block') }} @@ -37,7 +37,7 @@ </ProgressButton> <ProgressButton v-if="selected.length > 0" - class="btn btn-default" + class="btn button-default" :click="() => unblockUsers(selected)" > {{ $t('user_card.unblock') }} @@ -85,7 +85,7 @@ <div class="bulk-actions"> <ProgressButton v-if="selected.length > 0" - class="btn btn-default" + class="btn button-default" :click="() => muteUsers(selected)" > {{ $t('user_card.mute') }} @@ -95,7 +95,7 @@ </ProgressButton> <ProgressButton v-if="selected.length > 0" - class="btn btn-default" + class="btn button-default" :click="() => unmuteUsers(selected)" > {{ $t('user_card.unmute') }} @@ -141,7 +141,7 @@ <div class="bulk-actions"> <ProgressButton v-if="selected.length > 0" - class="btn btn-default" + class="btn button-default" :click="() => unmuteDomains(selected)" > {{ $t('domain_mute_card.unmute') }} diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue index 86eed3f5..8f8fe48e 100644 --- a/src/components/settings_modal/tabs/notifications_tab.vue +++ b/src/components/settings_modal/tabs/notifications_tab.vue @@ -21,7 +21,7 @@ <p>{{ $t('settings.notification_mutes') }}</p> <p>{{ $t('settings.notification_blocks') }}</p> <button - class="btn btn-default" + class="btn button-default" @click="updateNotificationSettings" > {{ $t('general.submit') }} diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index a3e4feaf..a4fed629 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -68,8 +68,7 @@ const ProfileTab = { ...this.$store.state.instance.emoji, ...this.$store.state.instance.customEmoji ], - users: this.$store.state.users.users, - updateUsersList: (query) => this.$store.dispatch('searchUsers', { query }) + store: this.$store }) }, emojiSuggestor () { @@ -79,10 +78,7 @@ const ProfileTab = { ] }) }, userSuggestor () { - return suggestor({ - users: this.$store.state.users.users, - updateUsersList: (query) => this.$store.dispatch('searchUsers', { query }) - }) + return suggestor({ store: this.$store }) }, fieldsLimits () { return this.$store.state.instance.fieldsLimits diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index d62bc392..50d3ee63 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -150,7 +150,7 @@ </p> <button :disabled="newName && newName.length === 0" - class="btn btn-default" + class="btn button-default" @click="updateProfile" > {{ $t('general.submit') }} @@ -179,7 +179,7 @@ <button v-show="pickAvatarBtnVisible" id="pick-avatar" - class="btn" + class="button-default btn" type="button" > {{ $t('settings.upload_a_photo') }} @@ -224,7 +224,7 @@ /> <button v-else-if="bannerPreview" - class="btn btn-default" + class="btn button-default" @click="submitBanner(banner)" > {{ $t('general.submit') }} @@ -274,7 +274,7 @@ /> <button v-else-if="backgroundPreview" - class="btn btn-default" + class="btn button-default" @click="submitBackground(background)" > {{ $t('general.submit') }} diff --git a/src/components/settings_modal/tabs/security_tab/confirm.vue b/src/components/settings_modal/tabs/security_tab/confirm.vue index 69b3811b..38c2a610 100644 --- a/src/components/settings_modal/tabs/security_tab/confirm.vue +++ b/src/components/settings_modal/tabs/security_tab/confirm.vue @@ -2,14 +2,14 @@ <div> <slot /> <button - class="btn btn-default" + class="btn button-default" :disabled="disabled" @click="confirm" > {{ $t('general.confirm') }} </button> <button - class="btn btn-default" + class="btn button-default" :disabled="disabled" @click="cancel" > diff --git a/src/components/settings_modal/tabs/security_tab/mfa.vue b/src/components/settings_modal/tabs/security_tab/mfa.vue index 7aca3c8d..455d17b6 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.vue +++ b/src/components/settings_modal/tabs/security_tab/mfa.vue @@ -29,7 +29,7 @@ /> <button v-if="!confirmNewBackupCodes" - class="btn btn-default" + class="btn button-default" @click="getBackupCodes" > {{ $t('settings.mfa.generate_new_recovery_codes') }} @@ -61,7 +61,7 @@ <button v-if="canSetupOTP" - class="btn btn-default" + class="btn button-default" @click="cancelSetup" > {{ $t('general.cancel') }} @@ -69,7 +69,7 @@ <button v-if="canSetupOTP" - class="btn btn-default" + class="btn button-default" @click="setupOTP" > {{ $t('settings.mfa.setup_otp') }} @@ -108,13 +108,13 @@ > <div class="confirm-otp-actions"> <button - class="btn btn-default" + class="btn button-default" @click="doConfirmOTP" > {{ $t('settings.mfa.confirm_and_enable') }} </button> <button - class="btn btn-default" + class="btn button-default" @click="cancelSetup" > {{ $t('general.cancel') }} diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.vue b/src/components/settings_modal/tabs/security_tab/mfa_totp.vue index c6f2cc7b..8e767bd0 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.vue +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.vue @@ -4,7 +4,7 @@ <strong>{{ $t('settings.mfa.otp') }}</strong> <button v-if="!isActivated" - class="btn btn-default" + class="btn button-default" @click="doActivate" > {{ $t('general.enable') }} @@ -12,7 +12,7 @@ <button v-if="isActivated" - class="btn btn-default" + class="btn button-default" :disabled="deactivate" @click="doDeactivate" > diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue index 3d32d73d..56bea1f4 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.vue +++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue @@ -19,7 +19,7 @@ > </div> <button - class="btn btn-default" + class="btn button-default" @click="changeEmail" > {{ $t('general.submit') }} @@ -57,7 +57,7 @@ > </div> <button - class="btn btn-default" + class="btn button-default" @click="changePassword" > {{ $t('general.submit') }} @@ -92,7 +92,7 @@ <td>{{ oauthToken.validUntil }}</td> <td class="actions"> <button - class="btn btn-default" + class="btn button-default" @click="revokeToken(oauthToken.id)" > {{ $t('settings.revoke_token') }} @@ -116,7 +116,7 @@ type="password" > <button - class="btn btn-default" + class="btn button-default" @click="deleteAccount" > {{ $t('settings.delete_account') }} @@ -130,7 +130,7 @@ </p> <button v-if="!deletingAccount" - class="btn btn-default" + class="btn button-default" @click="confirmDelete" > {{ $t('general.submit') }} diff --git a/src/components/settings_modal/tabs/theme_tab/preview.vue b/src/components/settings_modal/tabs/theme_tab/preview.vue index 02fea0b6..7ac7b9d3 100644 --- a/src/components/settings_modal/tabs/theme_tab/preview.vue +++ b/src/components/settings_modal/tabs/theme_tab/preview.vue @@ -15,7 +15,7 @@ <span class="alert error"> {{ $t('settings.style.preview.error') }} </span> - <button class="btn"> + <button class="btn button-default"> {{ $t('settings.style.preview.button') }} </button> </div> @@ -102,7 +102,7 @@ > <label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label> </span> - <button class="btn"> + <button class="btn button-default"> {{ $t('settings.style.preview.button') }} </button> </div> diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue index 280e1955..4ab793d6 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue @@ -12,13 +12,13 @@ <div class="buttons"> <template v-if="themeWarning.type === 'snapshot_source_mismatch'"> <button - class="btn" + class="btn button-default" @click="forceLoad" > {{ $t('settings.style.switcher.use_source') }} </button> <button - class="btn" + class="btn button-default" @click="forceSnapshot" > {{ $t('settings.style.switcher.use_snapshot') }} @@ -26,7 +26,7 @@ </template> <template v-else-if="themeWarning.noActionsPossible"> <button - class="btn" + class="btn button-default" @click="dismissWarning" > {{ $t('general.dismiss') }} @@ -34,13 +34,13 @@ </template> <template v-else> <button - class="btn" + class="btn button-default" @click="forceLoad" > {{ $t('settings.style.switcher.load_theme') }} </button> <button - class="btn" + class="btn button-default" @click="dismissWarning" > {{ $t('settings.style.switcher.keep_as_is') }} @@ -131,13 +131,13 @@ <p>{{ $t('settings.theme_help') }}</p> <div class="tab-header-buttons"> <button - class="btn" + class="btn button-default" @click="clearOpacity" > {{ $t('settings.style.switcher.clear_opacity') }} </button> <button - class="btn" + class="btn button-default" @click="clearV1" > {{ $t('settings.style.switcher.clear_all') }} @@ -238,13 +238,13 @@ <div class="tab-header"> <p>{{ $t('settings.theme_help') }}</p> <button - class="btn" + class="btn button-default" @click="clearOpacity" > {{ $t('settings.style.switcher.clear_opacity') }} </button> <button - class="btn" + class="btn button-default" @click="clearV1" > {{ $t('settings.style.switcher.clear_all') }} @@ -806,7 +806,7 @@ <div class="tab-header"> <p>{{ $t('settings.radii_help') }}</p> <button - class="btn" + class="btn button-default" @click="clearRoundness" > {{ $t('settings.style.switcher.clear_all') }} @@ -936,7 +936,7 @@ /> </div> <button - class="btn" + class="btn button-default" @click="clearShadows" > {{ $t('settings.style.switcher.clear_all') }} @@ -980,7 +980,7 @@ <div class="tab-header"> <p>{{ $t('settings.style.fonts.help') }}</p> <button - class="btn" + class="btn button-default" @click="clearFonts" > {{ $t('settings.style.switcher.clear_all') }} @@ -1017,14 +1017,14 @@ <div class="apply-container"> <button - class="btn submit" + class="btn button-default submit" :disabled="!themeValid" @click="setCustomTheme" > {{ $t('general.apply') }} </button> <button - class="btn" + class="btn button-default" @click="clearAll" > {{ $t('settings.style.switcher.reset') }} diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue index 78f0e544..37d491f0 100644 --- a/src/components/shadow_control/shadow_control.vue +++ b/src/components/shadow_control/shadow_control.vue @@ -84,7 +84,7 @@ /> </label> <button - class="btn btn-default" + class="btn button-default" :disabled="!ready || !present" @click="del" > @@ -94,7 +94,7 @@ /> </button> <button - class="btn btn-default" + class="btn button-default" :disabled="!moveUpValid" @click="moveUp" > @@ -104,7 +104,7 @@ /> </button> <button - class="btn btn-default" + class="btn button-default" :disabled="!moveDnValid" @click="moveDn" > @@ -114,7 +114,7 @@ /> </button> <button - class="btn btn-default" + class="btn button-default" :disabled="usingFallback" @click="add" > diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index 28c888fe..695ae03b 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -144,8 +144,8 @@ </router-link> </li> <li @click="toggleDrawer"> - <a - href="#" + <button + class="button-unstyled -link -fullwidth" @click="openSettingsModal" > <FAIcon @@ -153,7 +153,7 @@ class="fa-scale-110 fa-old-padding" icon="cog" /> {{ $t("settings.settings") }} - </a> + </button> </li> <li @click="toggleDrawer"> <router-link :to="{ name: 'about'}"> @@ -183,8 +183,8 @@ v-if="currentUser" @click="toggleDrawer" > - <a - href="#" + <button + class="button-unstyled -link -fullwidth" @click="doLogout" > <FAIcon @@ -192,7 +192,7 @@ class="fa-scale-110 fa-old-padding" icon="sign-out-alt" /> {{ $t("login.logout") }} - </a> + </button> </li> </ul> </div> @@ -331,7 +331,7 @@ .side-drawer li { padding: 0; - a { + a, button { box-sizing: border-box; display: block; height: 3em; diff --git a/src/components/status/status.scss b/src/components/status/status.scss index 0a94de32..70c6d03d 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -29,6 +29,8 @@ $status-margin: 0.75em; &.-conversation { border-left-width: 4px; border-left-style: solid; + border-left-color: $fallback--cRed; + border-left-color: var(--cRed, $fallback--cRed); } .gravestone { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 21412faa..896635ee 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -47,16 +47,15 @@ > {{ muteWordHits.join(', ') }} </small> - <a - href="#" - class="unmute fa-scale-110 fa-old-padding" + <button + class="unmute button-unstyled" @click.prevent="toggleMute" > <FAIcon icon="eye-slash" class="fa-scale-110 fa-old-padding" /> - </a> + </button> </div> </template> <template v-else> @@ -201,9 +200,9 @@ icon="external-link-square-alt" /> </a> - <a + <button v-if="expandable && !isPreview" - href="#" + class="button-unstyled" title="Expand" @click.prevent="toggleExpanded" > @@ -211,17 +210,17 @@ class="fa-scale-110 fa-old-padding" icon="plus-square" /> - </a> - <a + </button> + <button v-if="unmuted" - href="#" + class="button-unstyled" @click.prevent="toggleMute" > <FAIcon icon="eye-slash" class="fa-scale-110 fa-old-padding" /> - </a> + </button> </span> </div> @@ -237,9 +236,8 @@ style="min-width: 0" :class="{ '-strikethrough': !status.parent_visible }" > - <a - class="reply-to" - href="#" + <button + class="button-unstyled reply-to" :aria-label="$t('tool_tip.reply')" @click.prevent="gotoOriginal(status.in_reply_to_status_id)" > @@ -253,7 +251,7 @@ > {{ $t('status.reply_to') }} </span> - </a> + </button> </StatusPopover> <span @@ -286,11 +284,12 @@ :key="reply.id" :status-id="reply.id" > - <a - href="#" - class="reply-link" + <button + class="button-unstyled -link reply-link" @click.prevent="gotoOriginal(reply.id)" - >{{ reply.name }}</a> + > + {{ reply.name }} + </button> </StatusPopover> </div> </div> diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue index 321cd477..90bfaf40 100644 --- a/src/components/status_content/status_content.vue +++ b/src/components/status_content/status_content.vue @@ -12,35 +12,34 @@ @click.prevent="linkClicked" v-html="status.summary_html" /> - <a + <button v-if="longSubject && showingLongSubject" - href="#" - class="tall-subject-hider" + class="button-unstyled -link tall-subject-hider" @click.prevent="showingLongSubject=false" - >{{ $t("status.hide_full_subject") }}</a> - <a + > + {{ $t("status.hide_full_subject") }} + </button> + <button v-else-if="longSubject" - class="tall-subject-hider" + class="button-unstyled -link tall-subject-hider" :class="{ 'tall-subject-hider_focused': focused }" - href="#" @click.prevent="showingLongSubject=true" > {{ $t("status.show_full_subject") }} - </a> + </button> </div> <div :class="{'tall-status': hideTallStatus}" class="status-content-wrapper" > - <a + <button v-if="hideTallStatus" - class="tall-status-hider" + class="button-unstyled -link tall-status-hider" :class="{ 'tall-status-hider_focused': focused }" - href="#" @click.prevent="toggleShowMore" > {{ $t("general.show_more") }} - </a> + </button> <div v-if="!hideSubjectStatus" :class="{ 'single-line': singleLine }" @@ -48,10 +47,9 @@ @click.prevent="linkClicked" v-html="postBodyHtml" /> - <a + <button v-if="hideSubjectStatus" - href="#" - class="cw-status-hider" + class="button-unstyled -link cw-status-hider" @click.prevent="toggleShowMore" > {{ $t("status.show_content") }} @@ -79,15 +77,14 @@ v-if="status.card" icon="link" /> - </a> - <a + </button> + <button v-if="showingMore && !fullContent" - href="#" - class="status-unhider" + class="button-unstyled -link status-unhider" @click.prevent="toggleShowMore" > {{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }} - </a> + </button> </div> <div v-if="status.poll && status.poll.options && !hideSubjectStatus"> diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js index 6e6e8193..76e7ef03 100644 --- a/src/components/tab_switcher/tab_switcher.js +++ b/src/components/tab_switcher/tab_switcher.js @@ -81,7 +81,7 @@ export default Vue.component('tab-switcher', { const tabs = this.$slots.default .map((slot, index) => { if (!slot.tag) return - const classesTab = ['tab'] + const classesTab = ['tab', 'button-default'] const classesWrapper = ['tab-wrapper'] if (this.activeIndex === index) { classesTab.push('active') diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index cba46daf..665d195e 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -50,17 +50,10 @@ const Timeline = { TimelineMenu }, computed: { - timelineError () { - return this.$store.state.statuses.error - }, - errorData () { - return this.$store.state.statuses.errorData - }, newStatusCount () { return this.timeline.newStatusCount }, showLoadButton () { - if (this.timelineError || this.errorData) return false return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0 }, loadButtonString () { @@ -171,11 +164,12 @@ const Timeline = { userId: this.userId, tag: this.tag }).then(({ statuses }) => { - store.commit('setLoading', { timeline: this.timelineName, value: false }) if (statuses && statuses.length === 0) { this.bottomedOut = true } - }) + }).finally(() => + store.commit('setLoading', { timeline: this.timelineName, value: false }) + ) }, 1000, this), determineVisibleStatuses () { if (!this.$refs.timeline) return diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 04859852..0326342b 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -2,23 +2,9 @@ <div :class="[classes.root, 'Timeline']"> <div :class="classes.header"> <TimelineMenu v-if="!embedded" /> - <div - v-if="timelineError" - class="loadmore-error alert error" - @click.prevent - > - {{ $t('timeline.error_fetching') }} - </div> - <div - v-else-if="errorData" - class="loadmore-error alert error" - @click.prevent - > - {{ errorData.statusText }} - </div> <button - v-else-if="showLoadButton" - class="loadmore-button" + v-if="showLoadButton" + class="button-default loadmore-button" @click.prevent="showNewStatuses" > {{ loadButtonString }} @@ -75,19 +61,15 @@ > {{ $t('timeline.no_more_statuses') }} </div> - <a - v-else-if="!timeline.loading && !errorData" - href="#" + <button + v-else-if="!timeline.loading" + class="button-unstyled -link -fullwidth" @click.prevent="fetchOlderStatuses()" > - <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div> - </a> - <a - v-else-if="errorData" - href="#" - > - <div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div> - </a> + <div class="new-status-notification text-center panel-footer"> + {{ $t('timeline.load_older') }} + </div> + </button> <div v-else class="new-status-notification text-center panel-footer" diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js index 4ccd52b4..ef8a5813 100644 --- a/src/components/timeline_menu/timeline_menu.js +++ b/src/components/timeline_menu/timeline_menu.js @@ -19,7 +19,7 @@ library.add( faChevronDown ) -// Route -> i18n key mapping, exported andnot in the computed +// Route -> i18n key mapping, exported and not in the computed // because nav panel benefits from the same information. export const timelineNames = () => { return { @@ -27,8 +27,7 @@ export const timelineNames = () => { 'bookmarks': 'nav.bookmarks', 'dms': 'nav.dms', 'public-timeline': 'nav.public_tl', - 'public-external-timeline': 'nav.twkn', - 'tag-timeline': 'tag' + 'public-external-timeline': 'nav.twkn' } } diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index f916af9d..16dd5249 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -162,7 +162,7 @@ <template v-if="relationship.following"> <ProgressButton v-if="!relationship.subscribing" - class="btn btn-default" + class="btn button-default" :click="subscribeUser" :title="$t('user_card.subscribe')" > @@ -170,7 +170,7 @@ </ProgressButton> <ProgressButton v-else - class="btn btn-default toggled" + class="btn button-default toggled" :click="unsubscribeUser" :title="$t('user_card.unsubscribe')" > @@ -192,14 +192,14 @@ <div> <button v-if="relationship.muting" - class="btn btn-default btn-block toggled" + class="btn button-default btn-block toggled" @click="unmuteUser" > {{ $t('user_card.muted') }} </button> <button v-else - class="btn btn-default btn-block" + class="btn button-default btn-block" @click="muteUser" > {{ $t('user_card.mute') }} @@ -207,7 +207,7 @@ </div> <div> <button - class="btn btn-default btn-block" + class="btn button-default btn-block" @click="mentionUser" > {{ $t('user_card.mention') }} diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue index 2a8d8d48..fb43094f 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.vue +++ b/src/components/user_reporting_modal/user_reporting_modal.vue @@ -29,7 +29,7 @@ </div> <div> <button - class="btn btn-default" + class="btn button-default" :disabled="processing" @click="reportUser" > diff --git a/src/components/video_attachment/video_attachment.vue b/src/components/video_attachment/video_attachment.vue index a4bf01e8..8a3ea1e3 100644 --- a/src/components/video_attachment/video_attachment.vue +++ b/src/components/video_attachment/video_attachment.vue @@ -1,6 +1,7 @@ <template> <video class="video" + preload="metadata" :src="attachment.url" :loop="loopVideo" :controls="controls" diff --git a/src/hocs/with_load_more/with_load_more.js b/src/hocs/with_load_more/with_load_more.js index afb51a0f..7df9dbb2 100644 --- a/src/hocs/with_load_more/with_load_more.js +++ b/src/hocs/with_load_more/with_load_more.js @@ -91,7 +91,11 @@ const withLoadMore = ({ {children} </WrappedComponent> <div class="with-load-more-footer"> - {this.error && <a onClick={this.fetchEntries} class="alert error">{this.$t('general.generic_error')}</a>} + {this.error && + <button onClick={this.fetchEntries} class="button-unstyled -link -fullwidth alert error"> + {this.$t('general.generic_error')} + </button> + } {!this.error && this.loading && <FAIcon spin icon="circle-notch"/>} {!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries}>{this.$t('general.more')}</a>} </div> diff --git a/src/i18n/en.json b/src/i18n/en.json index d3d57562..ef23efd6 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -130,6 +130,7 @@ }, "notifications": { "broken_favorite": "Unknown status, searching for it…", + "error": "Error fetching notifications: {0}", "favorited_you": "favorited your status", "followed_you": "followed you", "follow_request": "wants to follow you", @@ -376,7 +377,7 @@ "hide_followers_count_description": "Don't show follower count", "show_admin_badge": "Show Admin badge in my profile", "show_moderator_badge": "Show Moderator badge in my profile", - "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", + "nsfw_clickthrough": "Enable clickthrough attachment and link preview image hiding for NSFW statuses", "oauth_tokens": "OAuth tokens", "token": "Token", "refresh_token": "Refresh Token", @@ -634,7 +635,7 @@ "timeline": { "collapse": "Collapse", "conversation": "Conversation", - "error_fetching": "Error fetching updates", + "error": "Error fetching timeline: {0}", "load_older": "Load older statuses", "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated", "repeated": "repeated", @@ -666,7 +667,8 @@ "hide_full_subject": "Hide full subject", "show_content": "Show content", "hide_content": "Hide content", - "status_deleted": "This post was deleted" + "status_deleted": "This post was deleted", + "nsfw": "NSFW" }, "user_card": { "approve": "Approve", diff --git a/src/i18n/eo.json b/src/i18n/eo.json index 1247d50d..b0a15cfe 100644 --- a/src/i18n/eo.json +++ b/src/i18n/eo.json @@ -134,14 +134,14 @@ "registration": { "bio": "Priskribo", "email": "Retpoŝtadreso", - "fullname": "Vidiga nomo", + "fullname": "Prezenta nomo", "password_confirm": "Konfirmo de pasvorto", "registration": "Registriĝo", "token": "Invita ĵetono", "captcha": "TESTO DE HOMECO", "new_captcha": "Klaku la bildon por akiri novan teston", "username_placeholder": "ekz. lain", - "fullname_placeholder": "ekz. Lain Iwakura", + "fullname_placeholder": "ekz. Lain Ivakura", "bio_placeholder": "ekz.\nSaluton, mi estas Lain.\nMi estas animea knabino vivanta en Japanujo. Eble vi konas min pro la retejo « Wired ».", "validations": { "username_required": "ne povas resti malplena", @@ -164,7 +164,7 @@ "blocks_tab": "Blokitoj", "btnRadius": "Butonoj", "cBlue": "Blua (respondi, aboni)", - "cGreen": "Verda (kunhavigi)", + "cGreen": "Verda (diskonigi)", "cOrange": "Oranĝa (ŝati)", "cRed": "Ruĝa (nuligi)", "change_password": "Ŝanĝi pasvorton", @@ -207,8 +207,8 @@ "import_theme": "Enlegi antaŭagordojn", "inputRadius": "Enigaj kampoj", "checkboxRadius": "Markbutonoj", - "instance_default": "(implicita: {value})", - "instance_default_simple": "(implicita)", + "instance_default": "(originale: {value})", + "instance_default_simple": "(originale)", "interface": "Fasado", "interfaceLanguage": "Lingvo de fasado", "invalid_theme_imported": "La elektita dosiero ne estas subtenata haŭto de Pleromo. Neniuj ŝanĝoj al via haŭto okazis.", @@ -219,7 +219,7 @@ "loop_video_silent_only": "Ripetadi nur filmojn sen sono (ekz. la «GIF-ojn» de Mastodon)", "mutes_tab": "Silentigoj", "play_videos_in_modal": "Ludi filmojn en ŝpruca kadro", - "use_contain_fit": "Ne tondi la kunsendaĵon en bildetoj", + "use_contain_fit": "Ne pritondi bildetojn de kunsendaĵoj", "name": "Nomo", "name_bio": "Nomo kaj priskribo", "new_password": "Nova pasvorto", @@ -265,7 +265,7 @@ "subject_line_email": "Kiel retpoŝto: «re: temo»", "subject_line_mastodon": "Kiel Mastodon: kopii senŝanĝe", "subject_line_noop": "Ne kopii", - "post_status_content_type": "Afiŝi specon de la enhavo de la stato", + "post_status_content_type": "Speco de enhavo de afiŝo", "stop_gifs": "Movi GIF-bildojn dum ŝvebo de muso", "streaming": "Ŝalti memagan fluigon de novaj afiŝoj kiam vi vidas la supron de la paĝo", "text": "Teksto", @@ -379,7 +379,7 @@ "hint": "Por ombroj vi ankaŭ povas uzi --variable kiel koloran valoron, por uzi variantojn de CSS3. Bonvolu rimarki, ke tiuokaze agordoj de maltravidebleco ne funkcios.", "filter_hint": { "always_drop_shadow": "Averto: ĉi tiu ombro ĉiam uzas {0} kiam la foliumilo tion subtenas.", - "drop_shadow_syntax": "{0} ne subtenas parametron {1} kaj ŝlosilvorton {2}.", + "drop_shadow_syntax": "{0} ne subtenas parametron {1} kaj ĉefvorton {2}.", "avatar_inset": "Bonvolu rimarki, ke agordi ambaŭ internajn kaj eksterajn ombrojn por profilbildoj povas redoni neatenditajn rezultojn ĉe profilbildoj travideblaj.", "spread_zero": "Ombroj kun vastigo > 0 aperos kvazaŭ ĝi estus fakte nulo", "inset_classic": "Internaj ombroj uzos {0}" @@ -394,7 +394,7 @@ "button": "Butono", "buttonHover": "Butono (je ŝvebo)", "buttonPressed": "Butono (premita)", - "buttonPressedHover": "Butono (premita kaj je ŝvebo)", + "buttonPressedHover": "Butono (je premo kaj ŝvebo)", "input": "Eniga kampo" }, "hintV3": "Kolorojn de ombroj vi ankaŭ povas skribi per la sistemo {0}." @@ -683,7 +683,7 @@ "replace": "Anstataŭigi", "reject": "Rifuzi", "ftl_removal": "Forigo el la historio de «La tuta konata reto»", - "keyword_policies": "Politiko pri ŝlosilvortoj" + "keyword_policies": "Politiko pri ĉefvortoj" }, "federation": "Federado", "mrf_policies_desc": "Politikoj de Mesaĝa ŝanĝilaro (MRF) efikas sur federa konduto de la nodo. La sekvaj politikoj estas ŝaltitaj:" @@ -739,8 +739,8 @@ "week_short": "{0}s", "weeks": "{0} semajnoj", "week": "{0} semajno", - "seconds_short": "{0}s", - "second_short": "{0}s", + "seconds_short": "{0}sek", + "second_short": "{0}sek", "seconds": "{0} sekundoj", "second": "{0} sekundo", "now_short": "nun", @@ -749,14 +749,14 @@ "month_short": "{0}m", "months": "{0} monatoj", "month": "{0} monato", - "minutes_short": "{0}m", - "minute_short": "{0}m", + "minutes_short": "{0}min", + "minute_short": "{0}min", "minutes": "{0} minutoj", "minute": "{0} minuto", "in_past": "antaŭ {0}", "in_future": "post {0}", - "hours_short": "{0}h", - "hour_short": "{0}h", + "hours_short": "{0}hor", + "hour_short": "{0}hor", "hours": "{0} horoj", "hour": "{0} horo", "days_short": "{0}t", diff --git a/src/i18n/es.json b/src/i18n/es.json index 6889df9a..0c2cc3e9 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -104,7 +104,8 @@ "no_more_notifications": "No hay más notificaciones", "reacted_with": "reaccionó con {0}", "migrated_to": "migrado a", - "follow_request": "quiere seguirte" + "follow_request": "quiere seguirte", + "error": "Error obteniendo notificaciones:{0}" }, "polls": { "add_poll": "Añadir encuesta", @@ -313,7 +314,7 @@ "hide_followers_count_description": "No mostrar el número de cuentas que me siguen", "show_admin_badge": "Mostrar la insignia de Administrador en mi perfil", "show_moderator_badge": "Mostrar la insignia de Moderador en mi perfil", - "nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW", + "nsfw_clickthrough": "Habilitar la ocultación de la imagen de vista previa del enlace y el adjunto para los estados NSFW por defecto", "oauth_tokens": "Tokens de OAuth", "token": "Token", "refresh_token": "Actualizar el token", @@ -605,7 +606,8 @@ "up_to_date": "Actualizado", "no_more_statuses": "No hay más estados", "no_statuses": "Sin estados", - "reload": "Recargar" + "reload": "Recargar", + "error": "Error obteniendo la linea de tiempo:{0}" }, "status": { "favorites": "Favoritos", @@ -628,7 +630,9 @@ "copy_link": "Copiar el enlace al estado", "status_unavailable": "Estado no disponible", "bookmark": "Marcar", - "unbookmark": "Desmarcar" + "unbookmark": "Desmarcar", + "status_deleted": "Esta entrada ha sido eliminada", + "nsfw": "NSFW (No apropiado para el trabajo)" }, "user_card": { "approve": "Aprobar", diff --git a/src/i18n/he.json b/src/i18n/he.json index 7f2bf58f..4b920536 100644 --- a/src/i18n/he.json +++ b/src/i18n/he.json @@ -390,5 +390,13 @@ "GiB": "GiB", "TiB": "TiB" } + }, + "about": { + "mrf": { + "keyword": { + "keyword_policies": "פוליסת מילות מפתח" + }, + "federation": "פדרציה" + } } } diff --git a/src/i18n/it.json b/src/i18n/it.json index 67e92b32..58dafca5 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -50,7 +50,8 @@ "follow_request": "vuole seguirti", "no_more_notifications": "Fine delle notifiche", "migrated_to": "è migrato verso", - "reacted_with": "ha reagito con {0}" + "reacted_with": "ha reagito con {0}", + "error": "Errore nel caricare le notifiche: {0}" }, "settings": { "attachments": "Allegati", @@ -427,7 +428,8 @@ "repeated": "condiviso", "no_statuses": "Nessun messaggio", "no_more_statuses": "Fine dei messaggi", - "reload": "Ricarica" + "reload": "Ricarica", + "error": "Errore nel caricare la sequenza: {0}" }, "user_card": { "follow": "Segui", @@ -703,7 +705,8 @@ "delete_confirm": "Vuoi veramente eliminare questo messaggio?", "unbookmark": "Rimuovi segnalibro", "bookmark": "Aggiungi segnalibro", - "status_deleted": "Questo messagio è stato cancellato" + "status_deleted": "Questo messagio è stato cancellato", + "nsfw": "Pruriginoso" }, "time": { "years_short": "{0}a", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 8f421b50..f636bdf8 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -18,7 +18,13 @@ "generic_error": "Произошла ошибка", "optional": "не обязательно", "show_less": "Показать меньше", - "show_more": "Показать больше" + "show_more": "Показать больше", + "peek": "Взглянуть", + "dismiss": "Закрыть", + "retry": "Попробуйте еще раз", + "error_retry": "Пожалуйста попробуйте еще раз", + "close": "Закрыть", + "loading": "Загрузка…" }, "login": { "login": "Войти", @@ -468,7 +474,9 @@ "media_proxy": "Прокси для внешних вложений", "text_limit": "Лимит символов", "title": "Особенности", - "gopher": "Gopher" + "gopher": "Gopher", + "who_to_follow": "Предложения кого читать", + "pleroma_chat_messages": "Pleroma Чат" }, "tool_tip": { "accept_follow_request": "Принять запрос на чтение", @@ -477,6 +485,7 @@ "image_cropper": { "save_without_cropping": "Сохранить не обрезая", "save": "Сохранить", - "crop_picture": "Обрезать картинку" + "crop_picture": "Обрезать картинку", + "cancel": "Отмена" } } diff --git a/src/i18n/uk.json b/src/i18n/uk.json new file mode 100644 index 00000000..73006e6e --- /dev/null +++ b/src/i18n/uk.json @@ -0,0 +1,99 @@ +{ + "general": { + "dismiss": "Закрити", + "close": "Закрити", + "verify": "Перевірити", + "confirm": "Підтвердити", + "enable": "Увімкнути", + "disable": "Вимкнути", + "cancel": "Скасувати", + "show_less": "Показати менше", + "show_more": "Показати більше", + "optional": "необов'язково", + "retry": "Спробуйте ще раз", + "error_retry": "Будь ласка, спробуйте ще раз", + "generic_error": "Виникла помилка", + "loading": "Завантаження…", + "more": "Більше", + "submit": "Відправити", + "apply": "Застосувати", + "peek": "Глянути" + }, + "finder": { + "error_fetching_user": "Користувача не знайдено", + "find_user": "Знайти користувача" + }, + "features_panel": { + "gopher": "Gopher", + "pleroma_chat_messages": "Чат Pleroma", + "chat": "Чат", + "who_to_follow": "Кого відстежувати", + "title": "Особливості", + "scope_options": "Параметри осягу", + "media_proxy": "Посередник медіа-даних", + "text_limit": "Ліміт символів" + }, + "exporter": { + "processing": "Опрацьовую, скоро ви зможете завантажити файл", + "export": "Експорт" + }, + "domain_mute_card": { + "unmute_progress": "Вимикаю…", + "unmute": "Вимкнути ігнорування", + "mute_progress": "Вмикаю…", + "mute": "Ігнорувати" + }, + "shoutbox": { + "title": "Для воплів" + }, + "about": { + "staff": "Адміністрація", + "mrf": { + "simple": { + "media_nsfw_desc": "Даний інстанс примусово позначає вкладення з наступних інстансів як NSFW:", + "media_nsfw": "Примусове визначення вкладення як дратівливого", + "media_removal_desc": "Поточний інстанс видаляє вкладення на перелічених інстансах:", + "media_removal": "Видалення вкладень", + "ftl_removal_desc": "Цей інстанс видаляє перелічені інстанси з \"Усієї відомої мережі\":", + "ftl_removal": "Видалення з \"Вся відома мережа\"", + "quarantine_desc": "Поточний інстанс буде надсилати тільки публічні пости наступним інстансам:", + "quarantine": "Карантин", + "reject_desc": "Поточний інстанс не прийматиме повідомлення з перелічених інстансів:", + "accept": "Прийняти", + "reject": "Відхилити", + "accept_desc": "Поточний інстанс приймає повідомлення тільки з перелічених інстансів:", + "simple_policies": "Правила поточного інстансу" + }, + "mrf_policies_desc": "Правила MRF розповсюджуються на данний інстанс. Наступні правила активні:", + "mrf_policies": "Активні правила MRF (модуль переписування повідомлень)", + "keyword": { + "is_replaced_by": "→", + "replace": "Замінити", + "reject": "Відхилити", + "ftl_removal": "Прибрати з федеративної стрічки", + "keyword_policies": "Політика щодо ключових слів" + }, + "federation": "Федерація" + } + }, + "login": { + "hint": "Увійдіть, щоб доєднатися до дискусії", + "username": "Ім'я користувача", + "register": "Зареєструватись", + "password": "Пароль", + "logout": "Вийти", + "description": "Увійти за допомогою OAuth", + "login": "Увійти" + }, + "importer": { + "error": "Під час імпортування файлу сталася помилка.", + "success": "Імпортовано успішно.", + "submit": "Відправити" + }, + "image_cropper": { + "cancel": "Відмінити", + "save_without_cropping": "Зберегти не обрізаючи", + "crop_picture": "Обрізати малюнок", + "save": "Зберегти" + } +} diff --git a/src/i18n/zh.json b/src/i18n/zh.json index 09e2ab0d..7f8e5593 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -22,7 +22,7 @@ }, "general": { "apply": "应用", - "submit": "提交", + "submit": "发送", "more": "更多", "generic_error": "发生了一个错误", "optional": "可选", @@ -297,7 +297,7 @@ "hide_follows_description": "不要显示我所关注的人", "hide_followers_description": "不要显示关注我的人", "show_admin_badge": "显示管理徽章", - "show_moderator_badge": "显示版主徽章", + "show_moderator_badge": "在我的个人资料中显示监察员标志", "nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开", "oauth_tokens": "OAuth令牌", "token": "令牌", @@ -655,8 +655,8 @@ "moderation": "权限", "grant_admin": "赋予管理权限", "revoke_admin": "撤销管理权限", - "grant_moderator": "赋予版主权限", - "revoke_moderator": "撤销版主权限", + "grant_moderator": "赋予监察员权限", + "revoke_moderator": "撤销监察员权限", "activate_account": "激活账号", "deactivate_account": "关闭账号", "delete_account": "删除账号", @@ -683,7 +683,7 @@ }, "user_reporting": { "title": "报告 {0}", - "add_comment_description": "此报告会发送给您的实例管理员。您可以在下面提供更多详细信息解释报告的缘由:", + "add_comment_description": "此报告会发送给您的实例监察员。您可以在下面提供更多详细信息解释报告的缘由:", "additional_comments": "其它信息", "forward_description": "这个账号是从另外一个服务器。同时发送一个副本到那里?", "forward_to": "转发 {0}", diff --git a/src/modules/statuses.js b/src/modules/statuses.js index e673141d..33c68c57 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -39,8 +39,7 @@ const emptyNotifications = () => ({ minId: Number.POSITIVE_INFINITY, data: [], idStore: {}, - loading: false, - error: false + loading: false }) export const defaultState = () => ({ @@ -50,8 +49,6 @@ export const defaultState = () => ({ maxId: 0, notifications: emptyNotifications(), favorites: new Set(), - error: false, - errorData: null, timelines: { mentions: emptyTl(), public: emptyTl(), @@ -462,18 +459,9 @@ export const mutations = { const newStatus = state.allStatusesObject[id] newStatus.nsfw = nsfw }, - setError (state, { value }) { - state.error = value - }, - setErrorData (state, { value }) { - state.errorData = value - }, setNotificationsLoading (state, { value }) { state.notifications.loading = value }, - setNotificationsError (state, { value }) { - state.notifications.error = value - }, setNotificationsSilence (state, { value }) { state.notifications.desktopNotificationSilence = value }, @@ -588,18 +576,9 @@ const statuses = { } commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects }) }, - setError ({ rootState, commit }, { value }) { - commit('setError', { value }) - }, - setErrorData ({ rootState, commit }, { value }) { - commit('setErrorData', { value }) - }, setNotificationsLoading ({ rootState, commit }, { value }) { commit('setNotificationsLoading', { value }) }, - setNotificationsError ({ rootState, commit }, { value }) { - commit('setNotificationsError', { value }) - }, setNotificationsSilence ({ rootState, commit }, { value }) { commit('setNotificationsSilence', { value }) }, diff --git a/src/modules/users.js b/src/modules/users.js index 9245db5c..655db4c7 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -137,11 +137,11 @@ export const mutations = { }, saveFriendIds (state, { id, friendIds }) { const user = state.usersObject[id] - user.friendIds = uniq(concat(user.friendIds, friendIds)) + user.friendIds = uniq(concat(user.friendIds || [], friendIds)) }, saveFollowerIds (state, { id, followerIds }) { const user = state.usersObject[id] - user.followerIds = uniq(concat(user.followerIds, followerIds)) + user.followerIds = uniq(concat(user.followerIds || [], followerIds)) }, // Because frontend doesn't have a reason to keep these stuff in memory // outside of viewing someones user profile. @@ -202,7 +202,9 @@ export const mutations = { }, setPinnedToUser (state, status) { const user = state.usersObject[status.user.id] + user.pinnedStatusIds = user.pinnedStatusIds || [] const index = user.pinnedStatusIds.indexOf(status.id) + if (status.pinned && index === -1) { user.pinnedStatusIds.push(status.id) } else if (!status.pinned && index !== -1) { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 22b5e8ba..8da933c4 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -560,7 +560,7 @@ const fetchTimeline = ({ }) .then((data) => data.json()) .then((data) => { - if (!data.error) { + if (!data.errors) { return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination } } else { data.status = status diff --git a/src/services/chat_service/chat_service.js b/src/services/chat_service/chat_service.js index 1fc4e390..e653ebc1 100644 --- a/src/services/chat_service/chat_service.js +++ b/src/services/chat_service/chat_service.js @@ -21,7 +21,7 @@ const clear = (storage) => { failedMessageIds.push(message.id) } else { delete storage.idIndex[message.id] - delete storage.idempotencyKeyIndex[message.id] + delete storage.idempotencyKeyIndex[message.idempotency_key] } } diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 093472e4..0fd01176 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -2,6 +2,15 @@ import escape from 'escape-html' import parseLinkHeader from 'parse-link-header' import { isStatusNotification } from '../notification_utils/notification_utils.js' +/** NOTICE! ** + * Do not initialize UI-generated data here. + * It will override existing data. + * + * i.e. user.pinnedStatusIds was set to [] here + * UI code would update it with data but upon next user fetch + * it would be reverted back to [] + */ + const qvitterStatusType = (status) => { if (status.is_post_verb) { return 'status' @@ -173,9 +182,6 @@ export const parseUser = (data) => { output.locked = data.locked output.followers_count = data.followers_count output.statuses_count = data.statuses_count - output.friendIds = [] - output.followerIds = [] - output.pinnedStatusIds = [] if (data.pleroma) { output.follow_request_count = data.pleroma.follow_request_count diff --git a/src/services/favicon_service/favicon_service.js b/src/services/favicon_service/favicon_service.js new file mode 100644 index 00000000..d1ddee41 --- /dev/null +++ b/src/services/favicon_service/favicon_service.js @@ -0,0 +1,61 @@ +import { find } from 'lodash' + +const createFaviconService = () => { + let favimg, favcanvas, favcontext, favicon + const faviconWidth = 128 + const faviconHeight = 128 + const badgeRadius = 32 + + const initFaviconService = () => { + const nodes = document.getElementsByTagName('link') + favicon = find(nodes, node => node.rel === 'icon') + if (favicon) { + favcanvas = document.createElement('canvas') + favcanvas.width = faviconWidth + favcanvas.height = faviconHeight + favimg = new Image() + favimg.src = favicon.href + favcontext = favcanvas.getContext('2d') + } + } + + const isImageLoaded = (img) => img.complete && img.naturalHeight !== 0 + + const clearFaviconBadge = () => { + if (!favimg || !favcontext || !favicon) return + + favcontext.clearRect(0, 0, faviconWidth, faviconHeight) + if (isImageLoaded(favimg)) { + favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight) + } + favicon.href = favcanvas.toDataURL('image/png') + } + + const drawFaviconBadge = () => { + if (!favimg || !favcontext || !favcontext) return + + clearFaviconBadge() + + const style = getComputedStyle(document.body) + const badgeColor = `${style.getPropertyValue('--badgeNotification') || 'rgb(240, 100, 100)'}` + + if (isImageLoaded(favimg)) { + favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight) + } + favcontext.fillStyle = badgeColor + favcontext.beginPath() + favcontext.arc(faviconWidth - badgeRadius, badgeRadius, badgeRadius, 0, 2 * Math.PI, false) + favcontext.fill() + favicon.href = favcanvas.toDataURL('image/png') + } + + return { + initFaviconService, + clearFaviconBadge, + drawFaviconBadge + } +} + +const FaviconService = createFaviconService() + +export default FaviconService diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index c908b644..beeb167c 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -2,7 +2,6 @@ import apiService from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' const update = ({ store, notifications, older }) => { - store.dispatch('setNotificationsError', { value: false }) store.dispatch('addNewNotifications', { notifications, older }) } @@ -47,11 +46,22 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => { const fetchNotifications = ({ store, args, older }) => { return apiService.fetchTimeline(args) - .then(({ data: notifications }) => { + .then((response) => { + if (response.errors) { + throw new Error(`${response.status} ${response.statusText}`) + } + const notifications = response.data update({ store, notifications, older }) return notifications - }, () => store.dispatch('setNotificationsError', { value: true })) - .catch(() => store.dispatch('setNotificationsError', { value: true })) + }) + .catch((error) => { + store.dispatch('pushGlobalNotice', { + level: 'error', + messageKey: 'notifications.error', + messageArgs: [error.message], + timeout: 5000 + }) + }) } const startFetching = ({ credentials, store }) => { diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 72ea4890..921df3ed 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -6,9 +6,6 @@ import { promiseInterval } from '../promise_interval/promise_interval.js' const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => { const ccTimeline = camelCase(timeline) - store.dispatch('setError', { value: false }) - store.dispatch('setErrorData', { value: null }) - store.dispatch('addNewStatuses', { timeline: ccTimeline, userId, @@ -52,9 +49,8 @@ const fetchAndUpdate = ({ return apiService.fetchTimeline(args) .then(response => { - if (response.error) { - store.dispatch('setErrorData', { value: response }) - return + if (response.errors) { + throw new Error(`${response.status} ${response.statusText}`) } const { data: statuses, pagination } = response @@ -63,7 +59,15 @@ const fetchAndUpdate = ({ } update({ store, statuses, timeline, showImmediately, userId, pagination }) return { statuses, pagination } - }, () => store.dispatch('setError', { value: true })) + }) + .catch((error) => { + store.dispatch('pushGlobalNotice', { + level: 'error', + messageKey: 'timeline.error', + messageArgs: [error.message], + timeout: 5000 + }) + }) } const startFetching = ({ timeline = 'friends', credentials, store, userId = false, tag = false }) => { |
