Merge remote-tracking branch 'upstream/develop' into neckbeard

new-bloop
Your New SJW Waifu 4 years ago
commit ebd893a3a2

@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Autocomplete domains from list of known instances - Autocomplete domains from list of known instances
- 'Bot' settings option and badge - 'Bot' settings option and badge
- Added profile meta data fields that can be set in profile settings - Added profile meta data fields that can be set in profile settings
- Descriptions can be set on uploaded files before posting
- Added status preview option to preview your statuses before posting - Added status preview option to preview your statuses before posting
- When a post is a reply to an unavailable post, the 'Reply to'-text has a strike-through style - When a post is a reply to an unavailable post, the 'Reply to'-text has a strike-through style
@ -28,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Close the media modal on navigation events - Close the media modal on navigation events
- Add colons to the emoji alt text, to make them copyable - Add colons to the emoji alt text, to make them copyable
- Add better visual indication for drag-and-drop for files - Add better visual indication for drag-and-drop for files
- When disabling attachments, the placeholder links now show an icon and the description instead of just IMAGE or VIDEO etc
### Fixed ### Fixed
- Custom Emoji will display in poll options now. - Custom Emoji will display in poll options now.
@ -39,6 +41,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Subject field now appears disabled when posting - Subject field now appears disabled when posting
- Fix status ellipsis menu being cut off in notifications column - Fix status ellipsis menu being cut off in notifications column
- Fixed autocomplete sometimes not returning the right user when there's already some results - Fixed autocomplete sometimes not returning the right user when there's already some results
- Videos and audio and misc files show description as alt/title properly now
- Clicking on non-image/video files no longer opens an empty modal
- Audio files can now be played back in the frontend with hidden attachments
- Videos are not cropped awkwardly in the uploads section anymore
- Reply filtering options in Settings -> Filtering now work again using filtering on server - Reply filtering options in Settings -> Filtering now work again using filtering on server
- Don't show just blank-screen when cookies are disabled - Don't show just blank-screen when cookies are disabled

@ -25,10 +25,10 @@ const preloadFetch = async (request) => {
if (!data || !data[request]) { if (!data || !data[request]) {
return window.fetch(request) return window.fetch(request)
} }
const requestData = atob(data[request]) const requestData = JSON.parse(atob(data[request]))
return { return {
ok: true, ok: true,
json: () => JSON.parse(requestData), json: () => requestData,
text: () => requestData text: () => requestData
} }
} }
@ -215,7 +215,6 @@ const getAppSecret = async ({ store }) => {
const resolveStaffAccounts = ({ store, accounts }) => { const resolveStaffAccounts = ({ store, accounts }) => {
const nicknames = accounts.map(uri => uri.split('/').pop()) const nicknames = accounts.map(uri => uri.split('/').pop())
nicknames.map(nickname => store.dispatch('fetchUser', nickname))
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames }) store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
} }

