* upstream/develop: (173 commits) Fix: Change condition fix typo update store according to retweeted status #433 - update sort by for conversation display replies_count right after reply icon expose replies_count from mastodon api Apparently, MastoAPI gives status in ancestors if you try opening a repeat... make side drawer use gesture service and fix its animations review/remove error hiding errata review #433 - sort conversation for retweets and clean up Revert "Merge branch 'revert-987b5162' into 'develop'" Revert "Merge branch 'mastoapi/friends-tl' into 'develop'" Add await to login action' Remove console log Fix warnings in user profile routing Add tests for gesture service, fix bug with perpendicular directions #255 - clean up autocomplete form #255 - clean up user settings page with self-closing html tags ...fix-emoji-picker
commit
9f4a9bff46
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<conversation :collapsable="false" :statusoid="statusoid"></conversation>
|
||||
<conversation
|
||||
:collapsable="false"
|
||||
isPage="true"
|
||||
:statusoid="statusoid"
|
||||
></conversation>
|
||||
</template>
|
||||
|
||||
<script src="./conversation-page.js"></script>
|
||||
|
@ -1,26 +1,42 @@
|
||||
<template>
|
||||
<div class="timeline panel panel-default">
|
||||
<div class="panel-heading conversation-heading">
|
||||
<div class="timeline panel-default" :class="[isExpanded ? 'panel' : 'panel-disabled']">
|
||||
<div v-if="isExpanded" class="panel-heading conversation-heading">
|
||||
<span class="title"> {{ $t('timeline.conversation') }} </span>
|
||||
<span v-if="collapsable">
|
||||
<a href="#" @click.prevent="$emit('toggleExpanded')">{{ $t('timeline.collapse') }}</a>
|
||||
<a href="#" @click.prevent="toggleExpanded">{{ $t('timeline.collapse') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="timeline">
|
||||
<status
|
||||
v-for="status in conversation"
|
||||
@goto="setHighlight" :key="status.id"
|
||||
:inlineExpanded="collapsable" :statusoid="status"
|
||||
:expandable='false' :focused="focused(status.id)"
|
||||
:inConversation='true'
|
||||
:highlight="highlight"
|
||||
:replies="getReplies(status.id)"
|
||||
class="status-fadein">
|
||||
</status>
|
||||
</div>
|
||||
</div>
|
||||
<status
|
||||
v-for="status in conversation"
|
||||
@goto="setHighlight"
|
||||
@toggleExpanded="toggleExpanded"
|
||||
:key="status.id"
|
||||
:inlineExpanded="collapsable"
|
||||
:statusoid="status"
|
||||
:expandable='!expanded'
|
||||
:focused="focused(status.id)"
|
||||
:inConversation="isExpanded"
|
||||
:highlight="getHighlight()"
|
||||
:replies="getReplies(status.id)"
|
||||
class="status-fadein panel-body"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./conversation.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.timeline {
|
||||
.panel-disabled {
|
||||
.status-el {
|
||||
border-left: none;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-color: var(--border, $fallback--border);
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,107 @@
|
||||
import Completion from '../../services/completion/completion.js'
|
||||
import { take, filter, map } from 'lodash'
|
||||
|
||||
const EmojiInput = {
|
||||
props: [
|
||||
'value',
|
||||
'placeholder',
|
||||
'type',
|
||||
'classname'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
highlighted: 0,
|
||||
caret: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
suggestions () {
|
||||
const firstchar = this.textAtCaret.charAt(0)
|
||||
if (firstchar === ':') {
|
||||
if (this.textAtCaret === ':') { return }
|
||||
const matchedEmoji = filter(this.emoji.concat(this.customEmoji), (emoji) => emoji.shortcode.startsWith(this.textAtCaret.slice(1)))
|
||||
if (matchedEmoji.length <= 0) {
|
||||
return false
|
||||
}
|
||||
return map(take(matchedEmoji, 5), ({shortcode, image_url, utf}, index) => ({
|
||||
shortcode: `:${shortcode}:`,
|
||||
utf: utf || '',
|
||||
// eslint-disable-next-line camelcase
|
||||
img: utf ? '' : this.$store.state.instance.server + image_url,
|
||||
highlighted: index === this.highlighted
|
||||
}))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
textAtCaret () {
|
||||
return (this.wordAtCaret || {}).word || ''
|
||||
},
|
||||
wordAtCaret () {
|
||||
const word = Completion.wordAtPosition(this.value, this.caret - 1) || {}
|
||||
return word
|
||||
},
|
||||
emoji () {
|
||||
return this.$store.state.instance.emoji || []
|
||||
},
|
||||
customEmoji () {
|
||||
return this.$store.state.instance.customEmoji || []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
replace (replacement) {
|
||||
const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement)
|
||||
this.$emit('input', newValue)
|
||||
this.caret = 0
|
||||
},
|
||||
replaceEmoji (e) {
|
||||
const len = this.suggestions.length || 0
|
||||
if (this.textAtCaret === ':' || e.ctrlKey) { return }
|
||||
if (len > 0) {
|
||||
e.preventDefault()
|
||||
const emoji = this.suggestions[this.highlighted]
|
||||
const replacement = emoji.utf || (emoji.shortcode + ' ')
|
||||
const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement)
|
||||
this.$emit('input', newValue)
|
||||
this.caret = 0
|
||||
this.highlighted = 0
|
||||
}
|
||||
},
|
||||
cycleBackward (e) {
|
||||
const len = this.suggestions.length || 0
|
||||
if (len > 0) {
|
||||
e.preventDefault()
|
||||
this.highlighted -= 1
|
||||
if (this.highlighted < 0) {
|
||||
this.highlighted = this.suggestions.length - 1
|
||||
}
|
||||
} else {
|
||||
this.highlighted = 0
|
||||
}
|
||||
},
|
||||
cycleForward (e) {
|
||||
const len = this.suggestions.length || 0
|
||||
if (len > 0) {
|
||||
if (e.shiftKey) { return }
|
||||
e.preventDefault()
|
||||
this.highlighted += 1
|
||||
if (this.highlighted >= len) {
|
||||
this.highlighted = 0
|
||||
}
|
||||
} else {
|
||||
this.highlighted = 0
|
||||
}
|
||||
},
|
||||
onKeydown (e) {
|
||||
e.stopPropagation()
|
||||
},
|
||||
onInput (e) {
|
||||
this.$emit('input', e.target.value)
|
||||
},
|
||||
setCaret ({target: {selectionStart}}) {
|
||||
this.caret = selectionStart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EmojiInput
|
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="emoji-input">
|
||||
<input
|
||||
v-if="type !== 'textarea'"
|
||||
:class="classname"
|
||||
:type="type"
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@input="onInput"
|
||||
@click="setCaret"
|
||||
@keyup="setCaret"
|
||||
@keydown="onKeydown"
|
||||
@keydown.down="cycleForward"
|
||||
@keydown.up="cycleBackward"
|
||||
@keydown.shift.tab="cycleBackward"
|
||||
@keydown.tab="cycleForward"
|
||||
@keydown.enter="replaceEmoji"
|
||||
/>
|
||||
<textarea
|
||||
v-else
|
||||
:class="classname"
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@input="onInput"
|
||||
@click="setCaret"
|
||||
@keyup="setCaret"
|
||||
@keydown="onKeydown"
|
||||
@keydown.down="cycleForward"
|
||||
@keydown.up="cycleBackward"
|
||||
@keydown.shift.tab="cycleBackward"
|
||||
@keydown.tab="cycleForward"
|
||||
@keydown.enter="replaceEmoji"
|
||||
></textarea>
|
||||
<div class="autocomplete-panel" v-if="suggestions">
|
||||
<div class="autocomplete-panel-body">
|
||||
<div
|
||||
v-for="(emoji, index) in suggestions"
|
||||
:key="index"
|
||||
@click="replace(emoji.utf || (emoji.shortcode + ' '))"
|
||||
class="autocomplete-item"
|
||||
:class="{ highlighted: emoji.highlighted }"
|
||||
>
|
||||
<span v-if="emoji.img">
|
||||
<img :src="emoji.img" />
|
||||
</span>
|
||||
<span v-else>{{emoji.utf}}</span>
|
||||
<span>{{emoji.shortcode}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./emoji-input.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.emoji-input {
|
||||
.form-control {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,91 @@
|
||||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import { throttle } from 'lodash'
|
||||
|
||||
const MobilePostStatusModal = {
|
||||
components: {
|
||||
PostStatusForm
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hidden: false,
|
||||
postFormOpen: false,
|
||||
scrollingDown: false,
|
||||
inputActive: false,
|
||||
oldScrollPos: 0,
|
||||
amountScrolled: 0
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
window.addEventListener('resize', this.handleOSK)
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('scroll', this.handleScroll)
|
||||
window.removeEventListener('resize', this.handleOSK)
|
||||
},
|
||||
computed: {
|
||||
currentUser () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
isHidden () {
|
||||
return this.hidden || this.inputActive
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openPostForm () {
|
||||
this.postFormOpen = true
|
||||
this.hidden = true
|
||||
|
||||
const el = this.$el.querySelector('textarea')
|
||||
this.$nextTick(function () {
|
||||
el.focus()
|
||||
})
|
||||
},
|
||||
closePostForm () {
|
||||
this.postFormOpen = false
|
||||
this.hidden = false
|
||||
},
|
||||
handleOSK () {
|
||||
// This is a big hack: we're guessing from changed window sizes if the
|
||||
// on-screen keyboard is active or not. This is only really important
|
||||
// for phones in portrait mode and it's more important to show the button
|
||||
// in normal scenarios on all phones, than it is to hide it when the
|
||||
// keyboard is active.
|
||||
// Guesswork based on https://www.mydevice.io/#compare-devices
|
||||
|
||||
// for example, iphone 4 and android phones from the same time period
|
||||
const smallPhone = window.innerWidth < 350
|
||||
const smallPhoneKbOpen = smallPhone && window.innerHeight < 345
|
||||
|
||||
const biggerPhone = !smallPhone && window.innerWidth < 450
|
||||
const biggerPhoneKbOpen = biggerPhone && window.innerHeight < 560
|
||||
if (smallPhoneKbOpen || biggerPhoneKbOpen) {
|
||||
this.inputActive = true
|
||||
} else {
|
||||
this.inputActive = false
|
||||
}
|
||||
},
|
||||
handleScroll: throttle(function () {
|
||||
const scrollAmount = window.scrollY - this.oldScrollPos
|
||||
const scrollingDown = scrollAmount > 0
|
||||
|
||||
if (scrollingDown !== this.scrollingDown) {
|
||||
this.amountScrolled = 0
|
||||
this.scrollingDown = scrollingDown
|
||||
if (!scrollingDown) {
|
||||
this.hidden = false
|
||||
}
|
||||
} else if (scrollingDown) {
|
||||
this.amountScrolled += scrollAmount
|
||||
if (this.amountScrolled > 100 && !this.hidden) {
|
||||
this.hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
this.oldScrollPos = window.scrollY
|
||||
this.scrollingDown = scrollingDown
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
export default MobilePostStatusModal
|
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div v-if="currentUser">
|
||||
<div
|
||||
class="post-form-modal-view modal-view"
|
||||
v-show="postFormOpen"
|
||||
@click="closePostForm"
|
||||
>
|
||||
<div class="post-form-modal-panel panel" @click.stop="">
|
||||
<div class="panel-heading">{{$t('post_status.new_status')}}</div>
|
||||
<PostStatusForm class="panel-body" @posted="closePostForm"/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="new-status-button"
|
||||
:class="{ 'hidden': isHidden }"
|
||||
@click="openPostForm"
|
||||
>
|
||||
<i class="icon-edit" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./mobile_post_status_modal.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.post-form-modal-view {
|
||||
max-height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-form-modal-panel {
|
||||
flex-shrink: 0;
|
||||
margin: 25% 0 4em 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.new-status-button {
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
border-radius: 100%;
|
||||
position: fixed;
|
||||
bottom: 1.5em;
|
||||
right: 1.5em;
|
||||
// TODO: this needs its own color, it has to stand out enough and link color
|
||||
// is not very optimal for this particular use.
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--btn, $fallback--fg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
|
||||
transition: 0.35s transform;
|
||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
|
||||
&.hidden {
|
||||
transform: translateY(150%);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.5em;
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 801px) {
|
||||
.new-status-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,10 @@
|
||||
export default {
|
||||
props: [ 'user' ],
|
||||
computed: {
|
||||
subscribeUrl () {
|
||||
// eslint-disable-next-line no-undef
|
||||
const serverUrl = new URL(this.user.statusnet_profile_url)
|
||||
return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus`
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="remote-follow">
|
||||
<form method="POST" :action='subscribeUrl'>
|
||||
<input type="hidden" name="nickname" :value="user.screen_name">
|
||||
<input type="hidden" name="profile" value="">
|
||||
<button click="submit" class="remote-button">
|
||||
{{ $t('user_card.remote_follow') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./remote_follow.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.remote-follow {
|
||||
max-width: 220px;
|
||||
|
||||
.remote-button {
|
||||
width: 100%;
|
||||
min-height: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,22 +0,0 @@
|
||||
import Status from '../status/status.vue'
|
||||
import Conversation from '../conversation/conversation.vue'
|
||||
|
||||
const statusOrConversation = {
|
||||
props: ['statusoid'],
|
||||
data () {
|
||||
return {
|
||||
expanded: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Status,
|
||||
Conversation
|
||||
},
|
||||
methods: {
|
||||
toggleExpanded () {
|
||||
this.expanded = !this.expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default statusOrConversation
|
@ -1,14 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<conversation v-if="expanded" @toggleExpanded="toggleExpanded" :collapsable="true" :statusoid="statusoid"></conversation>
|
||||
<status v-if="!expanded" @toggleExpanded="toggleExpanded" :expandable="true" :inConversation="false" :focused="false" :statusoid="statusoid"></status>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./status_or_conversation.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.spacer {
|
||||
height: 1em
|
||||
}
|
||||
</style>
|
@ -0,0 +1,428 @@
|
||||
{
|
||||
"chat": {
|
||||
"title": "Chat"
|
||||
},
|
||||
"features_panel": {
|
||||
"chat": "Chat",
|
||||
"gopher": "Gopher",
|
||||
"media_proxy": "Mediální proxy",
|
||||
"scope_options": "Možnosti rozsahů",
|
||||
"text_limit": "Textový limit",
|
||||
"title": "Vlastnosti",
|
||||
"who_to_follow": "Koho sledovat"
|
||||
},
|
||||
"finder": {
|
||||
"error_fetching_user": "Chyba při načítání uživatele",
|
||||
"find_user": "Najít uživatele"
|
||||
},
|
||||
"general": {
|
||||
"apply": "Použít",
|
||||
"submit": "Odeslat",
|
||||
"more": "Více",
|
||||
"generic_error": "Vyskytla se chyba",
|
||||
"optional": "volitelné"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop_picture": "Oříznout obrázek",
|
||||
"save": "Uložit",
|
||||
"cancel": "Zrušit"
|
||||
},
|
||||
"login": {
|
||||
"login": "Přihlásit",
|
||||
"description": "Přihlásit pomocí OAuth",
|
||||
"logout": "Odhlásit",
|
||||
"password": "Heslo",
|
||||
"placeholder": "např. lain",
|
||||
"register": "Registrovat",
|
||||
"username": "Uživatelské jméno",
|
||||
"hint": "Chcete-li se přidat do diskuze, přihlaste se"
|
||||
},
|
||||
"media_modal": {
|
||||
"previous": "Předchozí",
|
||||
"next": "Další"
|
||||
},
|
||||
"nav": {
|
||||
"about": "O instanci",
|
||||
"back": "Zpět",
|
||||
"chat": "Místní chat",
|
||||
"friend_requests": "Požadavky o sledování",
|
||||
"mentions": "Zmínky",
|
||||
"dms": "Přímé zprávy",
|
||||
"public_tl": "Veřejná časová osa",
|
||||
"timeline": "Časová osa",
|
||||
"twkn": "Celá známá síť",
|
||||
"user_search": "Hledání uživatelů",
|
||||
"who_to_follow": "Koho sledovat",
|
||||
"preferences": "Předvolby"
|
||||
},
|
||||
"notifications": {
|
||||
"broken_favorite": "Neznámý příspěvek, hledám jej…",
|
||||
"favorited_you": "si oblíbil/a váš příspěvek",
|
||||
"followed_you": "vás nyní sleduje",
|
||||
"load_older": "Načíst starší oznámení",
|
||||
"notifications": "Oznámení",
|
||||
"read": "Číst!",
|
||||
"repeated_you": "zopakoval/a váš příspěvek",
|
||||
"no_more_notifications": "Žádná další oznámení"
|
||||
},
|
||||
"post_status": {
|
||||
"new_status": "Napsat nový příspěvek",
|
||||
"account_not_locked_warning": "Váš účet není {0}. Kdokoliv vás může sledovat a vidět vaše příspěvky pouze pro sledující.",
|
||||
"account_not_locked_warning_link": "uzamčen",
|
||||
"attachments_sensitive": "Označovat přílohy jako citlivé",
|
||||
"content_type": {
|
||||
"text/plain": "Prostý text",
|
||||
"text/html": "HTML",
|
||||
"text/markdown": "Markdown"
|
||||
},
|
||||
"content_warning": "Předmět (volitelný)",
|
||||
"default": "Právě jsem přistál v L.A.",
|
||||
"direct_warning": "Tento příspěvek uvidí pouze všichni zmínění uživatelé.",
|
||||
"posting": "Přispívání",
|
||||
"scope": {
|
||||
"direct": "Přímý - Poslat pouze zmíněným uživatelům",
|
||||
"private": "Pouze pro sledující - Poslat pouze sledujícím",
|
||||
"public": "Veřejný - Poslat na veřejné časové osy",
|
||||
"unlisted": "Neuvedený - Neposlat na veřejné časové osy"
|
||||
}
|
||||
},
|
||||
"registration": {
|
||||
"bio": "O vás",
|
||||
"email": "E-mail",
|
||||
"fullname": "Zobrazované jméno",
|
||||
"password_confirm": "Potvrzení hesla",
|
||||
"registration": "Registrace",
|
||||
"token": "Token pozvánky",
|
||||
"captcha": "CAPTCHA",
|
||||
"new_captcha": "Kliknutím na obrázek získáte novou CAPTCHA",
|
||||
"username_placeholder": "např. lain",
|
||||
"fullname_placeholder": "např. Lain Iwakura",
|
||||
"bio_placeholder": "např.\nNazdar, jsem Lain\nJsem anime dívka žijící v příměstském Japonsku. Možná mě znáte z Wired.",
|
||||
"validations": {
|
||||
"username_required": "nemůže být prázdné",
|
||||
"fullname_required": "nemůže být prázdné",
|
||||
"email_required": "nemůže být prázdný",
|
||||
"password_required": "nemůže být prázdné",
|
||||
"password_confirmation_required": "nemůže být prázdné",
|
||||
"password_confirmation_match": "musí být stejné jako heslo"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"app_name": "Název aplikace",
|
||||
"attachmentRadius": "Přílohy",
|
||||
"attachments": "Přílohy",
|
||||
"autoload": "Povolit automatické načítání při rolování dolů",
|
||||
"avatar": "Avatar",
|
||||
"avatarAltRadius": "Avatary (oznámení)",
|
||||
"avatarRadius": "Avatary",
|
||||
"background": "Pozadí",
|
||||
"bio": "O vás",
|
||||
"blocks_tab": "Blokování",
|
||||
"btnRadius": "Tlačítka",
|
||||
"cBlue": "Modrá (Odpovědět, sledovat)",
|
||||
"cGreen": "Zelená (Zopakovat)",
|
||||
"cOrange": "Oranžová (Oblíbit)",
|
||||
"cRed": "Červená (Zrušit)",
|
||||
"change_password": "Změnit heslo",
|
||||
"change_password_error": "Při změně vašeho hesla se vyskytla chyba.",
|
||||
"changed_password": "Heslo bylo úspěšně změněno!",
|
||||
"collapse_subject": "Zabalit příspěvky s předměty",
|
||||
"composing": "Komponování",
|
||||
"confirm_new_password": "Potvrďte nové heslo",
|
||||
"current_avatar": "Váš současný avatar",
|
||||
"current_password": "Současné heslo",
|
||||
"current_profile_banner": "Váš současný profilový banner",
|
||||
"data_import_export_tab": "Import/export dat",
|
||||
"default_vis": "Výchozí rozsah viditelnosti",
|
||||
"delete_account": "Smazat účet",
|
||||
"delete_account_description": "Trvale smaže váš účet a všechny vaše příspěvky.",
|
||||
"delete_account_error": "Při mazání vašeho účtu nastala chyba. Pokud tato chyba bude trvat, kontaktujte prosím admministrátora vaší instance.",
|
||||
"delete_account_instructions": "Pro potvrzení smazání účtu napište své heslo do pole níže.",
|
||||
"avatar_size_instruction": "Doporučená minimální velikost pro avatarové obrázky je 150x150 pixelů.",
|
||||
"export_theme": "Uložit přednastavení",
|
||||
"filtering": "Filtrování",
|
||||
"filtering_explanation": "Všechny příspěvky obsahující tato slova budou skryty. Napište jedno slovo na každý řádek",
|
||||
"follow_export": "Export sledovaných",
|
||||
"follow_export_button": "Exportovat vaše sledované do souboru CSV",
|
||||
"follow_export_processing": "Zpracovávám, brzy si budete moci stáhnout váš soubor",
|
||||
"follow_import": "Import sledovaných",
|
||||
"follow_import_error": "Chyba při importování sledovaných",
|
||||
"follows_imported": "Sledovaní importováni! Jejich zpracování bude chvilku trvat.",
|
||||
"foreground": "Popředí",
|
||||
"general": "Obecné",
|
||||
"hide_attachments_in_convo": "Skrývat přílohy v konverzacích",
|
||||
"hide_attachments_in_tl": "Skrývat přílohy v časové ose",
|
||||
"max_thumbnails": "Maximální počet miniatur na příspěvek",
|
||||
"hide_isp": "Skrýt panel specifický pro instanci",
|
||||
"preload_images": "Přednačítat obrázky",
|
||||
"use_one_click_nsfw": "Otevírat citlivé přílohy pouze jedním kliknutím",
|
||||
"hide_post_stats": "Skrývat statistiky příspěvků (např. počet oblíbení)",
|
||||
"hide_user_stats": "Skrývat statistiky uživatelů (např. počet sledujících)",
|
||||
"hide_filtered_statuses": "Skrývat filtrované příspěvky",
|
||||
"import_followers_from_a_csv_file": "Importovat sledované ze souboru CSV",
|
||||
"import_theme": "Načíst přednastavení",
|
||||
"inputRadius": "Vstupní pole",
|
||||
"checkboxRadius": "Zaškrtávací pole",
|
||||
"instance_default": "(výchozí: {value})",
|
||||
"instance_default_simple": "(výchozí)",
|
||||
"interface": "Rozhraní",
|
||||
"interfaceLanguage": "Jazyk rozhraní",
|
||||
"invalid_theme_imported": "Zvolený soubor není podporovaný motiv Pleroma. Nebyly provedeny žádné změny s vaším motivem.",
|
||||
"limited_availability": "Nedostupné ve vašem prohlížeči",
|
||||
"links": "Odkazy",
|
||||
"lock_account_description": "Omezit váš účet pouze na schválené sledující",
|
||||
"loop_video": "Opakovat videa",
|
||||
"loop_video_silent_only": "Opakovat pouze videa beze zvuku (t.j. „GIFy“ na Mastodonu)",
|
||||
"mutes_tab": "Ignorování",
|
||||
"play_videos_in_modal": "Přehrávat videa přímo v prohlížeči médií",
|
||||
"use_contain_fit": "Neořezávat přílohu v miniaturách",
|
||||
"name": "Jméno",
|
||||
"name_bio": "Jméno a popis",
|
||||
"new_password": "Nové heslo",
|
||||
"notification_visibility": "Typy oznámení k zobrazení",
|
||||
"notification_visibility_follows": "Sledující",
|
||||
"notification_visibility_likes": "Oblíbení",
|
||||
"notification_visibility_mentions": "Zmínky",
|
||||
"notification_visibility_repeats": "Zopakování",
|
||||
"no_rich_text_description": "Odstranit ze všech příspěvků formátování textu",
|
||||
"no_blocks": "Žádná blokování",
|
||||
"no_mutes": "Žádná ignorování",
|
||||
"hide_follows_description": "Nezobrazovat, koho sleduji",
|
||||
"hide_followers_description": "Nezobrazovat, kdo mě sleduje",
|
||||
"show_admin_badge": "Zobrazovat v mém profilu odznak administrátora",
|
||||
"show_moderator_badge": "Zobrazovat v mém profilu odznak moderátora",
|
||||
"nsfw_clickthrough": "Povolit prokliknutelné skrývání citlivých příloh",
|
||||
"oauth_tokens": "Tokeny OAuth",
|
||||
"token": "Token",
|
||||
"refresh_token": "Obnovit token",
|
||||
"valid_until": "Platný do",
|
||||
"revoke_token": "Odvolat",
|
||||
"panelRadius": "Panely",
|
||||
"pause_on_unfocused": "Pozastavit streamování, pokud není záložka prohlížeče v soustředění",
|
||||
"presets": "Přednastavení",
|
||||
"profile_background": "Profilové pozadí",
|
||||
"profile_banner": "Profilový banner",
|
||||
"profile_tab": "Profil",
|
||||
"radii_help": "Nastavit zakulacení rohů rozhraní (v pixelech)",
|
||||
"replies_in_timeline": "Odpovědi v časové ose",
|
||||
"reply_link_preview": "Povolit náhledy odkazu pro odpověď při přejetí myši",
|
||||
"reply_visibility_all": "Zobrazit všechny odpovědi",
|
||||
"reply_visibility_following": "Zobrazit pouze odpovědi směřované na mě nebo uživatele, které sleduji",
|
||||
"reply_visibility_self": "Zobrazit pouze odpovědi směřované na mě",
|
||||
"saving_err": "Chyba při ukládání nastavení",
|
||||
"saving_ok": "Nastavení uložena",
|
||||
"security_tab": "Bezpečnost",
|
||||
"scope_copy": "Kopírovat rozsah při odpovídání (přímé zprávy jsou vždy kopírovány)",
|
||||
"set_new_avatar": "Nastavit nový avatar",
|
||||
"set_new_profile_background": "Nastavit nové profilové pozadí",
|
||||
"set_new_profile_banner": "Nastavit nový profilový banner",
|
||||
"settings": "Nastavení",
|
||||
"subject_input_always_show": "Vždy zobrazit pole pro předmět",
|
||||
"subject_line_behavior": "Kopírovat předmět při odpovídání",
|
||||
"subject_line_email": "Jako u e-mailu: „re: předmět“",
|
||||
"subject_line_mastodon": "Jako u Mastodonu: zkopírovat tak, jak je",
|
||||
"subject_line_noop": "Nekopírovat",
|
||||
"post_status_content_type": "Publikovat typ obsahu příspěvku",
|
||||
"stop_gifs": "Přehrávat GIFy při přejetí myši",
|
||||
"streaming": "Povolit automatické streamování nových příspěvků při rolování nahoru",
|
||||
"text": "Text",
|
||||
"theme": "Motiv",
|
||||
"theme_help": "Použijte hexadecimální barevné kódy (#rrggbb) pro přizpůsobení vašeho barevného motivu.",
|
||||
"theme_help_v2_1": "Zaškrtnutím pole můžete také přepsat barvy a průhlednost některých komponentů, pro smazání všech přednastavení použijte tlačítko „Smazat vše“.",
|
||||
"theme_help_v2_2": "Ikony pod některými položkami jsou indikátory kontrastu pozadí/textu, pro detailní informace nad nimi přejeďte myší. Prosím berte na vědomí, že při používání kontrastu průhlednosti ukazují indikátory nejhorší možný případ.",
|
||||
"tooltipRadius": "Popisky/upozornění",
|
||||
"upload_a_photo": "Nahrát fotku",
|
||||
"user_settings": "Uživatelská nastavení",
|
||||
"values": {
|
||||
"false": "ne",
|
||||
"true": "ano"
|
||||
},
|
||||
"notifications": "Oznámení",
|
||||
"enable_web_push_notifications": "Povolit webová push oznámení",
|
||||
"style": {
|
||||
"switcher": {
|
||||
"keep_color": "Ponechat barvy",
|
||||
"keep_shadows": "Ponechat stíny",
|
||||
"keep_opacity": "Ponechat průhlednost",
|
||||
"keep_roundness": "Ponechat kulatost",
|
||||
"keep_fonts": "Keep fonts",
|
||||
"save_load_hint": "Možnosti „Ponechat“ dočasně ponechávají aktuálně nastavené možností při volení či nahrávání motivů, také tyto možnosti ukládají při exportování motivu. Pokud není žádné pole zaškrtnuto, uloží export motivu všechno.",
|
||||
"reset": "Resetovat",
|
||||
"clear_all": "Vymazat vše",
|
||||
"clear_opacity": "Vymazat průhlednost"
|
||||
},
|
||||
"common": {
|
||||
"color": "Barva",
|
||||
"opacity": "Průhlednost",
|
||||
"contrast": {
|
||||
"hint": "Poměr kontrastu je {ratio}, {level} {context}",
|
||||
"level": {
|
||||
"aa": "splňuje směrnici úrovně AA (minimální)",
|
||||
"aaa": "splňuje směrnici úrovně AAA (doporučováno)",
|
||||
"bad": "nesplňuje žádné směrnice přístupnosti"
|
||||
},
|
||||
"context": {
|
||||
"18pt": "pro velký (18+ bodů) text",
|
||||
"text": "pro text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_colors": {
|
||||
"_tab_label": "Obvyklé",
|
||||
"main": "Obvyklé barvy",
|
||||
"foreground_hint": "Pro detailnější kontrolu viz záložka „Pokročilé“",
|
||||
"rgbo": "Ikony, odstíny, odznaky"
|
||||
},
|
||||
"advanced_colors": {
|
||||
"_tab_label": "Pokročilé",
|
||||
"alert": "Pozadí upozornění",
|
||||
"alert_error": "Chyba",
|
||||
"badge": "Pozadí odznaků",
|
||||
"badge_notification": "Oznámení",
|
||||
"panel_header": "Záhlaví panelu",
|
||||
"top_bar": "Vrchní pruh",
|
||||
"borders": "Okraje",
|
||||
"buttons": "Tlačítka",
|
||||
"inputs": "Vstupní pole",
|
||||
"faint_text": "Vybledlý text"
|
||||
},
|
||||
"radii": {
|
||||
"_tab_label": "Kulatost"
|
||||
},
|
||||
"shadows": {
|
||||
"_tab_label": "Stín a osvětlení",
|
||||
"component": "Komponent",
|
||||
"override": "Přepsat",
|
||||
"shadow_id": "Stín #{value}",
|
||||
"blur": "Rozmazání",
|
||||
"spread": "Rozsah",
|
||||
"inset": "Vsazení",
|
||||
"hint": "Pro stíny můžete také použít --variable jako hodnotu barvy pro použití proměnných CSS3. Prosím berte na vědomí, že nastavení průhlednosti v tomto případě nebude fungovat.",
|
||||
"filter_hint": {
|
||||
"always_drop_shadow": "Varování, tento stín vždy používá {0}, když to prohlížeč podporuje.",
|
||||
"drop_shadow_syntax": "{0} nepodporuje parametr {1} a klíčové slovo {2}.",
|
||||
"avatar_inset": "Prosím berte na vědomí, že kombinování vsazených i nevsazených stínů u avatarů může u průhledných avatarů dát neočekávané výsledky.",
|
||||
"spread_zero": "Stíny s rozsahem > 0 se zobrazí, jako kdyby byl rozsah nastaven na nulu",
|
||||
"inset_classic": "Vsazené stíny budou používat {0}"
|
||||
},
|
||||
"components": {
|
||||
"panel": "Panel",
|
||||
"panelHeader": "Záhlaví panelu",
|
||||
"topBar": "Vrchní pruh",
|
||||
"avatar": "Avatar uživatele (v zobrazení profilu)",
|
||||
"avatarStatus": "Avatar uživatele (v zobrazení příspěvku)",
|
||||
"popup": "Vyskakovací okna a popisky",
|
||||
"button": "Tlačítko",
|
||||
"buttonHover": "Tlačítko (přejetí myši)",
|
||||
"buttonPressed": "Tlačítko (stisknuto)",
|
||||
"buttonPressedHover": "Button (stisknuto+přejetí myši)",
|
||||
"input": "Vstupní pole"
|
||||
}
|
||||
},
|
||||
"fonts": {
|
||||
"_tab_label": "Písma",
|
||||
"help": "Zvolte písmo, které bude použito pro prvky rozhraní. U možnosti „vlastní“ musíte zadat přesný název písma tak, jak se zobrazuje v systému.",
|
||||
"components": {
|
||||
"interface": "Rozhraní",
|
||||
"input": "Vstupní pole",
|
||||
"post": "Text příspěvků",
|
||||
"postCode": "Neproporcionální text v příspěvku (formátovaný text)"
|
||||
},
|
||||
"family": "Název písma",
|
||||
"size": "Velikost (v pixelech)",
|
||||
"weight": "Tloušťka",
|
||||
"custom": "Vlastní"
|
||||
},
|
||||
"preview": {
|
||||
"header": "Náhled",
|
||||
"content": "Obsah",
|
||||
"error": "Příklad chyby",
|
||||
"button": "Tlačítko",
|
||||
"text": "Spousta dalšího {0} a {1}",
|
||||
"mono": "obsahu",
|
||||
"input": "Právě jsem přistál v L.A.",
|
||||
"faint_link": "pomocný manuál",
|
||||
"fine_print": "Přečtěte si náš {0} a nenaučte se nic užitečného!",
|
||||
"header_faint": "Tohle je v pohodě",
|
||||
"checkbox": "Pročetl/a jsem podmínky používání",
|
||||
"link": "hezký malý odkaz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"collapse": "Zabalit",
|
||||
"conversation": "Konverzace",
|
||||
"error_fetching": "Chyba při načítání aktualizací",
|
||||
"load_older": "Načíst starší příspěvky",
|
||||
"no_retweet_hint": "Příspěvek je označen jako pouze pro sledující či přímý a nemůže být zopakován",
|
||||
"repeated": "zopakoval/a",
|
||||
"show_new": "Zobrazit nové",
|
||||
"up_to_date": "Aktuální",
|
||||
"no_more_statuses": "Žádné další příspěvky",
|
||||
"no_statuses": "Žádné příspěvky"
|
||||
},
|
||||
"status": {
|
||||
"reply_to": "Odpověď uživateli",
|
||||
"replies_list": "Odpovědi:"
|
||||
},
|
||||
|
||||
"user_card": {
|
||||
"approve": "Schválit",
|
||||
"block": "Blokovat",
|
||||
"blocked": "Blokován/a!",
|
||||
"deny": "Zamítnout",
|
||||
"favorites": "Oblíbené",
|
||||
"follow": "Sledovat",
|
||||
"follow_sent": "Požadavek odeslán!",
|
||||
"follow_progress": "Odeslílám požadavek…",
|
||||
"follow_again": "Odeslat požadavek znovu?",
|
||||
"follow_unfollow": "Přestat sledovat",
|
||||
"followees": "Sledovaní",
|
||||
"followers": "Sledující",
|
||||
"following": "Sledujete!",
|
||||
"follows_you": "Sleduje vás!",
|
||||
"its_you": "Jste to vy!",
|
||||
"media": "Média",
|
||||
"mute": "Ignorovat",
|
||||
"muted": "Ignorován/a",
|
||||
"per_day": "za den",
|
||||
"remote_follow": "Vzdálené sledování",
|
||||
"statuses": "Příspěvky",
|
||||
"unblock": "Odblokovat",
|
||||
"unblock_progress": "Odblokuji…",
|
||||
"block_progress": "Blokuji…",
|
||||
"unmute": "Přestat ignorovat",
|
||||
"unmute_progress": "Ruším ignorování…",
|
||||
"mute_progress": "Ignoruji…"
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "Uživatelská časová osa",
|
||||
"profile_does_not_exist": "Omlouváme se, tento profil neexistuje.",
|
||||
"profile_loading_error": "Omlouváme se, při načítání tohoto profilu se vyskytla chyba."
|
||||
},
|
||||
"who_to_follow": {
|
||||
"more": "Více",
|
||||
"who_to_follow": "Koho sledovat"
|
||||
},
|
||||
"tool_tip": {
|
||||
"media_upload": "Nahrát média",
|
||||
"repeat": "Zopakovat",
|
||||
"reply": "Odpovědět",
|
||||
"favorite": "Oblíbit",
|
||||
"user_settings": "Uživatelské nastavení"
|
||||
},
|
||||
"upload":{
|
||||
"error": {
|
||||
"base": "Nahrávání selhalo.",
|
||||
"file_too_big": "Soubor je příliš velký [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
|
||||
"default": "Zkuste to znovu později"
|
||||
},
|
||||
"file_size_units": {
|
||||
"B": "B",
|
||||
"KiB": "KiB",
|
||||
"MiB": "MiB",
|
||||
"GiB": "GiB",
|
||||
"TiB": "TiB"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
|
||||
const DIRECTION_LEFT = [-1, 0]
|
||||
const DIRECTION_RIGHT = [1, 0]
|
||||
const DIRECTION_UP = [0, -1]
|
||||
const DIRECTION_DOWN = [0, 1]
|
||||
|
||||
const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
|
||||
|
||||
const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY])
|
||||
|
||||
const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1])
|
||||
|
||||
const perpendicular = v => [v[1], -v[0]]
|
||||
|
||||
const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1]
|
||||
|
||||
const project = (v1, v2) => {
|
||||
const scalar = (dotProduct(v1, v2) / dotProduct(v2, v2))
|
||||
return [scalar * v2[0], scalar * v2[1]]
|
||||
}
|
||||
|
||||
// direction: either use the constants above or an arbitrary 2d vector.
|
||||
// threshold: how many Px to move from touch origin before checking if the
|
||||
// callback should be called.
|
||||
// divergentTolerance: a scalar for much of divergent direction we tolerate when
|
||||
// above threshold. for example, with 1.0 we only call the callback if
|
||||
// divergent component of delta is < 1.0 * direction component of delta.
|
||||
const swipeGesture = (direction, onSwipe, threshold = 30, perpendicularTolerance = 1.0) => {
|
||||
return {
|
||||
direction,
|
||||
onSwipe,
|
||||
threshold,
|
||||
perpendicularTolerance,
|
||||
_startPos: [0, 0],
|
||||
_swiping: false
|
||||
}
|
||||
}
|
||||
|
||||
const beginSwipe = (event, gesture) => {
|
||||
gesture._startPos = touchEventCoord(event)
|
||||
gesture._swiping = true
|
||||
}
|
||||
|
||||
const updateSwipe = (event, gesture) => {
|
||||
if (!gesture._swiping) return
|
||||
// movement too small
|
||||
const delta = deltaCoord(gesture._startPos, touchEventCoord(event))
|
||||
if (vectorLength(delta) < gesture.threshold) return
|
||||
// movement is opposite from direction
|
||||
if (dotProduct(delta, gesture.direction) < 0) return
|
||||
// movement perpendicular to direction is too much
|
||||
const towardsDir = project(delta, gesture.direction)
|
||||
const perpendicularDir = perpendicular(gesture.direction)
|
||||
const towardsPerpendicular = project(delta, perpendicularDir)
|
||||
if (
|
||||
vectorLength(towardsDir) * gesture.perpendicularTolerance <
|
||||
vectorLength(towardsPerpendicular)
|
||||
) return
|
||||
|
||||
gesture.onSwipe()
|
||||
gesture._swiping = false
|
||||
}
|
||||
|
||||
const GestureService = {
|
||||
DIRECTION_LEFT,
|
||||
DIRECTION_RIGHT,
|
||||
DIRECTION_UP,
|
||||
DIRECTION_DOWN,
|
||||
swipeGesture,
|
||||
beginSwipe,
|
||||
updateSwipe
|
||||
}
|
||||
|
||||
export default GestureService
|
@ -0,0 +1,6 @@
|
||||
|
||||
export const extractCommit = versionString => {
|
||||
const regex = /-g(\w+)$/i
|
||||
const matches = versionString.match(regex)
|
||||
return matches ? matches[1] : ''
|
||||
}
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue