Merge pull request '2022.10 stable' (#177) from develop into stable
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: AkkomaGang/pleroma-fe#177
This commit is contained in:
commit
c8c8d40827
48 changed files with 2973 additions and 1755 deletions
|
@ -1,47 +0,0 @@
|
|||
# This file is a template, and might need editing before it works on your project.
|
||||
# Official framework image. Look for the different tagged releases at:
|
||||
# https://hub.docker.com/r/library/node/tags/
|
||||
image: node:12
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
|
||||
lint:
|
||||
stage: lint
|
||||
script:
|
||||
- yarn
|
||||
- npm run lint
|
||||
- npm run stylelint
|
||||
|
||||
test:
|
||||
stage: test
|
||||
variables:
|
||||
APT_CACHE_DIR: apt-cache
|
||||
script:
|
||||
- mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
||||
- apt install firefox-esr -y --no-install-recommends
|
||||
- firefox --version
|
||||
- yarn
|
||||
- yarn unit
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- yarn
|
||||
- npm run build
|
||||
artifacts:
|
||||
paths:
|
||||
- dist/
|
||||
|
||||
docs-deploy:
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
only:
|
||||
- develop@pleroma/pleroma-fe
|
||||
before_script:
|
||||
- apk add curl
|
||||
script:
|
||||
- curl -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' https://git.pleroma.social/api/v4/projects/673/trigger/pipeline
|
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
- Implemented remote interaction with statuses
|
||||
|
||||
|
||||
## 2022.09 - 2022-09-10
|
||||
### Added
|
||||
- Automatic post translations. Must be configured on the backend in order to work.
|
||||
- Post editing, including a log of previous edits.
|
||||
|
||||
### Changed
|
||||
- Top bar now has navigation shortcuts. Can be enabled or disabled by admins or users.
|
||||
- Optional replacement of timeline drop-down with navigation buttons. Also configurable.
|
||||
- Posts and posts with replies are now separated on user profiles.
|
||||
- Custom emoji from remote instances on a post can now also be used.
|
||||
|
||||
## 2022.08 - 2022-08-12
|
||||
### Added
|
||||
- Ability to quote public and unlisted posts
|
||||
- Bubble timeline
|
||||
|
||||
### Changed
|
||||
- Emoji in emoji picker is separated by packs
|
||||
|
||||
### Removed
|
||||
- Chats, they were half-baked. Just use PMs.
|
||||
|
||||
## 2022.07 - 2022-07-16
|
||||
### Fixed
|
||||
- AdminFE button no longer scrolls page to top when clicked
|
||||
- Pinned statuses no longer appear at bottom of user timeline (still appear as part of the timeline when fetched deep enough)
|
||||
|
@ -16,6 +43,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
|
||||
- Attachment description is prefilled with backend-provided default when uploading
|
||||
- Proper visual feedback that next image is loading when browsing
|
||||
- Misskey-Flavoured Markdown support
|
||||
- Custom emoji reactions
|
||||
|
||||
### Changed
|
||||
- (You)s are optional (opt-in) now, bolding your nickname is also optional (opt-out)
|
||||
|
|
|
@ -6,6 +6,7 @@ You have several timelines to browse trough
|
|||
- **Bookmarks** all the posts you've bookmarked. You can bookmark a post by clicking the three dots on the bottom right of the post and choose Bookmark.
|
||||
- **Direct Messages** all posts with `direct` scope addressed to you or mentioning you.
|
||||
- **Public Timelines** all public posts made by users on the instance you're on
|
||||
- **Bubble Timeline** all public posts from instances recommended by your admin(s) in the instance settings. This won't appear if they haven't set anything up for it.
|
||||
- **The Whole Known Network** also known as **TWKN** or **Federated Timeline** - all public posts known by your instance. Due to nature of the network your instance may not know *all* the posts on the network, so only posts known by your instance are shown there.
|
||||
|
||||
Note that by default you will see all posts made by other users on your Home Timeline, this contrast behavior of Twitter and Mastodon, which shows you only non-reply posts and replies to people you follow. You can change said behavior in the [settings](settings.md#filtering).
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "pleroma_fe",
|
||||
"version": "1.0.0",
|
||||
"description": "A Qvitter-style frontend for certain GS servers.",
|
||||
"version": "3.2.0",
|
||||
"description": "A frontend for Akkoma instances",
|
||||
"author": "Roger Braun <roger@rogerbraun.net>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -20,7 +20,7 @@
|
|||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.3.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@fortawesome/vue-fontawesome": "3.0.1",
|
||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
||||
"@vuelidate/core": "2.0.0-alpha.42",
|
||||
|
@ -41,7 +41,7 @@
|
|||
"qrcode": "1",
|
||||
"ruffle-mirror": "2021.12.31",
|
||||
"vue": "^3.2.31",
|
||||
"vue-i18n": "^9.2.0-beta.39",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "4.0.14",
|
||||
"vue-template-compiler": "2.6.11",
|
||||
"vuex": "4.0.2"
|
||||
|
|
|
@ -61,7 +61,6 @@
|
|||
<EditStatusModal v-if="editingAvailable" />
|
||||
<StatusHistoryModal v-if="editingAvailable" />
|
||||
<SettingsModal />
|
||||
<UpdateNotification />
|
||||
<GlobalNoticeList />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -398,7 +398,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
store.dispatch('startFetchingAnnouncements')
|
||||
getTOS({ store })
|
||||
getStickers({ store })
|
||||
store.dispatch('getSupportedTranslationlanguages')
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
|
|
|
@ -20,6 +20,9 @@ const About = {
|
|||
return this.$store.state.instance.showInstanceSpecificPanel &&
|
||||
!this.$store.getters.mergedConfig.hideISP &&
|
||||
this.$store.state.instance.instanceSpecificPanelContent
|
||||
},
|
||||
showLocalBubblePanel () {
|
||||
return this.$store.state.instance.localBubbleInstances.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
||||
<staff-panel />
|
||||
<terms-of-service-panel />
|
||||
<LocalBubblePanel />
|
||||
<LocalBubblePanel v-if="showLocalBubblePanel" />
|
||||
<MRFTransparencyPanel />
|
||||
<features-panel v-if="showFeaturesPanel" />
|
||||
</div>
|
||||
|
|
|
@ -52,6 +52,9 @@ const AccountActions = {
|
|||
unblockUser () {
|
||||
this.$store.dispatch('unblockUser', this.user.id)
|
||||
},
|
||||
removeUserFromFollowers () {
|
||||
this.$store.dispatch('removeUserFromFollowers', this.user.id)
|
||||
},
|
||||
reportUser () {
|
||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
||||
}
|
||||
|
|
|
@ -28,6 +28,13 @@
|
|||
class="dropdown-divider"
|
||||
/>
|
||||
</template>
|
||||
<button
|
||||
v-if="relationship.followed_by"
|
||||
class="btn button-default btn-block dropdown-item"
|
||||
@click="removeUserFromFollowers"
|
||||
>
|
||||
{{ $t('user_card.remove_follower') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="relationship.blocking"
|
||||
class="btn button-default btn-block dropdown-item"
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
faThumbtack,
|
||||
faShareAlt,
|
||||
faExternalLinkAlt,
|
||||
faHistory
|
||||
faHistory,
|
||||
faFilePen
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faBookmark as faBookmarkReg,
|
||||
|
@ -24,7 +25,8 @@ library.add(
|
|||
faShareAlt,
|
||||
faExternalLinkAlt,
|
||||
faFlag,
|
||||
faHistory
|
||||
faHistory,
|
||||
faFilePen
|
||||
)
|
||||
|
||||
const ExtraButtons = {
|
||||
|
@ -36,7 +38,8 @@ const ExtraButtons = {
|
|||
data () {
|
||||
return {
|
||||
expanded: false,
|
||||
showingDeleteDialog: false
|
||||
showingDeleteDialog: false,
|
||||
showingRedraftDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -122,6 +125,34 @@ const ExtraButtons = {
|
|||
const stripFieldsList = ['attachments', 'created_at', 'emojis', 'text', 'raw_html', 'nsfw', 'poll', 'summary', 'summary_raw_html']
|
||||
stripFieldsList.forEach(p => delete originalStatus[p])
|
||||
this.$store.dispatch('openStatusHistoryModal', originalStatus)
|
||||
},
|
||||
redraftStatus () {
|
||||
if (this.shouldConfirmDelete) {
|
||||
this.showRedraftStatusConfirmDialog()
|
||||
} else {
|
||||
this.doRedraftStatus()
|
||||
}
|
||||
},
|
||||
doRedraftStatus () {
|
||||
this.$store.dispatch('fetchStatusSource', { id: this.status.id })
|
||||
.then(data => this.$store.dispatch('openPostStatusModal', {
|
||||
isRedraft: true,
|
||||
statusId: this.status.id,
|
||||
subject: data.spoiler_text,
|
||||
statusText: data.text,
|
||||
statusIsSensitive: this.status.nsfw,
|
||||
statusPoll: this.status.poll,
|
||||
statusFiles: [...this.status.attachments],
|
||||
statusScope: this.status.visibility,
|
||||
statusContentType: data.content_type
|
||||
}))
|
||||
this.doDeleteStatus()
|
||||
},
|
||||
showRedraftStatusConfirmDialog () {
|
||||
this.showingRedraftDialog = true
|
||||
},
|
||||
hideRedraftStatusConfirmDialog () {
|
||||
this.showingRedraftDialog = false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -95,6 +95,17 @@
|
|||
icon="history"
|
||||
/><span>{{ $t("status.edit_history") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="ownStatus"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="redraftStatus"
|
||||
@click="close"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="file-pen"
|
||||
/><span>{{ $t("status.redraft") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="canDelete"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
|
@ -179,6 +190,16 @@
|
|||
>
|
||||
{{ $t('status.delete_confirm') }}
|
||||
</ConfirmModal>
|
||||
<ConfirmModal
|
||||
v-if="showingRedraftDialog"
|
||||
:title="$t('status.redraft_confirm_title')"
|
||||
:cancel-text="$t('status.redraft_confirm_cancel_button')"
|
||||
:confirm-text="$t('status.redraft_confirm_accept_button')"
|
||||
@cancelled="hideRedraftStatusConfirmDialog"
|
||||
@accepted="doRedraftStatus"
|
||||
>
|
||||
{{ $t('status.redraft_confirm') }}
|
||||
</ConfirmModal>
|
||||
</teleport>
|
||||
</template>
|
||||
</Popover>
|
||||
|
|
|
@ -31,7 +31,10 @@ const FavoriteButton = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['mergedConfig'])
|
||||
...mapGetters(['mergedConfig']),
|
||||
remoteInteractionLink () {
|
||||
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,13 +13,19 @@
|
|||
:spin="animated"
|
||||
/>
|
||||
</button>
|
||||
<span v-else>
|
||||
<a
|
||||
v-else
|
||||
class="button-unstyled interactive"
|
||||
target="_blank"
|
||||
role="button"
|
||||
:href="remoteInteractionLink"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
:title="$t('tool_tip.favorite')"
|
||||
:icon="['far', 'star']"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
<span
|
||||
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
|
||||
class="action-counter"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||
import RemoteFollow from '../remote_follow/remote_follow.vue'
|
||||
import FollowButton from '../follow_button/follow_button.vue'
|
||||
import RemoveFollowerButton from '../remove_follower_button/remove_follower_button.vue'
|
||||
|
||||
const FollowCard = {
|
||||
props: [
|
||||
|
@ -10,7 +11,8 @@ const FollowCard = {
|
|||
components: {
|
||||
BasicUserCard,
|
||||
RemoteFollow,
|
||||
FollowButton
|
||||
FollowButton,
|
||||
RemoveFollowerButton
|
||||
},
|
||||
computed: {
|
||||
isMe () {
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
class="follow-card-follow-button"
|
||||
:user="user"
|
||||
/>
|
||||
<RemoveFollowerButton
|
||||
v-if="noFollowsYou && relationship.followed_by"
|
||||
:relationship="relationship"
|
||||
class="follow-card-button"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</basic-user-card>
|
||||
|
@ -40,6 +45,12 @@
|
|||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
&-button {
|
||||
margin-top: 0.5em;
|
||||
padding: 0 1.5em;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
&-follow-button {
|
||||
margin-top: 0.5em;
|
||||
margin-left: auto;
|
||||
|
|
|
@ -86,7 +86,8 @@ const PostStatusForm = {
|
|||
'fileLimit',
|
||||
'submitOnEnter',
|
||||
'emojiPickerPlacement',
|
||||
'optimisticPosting'
|
||||
'optimisticPosting',
|
||||
'isRedraft'
|
||||
],
|
||||
emits: [
|
||||
'posted',
|
||||
|
@ -141,7 +142,7 @@ const PostStatusForm = {
|
|||
contentType
|
||||
}
|
||||
|
||||
if (this.statusId) {
|
||||
if (this.statusId || this.isRedraft) {
|
||||
const statusContentType = this.statusContentType || contentType
|
||||
statusParams = {
|
||||
spoilerText: this.subject || '',
|
||||
|
@ -259,7 +260,7 @@ const PostStatusForm = {
|
|||
return this.newStatus.files.length >= this.fileLimit
|
||||
},
|
||||
isEdit () {
|
||||
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
|
||||
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== '' && !this.isRedraft
|
||||
},
|
||||
...mapGetters(['mergedConfig']),
|
||||
...mapState({
|
||||
|
|
|
@ -28,7 +28,8 @@ const PostStatusModal = {
|
|||
},
|
||||
watch: {
|
||||
params (newVal, oldVal) {
|
||||
if (get(newVal, 'repliedUser.id') !== get(oldVal, 'repliedUser.id')) {
|
||||
if (get(newVal, 'repliedUser.id') !== get(oldVal, 'repliedUser.id') ||
|
||||
get(newVal, 'statusId') !== get(oldVal, 'statusId')) {
|
||||
this.resettingForm = true
|
||||
this.$nextTick(() => {
|
||||
this.resettingForm = false
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
export default {
|
||||
props: ['relationship'],
|
||||
data () {
|
||||
return {
|
||||
inProgress: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
label () {
|
||||
if (this.inProgress) {
|
||||
return this.$t('user_card.follow_progress')
|
||||
} else {
|
||||
return this.$t('user_card.remove_follower')
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick () {
|
||||
this.inProgress = true
|
||||
this.$store.dispatch('removeUserFromFollowers', this.relationship.id).then(() => {
|
||||
this.inProgress = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<button
|
||||
class="btn button-default follow-button"
|
||||
:class="{ toggled: inProgress }"
|
||||
:disabled="inProgress"
|
||||
:title="$t('user_card.remove_follower')"
|
||||
@click="onClick"
|
||||
>
|
||||
{{ label }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script src="./remove_follower_button.js"></script>
|
|
@ -9,6 +9,9 @@ const ReplyButton = {
|
|||
computed: {
|
||||
loggedIn () {
|
||||
return !!this.$store.state.users.currentUser
|
||||
},
|
||||
remoteInteractionLink () {
|
||||
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,19 @@
|
|||
icon="reply"
|
||||
/>
|
||||
</button>
|
||||
<span v-else>
|
||||
<a
|
||||
v-else
|
||||
class="button-unstyled interactive"
|
||||
target="_blank"
|
||||
role="button"
|
||||
:href="remoteInteractionLink"
|
||||
>
|
||||
<FAIcon
|
||||
icon="reply"
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
:title="$t('tool_tip.reply')"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
<span
|
||||
v-if="status.replies_count > 0"
|
||||
class="action-counter"
|
||||
|
|
|
@ -51,6 +51,9 @@ const RetweetButton = {
|
|||
},
|
||||
shouldConfirmRepeat () {
|
||||
return this.mergedConfig.modalOnRepeat
|
||||
},
|
||||
remoteInteractionLink () {
|
||||
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,13 +20,19 @@
|
|||
:title="$t('timeline.no_retweet_hint')"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
<a
|
||||
v-else
|
||||
class="button-unstyled interactive"
|
||||
target="_blank"
|
||||
role="button"
|
||||
:href="remoteInteractionLink"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="retweet"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
<span
|
||||
v-if="!mergedConfig.hidePostStats && status.repeat_num > 0"
|
||||
class="no-event"
|
||||
|
|
|
@ -12,7 +12,8 @@ export default {
|
|||
'path',
|
||||
'disabled',
|
||||
'options',
|
||||
'expert'
|
||||
'expert',
|
||||
'hideDefaultLabel'
|
||||
],
|
||||
computed: {
|
||||
pathDefault () {
|
||||
|
|
|
@ -16,7 +16,11 @@
|
|||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
{{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }}
|
||||
<template
|
||||
v-if="hideDefaultLabel !== true"
|
||||
>
|
||||
{{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }}
|
||||
</template>
|
||||
</option>
|
||||
</Select>
|
||||
<ModifiedIndicator :changed="isChanged" />
|
||||
|
|
|
@ -19,7 +19,7 @@ const SharedComputedObject = () => ({
|
|||
.map(key => [key, {
|
||||
get () { return this.$store.getters.mergedConfig[key] },
|
||||
set (value) {
|
||||
this.$store.dispatch('setOption', { name: key, value })
|
||||
this.$store.dispatch('setOption', { name: key, value, manual: true })
|
||||
}
|
||||
}])
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
||||
|
@ -27,7 +27,7 @@ const SharedComputedObject = () => ({
|
|||
.map(key => ['serverSide_' + key, {
|
||||
get () { return this.$store.state.serverSideConfig[key] },
|
||||
set (value) {
|
||||
this.$store.dispatch('setServerSideOption', { name: key, value })
|
||||
this.$store.dispatch('setServerSideOption', { name: key, value, manual: true })
|
||||
}
|
||||
}])
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
||||
|
|
|
@ -8,11 +8,12 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
|
|||
import ServerSideIndicator from '../helpers/server_side_indicator.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faGlobe
|
||||
faGlobe, faSync
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faGlobe
|
||||
faGlobe,
|
||||
faSync
|
||||
)
|
||||
|
||||
const GeneralTab = {
|
||||
|
@ -48,6 +49,8 @@ const GeneralTab = {
|
|||
value: tab,
|
||||
label: this.$t(`user_card.${tab}`)
|
||||
})),
|
||||
profilesExpanded: false,
|
||||
newProfileName: '',
|
||||
loopSilentAvailable:
|
||||
// Firefox
|
||||
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
|
||||
|
@ -88,8 +91,22 @@ const GeneralTab = {
|
|||
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
||||
}
|
||||
},
|
||||
settingsProfiles () {
|
||||
return (this.$store.state.instance.settingsProfiles || [])
|
||||
},
|
||||
settingsProfile: {
|
||||
get: function () { return this.$store.getters.mergedConfig.profile },
|
||||
set: function (val) {
|
||||
this.$store.dispatch('setOption', { name: 'profile', value: val })
|
||||
this.$store.dispatch('getSettingsProfile')
|
||||
}
|
||||
},
|
||||
settingsVersion () {
|
||||
return this.$store.getters.mergedConfig.profileVersion
|
||||
},
|
||||
translationLanguages () {
|
||||
return (this.$store.getters.mergedConfig.supportedTranslationLanguages.target || []).map(lang => ({ key: lang.code, value: lang.code, label: lang.name }))
|
||||
const langs = this.$store.state.instance.translationLanguages || []
|
||||
return (langs || []).map(lang => ({ key: lang.code, value: lang.code, label: lang.name }))
|
||||
},
|
||||
translationLanguage: {
|
||||
get: function () { return this.$store.getters.mergedConfig.translationLanguage },
|
||||
|
@ -105,6 +122,30 @@ const GeneralTab = {
|
|||
},
|
||||
setTranslationLanguage (value) {
|
||||
this.$store.dispatch('setOption', { name: 'translationLanguage', value })
|
||||
},
|
||||
toggleExpandedSettings () {
|
||||
this.profilesExpanded = !this.profilesExpanded
|
||||
},
|
||||
loadSettingsProfile (name) {
|
||||
this.$store.commit('setOption', { name: 'profile', value: name })
|
||||
this.$store.dispatch('getSettingsProfile', true)
|
||||
},
|
||||
createSettingsProfile () {
|
||||
this.$store.dispatch('setOption', { name: 'profile', value: this.newProfileName })
|
||||
this.$store.dispatch('setOption', { name: 'profileVersion', value: 1 })
|
||||
this.$store.dispatch('syncSettings')
|
||||
this.newProfileName = ''
|
||||
},
|
||||
forceSync () {
|
||||
this.$store.dispatch('getSettingsProfile')
|
||||
},
|
||||
refreshProfiles () {
|
||||
this.$store.dispatch('listSettingsProfiles')
|
||||
},
|
||||
deleteSettingsProfile (name) {
|
||||
if (confirm(this.$t('settings.settings_profile_delete_confirm'))) {
|
||||
this.$store.dispatch('deleteSettingsProfile', name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<div :label="$t('settings.general')">
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.interface') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<interface-language-switcher
|
||||
|
@ -10,6 +9,94 @@
|
|||
:set-language="val => language = val"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
v-if="user && (settingsProfiles.length > 0)"
|
||||
>
|
||||
<h2>{{ $t('settings.settings_profile') }}</h2>
|
||||
<p>
|
||||
{{ $t('settings.settings_profile_currently', { name: settingsProfile, version: settingsVersion }) }}
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click="forceSync()"
|
||||
>
|
||||
{{ $t('settings.settings_profile_force_sync') }}
|
||||
</button>
|
||||
|
||||
</p>
|
||||
<div
|
||||
@click="toggleExpandedSettings"
|
||||
>
|
||||
<template
|
||||
v-if="profilesExpanded"
|
||||
>
|
||||
<button class="btn button-default">
|
||||
{{ $t('settings.settings_profiles_unshow') }}
|
||||
</button>
|
||||
</template>
|
||||
<template
|
||||
v-else
|
||||
>
|
||||
<button class="btn button-default">
|
||||
{{ $t('settings.settings_profiles_show') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<br>
|
||||
<template
|
||||
v-if="profilesExpanded"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="profile in settingsProfiles"
|
||||
:key="profile.id"
|
||||
class="settings-profile"
|
||||
>
|
||||
<h4>{{ profile.name }} ({{ profile.version }})</h4>
|
||||
<template
|
||||
v-if="settingsProfile === profile.name"
|
||||
>
|
||||
{{ $t('settings.settings_profile_in_use') }}
|
||||
</template>
|
||||
<template
|
||||
v-else
|
||||
>
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click="loadSettingsProfile(profile.name)"
|
||||
>
|
||||
{{ $t('settings.settings_profile_use') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click="deleteSettingsProfile(profile.name)"
|
||||
>
|
||||
{{ $t('settings.settings_profile_delete') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<button class="btn button-default" @click="refreshProfiles()">
|
||||
{{ $t('settings.settings_profiles_refresh') }}
|
||||
<FAIcon icon="sync" @click="refreshProfiles()" />
|
||||
</button>
|
||||
<h3>{{ $t('settings.settings_profile_creation') }}</h3>
|
||||
<label for="settings-profile-new-name">
|
||||
{{ $t('settings.settings_profile_creation_new_name_label') }}
|
||||
</label>
|
||||
<input v-model="newProfileName" id="settings-profile-new-name">
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click="createSettingsProfile"
|
||||
>
|
||||
{{ $t('settings.settings_profile_creation_submit') }}
|
||||
</button>
|
||||
</template>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.interface') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li v-if="instanceSpecificPanelPresent">
|
||||
<BooleanSetting path="hideISP">
|
||||
{{ $t('settings.hide_isp') }}
|
||||
|
@ -463,7 +550,6 @@
|
|||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<!-- <BooleanSetting path="serverSide_defaultNSFW"> -->
|
||||
<BooleanSetting path="sensitiveByDefault">
|
||||
{{ $t('settings.sensitive_by_default') }}
|
||||
</BooleanSetting>
|
||||
|
@ -546,3 +632,13 @@
|
|||
</template>
|
||||
|
||||
<script src="./general_tab.js"></script>
|
||||
<style lang="scss">
|
||||
.settings-profile {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#settings-profile-new-name {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -83,7 +83,7 @@ const StatusContent = {
|
|||
return this.status.attachments.map(file => fileType.fileType(file.mimetype))
|
||||
},
|
||||
translationLanguages () {
|
||||
return (this.$store.getters.mergedConfig.supportedTranslationLanguages.source || []).map(lang => ({ key: lang.code, value: lang.code, label: lang.name }))
|
||||
return (this.$store.state.instance.supportedTranslationLanguages.source || []).map(lang => ({ key: lang.code, value: lang.code, label: lang.name }))
|
||||
},
|
||||
...mapGetters(['mergedConfig'])
|
||||
},
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
v-if="status.translation"
|
||||
class="translation"
|
||||
>
|
||||
<h4>{{ $t('status.translated_from', { language: status.translation.detected_language }) }}</h4>
|
||||
<h4>{{ $t(`languages.translated_from.${status.translation.detected_language.toLowerCase()}`) }}</h4>
|
||||
<RichContent
|
||||
:class="{ '-single-line': singleLine }"
|
||||
class="text media-body"
|
||||
|
@ -85,7 +85,7 @@
|
|||
:key="language.key"
|
||||
:value="language.value"
|
||||
>
|
||||
{{ language.label }}
|
||||
{{ $t(`languages.${language.value.toLowerCase()}`) }}
|
||||
</option>
|
||||
</Select>
|
||||
{{ ' ' }}
|
||||
|
|
|
@ -11,12 +11,13 @@ const StillImage = {
|
|||
],
|
||||
data () {
|
||||
return {
|
||||
stopGifs: this.$store.getters.mergedConfig.stopGifs
|
||||
stopGifs: this.$store.getters.mergedConfig.stopGifs,
|
||||
isAnimated: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
animated () {
|
||||
return this.stopGifs && (this.mimetype === 'image/gif' || this.src.endsWith('.gif'))
|
||||
return this.stopGifs && this.isAnimated
|
||||
},
|
||||
style () {
|
||||
const appendPx = (str) => /\d$/.test(str) ? str + 'px' : str
|
||||
|
@ -31,17 +32,89 @@ const StillImage = {
|
|||
const image = this.$refs.src
|
||||
if (!image) return
|
||||
this.imageLoadHandler && this.imageLoadHandler(image)
|
||||
this.detectAnimation(image)
|
||||
this.drawThumbnail()
|
||||
},
|
||||
onError () {
|
||||
this.imageLoadError && this.imageLoadError()
|
||||
},
|
||||
detectAnimation (image) {
|
||||
if (this.mimetype === 'image/gif' || this.src.endsWith('.gif')) {
|
||||
this.isAnimated = true
|
||||
return
|
||||
}
|
||||
// harmless CORS errors without-- clean console with
|
||||
if (!this.$store.state.instance.mediaProxyAvailable) return
|
||||
// Animated JPEGs?
|
||||
if (!(this.src.endsWith('.webp') || this.src.endsWith('.png'))) return
|
||||
// Browser Cache should ensure image doesn't get loaded twice if cache exists
|
||||
fetch(image.src, {
|
||||
referrerPolicy: 'same-origin'
|
||||
})
|
||||
.then(data => {
|
||||
// We don't need to read the whole file so only call it once
|
||||
data.body.getReader().read()
|
||||
.then(reader => {
|
||||
if (this.src.endsWith('.webp') && this.isAnimatedWEBP(reader.value)) {
|
||||
this.isAnimated = true
|
||||
return
|
||||
}
|
||||
if (this.src.endsWith('.png') && this.isAnimatedPNG(reader.value)) {
|
||||
this.isAnimated = true
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// this.imageLoadError && this.imageLoadError()
|
||||
})
|
||||
},
|
||||
isAnimatedWEBP (data) {
|
||||
/**
|
||||
* WEBP HEADER CHUNK
|
||||
* === START HEADER ===
|
||||
* 82 73 70 70 ("RIFF")
|
||||
* xx xx xx xx (SIZE)
|
||||
* 87 69 66 80 ("WEBP")
|
||||
* === END OF HEADER ===
|
||||
* 86 80 56 88 ("VP8X") ← Extended VP8X
|
||||
* xx xx xx xx (VP8X)
|
||||
* [++] ← RSVILEX(A)R (1 byte)
|
||||
* A → Animated bit
|
||||
*/
|
||||
// Relevant bytes
|
||||
const segment = data.slice(4 * 3, (4 * 5) + 1)
|
||||
// Check for VP8X string
|
||||
if (segment.join('').includes(['86805688'])) {
|
||||
// Check for Animation bit
|
||||
return !!((segment[8] >> 1) & 1)
|
||||
}
|
||||
// No VP8X = Not Animated (X is for Extended)
|
||||
return false
|
||||
},
|
||||
isAnimatedPNG (data) {
|
||||
// Find acTL before IDAT in PNG; if found it is animated
|
||||
const segment = []
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
segment.push(String.fromCharCode(data[i]))
|
||||
}
|
||||
const str = segment.join('')
|
||||
const idatPos = str.indexOf('IDAT')
|
||||
return (str.substring(0, idatPos > 0 ? idatPos : 0).indexOf('acTL') > 0)
|
||||
},
|
||||
drawThumbnail () {
|
||||
const canvas = this.$refs.canvas
|
||||
if (!canvas) return
|
||||
if (!this.$refs.canvas) return
|
||||
const image = this.$refs.src
|
||||
const width = image.naturalWidth
|
||||
const height = image.naturalHeight
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
|
||||
},
|
||||
onError () {
|
||||
this.imageLoadError && this.imageLoadError()
|
||||
}
|
||||
},
|
||||
updated () {
|
||||
// On computed animated change
|
||||
this.drawThumbnail()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -135,7 +135,6 @@
|
|||
a {
|
||||
display: block;
|
||||
padding: 0.6em 0.65em;
|
||||
padding-bottom: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: $fallback--lightBg;
|
||||
|
|
|
@ -23,7 +23,8 @@ const TimelineMenuContent = {
|
|||
...mapState({
|
||||
currentUser: state => state.users.currentUser,
|
||||
privateMode: state => state.instance.private,
|
||||
federating: state => state.instance.federating
|
||||
federating: state => state.instance.federating,
|
||||
showBubbleTimeline: state => (state.instance.localBubbleInstances.length > 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
>{{ $t("nav.home_timeline") }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser">
|
||||
<li v-if="currentUser && showBubbleTimeline">
|
||||
<router-link
|
||||
class="menu-item"
|
||||
:to="{ name: 'bubble-timeline' }"
|
||||
|
|
341
src/i18n/de.json
341
src/i18n/de.json
|
@ -5,7 +5,7 @@
|
|||
"keyword": {
|
||||
"ftl_removal": "Von der Zeitleiste \"Das gesamte bekannte Netzwerk\" entfernen",
|
||||
"is_replaced_by": "→",
|
||||
"keyword_policies": "Keyword Richtlinien",
|
||||
"keyword_policies": "Richtlinien für Schlüsselwörter",
|
||||
"reject": "Ablehnen",
|
||||
"replace": "Ersetzen"
|
||||
},
|
||||
|
@ -16,12 +16,15 @@
|
|||
"accept_desc": "Diese Instanz akzeptiert nur Nachrichten von den folgenden Instanzen:",
|
||||
"ftl_removal": "Von der Zeitleiste \"Das bekannte Netzwerk\" entfernen",
|
||||
"ftl_removal_desc": "Dieser Instanz entfernt folgende Instanzen von der \"Das bekannte Netzwerk\" Zeitleiste:",
|
||||
"instance": "Instanz",
|
||||
"media_nsfw": "Erzwingen Medien als heikel zu makieren",
|
||||
"media_nsfw_desc": "Diese Instanz makiert die Medien in Beiträgen der folgenden Instanzen als heikel:",
|
||||
"media_removal": "Medienentfernung",
|
||||
"media_removal_desc": "Diese Instanz entfernt Medien von den Beiträgen der folgenden Instanzen:",
|
||||
"not_applicable": "entfällt",
|
||||
"quarantine": "Quarantäne",
|
||||
"quarantine_desc": "Diese Instanz sendet nur öffentliche Beiträge zu den folgenden Instanzen:",
|
||||
"quarantine_desc": "Diese Instanz sendet keine Beiträge zu den folgenden Instanzen:",
|
||||
"reason": "Grund",
|
||||
"reject": "Ablehnen",
|
||||
"reject_desc": "Diese Instanz akzeptiert keine Nachrichten der folgenden Instanzen:",
|
||||
"simple_policies": "Instanzspezifische Richtlinien"
|
||||
|
@ -29,6 +32,27 @@
|
|||
},
|
||||
"staff": "Mitarbeiter"
|
||||
},
|
||||
"announcements": {
|
||||
"all_day_prompt": "Dies ist ein Ganztagsereignis",
|
||||
"cancel_edit_action": "Abbrechen",
|
||||
"close_error": "Schließen",
|
||||
"delete_action": "Löschen",
|
||||
"edit_action": "Bearbeiten",
|
||||
"end_time_display": "Endet um {time}",
|
||||
"end_time_prompt": "Ende: ",
|
||||
"inactive_message": "Diese Ankündigung ist inaktiv",
|
||||
"mark_as_read_action": "Als gelesen markieren",
|
||||
"page_header": "Ankündigungen",
|
||||
"post_action": "Veröffentlichen",
|
||||
"post_error": "Fehler: {error}",
|
||||
"post_form_header": "Ankündigung veröffentlichen",
|
||||
"post_placeholder": "Inhalt der Ankündigung",
|
||||
"published_time_display": "Veröffentlicht um {time}",
|
||||
"start_time_display": "Startet um {time}",
|
||||
"start_time_prompt": "Start: ",
|
||||
"submit_edit_action": "Absenden",
|
||||
"title": "Ankündigung"
|
||||
},
|
||||
"chats": {
|
||||
"chats": "Chats",
|
||||
"delete": "Löschen",
|
||||
|
@ -109,6 +133,13 @@
|
|||
"admin": "Admin",
|
||||
"moderator": "Moderator"
|
||||
},
|
||||
"scope_in_timeline": {
|
||||
"direct": "Direkt",
|
||||
"local": "Lokal - nur deine eigene Instanz kann diesen Beitrag sehen",
|
||||
"private": "Nur an Folgende",
|
||||
"public": "Öffentlich",
|
||||
"unlisted": "Nicht gelistet"
|
||||
},
|
||||
"show_less": "Zeige weniger",
|
||||
"show_more": "Zeige mehr",
|
||||
"submit": "Absenden",
|
||||
|
@ -131,6 +162,84 @@
|
|||
"load_older": "Lade ältere Interaktionen",
|
||||
"moves": "Benutzer migriert zu"
|
||||
},
|
||||
"languages": {
|
||||
"ar": "Arabisch",
|
||||
"az": "Aserbaidschanisch",
|
||||
"bg": "Bulgarisch",
|
||||
"cs": "Tschechisch",
|
||||
"da": "Dänisch",
|
||||
"de": "Deutsch",
|
||||
"el": "Griechisch",
|
||||
"en": "Englisch",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spanisch",
|
||||
"fa": "Persisch",
|
||||
"fi": "Finnisch",
|
||||
"fr": "Französisch",
|
||||
"ga": "Irisch",
|
||||
"he": "Hebräisch",
|
||||
"hi": "Hindi",
|
||||
"hu": "Ungarisch",
|
||||
"id": "Indonesisch",
|
||||
"it": "Italienisch",
|
||||
"ja": "Japanisch",
|
||||
"ko": "Koreanisch",
|
||||
"lt": "Litauisch",
|
||||
"lv": "Lettisch",
|
||||
"nl": "Niederländisch",
|
||||
"pl": "Polnisch",
|
||||
"pt": "Portugiesisch",
|
||||
"ru": "Russisch",
|
||||
"sk": "Slowakisch",
|
||||
"sv": "Schwedisch",
|
||||
"tr": "Türkisch",
|
||||
"translated_from": {
|
||||
"ar": "Übersetzt aus dem Arabischen",
|
||||
"az": "Übersetzt aus dem Aserbaidschanischen",
|
||||
"bg": "Übersetzt aus dem Bulgarischen",
|
||||
"cs": "Übersetzt aus dem Tschechischen",
|
||||
"da": "Übersetzt aus dem Dänischen",
|
||||
"de": "Übersetzt aus dem Deutschen",
|
||||
"el": "Übersetzt aus dem Griechischen",
|
||||
"en": "Übersetzt aus dem Englischen",
|
||||
"eo": "Übersetzt von @:languages.eo",
|
||||
"es": "Übersetzt aus dem Spanischen",
|
||||
"fa": "Übersetzt aus dem Persischen",
|
||||
"fi": "Übersetzt aus dem Finnischen",
|
||||
"fr": "Übersetzt aus dem Französischen",
|
||||
"ga": "Übersetzt aus dem Irischen",
|
||||
"he": "Übersetzt aus dem Hebräischen",
|
||||
"hi": "Übersetzt von @:languages.hi",
|
||||
"hu": "Übersetzt aus dem Ungarischen",
|
||||
"id": "Übersetzt aus dem Indonesischen",
|
||||
"it": "Übersetzt aus dem Italienischen",
|
||||
"ja": "Übersetzt aus dem Japanischen",
|
||||
"ko": "Übersetzt aus dem Koreanischen",
|
||||
"lt": "Übersetzt aus dem Litauischen",
|
||||
"lv": "Übersetzt aus dem Lettischen",
|
||||
"nl": "Übersetzt aus dem Niederländischen",
|
||||
"pl": "Übersetzt aus dem Polnischen",
|
||||
"pt": "Übersetzt aus dem Portugiesischen",
|
||||
"ru": "Übersetzt aus dem Russischen",
|
||||
"sk": "Übersetzt aus dem Slowakischen",
|
||||
"sv": "Übersetzt aus dem Schwedischen",
|
||||
"tr": "Übersetzt aus dem Türkischen",
|
||||
"uk": "Übersetzt aus dem Ukrainischen",
|
||||
"zh": "Übersetzt aus dem Chinesischen"
|
||||
},
|
||||
"uk": "Ukrainisch",
|
||||
"zh": "Chinesisch"
|
||||
},
|
||||
"lists": {
|
||||
"create": "Erstellen",
|
||||
"delete": "Liste löschen",
|
||||
"following_only": "Auf Folgende begrenzen",
|
||||
"lists": "Listen",
|
||||
"new": "Neue Liste",
|
||||
"save": "Änderungen speichern",
|
||||
"search": "Benutzer suchen",
|
||||
"title": "Listen-Titel"
|
||||
},
|
||||
"login": {
|
||||
"authentication_code": "Authentifizierungscode",
|
||||
"description": "Mit OAuth anmelden",
|
||||
|
@ -144,38 +253,45 @@
|
|||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"password": "Passwort",
|
||||
"placeholder": "z.B. lain",
|
||||
"placeholder": "meinbenutzername",
|
||||
"recovery_code": "Wiederherstellungscode",
|
||||
"register": "Registrieren",
|
||||
"username": "Benutzername"
|
||||
},
|
||||
"media_modal": {
|
||||
"counter": "{current} / {total}",
|
||||
"hide": "Medienansicht schließen",
|
||||
"next": "Weiter",
|
||||
"previous": "Zurück"
|
||||
},
|
||||
"nav": {
|
||||
"about": "Über",
|
||||
"administration": "Administration",
|
||||
"announcements": "Ankündigungen",
|
||||
"back": "Zurück",
|
||||
"bookmarks": "Lesezeichen",
|
||||
"chats": "Chats",
|
||||
"dms": "Direktnachrichten",
|
||||
"friend_requests": "Followanfragen",
|
||||
"home_timeline": "Heim Zeitlinie",
|
||||
"home_timeline": "Heimzeitleiste",
|
||||
"home_timeline_description": "Beiträge von Leuten, denen du folgst",
|
||||
"interactions": "Interaktionen",
|
||||
"lists": "Listen",
|
||||
"mentions": "Erwähnungen",
|
||||
"preferences": "Voreinstellungen",
|
||||
"public_timeline_description": "Öffentliche Beiträge von dieser Instanz",
|
||||
"public_tl": "Öffentliche Zeitleiste",
|
||||
"search": "Suche",
|
||||
"timeline": "Zeitleiste",
|
||||
"timelines": "Zeitlinie",
|
||||
"twkn": "Bekannte Netzwerk",
|
||||
"twkn": "Bekanntes Netzwerk",
|
||||
"twkn_timeline_description": "Beiträge aus dem gesamten bekannten Netzwerk",
|
||||
"user_search": "Benutzersuche",
|
||||
"who_to_follow": "Wem folgen"
|
||||
},
|
||||
"notifications": {
|
||||
"broken_favorite": "Unbekannte Nachricht, suche danach…",
|
||||
"error": "Error beim laden von Neuigkeiten",
|
||||
"error": "Fehler beim Laden neuer Benachrichtigungen: {0}",
|
||||
"favorited_you": "favorisierte deine Nachricht",
|
||||
"follow_request": "möchte dir folgen",
|
||||
"followed_you": "folgt dir",
|
||||
|
@ -183,6 +299,7 @@
|
|||
"migrated_to": "migrierte zu",
|
||||
"no_more_notifications": "Keine Benachrichtigungen mehr",
|
||||
"notifications": "Benachrichtigungen",
|
||||
"poll_ended": "Umfrage wurde beendet",
|
||||
"reacted_with": "reagierte mit {0}",
|
||||
"read": "Gelesen!",
|
||||
"repeated_you": "wiederholte deine Nachricht"
|
||||
|
@ -223,27 +340,34 @@
|
|||
"text/bbcode": "BBCode",
|
||||
"text/html": "HTML",
|
||||
"text/markdown": "Markdown",
|
||||
"text/plain": "Nur Text"
|
||||
"text/plain": "Nur Text",
|
||||
"text/x.misskeymarkdown": "MFM"
|
||||
},
|
||||
"content_warning": "Betreff (optional)",
|
||||
"default": "Sitze gerade im Hofbräuhaus.",
|
||||
"content_warning": "Inhaltswarnung (optional)",
|
||||
"default": "Sitze gerade im Hofbräuhaus",
|
||||
"direct_warning_to_all": "Dieser Beitrag wird für alle erwähnten Benutzer sichtbar sein.",
|
||||
"direct_warning_to_first_only": "Dieser Beitrag wird für alle Benutzer, die am Anfang der Nachricht erwähnt wurden, sichtbar sein.",
|
||||
"empty_status_error": "Eine leere Nachricht ohne Anhänge kann nicht gesendet werden",
|
||||
"edit_remote_warning": "Änderungen könnten auf manchen Instanzen nicht sichtbar sein!",
|
||||
"edit_status": "Beitrag ändern",
|
||||
"edit_unsupported_warning": "Umfragen und Erwähnungen werden durch die Bearbeitung nicht geändert.",
|
||||
"empty_status_error": "Eine Nachricht ohne Text und ohne Anhänge kann nicht gesendet werden",
|
||||
"media_description": "Medienbeschreibung",
|
||||
"media_description_error": "Medien konnten nicht neu geladen werden, versuche es erneut",
|
||||
"new_status": "Neuen Status veröffentlichen",
|
||||
"media_not_sensitive_warning": "Es wurde eine Inhaltswarnung eingestellt, aber die Anhänge sind nicht als heikel gekennzeichnet!",
|
||||
"new_status": "Neuer Post",
|
||||
"post": "Post",
|
||||
"posting": "Veröffentlichen",
|
||||
"preview": "Vorschau",
|
||||
"preview_empty": "Leer",
|
||||
"scope": {
|
||||
"direct": "Direkt - Beitrag nur an erwähnte Profile",
|
||||
"local": "Lokal - diesen Beitrag nicht föderieren",
|
||||
"private": "Nur Follower - Beitrag nur für Follower sichtbar",
|
||||
"public": "Öffentlich - Beitrag an öffentliche Zeitleisten",
|
||||
"unlisted": "Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen"
|
||||
},
|
||||
"scope_notice": {
|
||||
"local": "Dieser Bericht ist auf anderen Instanzen nicht sichbar",
|
||||
"private": "Dieser Beitrag wird nur für deine Follower sichtbar sein",
|
||||
"public": "Dieser Beitrag wird für alle sichtbar sein",
|
||||
"unlisted": "Dieser Beitrag wird weder in der öffentlichen Zeitleiste noch im gesamten bekannten Netzwerk sichtbar sein"
|
||||
|
@ -251,11 +375, |