@ -8,7 +8,6 @@ const Attachment = {
props: [ props: [
'attachment', 'attachment',
'nsfw', 'nsfw',
'statusId',
'size', 'size',
'allowPlay', 'allowPlay',
'setMedia', 'setMedia',
@ -30,9 +29,21 @@ const Attachment = {
VideoAttachment VideoAttachment
}, },
computed: { computed: {
usePlaceHolder () { usePlaceholder () {
return this.size === 'hide' || this.type === 'unknown' return this.size === 'hide' || this.type === 'unknown'
}, },
placeholderName () {
if (this.attachment.description === '' || !this.attachment.description) {
return this.type.toUpperCase()
}
return this.attachment.description
},
placeholderIconClass () {
if (this.type === 'image') return 'icon-picture'
if (this.type === 'video') return 'icon-video'
if (this.type === 'audio') return 'icon-music'
return 'icon-doc'
},
referrerpolicy () { referrerpolicy () {
return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer' return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'
}, },
@ -49,7 +60,15 @@ const Attachment = {
return this.size === 'small' return this.size === 'small'
}, },
fullwidth () { fullwidth () {
return this.type === 'html' || this.type === 'audio' if (this.size === 'hide') return false
return this.type === 'html' || this.type === 'audio' || this.type === 'unknown'
},
useModal () {
const modalTypes = this.size === 'hide' ? ['image', 'video', 'audio']
: this.mergedConfig.playVideosInModal
? ['image', 'video']
: ['image']
return modalTypes.includes(this.type)
}, },
...mapGetters(['mergedConfig']) ...mapGetters(['mergedConfig'])
}, },
@ -60,12 +79,7 @@ const Attachment = {
} }
}, },
openModal (event) { openModal (event) {
const modalTypes = this.mergedConfig.playVideosInModal if (this.useModal) {
? ['image', 'video']
: ['image']
if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) ||
this.usePlaceHolder
) {
event.stopPropagation() event.stopPropagation()
event.preventDefault() event.preventDefault()
this.setMedia() this.setMedia()

@ -1,6 +1,7 @@
<template> <template>
<div <div
v-if="usePlaceHolder" v-if="usePlaceholder"
:class="{ 'fullwidth': fullwidth }"
@click="openModal" @click="openModal"
> >
<a <a
@ -8,8 +9,11 @@
class="placeholder" class="placeholder"
target="_blank" target="_blank"
:href="attachment.url" :href="attachment.url"
:alt="attachment.description"
:title="attachment.description"
> >
[{{ nsfw ? "NSFW/" : "" }}{{ type.toUpperCase() }}] <span :class="placeholderIconClass" />
<b>{{ nsfw ? "NSFW / " : "" }}</b>{{ placeholderName }}
</a> </a>
</div> </div>
<div <div
@ -22,6 +26,8 @@
v-if="hidden" v-if="hidden"
class="image-attachment" class="image-attachment"
:href="attachment.url" :href="attachment.url"
:alt="attachment.description"
:title="attachment.description"
@click.prevent="toggleHidden" @click.prevent="toggleHidden"
> >
<img <img
@ -51,7 +57,6 @@
:class="{'hidden': hidden && preloadImage }" :class="{'hidden': hidden && preloadImage }"
:href="attachment.url" :href="attachment.url"
target="_blank" target="_blank"
:title="attachment.description"
@click="openModal" @click="openModal"
> >
<StillImage <StillImage
@ -59,6 +64,7 @@
:mimetype="attachment.mimetype" :mimetype="attachment.mimetype"
:src="attachment.large_thumb_url || attachment.url" :src="attachment.large_thumb_url || attachment.url"
:image-load-handler="onImageLoad" :image-load-handler="onImageLoad"
:alt="attachment.description"
/> />
</a> </a>
@ -83,6 +89,8 @@
<audio <audio
v-if="type === 'audio'" v-if="type === 'audio'"
:src="attachment.url" :src="attachment.url"
:alt="attachment.description"
:title="attachment.description"
controls controls
/> />
@ -116,22 +124,19 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
.attachment.media-upload-container { .non-gallery {
flex: 0 0 auto;
max-height: 200px;
max-width: 100%; max-width: 100%;
display: flex;
align-items: center;
video {
max-width: 100%;
}
} }
.placeholder { .placeholder {
margin-right: 8px; display: inline-block;
margin-bottom: 4px; padding: 0.3em 1em 0.3em 0;
color: $fallback--link; color: $fallback--link;
color: var(--postLink, $fallback--link); color: var(--postLink, $fallback--link);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
} }
.nsfw-placeholder { .nsfw-placeholder {

@ -50,9 +50,7 @@
align-content: stretch; align-content: stretch;
} }
// FIXME: specificity problem with this and .attachments.attachment .gallery-row-inner .attachment {
// we shouldn't have the need for .image here
.attachment.image {
margin: 0 0.5em 0 0; margin: 0 0.5em 0 0;
flex-grow: 1; flex-grow: 1;
height: 100%; height: 100%;

@ -8,6 +8,8 @@
v-if="type === 'image'" v-if="type === 'image'"
class="modal-image" class="modal-image"
:src="currentMedia.url" :src="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
@touchstart.stop="mediaTouchStart" @touchstart.stop="mediaTouchStart"
@touchmove.stop="mediaTouchMove" @touchmove.stop="mediaTouchMove"
@click="hide" @click="hide"
@ -18,6 +20,14 @@
:attachment="currentMedia" :attachment="currentMedia"
:controls="true" :controls="true"
/> />
<audio
v-if="type === 'audio'"
class="modal-image"
:src="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
controls
/>
<button <button
v-if="canNavigate" v-if="canNavigate"
:title="$t('media_modal.previous')" :title="$t('media_modal.previous')"

@ -3,6 +3,7 @@ import MediaUpload from '../media_upload/media_upload.vue'
import ScopeSelector from '../scope_selector/scope_selector.vue' import ScopeSelector from '../scope_selector/scope_selector.vue'
import EmojiInput from '../emoji_input/emoji_input.vue' import EmojiInput from '../emoji_input/emoji_input.vue'
import PollForm from '../poll/poll_form.vue' import PollForm from '../poll/poll_form.vue'
import Attachment from '../attachment/attachment.vue'
import StatusContent from '../status_content/status_content.vue' import StatusContent from '../status_content/status_content.vue'
import fileTypeService from '../../services/file_type/file_type.service.js' import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
@ -40,6 +41,7 @@ const PostStatusForm = {
PollForm, PollForm,
ScopeSelector, ScopeSelector,
Checkbox, Checkbox,
Attachment,
StatusContent StatusContent
}, },
mounted () { mounted () {
@ -80,6 +82,7 @@ const PostStatusForm = {
nsfw: false, nsfw: false,
files: [], files: [],
poll: {}, poll: {},
mediaDescriptions: {},
visibility: scope, visibility: scope,
contentType contentType
}, },
@ -184,7 +187,7 @@ const PostStatusForm = {
} }
}, },
methods: { methods: {
postStatus (newStatus) { async postStatus (newStatus) {
if (this.posting) { return } if (this.posting) { return }
if (this.submitDisabled) { return } if (this.submitDisabled) { return }
if (this.emptyStatus) { if (this.emptyStatus) {
@ -199,7 +202,16 @@ const PostStatusForm = {
} }
this.posting = true this.posting = true
statusPoster.postStatus({
try {
await this.setAllMediaDescriptions()
} catch (e) {
this.error = this.$t('post_status.media_description_error')
this.posting = false
return
}
const data = await statusPoster.postStatus({
status: newStatus.status, status: newStatus.status,
spoilerText: newStatus.spoilerText || null, spoilerText: newStatus.spoilerText || null,
visibility: newStatus.visibility, visibility: newStatus.visibility,
@ -209,30 +221,32 @@ const PostStatusForm = {
inReplyToStatusId: this.replyTo, inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType, contentType: newStatus.contentType,
poll poll
}).then((data) => {
if (!data.error) {
this.newStatus = {
status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {}
}
this.pollFormVisible = false
this.$refs.mediaUpload.clearFile()
this.clearPollForm()
this.$emit('posted')
let el = this.$el.querySelector('textarea')
el.style.height = 'auto'
el.style.height = undefined
this.error = null
this.previewStatus()
} else {
this.error = data.error
}
this.posting = false
}) })
if (!data.error) {
this.newStatus = {
status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {},
mediaDescriptions: {}
}
this.pollFormVisible = false
this.$refs.mediaUpload.clearFile()
this.clearPollForm()
this.$emit('posted')
let el = this.$el.querySelector('textarea')
el.style.height = 'auto'
el.style.height = undefined
this.error = null
if (this.preview) this.previewStatus()
} else {
this.error = data.error
}
this.posting = false
}, },
previewStatus () { previewStatus () {
if (this.emptyStatus && this.newStatus.spoilerText.trim() === '') { if (this.emptyStatus && this.newStatus.spoilerText.trim() === '') {
@ -457,6 +471,15 @@ const PostStatusForm = {
}, },
dismissScopeNotice () { dismissScopeNotice () {
this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true }) this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true })
},
setMediaDescription (id) {
const description = this.newStatus.mediaDescriptions[id]
if (!description || description.trim() === '') return
return statusPoster.setMediaDescription({ store: this.$store, id, description })
},
setAllMediaDescriptions () {
const ids = this.newStatus.files.map(file => file.id)
return Promise.all(ids.map(id => this.setMediaDescription(id)))
} }
} }
} }

