Merge branch 'develop' into feat/report-notification

This commit is contained in:
Shpuld Shpuldson 2020-12-04 11:20:01 +02:00
commit 1fd1553a1c
100 changed files with 6513 additions and 1627 deletions

View File

@ -5,9 +5,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added
- Mouseover titles for emojis in reaction picker
- Support to input emoji into the search box in reaction picker
- Added some missing unicode emoji
### Fixed ### Fixed
- Fixed the occasional bug where screen would scroll 1px when typing into a reply form - Fixed the occasional bug where screen would scroll 1px when typing into a reply form
- Fixed timeline errors locking timelines
- Fixed missing highlighted border in expanded conversations
- Fixed custom emoji not working in profile field names - Fixed custom emoji not working in profile field names
- Fixed pinned statuses not appearing in user profiles
- Fixed some elements not being keyboard navigation friendly
- Fixed your latest chat messages disappearing when closing chat view and opening it again during the same session
### Changed
- Errors when fetching are now shown with popup errors instead of "Error fetching updates" in panel headers
- Made reply/fav/repeat etc buttons easier to hit
## [2.2.1] - 2020-11-11 ## [2.2.1] - 2020-11-11
@ -22,6 +36,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Import/export a muted users - Import/export a muted users
- Proper handling of deletes when using websocket streaming - Proper handling of deletes when using websocket streaming
- Added optimistic chat message sending, so you can start writing next message before the previous one has been sent - Added optimistic chat message sending, so you can start writing next message before the previous one has been sent
- Added a small red badge to the favicon when there's unread notifications
- Added the NSFW alert to link previews
### Fixed ### Fixed
- Fixed clicking NSFW hider through status popover - Fixed clicking NSFW hider through status popover
@ -43,7 +59,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [2.1.2] - 2020-09-17 ## [2.1.2] - 2020-09-17
### Fixed ### Fixed
- Fixed chats list not updating its order when new messages come in - Fixed chats list not updating its order when new messages come in
- Fixed chat messages sometimes getting lost when you receive a message at the same time - Fixed chat messages sometimes getting lost when you receive a message at the same time
## [2.1.1] - 2020-09-08 ## [2.1.1] - 2020-09-08

View File