@ -282,27 +282,18 @@
class="fa button-icon icon-cancel" class="fa button-icon icon-cancel"
@click="removeMediaFile(file)" @click="removeMediaFile(file)"
/> />
<div class="media-upload-container attachment"> <attachment
<img :attachment="file"
v-if="type(file) === 'image'" :set-media="() => $store.dispatch('setMedia', newStatus.files)"
class="thumbnail media-upload" size="small"
:src="file.url" allow-play="false"
> />
<video <input
v-if="type(file) === 'video'" v-model="newStatus.mediaDescriptions[file.id]"
:src="file.url" type="text"
controls :placeholder="$t('post_status.media_description')"
/> @keydown.enter.prevent=""
<audio >
v-if="type(file) === 'audio'"
:src="file.url"
controls
/>
<a
v-if="type(file) === 'unknown'"
:href="file.url"
>{{ file.url }}</a>
</div>
</div> </div>
</div> </div>
<div <div
@ -458,11 +449,9 @@
} }
.media-upload-wrapper { .media-upload-wrapper {
flex: 0 0 auto;
max-width: 100%;
min-width: 50px;
margin-right: .2em; margin-right: .2em;
margin-bottom: .5em; margin-bottom: .5em;
width: 18em;
.icon-cancel { .icon-cancel {
display: inline-block; display: inline-block;
@ -476,6 +465,20 @@
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
img, video {
object-fit: contain;
max-height: 10em;
}
.video {
max-height: 10em;
}
input {
flex: 1;
width: 100%;
}
} }
.status-input-wrapper { .status-input-wrapper {
@ -490,23 +493,8 @@
.attachment { .attachment {
margin: 0; margin: 0;
padding: 0;
position: relative; position: relative;
flex: 0 0 auto;
border: 1px solid $fallback--border;
border: 1px solid var(--border, $fallback--border);
text-align: center;
audio {
min-width: 300px;
flex: 1 0 auto;
}
a {
display: block;
text-align: left;
line-height: 1.2;
padding: .5em;
}
} }
i { i {

@ -2,6 +2,10 @@ import map from 'lodash/map'
import BasicUserCard from '../basic_user_card/basic_user_card.vue' import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const StaffPanel = { const StaffPanel = {
created () {
const nicknames = this.$store.state.instance.staffAccounts
nicknames.forEach(nickname => this.$store.dispatch('fetchUserIfMissing', nickname))
},
components: { components: {
BasicUserCard BasicUserCard
}, },

@ -99,15 +99,8 @@ const StatusContent = {
file => !fileType.fileMatchesSomeType(this.galleryTypes, file) file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
) )
}, },
hasImageAttachments () { attachmentTypes () {
return this.status.attachments.some( return this.status.attachments.map(file => fileType.fileType(file.mimetype))
file => fileType.fileType(file.mimetype) === 'image'
)
},
hasVideoAttachments () {
return this.status.attachments.some(
file => fileType.fileType(file.mimetype) === 'video'
)
}, },
maxThumbnails () { maxThumbnails () {
return this.mergedConfig.maxThumbnails return this.mergedConfig.maxThumbnails

@ -55,13 +55,21 @@
> >
{{ $t("status.show_content") }} {{ $t("status.show_content") }}
<span <span
v-if="hasImageAttachments" v-if="attachmentTypes.includes('image')"
class="icon-picture" class="icon-picture"
/> />
<span <span
v-if="hasVideoAttachments" v-if="attachmentTypes.includes('video')"
class="icon-video" class="icon-video"
/> />
<span
v-if="attachmentTypes.includes('audio')"
class="icon-music"
/>
<span
v-if="attachmentTypes.includes('unknown')"
class="icon-doc"
/>
<span <span
v-if="status.card" v-if="status.card"
class="icon-link" class="icon-link"

@ -4,7 +4,8 @@ const StillImage = {
'referrerpolicy', 'referrerpolicy',
'mimetype', 'mimetype',
'imageLoadError', 'imageLoadError',
'imageLoadHandler' 'imageLoadHandler',
'alt'
], ],
data () { data () {
return { return {

@ -11,6 +11,8 @@
<img <img
ref="src" ref="src"
:key="src" :key="src"
:alt="alt"
:title="alt"
:src="src" :src="src"
:referrerpolicy="referrerpolicy" :referrerpolicy="referrerpolicy"
@load="onLoad" @load="onLoad"

@ -4,6 +4,8 @@
:src="attachment.url" :src="attachment.url"
:loop="loopVideo" :loop="loopVideo"
:controls="controls" :controls="controls"
:alt="attachment.description"
:title="attachment.description"
playsinline playsinline
@loadeddata="onVideoDataLoad" @loadeddata="onVideoDataLoad"
/> />

@ -178,6 +178,7 @@
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.", "account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
"account_not_locked_warning_link": "locked", "account_not_locked_warning_link": "locked",
"attachments_sensitive": "Mark attachments as sensitive", "attachments_sensitive": "Mark attachments as sensitive",
"media_description": "Media description",
"content_type": { "content_type": {
"text/plain": "Plain text", "text/plain": "Plain text",
"text/html": "HTML", "text/html": "HTML",
@ -192,6 +193,7 @@
"preview": "Preview", "preview": "Preview",
"preview_empty": "Empty", "preview_empty": "Empty",
"empty_status_error": "Can't post an empty status with no files", "empty_status_error": "Can't post an empty status with no files",
"media_description_error": "Failed to update media, try again",
"scope_notice": { "scope_notice": {
"public": "This post will be visible to everyone", "public": "This post will be visible to everyone",
"private": "This post will be visible to your followers only", "private": "This post will be visible to your followers only",

@ -66,7 +66,7 @@
"search": "Haku" "search": "Haku"
}, },
"notifications": { "notifications": {
"broken_favorite": "Viestiä ei löydetty...", "broken_favorite": "Viestiä ei löydetty",
"favorited_you": "tykkäsi viestistäsi", "favorited_you": "tykkäsi viestistäsi",
"followed_you": "seuraa sinua", "followed_you": "seuraa sinua",
"load_older": "Lataa vanhempia ilmoituksia", "load_older": "Lataa vanhempia ilmoituksia",
@ -101,7 +101,7 @@
}, },
"post_status": { "post_status": {
"new_status": "Uusi viesti", "new_status": "Uusi viesti",
"account_not_locked_warning": "Tilisi ei ole {0}. Kuka vain voi seurata sinua nähdäksesi 'vain-seuraajille' -viestisi", "account_not_locked_warning": "Tilisi ei ole {0}. Kuka vain voi seurata sinua nähdäksesi 'vain-seuraajille' -viestisi.",
"account_not_locked_warning_link": "lukittu", "account_not_locked_warning_link": "lukittu",
"attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi", "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi",
"content_type": { "content_type": {
@ -288,7 +288,7 @@
"authentication_methods": "Todennus", "authentication_methods": "Todennus",
"warning_of_generate_new_codes": "Luodessasi uudet palautuskoodit, vanhat koodisi lakkaavat toimimasta.", "warning_of_generate_new_codes": "Luodessasi uudet palautuskoodit, vanhat koodisi lakkaavat toimimasta.",
"recovery_codes": "Palautuskoodit.", "recovery_codes": "Palautuskoodit.",
"waiting_a_recovery_codes": "Odotetaan palautuskoodeja...", "waiting_a_recovery_codes": "Odotetaan palautuskoodeja",
"recovery_codes_warning": "Kirjoita koodit ylös tai tallenna ne turvallisesti, muuten et näe niitä uudestaan. Jos et voi käyttää monivaihetodennusta ja sinulla ei ole palautuskoodeja, et voi enää kirjautua sisään tilillesi.", "recovery_codes_warning": "Kirjoita koodit ylös tai tallenna ne turvallisesti, muuten et näe niitä uudestaan. Jos et voi käyttää monivaihetodennusta ja sinulla ei ole palautuskoodeja, et voi enää kirjautua sisään tilillesi.",
"scan": { "scan": {
"title": "Skannaa", "title": "Skannaa",
@ -575,7 +575,7 @@
"statuses": "Viestit", "statuses": "Viestit",
"hidden": "Piilotettu", "hidden": "Piilotettu",
"media": "Media", "media": "Media",
"block_progress": "Estetään...", "block_progress": "Estetään",
"admin_menu": { "admin_menu": {
"grant_admin": "Anna Ylläpitöoikeudet", "grant_admin": "Anna Ylläpitöoikeudet",
"force_nsfw": "Merkitse kaikki viestit NSFW:nä", "force_nsfw": "Merkitse kaikki viestit NSFW:nä",
@ -601,10 +601,10 @@
"subscribe": "Tilaa", "subscribe": "Tilaa",
"unsubscribe": "Poista tilaus", "unsubscribe": "Poista tilaus",
"unblock": "Poista esto", "unblock": "Poista esto",
"unblock_progress": "Postetaan estoa...", "unblock_progress": "Postetaan estoa",
"unmute": "Poista mykistys", "unmute": "Poista mykistys",
"unmute_progress": "Poistetaan mykistystä...", "unmute_progress": "Poistetaan mykistystä",
"mute_progress": "Mykistetään...", "mute_progress": "Mykistetään",
"hide_repeats": "Piilota toistot", "hide_repeats": "Piilota toistot",
"show_repeats": "Näytä toistot" "show_repeats": "Näytä toistot"
}, },
@ -674,8 +674,8 @@
"domain_mute_card": { "domain_mute_card": {
"mute": "Mykistä", "mute": "Mykistä",
"unmute": "Poista mykistys", "unmute": "Poista mykistys",
"mute_progress": "Mykistetään...", "mute_progress": "Mykistetään",
"unmute_progress": "Poistetaan mykistyst..." "unmute_progress": "Poistetaan mykistyst"
}, },
"exporter": { "exporter": {
"export": "Vie", "export": "Vie",

@ -34,7 +34,8 @@
"user_search": "Ricerca utenti", "user_search": "Ricerca utenti",
"search": "Ricerca", "search": "Ricerca",
"who_to_follow": "Chi seguire", "who_to_follow": "Chi seguire",
"preferences": "Preferenze" "preferences": "Preferenze",
"bookmarks": "Segnalibri"
}, },
"notifications": { "notifications": {
"followed_you": "ti segue", "followed_you": "ti segue",
@ -271,10 +272,59 @@
"shadow_id": "Ombra numero {value}", "shadow_id": "Ombra numero {value}",
"override": "Sostituisci", "override": "Sostituisci",
"component": "Componente", "component": "Componente",
"_tab_label": "Luci ed ombre" "_tab_label": "Luci ed ombre",
"components": {
"avatarStatus": "Icona utente (vista messaggio)",
"avatar": "Icona utente (vista profilo)",
"topBar": "Barra superiore",
"panelHeader": "Intestazione pannello",
"panel": "Pannello",
"input": "Campo d'immissione",
"buttonPressedHover": "Pulsante (puntato e premuto)",
"buttonPressed": "Pulsante (premuto)",
"buttonHover": "Pulsante (puntato)",
"button": "Pulsante",
"popup": "Sbalzi e suggerimenti"
},
"filter_hint": {
"inset_classic": "Le ombre incluse usano {0}",
"spread_zero": "Lo spandimento maggiore di zero si azzera sulle ombre",
"avatar_inset": "Tieni presente che combinare ombre (sia incluse che non) sulle icone utente potrebbe dare risultati strani con quelle trasparenti.",
"drop_shadow_syntax": "{0} non supporta il parametro {1} né la keyword {2}.",
"always_drop_shadow": "Attenzione: quest'ombra usa sempre {0} se il tuo browser lo supporta."
},
"hintV3": "Per le ombre puoi anche usare la sintassi {0} per sfruttare il secondo colore."
}, },
"radii": { "radii": {
"_tab_label": "Raggio" "_tab_label": "Raggio"
},
"fonts": {
"_tab_label": "Font",
"custom": "Personalizzato",
"weight": "Peso (grassettatura)",
"size": "Dimensione (in pixel)",
"family": "Nome font",
"components": {
"postCode": "Font a spaziatura fissa incluso in un messaggio",
"post": "Testo del messaggio",
"input": "Campi d'immissione",
"interface": "Interfaccia"
},
"help": "Seleziona il font da usare per gli elementi dell'interfaccia. Se scegli \"personalizzato\" devi inserire il suo nome di sistema."
},
"preview": {
"link": "un bel collegamentino",
"checkbox": "Ho dato uno sguardo a termini e condizioni",
"header_faint": "Tutto bene",
"fine_print": "Leggi il nostro {0} per imparare un bel niente!",
"faint_link": "utilissimo manuale",
"input": "Sono appena atterrato a Fiumicino.",
"mono": "contenuto",
"text": "Altro {0} e {1}",
"content": "Contenuto",
"button": "Pulsante",
"error": "Errore d'esempio",
"header": "Anteprima"
} }
}, },
"enable_web_push_notifications": "Abilita notifiche web push", "enable_web_push_notifications": "Abilita notifiche web push",
@ -336,7 +386,19 @@
"emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze", "emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze",
"pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore", "pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore",
"notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più.", "notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più.",
"mutes_and_blocks": "Zittiti e bloccati" "mutes_and_blocks": "Zittiti e bloccati",
"profile_fields": {
"value": "Contenuto",
"name": "Etichetta",
"add_field": "Aggiungi campo",
"label": "Metadati profilo"
},
"bot": "Questo profilo è di un robot",
"version": {
"frontend_version": "Versione interfaccia",
"backend_version": "Versione backend",
"title": "Versione"
}
}, },
"timeline": { "timeline": {
"error_fetching": "Errore nell'aggiornamento", "error_fetching": "Errore nell'aggiornamento",
@ -346,7 +408,10 @@
"collapse": "Riduci", "collapse": "Riduci",
"conversation": "Conversazione", "conversation": "Conversazione",
"no_retweet_hint": "Il messaggio è diretto o solo per seguaci e non può essere condiviso", "no_retweet_hint": "Il messaggio è diretto o solo per seguaci e non può essere condiviso",
"repeated": "condiviso" "repeated": "condiviso",
"no_statuses": "Nessun messaggio",
"no_more_statuses": "Fine dei messaggi",
"reload": "Ricarica"
}, },
"user_card": { "user_card": {
"follow": "Segui", "follow": "Segui",
@ -425,7 +490,10 @@
}, },
"direct_warning_to_first_only": "Questo messaggio sarà visibile solo agli utenti menzionati all'inizio.", "direct_warning_to_first_only": "Questo messaggio sarà visibile solo agli utenti menzionati all'inizio.",
"direct_warning_to_all": "Questo messaggio sarà visibile a tutti i menzionati.", "direct_warning_to_all": "Questo messaggio sarà visibile a tutti i menzionati.",
"new_status": "Nuovo messaggio" "new_status": "Nuovo messaggio",
"empty_status_error": "Non puoi pubblicare messaggi vuoti senza allegati",
"preview_empty": "Vuoto",
"preview": "Anteprima"
}, },
"registration": { "registration": {
"bio": "Introduzione", "bio": "Introduzione",
@ -548,5 +616,50 @@
"error": "Non trovato.", "error": "Non trovato.",
"searching_for": "Cerco", "searching_for": "Cerco",
"remote_user_resolver": "Cerca utenti remoti" "remote_user_resolver": "Cerca utenti remoti"
},
"errors": {
"storage_unavailable": "Pleroma non ha potuto accedere ai dati del tuo browser. Le tue credenziali o le tue impostazioni locali non potranno essere salvate e potresti incontrare strani errori. Prova ad abilitare i cookie."
},
"status": {
"pinned": "Intestato",
"unpin": "De-intesta",
"pin": "Intesta al profilo",
"delete": "Elimina messaggio",
"repeats": "Condivisi",
"favorites": "Preferiti"
},
"time": {
"years_short": "{0}a",
"year_short": "{0}a",
"years": "{0} anni",
"year": "{0} anno",
"weeks_short": "{0}set",
"week_short": "{0}set",
"seconds_short": "{0}sec",
"second_short": "{0}sec",
"weeks": "{0} settimane",
"week": "{0} settimana",
"seconds": "{0} secondi",
"second": "{0} secondo",
"now_short": "ora",
"now": "adesso",
"months_short": "{0}me",
"month_short": "{0}me",
"months": "{0} mesi",
"month": "{0} mese",
"minutes_short": "{0}min",
"minute_short": "{0}min",
"minutes": "{0} minuti",
"minute": "{0} minuto",
"in_past": "{0} fa",
"in_future": "fra {0}",
"hours_short": "{0}h",
"days_short": "{0}g",
"hour_short": "{0}h",
"hours": "{0} ore",
"hour": "{0} ora",
"day_short": "{0}g",
"days": "{0} giorni",
"day": "{0} giorno"
} }
} }

@ -85,7 +85,7 @@
"administration": "管理员" "administration": "管理员"
}, },
"notifications": { "notifications": {
"broken_favorite": "未知的状态,正在搜索中...", "broken_favorite": "未知的状态,正在搜索中",
"favorited_you": "收藏了你的状态", "favorited_you": "收藏了你的状态",
"followed_you": "关注了你", "followed_you": "关注了你",
"load_older": "加载更早的通知", "load_older": "加载更早的通知",
@ -185,7 +185,7 @@
"generate_new_recovery_codes": "生成新的恢复码", "generate_new_recovery_codes": "生成新的恢复码",
"warning_of_generate_new_codes": "当你生成新的恢复码时,你的旧恢复码就失效了。", "warning_of_generate_new_codes": "当你生成新的恢复码时,你的旧恢复码就失效了。",
"recovery_codes": "恢复码。", "recovery_codes": "恢复码。",
"waiting_a_recovery_codes": "正在接收备份码…", "waiting_a_recovery_codes": "正在接收备份码…",
"recovery_codes_warning": "抄写这些号码,或者保存在安全的地方。这些号码不会再次显示。如果你无法访问你的 2FA app也丢失了你的恢复码你的账号就再也无法登录了。", "recovery_codes_warning": "抄写这些号码,或者保存在安全的地方。这些号码不会再次显示。如果你无法访问你的 2FA app也丢失了你的恢复码你的账号就再也无法登录了。",
"authentication_methods": "身份验证方法", "authentication_methods": "身份验证方法",
"scan": { "scan": {
@ -564,11 +564,11 @@
"subscribe": "订阅", "subscribe": "订阅",
"unsubscribe": "退订", "unsubscribe": "退订",
"unblock": "取消拉黑", "unblock": "取消拉黑",
"unblock_progress": "取消拉黑中...", "unblock_progress": "取消拉黑中",
"block_progress": "拉黑中...", "block_progress": "拉黑中",
"unmute": "取消隐藏", "unmute": "取消隐藏",
"unmute_progress": "取消隐藏中...", "unmute_progress": "取消隐藏中",
"mute_progress": "隐藏中...", "mute_progress": "隐藏中",
"admin_menu": { "admin_menu": {
"moderation": "权限", "moderation": "权限",
"grant_admin": "赋予管理权限", "grant_admin": "赋予管理权限",
@ -690,9 +690,9 @@
} }
}, },
"domain_mute_card": { "domain_mute_card": {
"unmute_progress": "正在取消隐藏…", "unmute_progress": "正在取消隐藏…",
"unmute": "取消隐藏", "unmute": "取消隐藏",
"mute_progress": "隐藏中…", "mute_progress": "隐藏中…",
"mute": "隐藏" "mute": "隐藏"
} }
} }

@ -22,7 +22,7 @@ const mediaViewer = {
setMedia ({ commit }, attachments) { setMedia ({ commit }, attachments) {
const media = attachments.filter(attachment => { const media = attachments.filter(attachment => {
const type = fileTypeService.fileType(attachment.mimetype) const type = fileTypeService.fileType(attachment.mimetype)
return type === 'image' || type === 'video' return type === 'image' || type === 'video' || type === 'audio'
}) })
commit('setMedia', media) commit('setMedia', media)
}, },

@ -266,6 +266,11 @@ const users = {
mutations, mutations,
getters, getters,
actions: { actions: {
fetchUserIfMissing (store, id) {
if (!store.getters.findUser(id)) {
store.dispatch('fetchUser', id)
}
},
fetchUser (store, id) { fetchUser (store, id) {
return store.rootState.api.backendInteractor.fetchUser({ id }) return store.rootState.api.backendInteractor.fetchUser({ id })
.then((user) => { .then((user) => {

@ -708,6 +708,17 @@ const uploadMedia = ({ formData, credentials }) => {
.then((data) => parseAttachment(data)) .then((data) => parseAttachment(data))
} }
const setMediaDescription = ({ id, description, credentials }) => {
return promisedRequest({
url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`,
method: 'PUT',
headers: authHeaders(credentials),
payload: {
description
}
}).then((data) => parseAttachment(data))
}
const importBlocks = ({ file, credentials }) => { const importBlocks = ({ file, credentials }) => {
const formData = new FormData() const formData = new FormData()
formData.append('list', file) formData.append('list', file)
@ -1177,6 +1188,7 @@ const apiService = {
postStatus, postStatus,
deleteStatus, deleteStatus,
uploadMedia, uploadMedia,
setMediaDescription,
fetchMutes, fetchMutes,
muteUser, muteUser,
unmuteUser, unmuteUser,

@ -47,13 +47,18 @@ const postStatus = ({
const uploadMedia = ({ store, formData }) => { const uploadMedia = ({ store, formData }) => {
const credentials = store.state.users.currentUser.credentials const credentials = store.state.users.currentUser.credentials
return apiService.uploadMedia({ credentials, formData }) return apiService.uploadMedia({ credentials, formData })
} }
const setMediaDescription = ({ store, id, description }) => {
const credentials = store.state.users.currentUser.credentials
return apiService.setMediaDescription({ credentials, id, description })
}
const statusPosterService = { const statusPosterService = {
postStatus, postStatus,
uploadMedia uploadMedia,
setMediaDescription
} }
export default statusPosterService export default statusPosterService

@ -387,6 +387,18 @@
"css": "bookmark-empty", "css": "bookmark-empty",
"code": 61591, "code": 61591,
"src": "fontawesome" "src": "fontawesome"
},
{
"uid": "9ea0a737ccc45d6c510dcbae56058849",
"css": "music",
"code": 59432,
"src": "fontawesome"
},
{
"uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
"css": "doc",
"code": 59433,
"src": "fontawesome"
} }
] ]
} }
Loading…
Cancel
Save