@ -33,6 +33,7 @@ h4 {
max-width: 980px; max-width: 980px;
align-content: flex-start; align-content: flex-start;
} }
.underlay { .underlay {
background-color: rgba(0,0,0,0.15); background-color: rgba(0,0,0,0.15);
background-color: var(--underlay, 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); color: var(--link, $fallback--link);
} }
button { .button-default {
user-select: none; user-select: none;
color: $fallback--text; color: $fallback--text;
color: var(--btnText, $fallback--text); color: var(--btnText, $fallback--text);
@ -85,7 +86,8 @@ button {
font-family: sans-serif; font-family: sans-serif;
font-family: var(--interfaceFont, sans-serif); font-family: var(--interfaceFont, sans-serif);
i[class*=icon-], .svg-inline--fa { i[class*=icon-],
.svg-inline--fa {
color: $fallback--text; color: $fallback--text;
color: var(--btnText, $fallback--text); color: var(--btnText, $fallback--text);
} }
@ -107,7 +109,8 @@ button {
background-color: $fallback--fg; background-color: $fallback--fg;
background-color: var(--btnPressed, $fallback--fg); background-color: var(--btnPressed, $fallback--fg);
svg, i { svg,
i {
color: $fallback--text; color: $fallback--text;
color: var(--btnPressedText, $fallback--text); color: var(--btnPressedText, $fallback--text);
} }
@ -120,7 +123,8 @@ button {
background-color: $fallback--fg; background-color: $fallback--fg;
background-color: var(--btnDisabled, $fallback--fg); background-color: var(--btnDisabled, $fallback--fg);
svg, i { svg,
i {
color: $fallback--text; color: $fallback--text;
color: var(--btnDisabledText, $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: 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); box-shadow: var(--buttonPressedShadow);
svg, i { svg,
i {
color: $fallback--text; color: $fallback--text;
color: var(--btnToggledText, $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 { input, textarea, .select, .input {
&.unstyled { &.unstyled {
@ -442,6 +471,7 @@ main-router {
color: $fallback--faint; color: $fallback--faint;
color: var(--panelFaint, $fallback--faint); color: var(--panelFaint, $fallback--faint);
} }
.faint-link { .faint-link {
color: $fallback--faint; color: $fallback--faint;
color: var(--faintLink, $fallback--faint); color: var(--faintLink, $fallback--faint);
@ -453,11 +483,8 @@ main-router {
overflow-x: hidden; overflow-x: hidden;
} }
button { .button-default,
flex-shrink: 0; .alert {
}
button, .alert {
// height: 100%; // height: 100%;
line-height: 21px; line-height: 21px;
min-height: 0; min-height: 0;
@ -468,8 +495,11 @@ main-router {
align-self: stretch; align-self: stretch;
} }
button { .button-default {
&, i[class*=icon-] { flex-shrink: 0;
&,
i[class*=icon-] {
color: $fallback--text; color: $fallback--text;
color: var(--btnPanelText, $fallback--text); color: var(--btnPanelText, $fallback--text);
} }
@ -492,7 +522,8 @@ main-router {
} }
} }
a { a,
.-link {
color: $fallback--link; color: $fallback--link;
color: var(--panelLink, $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 $fallback--panelRadius $fallback--panelRadius;
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
.faint { .faint {
color: $fallback--faint; color: $fallback--faint;
color: var(--panelFaint, $fallback--faint); color: var(--panelFaint, $fallback--faint);
} }
a { a,
.-link {
color: $fallback--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; min-height: 28px;
} }

View File

@ -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 backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import { applyTheme } from '../services/style_setter/style_setter.js' import { applyTheme } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js'
let staticInitialResults = null let staticInitialResults = null
@ -326,6 +327,8 @@ const afterStoreSetup = async ({ store, i18n }) => {
const width = windowWidth() const width = windowWidth()
store.dispatch('setMobileLayout', width <= 800) store.dispatch('setMobileLayout', width <= 800)
FaviconService.initFaviconService()
const overrides = window.___pleromafe_dev_overrides || {} const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
store.dispatch('setInstanceOption', { name: 'server', value: server }) store.dispatch('setInstanceOption', { name: 'server', value: server })

View File

@ -4,6 +4,7 @@
trigger="click" trigger="click"
placement="bottom" placement="bottom"
:bound-to="{ x: 'container' }" :bound-to="{ x: 'container' }"
remove-padding
> >
<div <div
slot="content" slot="content"
@ -13,14 +14,14 @@
<template v-if="relationship.following"> <template v-if="relationship.following">
<button <button
v-if="relationship.showing_reblogs" v-if="relationship.showing_reblogs"
class="btn btn-default dropdown-item" class="btn button-default dropdown-item"
@click="hideRepeats" @click="hideRepeats"
> >
{{ $t('user_card.hide_repeats') }} {{ $t('user_card.hide_repeats') }}
</button> </button>
<button <button
v-if="!relationship.showing_reblogs" v-if="!relationship.showing_reblogs"
class="btn btn-default dropdown-item" class="btn button-default dropdown-item"
@click="showRepeats" @click="showRepeats"
> >
{{ $t('user_card.show_repeats') }} {{ $t('user_card.show_repeats') }}
@ -32,27 +33,27 @@
</template> </template>
<button <button
v-if="relationship.blocking" v-if="relationship.blocking"
class="btn btn-default btn-block dropdown-item" class="btn button-default btn-block dropdown-item"
@click="unblockUser" @click="unblockUser"
> >
{{ $t('user_card.unblock') }} {{ $t('user_card.unblock') }}
</button> </button>
<button <button
v-else v-else
class="btn btn-default btn-block dropdown-item" class="btn button-default btn-block dropdown-item"
@click="blockUser" @click="blockUser"
> >
{{ $t('user_card.block') }} {{ $t('user_card.block') }}
</button> </button>
<button <button
class="btn btn-default btn-block dropdown-item" class="btn button-default btn-block dropdown-item"
@click="reportUser" @click="reportUser"
> >
{{ $t('user_card.report') }} {{ $t('user_card.report') }}
</button> </button>
<button <button
v-if="pleromaChatMessagesAvailable" v-if="pleromaChatMessagesAvailable"
class="btn btn-default btn-block dropdown-item" class="btn button-default btn-block dropdown-item"
@click="openChat" @click="openChat"
> >
{{ $t('user_card.message') }} {{ $t('user_card.message') }}
@ -61,7 +62,7 @@
</div> </div>
<div <div
slot="trigger" slot="trigger"
class="btn btn-default ellipsis-button" class="ellipsis-button"
> >
<FAIcon <FAIcon
class="icon" class="icon"

View File

@ -8,7 +8,7 @@
{{ $t('general.error_retry') }} {{ $t('general.error_retry') }}
</p> </p>
<button <button
class="btn" class="btn button-default"
@click="retry" @click="retry"
> >
{{ $t('general.retry') }} {{ $t('general.retry') }}

View File

@ -8,14 +8,18 @@ import {
faFile, faFile,
faMusic, faMusic,
faImage, faImage,
faVideo faVideo,
faPlayCircle,
faTimes
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
library.add( library.add(
faFile, faFile,
faMusic, faMusic,
faImage, faImage,
faVideo faVideo,
faPlayCircle,
faTimes
) )
const Attachment = { const Attachment = {

View File

@ -42,15 +42,13 @@
icon="play-circle" icon="play-circle"
/> />
</a> </a>
<div <button
v-if="nsfw && hideNsfwLocal && !hidden" v-if="nsfw && hideNsfwLocal && !hidden"
class="hider" class="button-unstyled hider"
@click.prevent="toggleHidden"
> >
<a <FAIcon icon="times" />
href="#" </button>
@click.prevent="toggleHidden"
>Hide</a>
</div>
<a <a
v-if="type === 'image' && (!hidden || preloadImage)" v-if="type === 'image' && (!hidden || preloadImage)"
@ -234,15 +232,23 @@
.hider { .hider {
position: absolute; position: absolute;
right: 0; right: 0;
white-space: nowrap;
margin: 10px; margin: 10px;
padding: 5px; padding: 0;
background: rgba(230,230,230,0.6);
font-weight: bold;
z-index: 4; z-index: 4;
line-height: 1;
border-radius: $fallback--tooltipRadius; border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $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 { video {

View File

@ -3,7 +3,7 @@
<div class="block-card-content-container"> <div class="block-card-content-container">
<button <button
v-if="blocked" v-if="blocked"
class="btn btn-default" class="btn button-default"
:disabled="progress" :disabled="progress"
@click="unblockUser" @click="unblockUser"
> >
@ -16,7 +16,7 @@
</button> </button>
<button <button
v-else v-else
class="btn btn-default" class="btn button-default"
:disabled="progress" :disabled="progress"
@click="blockUser" @click="blockUser"
> >

View File

@ -10,7 +10,10 @@
<span class="title"> <span class="title">
{{ $t("chats.chats") }} {{ $t("chats.chats") }}
</span> </span>
<button @click="newChat"> <button
class="button-default"
@click="newChat"
>
{{ $t("chats.new") }} {{ $t("chats.new") }}
</button> </button>
</div> </div>

View File

@ -31,9 +31,6 @@
color: $fallback--text; color: $fallback--text;
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
} }
border-radius: $fallback--chatMessageRadius;
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
} }
.popover { .popover {

View File

@ -53,7 +53,7 @@
<div slot="content"> <div slot="content">
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
class="dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@click="deleteMessage" @click="deleteMessage"
> >
<FAIcon icon="times" /> {{ $t("chats.delete") }} <FAIcon icon="times" /> {{ $t("chats.delete") }}
@ -62,7 +62,7 @@
</div> </div>
<button <button
slot="trigger" slot="trigger"
class="menu-icon" class="button-default menu-icon"
:title="$t('chats.more')" :title="$t('chats.more')"
> >
<FAIcon icon="ellipsis-h" /> <FAIcon icon="ellipsis-h" />

View File

@ -10,12 +10,13 @@
class="panel-heading conversation-heading" class="panel-heading conversation-heading"
> >
<span class="title"> {{ $t('timeline.conversation') }} </span> <span class="title"> {{ $t('timeline.conversation') }} </span>
<span v-if="collapsable"> <button
<a v-if="collapsable"
href="#" class="button-unstyled -link"
@click.prevent="toggleExpanded" @click.prevent="toggleExpanded"
>{{ $t('timeline.collapse') }}</a> >
</span> {{ $t('timeline.collapse') }}
</button>
</div> </div>
<status <status
v-for="status in conversation" v-for="status in conversation"
@ -57,13 +58,6 @@
} }
&.-expanded { &.-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 { .conversation-status:last-child {
border-bottom: none; border-bottom: none;
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;

View File

@ -21,7 +21,7 @@
grid-template-areas: "logo sitename actions"; grid-template-areas: "logo sitename actions";
} }
button { .button-default {
&, svg { &, svg {
color: $fallback--text; color: $fallback--text;
color: var(--btnTopBarText, $fallback--text); color: var(--btnTopBarText, $fallback--text);
@ -80,12 +80,13 @@
.nav-icon { .nav-icon {
margin-left: 0.2em; margin-left: 0.2em;
width: 2em; width: 2em;
height: 100%;
text-align: center; text-align: center;
}
a, a svg { .svg-inline--fa {
color: $fallback--link; color: $fallback--link;
color: var(--topBarLink, $fallback--link); color: var(--topBarLink, $fallback--link);
}
} }
.sitename { .sitename {

View File

@ -36,9 +36,8 @@
@toggled="onSearchBarToggled" @toggled="onSearchBarToggled"
@click.stop.native @click.stop.native
/> />
<a <button
href="#" class="button-unstyled nav-icon"
class="nav-icon"
@click.stop="openSettingsModal" @click.stop="openSettingsModal"
> >
<FAIcon <FAIcon
@ -47,29 +46,32 @@
icon="cog" icon="cog"
:title="$t('nav.preferences')" :title="$t('nav.preferences')"
/> />
</a> </button>
<a <a
v-if="currentUser && currentUser.role === 'admin'" v-if="currentUser && currentUser.role === 'admin'"
href="/pleroma/admin/#/login-pleroma" href="/pleroma/admin/#/login-pleroma"
class="nav-icon" class="nav-icon"
target="_blank" target="_blank"
><FAIcon >
fixed-width <FAIcon
class="fa-scale-110 fa-old-padding" fixed-width
icon="tachometer-alt" class="fa-scale-110 fa-old-padding"
:title="$t('nav.administration')" icon="tachometer-alt"
/></a> :title="$t('nav.administration')"
<a />
</a>
<button
v-if="currentUser" v-if="currentUser"
href="#" class="button-unstyled nav-icon"
class="nav-icon"
@click.prevent="logout" @click.prevent="logout"
><FAIcon >
fixed-width <FAIcon
class="fa-scale-110 fa-old-padding" fixed-width
icon="sign-out-alt" class="fa-scale-110 fa-old-padding"
:title="$t('login.logout')" icon="sign-out-alt"
/></a> :title="$t('login.logout')"
/>
</button>
</div> </div>
</div> </div>
</nav> </nav>

View File

@ -6,7 +6,7 @@
<ProgressButton <ProgressButton
v-if="muted" v-if="muted"
:click="unmuteDomain" :click="unmuteDomain"
class="btn btn-default" class="btn button-default"
> >
{{ $t('domain_mute_card.unmute') }} {{ $t('domain_mute_card.unmute') }}
<template slot="progress"> <template slot="progress">
@ -16,7 +16,7 @@
<ProgressButton <ProgressButton
v-else v-else
:click="muteDomain" :click="muteDomain"
class="btn btn-default" class="btn button-default"
> >
{{ $t('domain_mute_card.mute') }} {{ $t('domain_mute_card.mute') }}
<template slot="progress"> <template slot="progress">

View File

@ -114,7 +114,8 @@ const EmojiInput = {
showPicker: false, showPicker: false,
temporarilyHideSuggestions: false, temporarilyHideSuggestions: false,
keepOpen: false, keepOpen: false,
disableClickOutside: false disableClickOutside: false,
suggestions: []
} }
}, },
components: { components: {
@ -124,21 +125,6 @@ const EmojiInput = {
padEmoji () { padEmoji () {
return this.$store.getters.mergedConfig.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 () { showSuggestions () {
return this.focused && return this.focused &&
this.suggestions && this.suggestions &&
@ -188,6 +174,23 @@ const EmojiInput = {
watch: { watch: {
showSuggestions: function (newValue) { showSuggestions: function (newValue) {
this.$emit('shown', 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: { methods: {

View File

@ -6,13 +6,13 @@
> >
<slot /> <slot />
<template v-if="enableEmojiPicker"> <template v-if="enableEmojiPicker">
<div <button
v-if="!hideEmojiButton" v-if="!hideEmojiButton"
class="emoji-picker-icon" class="button-unstyled emoji-picker-icon"
@click.prevent="togglePicker" @click.prevent="togglePicker"
> >
<FAIcon :icon="['far', 'smile-beam']" /> <FAIcon :icon="['far', 'smile-beam']" />
</div> </button>
<EmojiPicker <EmojiPicker
v-if="enableEmojiPicker" v-if="enableEmojiPicker"
ref="picker" ref="picker"
@ -37,7 +37,7 @@
v-for="(suggestion, index) in suggestions" v-for="(suggestion, index) in suggestions"
:key="index" :key="index"
class="autocomplete-item" class="autocomplete-item"
:class="{ highlighted: suggestion.highlighted }" :class="{ highlighted: index === highlighted }"
@click.stop.prevent="onClick($event, suggestion)" @click.stop.prevent="onClick($event, suggestion)"
> >
<span class="image"> <span class="image">

View File

@ -1,4 +1,3 @@
import { debounce } from 'lodash'
/** /**
* suggest - generates a suggestor function to be used by emoji-input * suggest - generates a suggestor function to be used by emoji-input
* data: object providing source information for specific types of suggestions: * 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. * doesn't support user linking you can just provide only emoji.
*/ */
const debounceUserSearch = debounce((data, input) => { export default data => {
data.updateUsersList(input) const emojiCurry = suggestEmoji(data.emoji)
}, 500) const usersCurry = data.store && suggestUsers(data.store)
return input => {
export default data => input => { const firstChar = input[0]
const firstChar = input[0] if (firstChar === ':' && data.emoji) {
if (firstChar === ':' && data.emoji) { return emojiCurry(input)
return suggestEmoji(data.emoji)(input) }
if (firstChar === '@' && usersCurry) {
return usersCurry(input)
}
return []
} }
if (firstChar === '@' && data.users) {
return suggestUsers(data)(input)
}
return []
} }
export const suggestEmoji = emojis => input => { export const suggestEmoji = emojis => input => {
@ -57,50 +56,75 @@ export const suggestEmoji = emojis => input => {
}) })
} }
export const suggestUsers = data => input => { export const suggestUsers = ({ dispatch, state }) => {
const noPrefix = input.toLowerCase().substr(1) // Keep some persistent values in closure, most importantly for the
const users = data.users // custom debounce to work. Lodash debounce does not return a promise.
let suggestions = []
let previousQuery = ''
let timeout = null
let cancelUserSearch = null
const newUsers = users.filter( const userSearch = (query) => dispatch('searchUsers', { query })
user => const debounceUserSearch = (query) => {
user.screen_name.toLowerCase().startsWith(noPrefix) || cancelUserSearch && cancelUserSearch()
user.name.toLowerCase().startsWith(noPrefix) return new Promise((resolve, reject) => {
timeout = setTimeout(() => {
/* taking only 20 results so that sorting is a bit cheaper, we display userSearch(query).then(resolve).catch(reject)
* only 5 anyway. could be inaccurate, but we ideally we should query }, 300)
* backend anyway cancelUserSearch = () => {
*/ clearTimeout(timeout)
).slice(0, 20).sort((a, b) => { resolve([])
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 return async input => {
bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0 const noPrefix = input.toLowerCase().substr(1)
if (previousQuery === noPrefix) return suggestions
// Matches on name takes second priority
aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 suggestions = []
bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0 previousQuery = noPrefix
// Fetch more and wait, don't fetch if there's the 2nd @ because
const diff = (bScore - aScore) * 10 // the backend user search can't deal with it.
// Reference semantics make it so that we get the updated data after
// Then sort alphabetically // the await.
const nameAlphabetically = a.name > b.name ? 1 : -1 if (!noPrefix.includes('@')) {
const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1 await debounceUserSearch(noPrefix)
}
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */ const newSuggestions = state.users.users.filter(
}).map(({ screen_name, name, profile_image_url_original }) => ({ user =>
displayText: screen_name, user.screen_name.toLowerCase().startsWith(noPrefix) ||
detailText: name, user.name.toLowerCase().startsWith(noPrefix)
imageUrl: profile_image_url_original, ).slice(0, 20).sort((a, b) => {
replacement: '@' + screen_name + ' ' let aScore = 0
})) let bScore = 0
// BE search users to get more comprehensive results // Matches on screen name (i.e. user@instance) makes a priority
if (data.updateUsersList) { aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
debounceUserSearch(data, noPrefix) 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 */
} }

View File

@ -6,7 +6,7 @@
:users="accountsForEmoji[reaction.name]" :users="accountsForEmoji[reaction.name]"
> >
<button <button
class="emoji-reaction btn btn-default" class="emoji-reaction btn button-default"
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }" :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
@click="emojiOnClick(reaction.name, $event)" @click="emojiOnClick(reaction.name, $event)"
@mouseenter="fetchEmojiReactionsByIfMissing()" @mouseenter="fetchEmojiReactionsByIfMissing()"

View File

@ -2,13 +2,13 @@
<div class="import-export-container"> <div class="import-export-container">
<slot name="before" /> <slot name="before" />
<button <button
class="btn" class="btn button-default"
@click="exportData" @click="exportData"
> >
{{ exportLabel }} {{ exportLabel }}
</button> </button>
<button <button
class="btn" class="btn button-default"
@click="importData" @click="importData"
> >
{{ importLabel }} {{ importLabel }}

View File

@ -11,7 +11,7 @@
</div> </div>
<button <button
v-else v-else
class="btn btn-default" class="btn button-default"
@click="process" @click="process"
> >
{{ exportButtonLabel }} {{ exportButtonLabel }}

View File

@ -2,8 +2,9 @@
<Popover <Popover
trigger="click" trigger="click"
placement="top" placement="top"
class="extra-button-popover" :offset="{ y: 5 }"
:bound-to="{ x: 'container' }" :bound-to="{ x: 'container' }"
remove-padding
> >
<div <div
slot="content" slot="content"
@ -12,7 +13,7 @@
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
v-if="canMute && !status.thread_muted" v-if="canMute && !status.thread_muted"
class="dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@click.prevent="muteConversation" @click.prevent="muteConversation"
> >
<FAIcon <FAIcon
@ -22,7 +23,7 @@
</button> </button>
<button <button
v-if="canMute && status.thread_muted" v-if="canMute && status.thread_muted"
class="dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@click.prevent="unmuteConversation" @click.prevent="unmuteConversation"
> >
<FAIcon <FAIcon
@ -32,7 +33,7 @@
</button> </button>
<button <button
v-if="!status.pinned && canPin" v-if="!status.pinned && canPin"
class="dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@click.prevent="pinStatus" @click.prevent="pinStatus"
@click="close" @click="close"
> >
@ -43,7 +44,7 @@
</button> </button>
<button <button
v-if="status.pinned && canPin" v-if="status.pinned && canPin"
class="dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@click.prevent="unpinStatus" @click.prevent="unpinStatus"
@click="close" @click="close"
> >
@ -54,7 +55,7 @@
</button> </button>
<button <button
v-if="!status.bookmarked" v-if="!status.bookmarked"
class="dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@click.prevent="bookmarkStatus" @click.prevent="bookmarkStatus"
@click="close" @click="close"
> >
@ -65,7 +66,7 @@
</button> </button>
<button <button
v-if="status.bookmarked" v-if="status.bookmarked"
class="dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@click.prevent="unbookmarkStatus" @click.prevent="unbookmarkStatus"
@click="close" @click="close"
> >
@ -76,7 +77,7 @@
</button> </button>
<button <button
v-if="canDelete" v-if="canDelete"
class="dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@click.prevent="deleteStatus" @click.prevent="deleteStatus"
@click="close" @click="close"
> >
@ -86,7 +87,7 @@
/><span>{{ $t("status.delete") }}</span> /><span>{{ $t("status.delete") }}</span>
</button> </button>
<button <button
class="dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@click.prevent="copyLink" @click.prevent="copyLink"
@click="close" @click="close"
> >
@ -97,9 +98,12 @@
</button> </button>
</div> </div>
</div> </div>
<span slot="trigger"> <span
slot="trigger"
class="ExtraButtons"
>
<FAIcon <FAIcon
class="ExtraButtons fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
icon="ellipsis-h" icon="ellipsis-h"
/> />
</span> </span>
@ -112,11 +116,11 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.ExtraButtons { .ExtraButtons {
cursor: pointer;
position: static; position: static;
padding: 10px;
margin: -10px;
&:hover, &:hover .svg-inline--fa {
.extra-button-popover.open & {
color: $fallback--text; color: $fallback--text;
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
} }

View File

@ -31,11 +31,6 @@ const FavoriteButton = {
} }
}, },
computed: { computed: {
classes () {
return {
'-favorited': this.status.favorited
}
},
...mapGetters(['mergedConfig']) ...mapGetters(['mergedConfig'])
} }
} }

View File

@ -1,23 +1,31 @@
<template> <template>
<div v-if="loggedIn"> <div class="FavoriteButton">
<FAIcon <button
:class="classes" v-if="loggedIn"
class="FavoriteButton fa-scale-110 fa-old-padding -interactive" class="button-unstyled interactive"
:class="status.favorited && '-favorited'"
:title="$t('tool_tip.favorite')" :title="$t('tool_tip.favorite')"
:icon="[status.favorited ? 'fas' : 'far', 'star']"
:spin="animated"
@click.prevent="favorite()" @click.prevent="favorite()"
/> >
<span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span> <FAIcon
</div> class="fa-scale-110 fa-old-padding"
<div v-else> :icon="[status.favorited ? 'fas' : 'far', 'star']"
<FAIcon :spin="animated"
:class="classes" />
class="FavoriteButton fa-scale-110 fa-old-padding" </button>
:title="$t('tool_tip.favorite')" <span v-else>
:icon="['far', 'star']" <FAIcon
/> class="fa-scale-110 fa-old-padding"
<span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span> :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> </div>
</template> </template>
@ -27,19 +35,28 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.FavoriteButton { .FavoriteButton {
&.-interactive { display: flex;
cursor: pointer;
animation-duration: 0.6s;
&: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,
&.-favorited .svg-inline--fa {
color: $fallback--cOrange; color: $fallback--cOrange;
color: var(--cOrange, $fallback--cOrange); color: var(--cOrange, $fallback--cOrange);
} }
} }
&.-favorited {
color: $fallback--cOrange;
color: var(--cOrange, $fallback--cOrange);
}
} }
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<button <button
class="btn btn-default follow-button" class="btn button-default follow-button"
:class="{ toggled: isPressed }" :class="{ toggled: isPressed }"
:disabled="inProgress" :disabled="inProgress"
:title="title" :title="title"

View File

@ -2,13 +2,13 @@
<basic-user-card :user="user"> <basic-user-card :user="user">
<div class="follow-request-card-content-container"> <div class="follow-request-card-content-container">
<button <button
class="btn btn-default" class="btn button-default"
@click="approveUser" @click="approveUser"
> >
{{ $t('user_card.approve') }} {{ $t('user_card.approve') }}
</button> </button>
<button <button
class="btn btn-default" class="btn button-default"
@click="denyUser" @click="denyUser"
> >
{{ $t('user_card.deny') }} {{ $t('user_card.deny') }}

View File

@ -11,21 +11,21 @@
</div> </div>
<div class="image-cropper-buttons-wrapper"> <div class="image-cropper-buttons-wrapper">
<button <button
class="btn" class="button-default btn"
type="button" type="button"
:disabled="submitting" :disabled="submitting"
@click="submit()" @click="submit()"
v-text="saveText" v-text="saveText"
/> />
<button <button
class="btn" class="button-default btn"
type="button" type="button"
:disabled="submitting" :disabled="submitting"
@click="destroy" @click="destroy"
v-text="cancelText" v-text="cancelText"
/> />
<button <button
class="btn" class="button-default btn"
type="button" type="button"
:disabled="submitting" :disabled="submitting"
@click="submit(false)" @click="submit(false)"

View File

@ -15,7 +15,7 @@
/> />
<button <button
v-else v-else
class="btn btn-default" class="btn button-default"
@click="submit" @click="submit"
> >
{{ submitButtonLabel }} {{ submitButtonLabel }}

View File

@ -1,3 +1,5 @@
import { mapGetters } from 'vuex'
const LinkPreview = { const LinkPreview = {
name: 'LinkPreview', name: 'LinkPreview',
props: [ props: [
@ -15,11 +17,20 @@ const LinkPreview = {
// Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid // 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 // as it makes sure to hide the image if somehow NSFW tagged preview can
// exist. // 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 () { useDescription () {
return this.card.description && /\S/.test(this.card.description) return this.card.description && /\S/.test(this.card.description)
} },
hideNsfwConfig () {
return this.mergedConfig.hideNsfw
},
...mapGetters([
'mergedConfig'
])
}, },
created () { created () {
if (this.useImage) { if (this.useImage) {

View File

@ -9,12 +9,17 @@
<div <div
v-if="useImage && imageLoaded" v-if="useImage && imageLoaded"
class="card-image" class="card-image"
:class="{ 'small-image': size === 'small' }"
> >
<img :src="card.image"> <img :src="card.image">
</div> </div>
<div class="card-content"> <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> <h4 class="card-title">{{ card.title }}</h4>
<p <p
v-if="useDescription" v-if="useDescription"
@ -50,10 +55,6 @@
} }
} }
.small-image {
width: 80px;
}
.card-content { .card-content {
max-height: 100%; max-height: 100%;
margin: 0.5em; margin: 0.5em;
@ -76,6 +77,10 @@
max-height: calc(1.2em * 3 - 1px); max-height: calc(1.2em * 3 - 1px);
} }
.nsfw-alert {
margin: 2em 0;
}
color: $fallback--text; color: $fallback--text;
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
border-style: solid; border-style: solid;

View File

@ -61,7 +61,7 @@
<button <button
:disabled="loggingIn" :disabled="loggingIn"
type="submit" type="submit"
class="btn btn-default" class="btn button-default"
> >
{{ $t('login.login') }} {{ $t('login.login') }}
</button> </button>

View File

@ -1,33 +1,29 @@
<template> <template>
<div <label
class="media-upload" class="media-upload"
:class="{ disabled: disabled }" :class="{ disabled: disabled }"
:title="$t('tool_tip.media_upload')"
> >
<label <FAIcon
class="label" v-if="uploading"
:title="$t('tool_tip.media_upload')" 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 </label>
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>
</template> </template>
<script src="./media_upload.js" ></script> <script src="./media_upload.js" ></script>
@ -36,12 +32,6 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.media-upload { .media-upload {
.label { cursor: pointer;
display: inline-block;
}
.new-icon {
cursor: pointer;
}
} }
</style> </style>

View File

@ -23,23 +23,23 @@
<div class="form-group"> <div class="form-group">
<div class="login-bottom"> <div class="login-bottom">
<div> <div>
<a <button
href="#" class="button-unstyled -link"
@click.prevent="requireTOTP" @click.prevent="requireTOTP"
> >
{{ $t('login.enter_two_factor_code') }} {{ $t('login.enter_two_factor_code') }}
</a> </button>
<br> <br>
<a <button
href="#" class="button-unstyled -link"
@click.prevent="abortMFA" @click.prevent="abortMFA"
> >
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</a> </button>
</div> </div>
<button <button
type="submit" type="submit"
class="btn btn-default" class="btn button-default"
> >
{{ $t('general.verify') }} {{ $t('general.verify') }}
</button> </button>

View File

@ -25,23 +25,23 @@
<div class="form-group"> <div class="form-group">
<div class="login-bottom"> <div class="login-bottom">
<div> <div>
<a <button
href="#" class="button-unstyled -link"
@click.prevent="requireRecovery" @click.prevent="requireRecovery"
> >
{{ $t('login.enter_recovery_code') }} {{ $t('login.enter_recovery_code') }}
</a> </button>
<br> <br>
<a <button
href="#" class="button-unstyled -link"
@click.prevent="abortMFA" @click.prevent="abortMFA"
> >
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</a> </button>
</div> </div>
<button <button
type="submit" type="submit"
class="btn btn-default" class="btn button-default"
> >
{{ $t('general.verify') }} {{ $t('general.verify') }}
</button> </button>

View File

@ -9,9 +9,8 @@
@click="scrollToTop()" @click="scrollToTop()"
> >
<div class="item"> <div class="item">
<a <button
href="#" class="button-unstyled mobile-nav-button"
class="mobile-nav-button"
@click.stop.prevent="toggleMobileSidebar()" @click.stop.prevent="toggleMobileSidebar()"
> >
<FAIcon <FAIcon
@ -22,7 +21,7 @@
v-if="unreadChatCount" v-if="unreadChatCount"
class="alert-dot" class="alert-dot"
/> />
</a> </button>
<router-link <router-link
v-if="!hideSitename" v-if="!hideSitename"
class="site-name" class="site-name"
@ -33,10 +32,9 @@
</router-link> </router-link>
</div> </div>
<div class="item right"> <div class="item right">
<a <button
v-if="currentUser" v-if="currentUser"
class="mobile-nav-button" class="button-unstyled mobile-nav-button"
href="#"
@click.stop.prevent="openMobileNotifications()" @click.stop.prevent="openMobileNotifications()"
> >
<FAIcon <FAIcon
@ -47,7 +45,7 @@
v-if="unseenNotificationsCount" v-if="unseenNotificationsCount"
class="alert-dot" class="alert-dot"
/> />
</a> </button>
</div> </div>
</nav> </nav>
<div <div

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-if="isLoggedIn"> <div v-if="isLoggedIn">
<button <button
class="new-status-button" class="button-default new-status-button"
:class="{ 'hidden': isHidden }" :class="{ 'hidden': isHidden }"
@click="openPostForm" @click="openPostForm"
> >

View File

@ -12,13 +12,13 @@
<div class="dropdown-menu"> <div class="dropdown-menu">
<span v-if="user.is_local"> <span v-if="user.is_local">
<button <button
class="dropdown-item" class="button-default dropdown-item"
@click="toggleRight(&quot;admin&quot;)" @click="toggleRight(&quot;admin&quot;)"
> >
{{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }} {{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }}
</button> </button>
<button <button
class="dropdown-item" class="button-default dropdown-item"
@click="toggleRight(&quot;moderator&quot;)" @click="toggleRight(&quot;moderator&quot;)"
> >
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }} {{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
@ -29,13 +29,13 @@
/> />
</span> </span>
<button <button
class="dropdown-item" class="button-default dropdown-item"
@click="toggleActivationStatus()" @click="toggleActivationStatus()"
> >
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }} {{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
</button> </button>
<button <button
class="dropdown-item" class="button-default dropdown-item"
@click="deleteUserDialog(true)" @click="deleteUserDialog(true)"
> >
{{ $t('user_card.admin_menu.delete_account') }} {{ $t('user_card.admin_menu.delete_account') }}
@ -47,7 +47,7 @@
/> />
<span v-if="hasTagPolicy"> <span v-if="hasTagPolicy">
<button <button
class="dropdown-item" class="button-default dropdown-item"
@click="toggleTag(tags.FORCE_NSFW)" @click="toggleTag(tags.FORCE_NSFW)"
> >
{{ $t('user_card.admin_menu.force_nsfw') }} {{ $t('user_card.admin_menu.force_nsfw') }}
@ -57,7 +57,7 @@
/> />
</button> </button>
<button <button
class="dropdown-item" class="button-default dropdown-item"
@click="toggleTag(tags.STRIP_MEDIA)" @click="toggleTag(tags.STRIP_MEDIA)"
> >
{{ $t('user_card.admin_menu.strip_media') }} {{ $t('user_card.admin_menu.strip_media') }}
@ -67,7 +67,7 @@
/> />
</button> </button>
<button <button
class="dropdown-item" class="button-default dropdown-item"
@click="toggleTag(tags.FORCE_UNLISTED)" @click="toggleTag(tags.FORCE_UNLISTED)"
> >
{{ $t('user_card.admin_menu.force_unlisted') }} {{ $t('user_card.admin_menu.force_unlisted') }}
@ -77,7 +77,7 @@
/> />
</button> </button>
<button <button
class="dropdown-item" class="button-default dropdown-item"
@click="toggleTag(tags.SANDBOX)" @click="toggleTag(tags.SANDBOX)"
> >
{{ $t('user_card.admin_menu.sandbox') }} {{ $t('user_card.admin_menu.sandbox') }}
@ -88,7 +88,7 @@
</button> </button>
<button <button
v-if="user.is_local" v-if="user.is_local"
class="dropdown-item" class="button-default dropdown-item"
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)" @click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
> >
{{ $t('user_card.admin_menu.disable_remote_subscription') }} {{ $t('user_card.admin_menu.disable_remote_subscription') }}
@ -99,7 +99,7 @@
</button> </button>
<button <button
v-if="user.is_local" v-if="user.is_local"
class="dropdown-item" class="button-default dropdown-item"
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)" @click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
> >
{{ $t('user_card.admin_menu.disable_any_subscription') }} {{ $t('user_card.admin_menu.disable_any_subscription') }}
@ -110,7 +110,7 @@
</button> </button>
<button <button
v-if="user.is_local" v-if="user.is_local"
class="dropdown-item" class="button-default dropdown-item"
@click="toggleTag(tags.QUARANTINE)" @click="toggleTag(tags.QUARANTINE)"
> >
{{ $t('user_card.admin_menu.quarantine') }} {{ $t('user_card.admin_menu.quarantine') }}
@ -124,7 +124,7 @@
</div> </div>
<button <button
slot="trigger" slot="trigger"
class="btn btn-default btn-block" class="btn button-default btn-block"
:class="{ toggled }" :class="{ toggled }"
> >
{{ $t('user_card.admin_menu.moderation') }} {{ $t('user_card.admin_menu.moderation') }}
@ -141,13 +141,13 @@
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p> <p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
<template slot="footer"> <template slot="footer">
<button <button
class="btn btn-default" class="btn button-default"
@click="deleteUserDialog(false)" @click="deleteUserDialog(false)"
> >
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</button> </button>
<button <button
class="btn btn-default danger" class="btn button-default danger"
@click="deleteUser()" @click="deleteUser()"
> >
{{ $t('user_card.admin_menu.delete_user') }} {{ $t('user_card.admin_menu.delete_user') }}

View File

@ -3,7 +3,7 @@
<div class="mute-card-content-container"> <div class="mute-card-content-container">
<button <button
v-if="muted" v-if="muted"
class="btn btn-default" class="btn button-default"
:disabled="progress" :disabled="progress"
@click="unmuteUser" @click="unmuteUser"
> >
@ -16,7 +16,7 @@
</button> </button>
<button <button
v-else v-else
class="btn btn-default" class="btn button-default"
:disabled="progress" :disabled="progress"
@click="muteUser" @click="muteUser"
> >

View File

@ -14,14 +14,15 @@
{{ notification.from_profile.screen_name }} {{ notification.from_profile.screen_name }}
</router-link> </router-link>
</small> </small>
<a <button
href="#" class="button-unstyled unmute"
class="unmute"
@click.prevent="toggleMute" @click.prevent="toggleMute"
><FAIcon >
class="fa-scale-110 fa-old-padding" <FAIcon
icon="eye-slash" class="fa-scale-110 fa-old-padding"
/></a> icon="eye-slash"
/>
</button>
</div> </div>
<div <div
v-else v-else
@ -132,14 +133,16 @@
/> />
</span> </span>
</div> </div>
<a <button
v-if="needMute" v-if="needMute"
href="#" class="button-unstyled"
@click.prevent="toggleMute" @click.prevent="toggleMute"
><FAIcon >
class="fa-scale-110 fa-old-padding" <FAIcon
icon="eye-slash" class="fa-scale-110 fa-old-padding"
/></a> icon="eye-slash"
/>
</button>
</span> </span>
<div <div
v-if="notification.type === 'follow' || notification.type === 'follow_request'" v-if="notification.type === 'follow' || notification.type === 'follow_request'"

View File

@ -6,6 +6,7 @@ import {
filteredNotificationsFromStore, filteredNotificationsFromStore,
unseenNotificationsFromStore unseenNotificationsFromStore
} from '../../services/notification_utils/notification_utils.js' } from '../../services/notification_utils/notification_utils.js'
import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
@ -75,8 +76,10 @@ const Notifications = {
watch: { watch: {
unseenCountTitle (count) { unseenCountTitle (count) {
if (count > 0) { if (count > 0) {
FaviconService.drawFaviconBadge()
this.$store.dispatch('setPageTitle', `(${count})`) this.$store.dispatch('setPageTitle', `(${count})`)
} else { } else {
FaviconService.clearFaviconBadge()
this.$store.dispatch('setPageTitle', '') this.$store.dispatch('setPageTitle', '')
} }
} }

View File

@ -15,16 +15,9 @@
class="badge badge-notification unseen-count" class="badge badge-notification unseen-count"
>{{ unseenCount }}</span> >{{ unseenCount }}</span>
</div> </div>
<div
v-if="error"
class="loadmore-error alert error"
@click.prevent
>
{{ $t('timeline.error_fetching') }}
</div>
<button <button
v-if="unseenCount" v-if="unseenCount"
class="read-button" class="button-default read-button"
@click.prevent="markAsSeen" @click.prevent="markAsSeen"
> >
{{ $t('notifications.read') }} {{ $t('notifications.read') }}
@ -48,15 +41,15 @@
> >
{{ $t('notifications.no_more_notifications') }} {{ $t('notifications.no_more_notifications') }}
</div> </div>
<a <button
v-else-if="!loading" v-else-if="!loading"
href="#" class="button-unstyled -link -fullwidth"
@click.prevent="fetchOlderNotifications()" @click.prevent="fetchOlderNotifications()"
> >
<div class="new-status-notification text-center panel-footer"> <div class="new-status-notification text-center panel-footer">
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }} {{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
</div> </div>
</a> </button>
<div <div
v-else v-else
class="new-status-notification text-center panel-footer" class="new-status-notification text-center panel-footer"

View File

@ -51,7 +51,7 @@
<button <button
:disabled="isPending" :disabled="isPending"
type="submit" type="submit"
class="btn btn-default btn-block" class="btn button-default btn-block"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
</button> </button>

View File

@ -49,7 +49,7 @@
<div class="footer faint"> <div class="footer faint">
<button <button
v-if="!showResults" v-if="!showResults"
class="btn btn-default poll-vote-button" class="btn button-default poll-vote-button"
type="button" type="button"
:disabled="isDisabled" :disabled="isDisabled"
@click="vote" @click="vote"

View File

@ -21,7 +21,10 @@ const Popover = {
// Replaces the classes you may want for the popover container. // Replaces the classes you may want for the popover container.
// Use 'popover-default' in addition to get the default popover // Use 'popover-default' in addition to get the default popover
// styles with your custom class. // 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 () { data () {
return { return {
@ -96,9 +99,15 @@ const Popover = {
if (origin.y + content.offsetHeight > yBounds.max) usingTop = true if (origin.y + content.offsetHeight > yBounds.max) usingTop = true
if (origin.y - content.offsetHeight < yBounds.min) usingTop = false 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 yOffset = (this.offset && this.offset.y) || 0
const translateY = usingTop const translateY = usingTop
? -anchorEl.offsetHeight - yOffset - content.offsetHeight ? -anchorEl.offsetHeight + vPadding - yOffset - content.offsetHeight
: yOffset : yOffset
const xOffset = (this.offset && this.offset.x) || 0 const xOffset = (this.offset && this.offset.x) || 0

View File

@ -3,12 +3,13 @@
@mouseenter="onMouseenter" @mouseenter="onMouseenter"
@mouseleave="onMouseleave" @mouseleave="onMouseleave"
> >
<div <button
ref="trigger" ref="trigger"
class="button-unstyled -fullwidth popover-trigger-button"
@click="onClick" @click="onClick"
> >
<slot name="trigger" /> <slot name="trigger" />
</div> </button>
<div <div
v-if="!hidden" v-if="!hidden"
ref="content" ref="content"
@ -30,6 +31,10 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.popover-trigger-button {
display: block;
}
.popover { .popover {
z-index: 8; z-index: 8;
position: absolute; position: absolute;

View File

@ -159,8 +159,7 @@ const PostStatusForm = {
...this.$store.state.instance.emoji, ...this.$store.state.instance.emoji,
...this.$store.state.instance.customEmoji ...this.$store.state.instance.customEmoji
], ],
users: this.$store.state.users.users, store: this.$store
updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
}) })
}, },
emojiSuggestor () { emojiSuggestor () {

View File

@ -24,12 +24,12 @@
tag="p" tag="p"
class="visibility-notice" class="visibility-notice"
> >
<a <button
href="#" class="button-unstyled -link"
@click="openProfileTab" @click="openProfileTab"
> >
{{ $t('post_status.account_not_locked_warning_link') }} {{ $t('post_status.account_not_locked_warning_link') }}
</a> </button>
</i18n> </i18n>
<p <p
v-if="!hideScopeNotice && newStatus.visibility === 'public'" v-if="!hideScopeNotice && newStatus.visibility === 'public'"
@ -243,38 +243,34 @@
@upload-failed="uploadFailed" @upload-failed="uploadFailed"
@all-uploaded="finishedUploadingFiles" @all-uploaded="finishedUploadingFiles"
/> />
<div <button
class="emoji-icon" class="emoji-icon button-unstyled"
:title="$t('emoji.add_emoji')"
@click="showEmojiPicker"
> >
<div <FAIcon icon="smile-beam" />
:title="$t('emoji.add_emoji')" </button>
class="btn btn-default" <button
@click="showEmojiPicker"
>
<FAIcon icon="smile-beam" />
</div>
</div>
<div
v-if="pollsAvailable" v-if="pollsAvailable"
class="poll-icon" class="poll-icon button-unstyled"
:class="{ selected: pollFormVisible }" :class="{ selected: pollFormVisible }"
:title="$t('polls.add_poll')" :title="$t('polls.add_poll')"
@click="togglePollForm" @click="togglePollForm"
> >
<FAIcon icon="poll-h" /> <FAIcon icon="poll-h" />
</div> </button>
</div> </div>
<button <button
v-if="posting" v-if="posting"
disabled disabled
class="btn btn-default" class="btn button-default"
> >
{{ $t('post_status.posting') }} {{ $t('post_status.posting') }}
</button> </button>
<button <button
v-else-if="isOverLengthLimit" v-else-if="isOverLengthLimit"
disabled disabled
class="btn btn-default" class="btn button-default"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
</button> </button>
@ -282,7 +278,7 @@
<button <button
v-else v-else
:disabled="uploadingFiles || disableSubmit" :disabled="uploadingFiles || disableSubmit"
class="btn btn-default" class="btn button-default"
@touchstart.stop.prevent="postStatus($event, newStatus)" @touchstart.stop.prevent="postStatus($event, newStatus)"
@click.stop.prevent="postStatus($event, newStatus)" @click.stop.prevent="postStatus($event, newStatus)"
> >

View File

@ -27,13 +27,21 @@ const ReactButton = {
}, },
computed: { computed: {
commonEmojis () { commonEmojis () {
return ['👍', '😠', '👀', '😂', '🔥'] return [
{ displayText: 'thumbsup', replacement: '👍' },
{ displayText: 'angry', replacement: '😠' },
{ displayText: 'eyes', replacement: '👀' },
{ displayText: 'joy', replacement: '😂' },
{ displayText: 'fire', replacement: '🔥' }
]
}, },
emojis () { emojis () {
if (this.filterWord !== '') { if (this.filterWord !== '') {
const filterWordLowercase = this.filterWord.toLowerCase() const filterWordLowercase = this.filterWord.toLowerCase()
let orderedEmojiList = [] let orderedEmojiList = []
for (const emoji of this.$store.state.instance.emoji) { for (const emoji of this.$store.state.instance.emoji) {
if (emoji.replacement === this.filterWord) return [emoji]
const indexOfFilterWord = emoji.displayText.toLowerCase().indexOf(filterWordLowercase) const indexOfFilterWord = emoji.displayText.toLowerCase().indexOf(filterWordLowercase)
if (indexOfFilterWord > -1) { if (indexOfFilterWord > -1) {
if (!Array.isArray(orderedEmojiList[indexOfFilterWord])) { if (!Array.isArray(orderedEmojiList[indexOfFilterWord])) {

View File

@ -3,8 +3,8 @@
trigger="click" trigger="click"
placement="top" placement="top"
:offset="{ y: 5 }" :offset="{ y: 5 }"
class="react-button-popover"
:bound-to="{ x: 'container' }" :bound-to="{ x: 'container' }"
remove-padding
> >
<div <div
slot="content" slot="content"
@ -22,15 +22,17 @@
v-for="emoji in commonEmojis" v-for="emoji in commonEmojis"
:key="emoji" :key="emoji"
class="emoji-button" class="emoji-button"
:title="emoji.displayText"
@click="addReaction($event, emoji, close)" @click="addReaction($event, emoji, close)"
> >
{{ emoji }} {{ emoji.replacement }}
</span> </span>
<div class="reaction-picker-divider" /> <div class="reaction-picker-divider" />
<span <span
v-for="(emoji, key) in emojis" v-for="(emoji, key) in emojis"
:key="key" :key="key"
class="emoji-button" class="emoji-button"
:title="emoji.displayText"
@click="addReaction($event, emoji.replacement, close)" @click="addReaction($event, emoji.replacement, close)"
> >
{{ emoji.replacement }} {{ emoji.replacement }}
@ -38,11 +40,14 @@
<div class="reaction-bottom-fader" /> <div class="reaction-bottom-fader" />
</div> </div>
</div> </div>
<span slot="trigger"> <span
slot="trigger"
class="ReactButton"
:title="$t('tool_tip.add_reaction')"
>
<FAIcon <FAIcon
class="fa-scale-110 fa-old-padding add-reaction-button" class="fa-scale-110 fa-old-padding"
:icon="['far', 'smile-beam']" :icon="['far', 'smile-beam']"
:title="$t('tool_tip.add_reaction')"
/> />
</span> </span>
</Popover> </Popover>
@ -102,10 +107,11 @@
} }
} }
.add-reaction-button { .ReactButton {
cursor: pointer; padding: 10px;
margin: -10px;
&:hover { &:hover .svg-inline--fa {
color: $fallback--text; color: $fallback--text;
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
} }

View File

@ -211,7 +211,7 @@
<button <button
:disabled="isPending" :disabled="isPending"
type="submit" type="submit"
class="btn btn-default" class="btn button-default"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
</button> </button>

View File

@ -1,20 +1,28 @@
<template> <template>
<div> <div class="ReplyButton">
<FAIcon <button
v-if="loggedIn" v-if="loggedIn"
class="ReplyButton fa-scale-110 fa-old-padding -interactive" class="button-unstyled interactive"
icon="reply"
:title="$t('tool_tip.reply')"
:class="{'-active': replying}" :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')" :title="$t('tool_tip.reply')"
/> @click.prevent="$emit('toggle')"
<span v-if="status.replies_count > 0"> >
<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 }} {{ status.replies_count }}
</span> </span>
</div> </div>
@ -26,14 +34,25 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.ReplyButton { .ReplyButton {
&.-interactive { display: flex;
cursor: pointer;
&:hover, > :first-child {
&.-active { 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: $fallback--cBlue;
color: var(--cBlue, $fallback--cBlue); color: var(--cBlue, $fallback--cBlue);
} }
} }
} }
</style> </style>

View File

@ -24,11 +24,6 @@ const RetweetButton = {
} }
}, },
computed: { computed: {
classes () {
return {
'-repeated': this.status.repeated
}
},
mergedConfig () { mergedConfig () {
return this.$store.getters.mergedConfig return this.$store.getters.mergedConfig
} }

View File

@ -1,33 +1,38 @@
<template> <template>
<div v-if="loggedIn"> <div class="RetweetButton">
<template v-if="visibility !== 'private' && visibility !== 'direct'"> <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 <FAIcon
:class="classes" class="fa-scale-110 fa-old-padding"
class="RetweetButton fa-scale-110 fa-old-padding -interactive"
icon="retweet" icon="retweet"
:spin="animated" :spin="animated"
:title="$t('tool_tip.repeat')"
@click.prevent="retweet()"
/> />
<span v-if="!mergedConfig.hidePostStats && status.repeat_num > 0">{{ status.repeat_num }}</span> </button>
</template> <span v-else-if="loggedIn">
<template v-else>
<FAIcon <FAIcon
:class="classes" class="fa-scale-110 fa-old-padding"
class="RetweetButton fa-scale-110 fa-old-padding"
icon="lock" icon="lock"
:title="$t('timeline.no_retweet_hint')" :title="$t('timeline.no_retweet_hint')"
/> />
</template> </span>
</div> <span v-else>
<div v-else-if="!loggedIn"> <FAIcon
<FAIcon class="fa-scale-110 fa-old-padding"
:class="classes" icon="retweet"
class="fa-scale-110 fa-old-padding" :title="$t('tool_tip.repeat')"
icon="retweet" />
:title="$t('tool_tip.repeat')" </span>
/> <span
<span v-if="!mergedConfig.hidePostStats && status.repeat_num > 0">{{ status.repeat_num }}</span> v-if="!mergedConfig.hidePostStats && status.repeat_num > 0"
class="no-event"
>
{{ status.repeat_num }}
</span>
</div> </div>
</template> </template>
@ -37,19 +42,28 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.RetweetButton { .RetweetButton {
&.-interactive { display: flex;
cursor: pointer;
animation-duration: 0.6s;
&: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: $fallback--cGreen;
color: var(--cGreen, $fallback--cGreen); color: var(--cGreen, $fallback--cGreen);
} }
} }
&.-repeated {
color: $fallback--cGreen;
color: var(--cGreen, $fallback--cGreen);
}
} }
</style> </style>

View File

@ -3,9 +3,9 @@
v-if="!showNothing" v-if="!showNothing"
class="ScopeSelector" class="ScopeSelector"
> >
<span <button
v-if="showDirect" v-if="showDirect"
class="scope" class="button-unstyled scope"
:class="css.direct" :class="css.direct"
:title="$t('post_status.scope.direct')" :title="$t('post_status.scope.direct')"
@click="changeVis('direct')" @click="changeVis('direct')"
@ -14,10 +14,10 @@
icon="envelope" icon="envelope"
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</span> </button>
<span <button
v-if="showPrivate" v-if="showPrivate"
class="scope" class="button-unstyled scope"
:class="css.private" :class="css.private"
:title="$t('post_status.scope.private')" :title="$t('post_status.scope.private')"
@click="changeVis('private')" @click="changeVis('private')"
@ -26,10 +26,10 @@
icon="lock" icon="lock"
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</span> </button>
<span <button
v-if="showUnlisted" v-if="showUnlisted"
class="scope" class="button-unstyled scope"
:class="css.unlisted" :class="css.unlisted"
:title="$t('post_status.scope.unlisted')" :title="$t('post_status.scope.unlisted')"
@click="changeVis('unlisted')" @click="changeVis('unlisted')"
@ -38,10 +38,10 @@
icon="lock-open" icon="lock-open"
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</span> </button>
<span <button
v-if="showPublic" v-if="showPublic"
class="scope" class="button-unstyled scope"
:class="css.public" :class="css.public"
:title="$t('post_status.scope.public')" :title="$t('post_status.scope.public')"
@click="changeVis('public')" @click="changeVis('public')"
@ -50,7 +50,7 @@
icon="globe" icon="globe"
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</span> </button>
</div> </div>
</template> </template>

View File

@ -14,7 +14,7 @@
@keyup.enter="newQuery(searchTerm)" @keyup.enter="newQuery(searchTerm)"
> >
<button <button
class="btn search-button" class="btn button-default search-button"
@click="newQuery(searchTerm)" @click="newQuery(searchTerm)"
> >
<FAIcon icon="search" /> <FAIcon icon="search" />

View File

@ -3,17 +3,18 @@
class="SearchBar" class="SearchBar"
:class="{ '-expanded': !hidden }" :class="{ '-expanded': !hidden }"
> >
<a <button
v-if="hidden" v-if="hidden"
href="#" class="button-unstyled nav-icon"
class="nav-icon"
:title="$t('nav.search')" :title="$t('nav.search')"
><FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="search"
@click.prevent.stop="toggleHidden" @click.prevent.stop="toggleHidden"
/></a> >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="search"
/>
</button>
<template v-else> <template v-else>
<input <input
id="search-bar-input" id="search-bar-input"
@ -25,7 +26,7 @@
@keyup.enter="find(searchTerm)" @keyup.enter="find(searchTerm)"
> >
<button <button
class="btn search-button" class="button-default search-button"
@click="find(searchTerm)" @click="find(searchTerm)"
> >
<FAIcon <FAIcon
@ -33,14 +34,16 @@
icon="search" icon="search"
/> />
</button> </button>
<span> <button
class="button-unstyled cancel-search"
@click.prevent.stop="toggleHidden"
>
<FAIcon <FAIcon
fixed-width fixed-width
icon="times" icon="times"
class="cancel-icon fa-scale-110 fa-old-padding" class="cancel-icon fa-scale-110 fa-old-padding"
@click.prevent.stop="toggleHidden"
/> />
</span> </button>
</template> </template>
</div> </div>
</template> </template>
@ -69,8 +72,11 @@
flex: 1 0 auto; flex: 1 0 auto;
} }
.cancel-search {
height: 50px;
}
.cancel-icon { .cancel-icon {
cursor: pointer;
color: $fallback--text; color: $fallback--text;
color: var(--btnTopBarText, $fallback--text); color: var(--btnTopBarText, $fallback--text);
} }

View File

@ -30,13 +30,13 @@
</template> </template>
</transition> </transition>
<button <button
class="btn" class="btn button-default"
@click="peekModal" @click="peekModal"
> >
{{ $t('general.peek') }} {{ $t('general.peek') }}
</button> </button>
<button <button
class="btn" class="btn button-default"
@click="closeModal" @click="closeModal"
> >
{{ $t('general.close') }} {{ $t('general.close') }}

View File

@ -27,7 +27,7 @@
<div class="bulk-actions"> <div class="bulk-actions">
<ProgressButton <ProgressButton
v-if="selected.length > 0" v-if="selected.length > 0"
class="btn btn-default bulk-action-button" class="btn button-default bulk-action-button"
:click="() => blockUsers(selected)" :click="() => blockUsers(selected)"
> >
{{ $t('user_card.block') }} {{ $t('user_card.block') }}
@ -37,7 +37,7 @@
</ProgressButton> </ProgressButton>
<ProgressButton <ProgressButton
v-if="selected.length > 0" v-if="selected.length > 0"
class="btn btn-default" class="btn button-default"
:click="() => unblockUsers(selected)" :click="() => unblockUsers(selected)"
> >
{{ $t('user_card.unblock') }} {{ $t('user_card.unblock') }}
@ -85,7 +85,7 @@
<div class="bulk-actions"> <div class="bulk-actions">
<ProgressButton <ProgressButton
v-if="selected.length > 0" v-if="selected.length > 0"
class="btn btn-default" class="btn button-default"
:click="() => muteUsers(selected)" :click="() => muteUsers(selected)"
> >
{{ $t('user_card.mute') }} {{ $t('user_card.mute') }}
@ -95,7 +95,7 @@
</ProgressButton> </ProgressButton>
<ProgressButton <ProgressButton
v-if="selected.length > 0" v-if="selected.length > 0"
class="btn btn-default" class="btn button-default"
:click="() => unmuteUsers(selected)" :click="() => unmuteUsers(selected)"
> >
{{ $t('user_card.unmute') }} {{ $t('user_card.unmute') }}
@ -141,7 +141,7 @@
<div class="bulk-actions"> <div class="bulk-actions">
<ProgressButton <ProgressButton
v-if="selected.length > 0" v-if="selected.length > 0"
class="btn btn-default" class="btn button-default"
:click="() => unmuteDomains(selected)" :click="() => unmuteDomains(selected)"
> >
{{ $t('domain_mute_card.unmute') }} {{ $t('domain_mute_card.unmute') }}

View File

@ -21,7 +21,7 @@
<p>{{ $t('settings.notification_mutes') }}</p> <p>{{ $t('settings.notification_mutes') }}</p>
<p>{{ $t('settings.notification_blocks') }}</p> <p>{{ $t('settings.notification_blocks') }}</p>
<button <button
class="btn btn-default" class="btn button-default"
@click="updateNotificationSettings" @click="updateNotificationSettings"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}

View File

@ -68,8 +68,7 @@ const ProfileTab = {
...this.$store.state.instance.emoji, ...this.$store.state.instance.emoji,
...this.$store.state.instance.customEmoji ...this.$store.state.instance.customEmoji
], ],
users: this.$store.state.users.users, store: this.$store
updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
}) })
}, },
emojiSuggestor () { emojiSuggestor () {
@ -79,10 +78,7 @@ const ProfileTab = {
] }) ] })
}, },
userSuggestor () { userSuggestor () {
return suggestor({ return suggestor({ store: this.$store })
users: this.$store.state.users.users,
updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
})
}, },
fieldsLimits () { fieldsLimits () {
return this.$store.state.instance.fieldsLimits return this.$store.state.instance.fieldsLimits

View File

@ -150,7 +150,7 @@
</p> </p>
<button <button
:disabled="newName && newName.length === 0" :disabled="newName && newName.length === 0"
class="btn btn-default" class="btn button-default"
@click="updateProfile" @click="updateProfile"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
@ -179,7 +179,7 @@
<button <button
v-show="pickAvatarBtnVisible" v-show="pickAvatarBtnVisible"
id="pick-avatar" id="pick-avatar"
class="btn" class="button-default btn"
type="button" type="button"
> >
{{ $t('settings.upload_a_photo') }} {{ $t('settings.upload_a_photo') }}
@ -224,7 +224,7 @@
/> />
<button <button
v-else-if="bannerPreview" v-else-if="bannerPreview"
class="btn btn-default" class="btn button-default"
@click="submitBanner(banner)" @click="submitBanner(banner)"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
@ -274,7 +274,7 @@
/> />
<button <button
v-else-if="backgroundPreview" v-else-if="backgroundPreview"
class="btn btn-default" class="btn button-default"
@click="submitBackground(background)" @click="submitBackground(background)"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}

View File

@ -2,14 +2,14 @@
<div> <div>
<slot /> <slot />
<button <button
class="btn btn-default" class="btn button-default"
:disabled="disabled" :disabled="disabled"
@click="confirm" @click="confirm"
> >
{{ $t('general.confirm') }} {{ $t('general.confirm') }}
</button> </button>
<button <button
class="btn btn-default" class="btn button-default"
:disabled="disabled" :disabled="disabled"
@click="cancel" @click="cancel"
> >

View File

@ -29,7 +29,7 @@
/> />
<button <button
v-if="!confirmNewBackupCodes" v-if="!confirmNewBackupCodes"
class="btn btn-default" class="btn button-default"
@click="getBackupCodes" @click="getBackupCodes"
> >
{{ $t('settings.mfa.generate_new_recovery_codes') }} {{ $t('settings.mfa.generate_new_recovery_codes') }}
@ -61,7 +61,7 @@
<button <button
v-if="canSetupOTP" v-if="canSetupOTP"
class="btn btn-default" class="btn button-default"
@click="cancelSetup" @click="cancelSetup"
> >
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
@ -69,7 +69,7 @@
<button <button
v-if="canSetupOTP" v-if="canSetupOTP"
class="btn btn-default" class="btn button-default"
@click="setupOTP" @click="setupOTP"
> >
{{ $t('settings.mfa.setup_otp') }} {{ $t('settings.mfa.setup_otp') }}
@ -108,13 +108,13 @@
> >
<div class="confirm-otp-actions"> <div class="confirm-otp-actions">
<button <button
class="btn btn-default" class="btn button-default"
@click="doConfirmOTP" @click="doConfirmOTP"
> >
{{ $t('settings.mfa.confirm_and_enable') }} {{ $t('settings.mfa.confirm_and_enable') }}
</button> </button>
<button <button
class="btn btn-default" class="btn button-default"
@click="cancelSetup" @click="cancelSetup"
> >
{{ $t('general.cancel') }} {{ $t('general.cancel') }}

View File

@ -4,7 +4,7 @@
<strong>{{ $t('settings.mfa.otp') }}</strong> <strong>{{ $t('settings.mfa.otp') }}</strong>
<button <button
v-if="!isActivated" v-if="!isActivated"
class="btn btn-default" class="btn button-default"
@click="doActivate" @click="doActivate"
> >
{{ $t('general.enable') }} {{ $t('general.enable') }}
@ -12,7 +12,7 @@
<button <button
v-if="isActivated" v-if="isActivated"
class="btn btn-default" class="btn button-default"
:disabled="deactivate" :disabled="deactivate"
@click="doDeactivate" @click="doDeactivate"
> >

View File

@ -19,7 +19,7 @@
> >
</div> </div>
<button <button
class="btn btn-default" class="btn button-default"
@click="changeEmail" @click="changeEmail"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
@ -57,7 +57,7 @@
> >
</div> </div>
<button <button
class="btn btn-default" class="btn button-default"
@click="changePassword" @click="changePassword"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
@ -92,7 +92,7 @@
<td>{{ oauthToken.validUntil }}</td> <td>{{ oauthToken.validUntil }}</td>
<td class="actions"> <td class="actions">
<button <button
class="btn btn-default" class="btn button-default"
@click="revokeToken(oauthToken.id)" @click="revokeToken(oauthToken.id)"
> >
{{ $t('settings.revoke_token') }} {{ $t('settings.revoke_token') }}
@ -116,7 +116,7 @@
type="password" type="password"
> >
<button <button
class="btn btn-default" class="btn button-default"
@click="deleteAccount" @click="deleteAccount"
> >
{{ $t('settings.delete_account') }} {{ $t('settings.delete_account') }}
@ -130,7 +130,7 @@
</p> </p>
<button <button
v-if="!deletingAccount" v-if="!deletingAccount"
class="btn btn-default" class="btn button-default"
@click="confirmDelete" @click="confirmDelete"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}

View File

@ -15,7 +15,7 @@
<span class="alert error"> <span class="alert error">
{{ $t('settings.style.preview.error') }} {{ $t('settings.style.preview.error') }}
</span> </span>
<button class="btn"> <button class="btn button-default">
{{ $t('settings.style.preview.button') }} {{ $t('settings.style.preview.button') }}
</button> </button>
</div> </div>
@ -102,7 +102,7 @@
> >
<label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label> <label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
</span> </span>
<button class="btn"> <button class="btn button-default">
{{ $t('settings.style.preview.button') }} {{ $t('settings.style.preview.button') }}
</button> </button>
</div> </div>

View File

@ -12,13 +12,13 @@
<div class="buttons"> <div class="buttons">
<template v-if="themeWarning.type === 'snapshot_source_mismatch'"> <template v-if="themeWarning.type === 'snapshot_source_mismatch'">
<button <button
class="btn" class="btn button-default"
@click="forceLoad" @click="forceLoad"
> >
{{ $t('settings.style.switcher.use_source') }} {{ $t('settings.style.switcher.use_source') }}
</button> </button>
<button <button
class="btn" class="btn button-default"
@click="forceSnapshot" @click="forceSnapshot"
> >
{{ $t('settings.style.switcher.use_snapshot') }} {{ $t('settings.style.switcher.use_snapshot') }}
@ -26,7 +26,7 @@
</template> </template>
<template v-else-if="themeWarning.noActionsPossible"> <template v-else-if="themeWarning.noActionsPossible">
<button <button
class="btn" class="btn button-default"
@click="dismissWarning" @click="dismissWarning"
> >
{{ $t('general.dismiss') }} {{ $t('general.dismiss') }}
@ -34,13 +34,13 @@
</template> </template>
<template v-else> <template v-else>
<button <button
class="btn" class="btn button-default"
@click="forceLoad" @click="forceLoad"
> >
{{ $t('settings.style.switcher.load_theme') }} {{ $t('settings.style.switcher.load_theme') }}
</button> </button>
<button <button
class="btn" class="btn button-default"
@click="dismissWarning" @click="dismissWarning"
> >
{{ $t('settings.style.switcher.keep_as_is') }} {{ $t('settings.style.switcher.keep_as_is') }}
@ -131,13 +131,13 @@
<p>{{ $t('settings.theme_help') }}</p> <p>{{ $t('settings.theme_help') }}</p>
<div class="tab-header-buttons"> <div class="tab-header-buttons">
<button <button
class="btn" class="btn button-default"
@click="clearOpacity" @click="clearOpacity"
> >
{{ $t('settings.style.switcher.clear_opacity') }} {{ $t('settings.style.switcher.clear_opacity') }}
</button> </button>
<button <button
class="btn" class="btn button-default"
@click="clearV1" @click="clearV1"
> >
{{ $t('settings.style.switcher.clear_all') }} {{ $t('settings.style.switcher.clear_all') }}
@ -238,13 +238,13 @@
<div class="tab-header"> <div class="tab-header">
<p>{{ $t('settings.theme_help') }}</p> <p>{{ $t('settings.theme_help') }}</p>
<button <button
class="btn" class="btn button-default"
@click="clearOpacity" @click="clearOpacity"
> >
{{ $t('settings.style.switcher.clear_opacity') }} {{ $t('settings.style.switcher.clear_opacity') }}
</button> </button>
<button <button
class="btn" class="btn button-default"
@click="clearV1" @click="clearV1"
> >
{{ $t('settings.style.switcher.clear_all') }} {{ $t('settings.style.switcher.clear_all') }}
@ -806,7 +806,7 @@
<div class="tab-header"> <div class="tab-header">
<p>{{ $t('settings.radii_help') }}</p> <p>{{ $t('settings.radii_help') }}</p>
<button <button
class="btn" class="btn button-default"
@click="clearRoundness" @click="clearRoundness"
> >
{{ $t('settings.style.switcher.clear_all') }} {{ $t('settings.style.switcher.clear_all') }}
@ -936,7 +936,7 @@
/> />
</div> </div>
<button <button
class="btn" class="btn button-default"
@click="clearShadows" @click="clearShadows"
> >
{{ $t('settings.style.switcher.clear_all') }} {{ $t('settings.style.switcher.clear_all') }}
@ -980,7 +980,7 @@
<div class="tab-header"> <div class="tab-header">
<p>{{ $t('settings.style.fonts.help') }}</p> <p>{{ $t('settings.style.fonts.help') }}</p>
<button <button
class="btn" class="btn button-default"
@click="clearFonts" @click="clearFonts"
> >
{{ $t('settings.style.switcher.clear_all') }} {{ $t('settings.style.switcher.clear_all') }}
@ -1017,14 +1017,14 @@
<div class="apply-container"> <div class="apply-container">
<button <button
class="btn submit" class="btn button-default submit"
:disabled="!themeValid" :disabled="!themeValid"
@click="setCustomTheme" @click="setCustomTheme"
> >
{{ $t('general.apply') }} {{ $t('general.apply') }}
</button> </button>
<button <button
class="btn" class="btn button-default"
@click="clearAll" @click="clearAll"
> >
{{ $t('settings.style.switcher.reset') }} {{ $t('settings.style.switcher.reset') }}

View File

@ -84,7 +84,7 @@
/> />
</label> </label>
<button <button
class="btn btn-default" class="btn button-default"
:disabled="!ready || !present" :disabled="!ready || !present"
@click="del" @click="del"
> >
@ -94,7 +94,7 @@
/> />
</button> </button>
<button <button
class="btn btn-default" class="btn button-default"
:disabled="!moveUpValid" :disabled="!moveUpValid"
@click="moveUp" @click="moveUp"
> >
@ -104,7 +104,7 @@
/> />
</button> </button>
<button <button
class="btn btn-default" class="btn button-default"
:disabled="!moveDnValid" :disabled="!moveDnValid"
@click="moveDn" @click="moveDn"
> >
@ -114,7 +114,7 @@
/> />
</button> </button>
<button <button
class="btn btn-default" class="btn button-default"
:disabled="usingFallback" :disabled="usingFallback"
@click="add" @click="add"
> >

View File

@ -144,8 +144,8 @@
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
<a <button
href="#" class="button-unstyled -link -fullwidth"
@click="openSettingsModal" @click="openSettingsModal"
> >
<FAIcon <FAIcon
@ -153,7 +153,7 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
icon="cog" icon="cog"
/> {{ $t("settings.settings") }} /> {{ $t("settings.settings") }}
</a> </button>
</li> </li>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
<router-link :to="{ name: 'about'}"> <router-link :to="{ name: 'about'}">
@ -183,8 +183,8 @@
v-if="currentUser" v-if="currentUser"
@click="toggleDrawer" @click="toggleDrawer"
> >
<a <button
href="#" class="button-unstyled -link -fullwidth"
@click="doLogout" @click="doLogout"
> >
<FAIcon <FAIcon
@ -192,7 +192,7 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
icon="sign-out-alt" icon="sign-out-alt"
/> {{ $t("login.logout") }} /> {{ $t("login.logout") }}
</a> </button>
</li> </li>
</ul> </ul>
</div> </div>
@ -331,7 +331,7 @@
.side-drawer li { .side-drawer li {
padding: 0; padding: 0;
a { a, button {
box-sizing: border-box; box-sizing: border-box;
display: block; display: block;
height: 3em; height: 3em;

View File

@ -29,6 +29,8 @@ $status-margin: 0.75em;
&.-conversation { &.-conversation {
border-left-width: 4px; border-left-width: 4px;
border-left-style: solid; border-left-style: solid;
border-left-color: $fallback--cRed;
border-left-color: var(--cRed, $fallback--cRed);
} }
.gravestone { .gravestone {

View File

@ -47,16 +47,15 @@
> >
{{ muteWordHits.join(', ') }} {{ muteWordHits.join(', ') }}
</small> </small>
<a <button
href="#" class="unmute button-unstyled"
class="unmute fa-scale-110 fa-old-padding"
@click.prevent="toggleMute" @click.prevent="toggleMute"
> >
<FAIcon <FAIcon
icon="eye-slash" icon="eye-slash"
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</a> </button>
</div> </div>
</template> </template>
<template v-else> <template v-else>
@ -201,9 +200,9 @@
icon="external-link-square-alt" icon="external-link-square-alt"
/> />
</a> </a>
<a <button
v-if="expandable && !isPreview" v-if="expandable && !isPreview"
href="#" class="button-unstyled"
title="Expand" title="Expand"
@click.prevent="toggleExpanded" @click.prevent="toggleExpanded"
> >
@ -211,17 +210,17 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
icon="plus-square" icon="plus-square"
/> />
</a> </button>
<a <button
v-if="unmuted" v-if="unmuted"
href="#" class="button-unstyled"
@click.prevent="toggleMute" @click.prevent="toggleMute"
> >
<FAIcon <FAIcon
icon="eye-slash" icon="eye-slash"
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</a> </button>
</span> </span>
</div> </div>
@ -237,9 +236,8 @@
style="min-width: 0" style="min-width: 0"
:class="{ '-strikethrough': !status.parent_visible }" :class="{ '-strikethrough': !status.parent_visible }"
> >
<a <button
class="reply-to" class="button-unstyled reply-to"
href="#"
:aria-label="$t('tool_tip.reply')" :aria-label="$t('tool_tip.reply')"
@click.prevent="gotoOriginal(status.in_reply_to_status_id)" @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
> >
@ -253,7 +251,7 @@
> >
{{ $t('status.reply_to') }} {{ $t('status.reply_to') }}
</span> </span>
</a> </button>
</StatusPopover> </StatusPopover>
<span <span
@ -286,11 +284,12 @@
:key="reply.id" :key="reply.id"
:status-id="reply.id" :status-id="reply.id"
> >
<a <button
href="#" class="button-unstyled -link reply-link"
class="reply-link"
@click.prevent="gotoOriginal(reply.id)" @click.prevent="gotoOriginal(reply.id)"
>{{ reply.name }}</a> >
{{ reply.name }}
</button>
</StatusPopover> </StatusPopover>
</div> </div>
</div> </div>

View File

@ -12,35 +12,34 @@
@click.prevent="linkClicked" @click.prevent="linkClicked"
v-html="status.summary_html" v-html="status.summary_html"
/> />
<a <button
v-if="longSubject && showingLongSubject" v-if="longSubject && showingLongSubject"
href="#" class="button-unstyled -link tall-subject-hider"
class="tall-subject-hider"
@click.prevent="showingLongSubject=false" @click.prevent="showingLongSubject=false"
>{{ $t("status.hide_full_subject") }}</a> >
<a {{ $t("status.hide_full_subject") }}
</button>
<button
v-else-if="longSubject" v-else-if="longSubject"
class="tall-subject-hider" class="button-unstyled -link tall-subject-hider"
:class="{ 'tall-subject-hider_focused': focused }" :class="{ 'tall-subject-hider_focused': focused }"
href="#"
@click.prevent="showingLongSubject=true" @click.prevent="showingLongSubject=true"
> >
{{ $t("status.show_full_subject") }} {{ $t("status.show_full_subject") }}
</a> </button>
</div> </div>
<div <div
:class="{'tall-status': hideTallStatus}" :class="{'tall-status': hideTallStatus}"
class="status-content-wrapper" class="status-content-wrapper"
> >
<a <button
v-if="hideTallStatus" v-if="hideTallStatus"
class="tall-status-hider" class="button-unstyled -link tall-status-hider"
:class="{ 'tall-status-hider_focused': focused }" :class="{ 'tall-status-hider_focused': focused }"
href="#"
@click.prevent="toggleShowMore" @click.prevent="toggleShowMore"
> >
{{ $t("general.show_more") }} {{ $t("general.show_more") }}
</a> </button>
<div <div
v-if="!hideSubjectStatus" v-if="!hideSubjectStatus"
:class="{ 'single-line': singleLine }" :class="{ 'single-line': singleLine }"
@ -48,10 +47,9 @@
@click.prevent="linkClicked" @click.prevent="linkClicked"
v-html="postBodyHtml" v-html="postBodyHtml"
/> />
<a <button
v-if="hideSubjectStatus" v-if="hideSubjectStatus"
href="#" class="button-unstyled -link cw-status-hider"
class="cw-status-hider"
@click.prevent="toggleShowMore" @click.prevent="toggleShowMore"
> >
{{ $t("status.show_content") }} {{ $t("status.show_content") }}
@ -79,15 +77,14 @@
v-if="status.card" v-if="status.card"
icon="link" icon="link"
/> />
</a> </button>
<a <button
v-if="showingMore && !fullContent" v-if="showingMore && !fullContent"
href="#" class="button-unstyled -link status-unhider"
class="status-unhider"
@click.prevent="toggleShowMore" @click.prevent="toggleShowMore"
> >
{{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }} {{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }}
</a> </button>
</div> </div>
<div v-if="status.poll && status.poll.options && !hideSubjectStatus"> <div v-if="status.poll && status.poll.options && !hideSubjectStatus">

View File

@ -81,7 +81,7 @@ export default Vue.component('tab-switcher', {
const tabs = this.$slots.default const tabs = this.$slots.default
.map((slot, index) => { .map((slot, index) => {
if (!slot.tag) return if (!slot.tag) return
const classesTab = ['tab'] const classesTab = ['tab', 'button-default']
const classesWrapper = ['tab-wrapper'] const classesWrapper = ['tab-wrapper']
if (this.activeIndex === index) { if (this.activeIndex === index) {
classesTab.push('active') classesTab.push('active')

View File

@ -50,17 +50,10 @@ const Timeline = {
TimelineMenu TimelineMenu
}, },
computed: { computed: {
timelineError () {
return this.$store.state.statuses.error
},
errorData () {
return this.$store.state.statuses.errorData
},
newStatusCount () { newStatusCount () {
return this.timeline.newStatusCount return this.timeline.newStatusCount
}, },
showLoadButton () { showLoadButton () {
if (this.timelineError || this.errorData) return false
return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0 return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0
}, },
loadButtonString () { loadButtonString () {
@ -171,11 +164,12 @@ const Timeline = {
userId: this.userId, userId: this.userId,
tag: this.tag tag: this.tag
}).then(({ statuses }) => { }).then(({ statuses }) => {
store.commit('setLoading', { timeline: this.timelineName, value: false })
if (statuses && statuses.length === 0) { if (statuses && statuses.length === 0) {
this.bottomedOut = true this.bottomedOut = true
} }
}) }).finally(() =>
store.commit('setLoading', { timeline: this.timelineName, value: false })
)
}, 1000, this), }, 1000, this),
determineVisibleStatuses () { determineVisibleStatuses () {
if (!this.$refs.timeline) return if (!this.$refs.timeline) return

View File

@ -2,23 +2,9 @@
<div :class="[classes.root, 'Timeline']"> <div :class="[classes.root, 'Timeline']">
<div :class="classes.header"> <div :class="classes.header">
<TimelineMenu v-if="!embedded" /> <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 <button
v-else-if="showLoadButton" v-if="showLoadButton"
class="loadmore-button" class="button-default loadmore-button"
@click.prevent="showNewStatuses" @click.prevent="showNewStatuses"
> >
{{ loadButtonString }} {{ loadButtonString }}
@ -75,19 +61,15 @@
> >
{{ $t('timeline.no_more_statuses') }} {{ $t('timeline.no_more_statuses') }}
</div> </div>
<a <button
v-else-if="!timeline.loading && !errorData" v-else-if="!timeline.loading"
href="#" class="button-unstyled -link -fullwidth"
@click.prevent="fetchOlderStatuses()" @click.prevent="fetchOlderStatuses()"
> >
<div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div> <div class="new-status-notification text-center panel-footer">
</a> {{ $t('timeline.load_older') }}
<a </div>
v-else-if="errorData" </button>
href="#"
>
<div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div>
</a>
<div <div
v-else v-else
class="new-status-notification text-center panel-footer" class="new-status-notification text-center panel-footer"

View File

@ -19,7 +19,7 @@ library.add(
faChevronDown 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. // because nav panel benefits from the same information.
export const timelineNames = () => { export const timelineNames = () => {
return { return {
@ -27,8 +27,7 @@ export const timelineNames = () => {
'bookmarks': 'nav.bookmarks', 'bookmarks': 'nav.bookmarks',
'dms': 'nav.dms', 'dms': 'nav.dms',
'public-timeline': 'nav.public_tl', 'public-timeline': 'nav.public_tl',
'public-external-timeline': 'nav.twkn', 'public-external-timeline': 'nav.twkn'
'tag-timeline': 'tag'
} }
} }

View File

@ -162,7 +162,7 @@
<template v-if="relationship.following"> <template v-if="relationship.following">
<ProgressButton <ProgressButton
v-if="!relationship.subscribing" v-if="!relationship.subscribing"
class="btn btn-default" class="btn button-default"
:click="subscribeUser" :click="subscribeUser"
:title="$t('user_card.subscribe')" :title="$t('user_card.subscribe')"
> >
@ -170,7 +170,7 @@
</ProgressButton> </ProgressButton>
<ProgressButton <ProgressButton
v-else v-else
class="btn btn-default toggled" class="btn button-default toggled"
:click="unsubscribeUser" :click="unsubscribeUser"
:title="$t('user_card.unsubscribe')" :title="$t('user_card.unsubscribe')"
> >
@ -192,14 +192,14 @@
<div> <div>
<button <button
v-if="relationship.muting" v-if="relationship.muting"
class="btn btn-default btn-block toggled" class="btn button-default btn-block toggled"
@click="unmuteUser" @click="unmuteUser"
> >
{{ $t('user_card.muted') }} {{ $t('user_card.muted') }}
</button> </button>
<button <button
v-else v-else
class="btn btn-default btn-block" class="btn button-default btn-block"
@click="muteUser" @click="muteUser"
> >
{{ $t('user_card.mute') }} {{ $t('user_card.mute') }}
@ -207,7 +207,7 @@
</div> </div>
<div> <div>
<button <button
class="btn btn-default btn-block" class="btn button-default btn-block"
@click="mentionUser" @click="mentionUser"
> >
{{ $t('user_card.mention') }} {{ $t('user_card.mention') }}

View File

@ -29,7 +29,7 @@
</div> </div>
<div> <div>
<button <button
class="btn btn-default" class="btn button-default"
:disabled="processing" :disabled="processing"
@click="reportUser" @click="reportUser"
> >

View File

@ -1,6 +1,7 @@
<template> <template>
<video <video
class="video" class="video"
preload="metadata"
:src="attachment.url" :src="attachment.url"
:loop="loopVideo" :loop="loopVideo"
:controls="controls" :controls="controls"

View File

@ -91,7 +91,11 @@ const withLoadMore = ({
{children} {children}
</WrappedComponent> </WrappedComponent>
<div class="with-load-more-footer"> <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 && <FAIcon spin icon="circle-notch"/>}
{!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries}>{this.$t('general.more')}</a>} {!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries}>{this.$t('general.more')}</a>}
</div> </div>

View File

@ -130,6 +130,7 @@
}, },
"notifications": { "notifications": {
"broken_favorite": "Unknown status, searching for it…", "broken_favorite": "Unknown status, searching for it…",
"error": "Error fetching notifications: {0}",
"favorited_you": "favorited your status", "favorited_you": "favorited your status",
"followed_you": "followed you", "followed_you": "followed you",
"follow_request": "wants to follow you", "follow_request": "wants to follow you",
@ -376,7 +377,7 @@
"hide_followers_count_description": "Don't show follower count", "hide_followers_count_description": "Don't show follower count",
"show_admin_badge": "Show Admin badge in my profile", "show_admin_badge": "Show Admin badge in my profile",
"show_moderator_badge": "Show Moderator 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", "oauth_tokens": "OAuth tokens",
"token": "Token", "token": "Token",
"refresh_token": "Refresh Token", "refresh_token": "Refresh Token",
@ -634,7 +635,7 @@
"timeline": { "timeline": {
"collapse": "Collapse", "collapse": "Collapse",
"conversation": "Conversation", "conversation": "Conversation",
"error_fetching": "Error fetching updates", "error": "Error fetching timeline: {0}",
"load_older": "Load older statuses", "load_older": "Load older statuses",
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated", "no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
"repeated": "repeated", "repeated": "repeated",
@ -666,7 +667,8 @@
"hide_full_subject": "Hide full subject", "hide_full_subject": "Hide full subject",
"show_content": "Show content", "show_content": "Show content",
"hide_content": "Hide content", "hide_content": "Hide content",
"status_deleted": "This post was deleted" "status_deleted": "This post was deleted",
"nsfw": "NSFW"
}, },
"user_card": { "user_card": {
"approve": "Approve", "approve": "Approve",

View File

@ -134,14 +134,14 @@
"registration": { "registration": {
"bio": "Priskribo", "bio": "Priskribo",
"email": "Retpoŝtadreso", "email": "Retpoŝtadreso",
"fullname": "Vidiga nomo", "fullname": "Prezenta nomo",
"password_confirm": "Konfirmo de pasvorto", "password_confirm": "Konfirmo de pasvorto",
"registration": "Registriĝo", "registration": "Registriĝo",
"token": "Invita ĵetono", "token": "Invita ĵetono",
"captcha": "TESTO DE HOMECO", "captcha": "TESTO DE HOMECO",
"new_captcha": "Klaku la bildon por akiri novan teston", "new_captcha": "Klaku la bildon por akiri novan teston",
"username_placeholder": "ekz. lain", "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».", "bio_placeholder": "ekz.\nSaluton, mi estas Lain.\nMi estas animea knabino vivanta en Japanujo. Eble vi konas min pro la retejo «Wired».",
"validations": { "validations": {
"username_required": "ne povas resti malplena", "username_required": "ne povas resti malplena",
@ -164,7 +164,7 @@
"blocks_tab": "Blokitoj", "blocks_tab": "Blokitoj",
"btnRadius": "Butonoj", "btnRadius": "Butonoj",
"cBlue": "Blua (respondi, aboni)", "cBlue": "Blua (respondi, aboni)",
"cGreen": "Verda (kunhavigi)", "cGreen": "Verda (diskonigi)",
"cOrange": "Oranĝa (ŝati)", "cOrange": "Oranĝa (ŝati)",
"cRed": "Ruĝa (nuligi)", "cRed": "Ruĝa (nuligi)",
"change_password": "Ŝanĝi pasvorton", "change_password": "Ŝanĝi pasvorton",
@ -207,8 +207,8 @@
"import_theme": "Enlegi antaŭagordojn", "import_theme": "Enlegi antaŭagordojn",
"inputRadius": "Enigaj kampoj", "inputRadius": "Enigaj kampoj",
"checkboxRadius": "Markbutonoj", "checkboxRadius": "Markbutonoj",
"instance_default": "(implicita: {value})", "instance_default": "(originale: {value})",
"instance_default_simple": "(implicita)", "instance_default_simple": "(originale)",
"interface": "Fasado", "interface": "Fasado",
"interfaceLanguage": "Lingvo de 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.", "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)", "loop_video_silent_only": "Ripetadi nur filmojn sen sono (ekz. la «GIF-ojn» de Mastodon)",
"mutes_tab": "Silentigoj", "mutes_tab": "Silentigoj",
"play_videos_in_modal": "Ludi filmojn en ŝpruca kadro", "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": "Nomo",
"name_bio": "Nomo kaj priskribo", "name_bio": "Nomo kaj priskribo",
"new_password": "Nova pasvorto", "new_password": "Nova pasvorto",
@ -265,7 +265,7 @@
"subject_line_email": "Kiel retpoŝto: «re: temo»", "subject_line_email": "Kiel retpoŝto: «re: temo»",
"subject_line_mastodon": "Kiel Mastodon: kopii senŝanĝe", "subject_line_mastodon": "Kiel Mastodon: kopii senŝanĝe",
"subject_line_noop": "Ne kopii", "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", "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", "streaming": "Ŝalti memagan fluigon de novaj afiŝoj kiam vi vidas la supron de la paĝo",
"text": "Teksto", "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.", "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": { "filter_hint": {
"always_drop_shadow": "Averto: ĉi tiu ombro ĉiam uzas {0} kiam la foliumilo tion subtenas.", "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.", "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", "spread_zero": "Ombroj kun vastigo > 0 aperos kvazaŭ ĝi estus fakte nulo",
"inset_classic": "Internaj ombroj uzos {0}" "inset_classic": "Internaj ombroj uzos {0}"
@ -394,7 +394,7 @@
"button": "Butono", "button": "Butono",
"buttonHover": "Butono (je ŝvebo)", "buttonHover": "Butono (je ŝvebo)",
"buttonPressed": "Butono (premita)", "buttonPressed": "Butono (premita)",
"buttonPressedHover": "Butono (premita kaj je ŝvebo)", "buttonPressedHover": "Butono (je premo kaj ŝvebo)",
"input": "Eniga kampo" "input": "Eniga kampo"
}, },
"hintV3": "Kolorojn de ombroj vi ankaŭ povas skribi per la sistemo {0}." "hintV3": "Kolorojn de ombroj vi ankaŭ povas skribi per la sistemo {0}."
@ -683,7 +683,7 @@
"replace": "Anstataŭigi", "replace": "Anstataŭigi",
"reject": "Rifuzi", "reject": "Rifuzi",
"ftl_removal": "Forigo el la historio de «La tuta konata reto»", "ftl_removal": "Forigo el la historio de «La tuta konata reto»",
"keyword_policies": "Politiko pri ŝlosilvortoj" "keyword_policies": "Politiko pri ĉefvortoj"
}, },
"federation": "Federado", "federation": "Federado",
"mrf_policies_desc": "Politikoj de Mesaĝa ŝanĝilaro (MRF) efikas sur federa konduto de la nodo. La sekvaj politikoj estas ŝaltitaj:" "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", "week_short": "{0}s",
"weeks": "{0} semajnoj", "weeks": "{0} semajnoj",
"week": "{0} semajno", "week": "{0} semajno",
"seconds_short": "{0}s", "seconds_short": "{0}sek",
"second_short": "{0}s", "second_short": "{0}sek",
"seconds": "{0} sekundoj", "seconds": "{0} sekundoj",
"second": "{0} sekundo", "second": "{0} sekundo",
"now_short": "nun", "now_short": "nun",
@ -749,14 +749,14 @@
"month_short": "{0}m", "month_short": "{0}m",
"months": "{0} monatoj", "months": "{0} monatoj",
"month": "{0} monato", "month": "{0} monato",
"minutes_short": "{0}m", "minutes_short": "{0}min",
"minute_short": "{0}m", "minute_short": "{0}min",
"minutes": "{0} minutoj", "minutes": "{0} minutoj",
"minute": "{0} minuto", "minute": "{0} minuto",
"in_past": "antaŭ {0}", "in_past": "antaŭ {0}",
"in_future": "post {0}", "in_future": "post {0}",
"hours_short": "{0}h", "hours_short": "{0}hor",
"hour_short": "{0}h", "hour_short": "{0}hor",
"hours": "{0} horoj", "hours": "{0} horoj",
"hour": "{0} horo", "hour": "{0} horo",
"days_short": "{0}t", "days_short": "{0}t",

View File

@ -104,7 +104,8 @@
"no_more_notifications": "No hay más notificaciones", "no_more_notifications": "No hay más notificaciones",
"reacted_with": "reaccionó con {0}", "reacted_with": "reaccionó con {0}",
"migrated_to": "migrado a", "migrated_to": "migrado a",
"follow_request": "quiere seguirte" "follow_request": "quiere seguirte",
"error": "Error obteniendo notificaciones:{0}"
}, },
"polls": { "polls": {
"add_poll": "Añadir encuesta", "add_poll": "Añadir encuesta",
@ -313,7 +314,7 @@
"hide_followers_count_description": "No mostrar el número de cuentas que me siguen", "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_admin_badge": "Mostrar la insignia de Administrador en mi perfil",
"show_moderator_badge": "Mostrar la insignia de Moderador 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", "oauth_tokens": "Tokens de OAuth",
"token": "Token", "token": "Token",
"refresh_token": "Actualizar el token", "refresh_token": "Actualizar el token",
@ -605,7 +606,8 @@
"up_to_date": "Actualizado", "up_to_date": "Actualizado",
"no_more_statuses": "No hay más estados", "no_more_statuses": "No hay más estados",
"no_statuses": "Sin estados", "no_statuses": "Sin estados",
"reload": "Recargar" "reload": "Recargar",
"error": "Error obteniendo la linea de tiempo:{0}"
}, },
"status": { "status": {
"favorites": "Favoritos", "favorites": "Favoritos",
@ -628,7 +630,9 @@
"copy_link": "Copiar el enlace al estado", "copy_link": "Copiar el enlace al estado",
"status_unavailable": "Estado no disponible", "status_unavailable": "Estado no disponible",
"bookmark": "Marcar", "bookmark": "Marcar",
"unbookmark": "Desmarcar" "unbookmark": "Desmarcar",
"status_deleted": "Esta entrada ha sido eliminada",
"nsfw": "NSFW (No apropiado para el trabajo)"
}, },
"user_card": { "user_card": {
"approve": "Aprobar", "approve": "Aprobar",

View File

@ -390,5 +390,13 @@
"GiB": "GiB", "GiB": "GiB",
"TiB": "TiB" "TiB": "TiB"
} }
},
"about": {
"mrf": {
"keyword": {
"keyword_policies": "פוליסת מילות מפתח"
},
"federation": "פדרציה"
}
} }
} }

View File

@ -50,7 +50,8 @@
"follow_request": "vuole seguirti", "follow_request": "vuole seguirti",
"no_more_notifications": "Fine delle notifiche", "no_more_notifications": "Fine delle notifiche",
"migrated_to": "è migrato verso", "migrated_to": "è migrato verso",
"reacted_with": "ha reagito con {0}" "reacted_with": "ha reagito con {0}",
"error": "Errore nel caricare le notifiche: {0}"
}, },
"settings": { "settings": {
"attachments": "Allegati", "attachments": "Allegati",
@ -427,7 +428,8 @@
"repeated": "condiviso", "repeated": "condiviso",
"no_statuses": "Nessun messaggio", "no_statuses": "Nessun messaggio",
"no_more_statuses": "Fine dei messaggi", "no_more_statuses": "Fine dei messaggi",
"reload": "Ricarica" "reload": "Ricarica",
"error": "Errore nel caricare la sequenza: {0}"
}, },
"user_card": { "user_card": {
"follow": "Segui", "follow": "Segui",
@ -703,7 +705,8 @@
"delete_confirm": "Vuoi veramente eliminare questo messaggio?", "delete_confirm": "Vuoi veramente eliminare questo messaggio?",
"unbookmark": "Rimuovi segnalibro", "unbookmark": "Rimuovi segnalibro",
"bookmark": "Aggiungi segnalibro", "bookmark": "Aggiungi segnalibro",
"status_deleted": "Questo messagio è stato cancellato" "status_deleted": "Questo messagio è stato cancellato",
"nsfw": "Pruriginoso"
}, },
"time": { "time": {
"years_short": "{0}a", "years_short": "{0}a",

View File

@ -18,7 +18,13 @@
"generic_error": "Произошла ошибка", "generic_error": "Произошла ошибка",
"optional": "не обязательно", "optional": "не обязательно",
"show_less": "Показать меньше", "show_less": "Показать меньше",
"show_more": "Показать больше" "show_more": "Показать больше",
"peek": "Взглянуть",
"dismiss": "Закрыть",
"retry": "Попробуйте еще раз",
"error_retry": "Пожалуйста попробуйте еще раз",
"close": "Закрыть",
"loading": "Загрузка…"
}, },
"login": { "login": {
"login": "Войти", "login": "Войти",
@ -468,7 +474,9 @@
"media_proxy": "Прокси для внешних вложений", "media_proxy": "Прокси для внешних вложений",
"text_limit": "Лимит символов", "text_limit": "Лимит символов",
"title": "Особенности", "title": "Особенности",
"gopher": "Gopher" "gopher": "Gopher",
"who_to_follow": "Предложения кого читать",
"pleroma_chat_messages": "Pleroma Чат"
}, },
"tool_tip": { "tool_tip": {
"accept_follow_request": "Принять запрос на чтение", "accept_follow_request": "Принять запрос на чтение",
@ -477,6 +485,7 @@
"image_cropper": { "image_cropper": {
"save_without_cropping": "Сохранить не обрезая", "save_without_cropping": "Сохранить не обрезая",
"save": "Сохранить", "save": "Сохранить",
"crop_picture": "Обрезать картинку" "crop_picture": "Обрезать картинку",
"cancel": "Отмена"
} }
} }

99
src/i18n/uk.json Normal file
View File

@ -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": "Зберегти"
}
}

View File

@ -22,7 +22,7 @@
}, },
"general": { "general": {
"apply": "应用", "apply": "应用",
"submit": "提交", "submit": "发送",
"more": "更多", "more": "更多",
"generic_error": "发生了一个错误", "generic_error": "发生了一个错误",
"optional": "可选", "optional": "可选",
@ -297,7 +297,7 @@
"hide_follows_description": "不要显示我所关注的人", "hide_follows_description": "不要显示我所关注的人",
"hide_followers_description": "不要显示关注我的人", "hide_followers_description": "不要显示关注我的人",
"show_admin_badge": "显示管理徽章", "show_admin_badge": "显示管理徽章",
"show_moderator_badge": "显示版主徽章", "show_moderator_badge": "在我的个人资料中显示监察员标志",
"nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开", "nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
"oauth_tokens": "OAuth令牌", "oauth_tokens": "OAuth令牌",
"token": "令牌", "token": "令牌",
@ -655,8 +655,8 @@
"moderation": "权限", "moderation": "权限",
"grant_admin": "赋予管理权限", "grant_admin": "赋予管理权限",
"revoke_admin": "撤销管理权限", "revoke_admin": "撤销管理权限",
"grant_moderator": "赋予版主权限", "grant_moderator": "赋予监察员权限",
"revoke_moderator": "撤销版主权限", "revoke_moderator": "撤销监察员权限",
"activate_account": "激活账号", "activate_account": "激活账号",
"deactivate_account": "关闭账号", "deactivate_account": "关闭账号",
"delete_account": "删除账号", "delete_account": "删除账号",
@ -683,7 +683,7 @@
}, },
"user_reporting": { "user_reporting": {
"title": "报告 {0}", "title": "报告 {0}",
"add_comment_description": "此报告会发送给您的实例管理员。您可以在下面提供更多详细信息解释报告的缘由:", "add_comment_description": "此报告会发送给您的实例监察员。您可以在下面提供更多详细信息解释报告的缘由:",
"additional_comments": "其它信息", "additional_comments": "其它信息",
"forward_description": "这个账号是从另外一个服务器。同时发送一个副本到那里?", "forward_description": "这个账号是从另外一个服务器。同时发送一个副本到那里?",
"forward_to": "转发 {0}", "forward_to": "转发 {0}",

View File

@ -39,8 +39,7 @@ const emptyNotifications = () => ({
minId: Number.POSITIVE_INFINITY, minId: Number.POSITIVE_INFINITY,
data: [], data: [],
idStore: {}, idStore: {},
loading: false, loading: false
error: false
}) })
export const defaultState = () => ({ export const defaultState = () => ({
@ -50,8 +49,6 @@ export const defaultState = () => ({
maxId: 0, maxId: 0,
notifications: emptyNotifications(), notifications: emptyNotifications(),
favorites: new Set(), favorites: new Set(),
error: false,
errorData: null,
timelines: { timelines: {
mentions: emptyTl(), mentions: emptyTl(),
public: emptyTl(), public: emptyTl(),
@ -462,18 +459,9 @@ export const mutations = {
const newStatus = state.allStatusesObject[id] const newStatus = state.allStatusesObject[id]
newStatus.nsfw = nsfw newStatus.nsfw = nsfw
}, },
setError (state, { value }) {
state.error = value
},
setErrorData (state, { value }) {
state.errorData = value
},
setNotificationsLoading (state, { value }) { setNotificationsLoading (state, { value }) {
state.notifications.loading = value state.notifications.loading = value
}, },
setNotificationsError (state, { value }) {
state.notifications.error = value
},
setNotificationsSilence (state, { value }) { setNotificationsSilence (state, { value }) {
state.notifications.desktopNotificationSilence = value state.notifications.desktopNotificationSilence = value
}, },
@ -588,18 +576,9 @@ const statuses = {
} }
commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects }) 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 }) { setNotificationsLoading ({ rootState, commit }, { value }) {
commit('setNotificationsLoading', { value }) commit('setNotificationsLoading', { value })
}, },
setNotificationsError ({ rootState, commit }, { value }) {
commit('setNotificationsError', { value })
},
setNotificationsSilence ({ rootState, commit }, { value }) { setNotificationsSilence ({ rootState, commit }, { value }) {
commit('setNotificationsSilence', { value }) commit('setNotificationsSilence', { value })
}, },

View File

@ -137,11 +137,11 @@ export const mutations = {
}, },
saveFriendIds (state, { id, friendIds }) { saveFriendIds (state, { id, friendIds }) {
const user = state.usersObject[id] const user = state.usersObject[id]
user.friendIds = uniq(concat(user.friendIds, friendIds)) user.friendIds = uniq(concat(user.friendIds || [], friendIds))
}, },
saveFollowerIds (state, { id, followerIds }) { saveFollowerIds (state, { id, followerIds }) {
const user = state.usersObject[id] 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 // Because frontend doesn't have a reason to keep these stuff in memory
// outside of viewing someones user profile. // outside of viewing someones user profile.
@ -202,7 +202,9 @@ export const mutations = {
}, },
setPinnedToUser (state, status) { setPinnedToUser (state, status) {
const user = state.usersObject[status.user.id] const user = state.usersObject[status.user.id]
user.pinnedStatusIds = user.pinnedStatusIds || []
const index = user.pinnedStatusIds.indexOf(status.id) const index = user.pinnedStatusIds.indexOf(status.id)
if (status.pinned && index === -1) { if (status.pinned && index === -1) {
user.pinnedStatusIds.push(status.id) user.pinnedStatusIds.push(status.id)
} else if (!status.pinned && index !== -1) { } else if (!status.pinned && index !== -1) {

View File

@ -560,7 +560,7 @@ const fetchTimeline = ({
}) })
.then((data) => data.json()) .then((data) => data.json())
.then((data) => { .then((data) => {
if (!data.error) { if (!data.errors) {
return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination } return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination }
} else { } else {
data.status = status data.status = status

View File

@ -21,7 +21,7 @@ const clear = (storage) => {
failedMessageIds.push(message.id) failedMessageIds.push(message.id)
} else { } else {
delete storage.idIndex[message.id] delete storage.idIndex[message.id]
delete storage.idempotencyKeyIndex[message.id] delete storage.idempotencyKeyIndex[message.idempotency_key]
} }
} }

View File

@ -2,6 +2,15 @@ import escape from 'escape-html'
import parseLinkHeader from 'parse-link-header' import parseLinkHeader from 'parse-link-header'
import { isStatusNotification } from '../notification_utils/notification_utils.js' 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) => { const qvitterStatusType = (status) => {
if (status.is_post_verb) { if (status.is_post_verb) {
return 'status' return 'status'
@ -173,9 +182,6 @@ export const parseUser = (data) => {
output.locked = data.locked output.locked = data.locked
output.followers_count = data.followers_count output.followers_count = data.followers_count
output.statuses_count = data.statuses_count output.statuses_count = data.statuses_count
output.friendIds = []
output.followerIds = []
output.pinnedStatusIds = []
if (data.pleroma) { if (data.pleroma) {
output.follow_request_count = data.pleroma.follow_request_count output.follow_request_count = data.pleroma.follow_request_count

View File

@ -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

View File

@ -2,7 +2,6 @@ import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js' import { promiseInterval } from '../promise_interval/promise_interval.js'
const update = ({ store, notifications, older }) => { const update = ({ store, notifications, older }) => {
store.dispatch('setNotificationsError', { value: false })
store.dispatch('addNewNotifications', { notifications, older }) store.dispatch('addNewNotifications', { notifications, older })
} }
@ -47,11 +46,22 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
const fetchNotifications = ({ store, args, older }) => { const fetchNotifications = ({ store, args, older }) => {
return apiService.fetchTimeline(args) 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 }) update({ store, notifications, older })
return notifications 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 }) => { const startFetching = ({ credentials, store }) => {

View File

@ -6,9 +6,6 @@ import { promiseInterval } from '../promise_interval/promise_interval.js'
const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => { const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => {
const ccTimeline = camelCase(timeline) const ccTimeline = camelCase(timeline)
store.dispatch('setError', { value: false })
store.dispatch('setErrorData', { value: null })
store.dispatch('addNewStatuses', { store.dispatch('addNewStatuses', {
timeline: ccTimeline, timeline: ccTimeline,
userId, userId,
@ -52,9 +49,8 @@ const fetchAndUpdate = ({
return apiService.fetchTimeline(args) return apiService.fetchTimeline(args)
.then(response => { .then(response => {
if (response.error) { if (response.errors) {
store.dispatch('setErrorData', { value: response }) throw new Error(`${response.status} ${response.statusText}`)
return
} }
const { data: statuses, pagination } = response const { data: statuses, pagination } = response
@ -63,7 +59,15 @@ const fetchAndUpdate = ({
} }
update({ store, statuses, timeline, showImmediately, userId, pagination }) update({ store, statuses, timeline, showImmediately, userId, pagination })
return { statuses, 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 }) => { const startFetching = ({ timeline = 'friends', credentials, store, userId = false, tag = false }) => {

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,7 @@ const localVue = createLocalVue()
localVue.use(Vuex) localVue.use(Vuex)
const mutations = { const mutations = {
clearTimeline: () => {}, clearTimeline: () => {}
setError: () => {}
} }
const actions = { const actions = {

67
tools/emoji_merger.js Normal file
View File

@ -0,0 +1,67 @@
/*
Emoji merger script, quick hack of a tool to:
- update some missing emoji from an external source
- sort the emoji
- remove all multipart emoji (reactions don't allow them)
Merges emoji from here: https://gist.github.com/oliveratgithub/0bf11a9aff0d6da7b46f1490f86a71eb
to the simpler format we're using.
*/
// Existing emojis we have
const oldEmojiFilename = '../static/emoji.json'
// The file downloaded from https://gist.github.com/oliveratgithub/0bf11a9aff0d6da7b46f1490f86a71eb
const newEmojiFilename = 'emojis.json'
// Output, replace the static/emoji.json with this file if it looks correct
const outputFilename = 'output.json'
const run = () => {
const fs = require('fs')
let newEmojisObject = {}
let emojisObject = {}
let data = fs.readFileSync(newEmojiFilename, 'utf8')
// First filter out anything that's more than one codepoint
const newEmojis = JSON.parse(data).emojis.filter(e => e.emoji.length <= 2)
// Create a table with format { shortname: emoji }, remove the :
newEmojis.forEach(e => {
const name = e.shortname.slice(1, e.shortname.length - 1).toLowerCase()
if (name.length > 0) {
newEmojisObject[name] = e.emoji
}
})
data = fs.readFileSync(oldEmojiFilename, 'utf8')
emojisObject = JSON.parse(data)
// Get rid of longer emojis that don't play nice with reactions
Object.keys(emojisObject).forEach(e => {
if (emojisObject[e].length > 2) emojisObject[e] = undefined
})
// Add new emojis from the new tables to the old table
Object.keys(newEmojisObject).forEach(e => {
if (!emojisObject[e] && newEmojisObject[e].length <= 2) {
emojisObject[e] = newEmojisObject[e]
}
})
// Sort by key
const sorted = Object.keys(emojisObject).sort().reduce((acc, key) => {
if (key.length === 0) return acc
acc[key] = emojisObject[key]
return acc
}, {})
fs.writeFile(outputFilename, JSON.stringify(sorted, null, 2), 'utf8', (err) => {
if (err) console.log('Error writing file', err)
})
}
run()

4036
tools/emojis.json Normal file

File diff suppressed because it is too large Load Diff