Merge remote-tracking branch 'upstream/develop' into shigusegubu
* upstream/develop: (29 commits) add hideISP to defaultState of config module add changelog entry mrf transparency panel: refactor to use vuex mapState mrf transparency panel: remove unneeded components{} boot: cleanup resolveStaffAccounts lint about: add MRF transparency panel about: add staff panel about page: fix hiding of instance-specific panel, flow ToS and ISP better nav panel: add link to about page redirect /remote-users/:username@:hostname -> /users/:id, /remote-users/:hostname/:username -> /users/:id clear filter on reopen, fix error message in console reset position when reopening emoji picker eslint fix fix not being able to see unicode emojis when there few of them when searching on emoji-a-ton instances replace sanity button with loading on scroll fix search not working, use computer property instead of state fix eslint warnings Lightbox/modal multi image improvements - #381 '/api/pleroma/profile/mfa' -> '/api/pleroma/accounts/mfa' ...
This commit is contained in:
commit
45a1d30bd6
31 changed files with 590 additions and 105 deletions
|
@ -1,14 +1,16 @@
|
|||
# Changelog
|
||||
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/),
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Ability to hide/show repeats from user
|
||||
- User profile button clutter organized into a menu
|
||||
- User profile button clutter organized into a menu
|
||||
- Emoji picker
|
||||
- Started changelog anew
|
||||
- Ability to change user's email
|
||||
- About page
|
||||
### Changed
|
||||
- changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes
|
||||
### Fixed
|
||||
|
|
|
@ -184,6 +184,15 @@ const getAppSecret = async ({ store }) => {
|
|||
})
|
||||
}
|
||||
|
||||
const resolveStaffAccounts = async ({ store, accounts }) => {
|
||||
const backendInteractor = store.state.api.backendInteractor
|
||||
let nicknames = accounts.map(uri => uri.split('/').pop())
|
||||
.map(id => backendInteractor.fetchUser({ id }))
|
||||
nicknames = await Promise.all(nicknames)
|
||||
|
||||
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
|
||||
}
|
||||
|
||||
const getNodeInfo = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/nodeinfo/2.0.json')
|
||||
|
@ -212,6 +221,12 @@ const getNodeInfo = async ({ store }) => {
|
|||
const frontendVersion = window.___pleromafe_commit_hash
|
||||
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
|
||||
store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') })
|
||||
|
||||
const federation = metadata.federation
|
||||
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
|
||||
|
||||
const accounts = metadata.staffAccounts
|
||||
await resolveStaffAccounts({ store, accounts })
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import AuthForm from 'components/auth_form/auth_form.js'
|
|||
import ChatPanel from 'components/chat_panel/chat_panel.vue'
|
||||
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
||||
import About from 'components/about/about.vue'
|
||||
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
|
||||
|
||||
export default (store) => {
|
||||
const validateAuthenticatedRoute = (to, from, next) => {
|
||||
|
@ -42,6 +43,16 @@ export default (store) => {
|
|||
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
||||
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
|
||||
{ name: 'remote-user-profile-acct',
|
||||
path: '/remote-users/(@?):username([^/@]+)@:hostname([^/@]+)',
|
||||
component: RemoteUserResolver,
|
||||
beforeEnter: validateAuthenticatedRoute
|
||||
},
|
||||
{ name: 'remote-user-profile',
|
||||
path: '/remote-users/:hostname/:username',
|
||||
component: RemoteUserResolver,
|
||||
beforeEnter: validateAuthenticatedRoute
|
||||
},
|
||||
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile },
|
||||
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
|
||||
|
|
|
@ -1,15 +1,24 @@
|
|||
import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
|
||||
import FeaturesPanel from '../features_panel/features_panel.vue'
|
||||
import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
|
||||
import StaffPanel from '../staff_panel/staff_panel.vue'
|
||||
import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue'
|
||||
|
||||
const About = {
|
||||
components: {
|
||||
InstanceSpecificPanel,
|
||||
FeaturesPanel,
|
||||
TermsOfServicePanel
|
||||
TermsOfServicePanel,
|
||||
StaffPanel,
|
||||
MRFTransparencyPanel
|
||||
},
|
||||
computed: {
|
||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }
|
||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
||||
showInstanceSpecificPanel () {
|
||||
return this.$store.state.instance.showInstanceSpecificPanel &&
|
||||
!this.$store.getters.mergedConfig.hideISP &&
|
||||
this.$store.state.instance.instanceSpecificPanelContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<template>
|
||||
<div class="sidebar">
|
||||
<instance-specific-panel />
|
||||
<features-panel v-if="showFeaturesPanel" />
|
||||
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
||||
<staff-panel />
|
||||
<terms-of-service-panel />
|
||||
<MRFTransparencyPanel />
|
||||
<features-panel v-if="showFeaturesPanel" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<label
|
||||
class="checkbox"
|
||||
:class="{ disabled, indeterminate }"
|
||||
>
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:disabled="disabled"
|
||||
|
@ -12,9 +12,9 @@
|
|||
>
|
||||
<i class="checkbox-indicator" />
|
||||
<span
|
||||
class="label"
|
||||
v-if="!!$slots.default"
|
||||
>
|
||||
class="label"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</label>
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import Checkbox from '../checkbox/checkbox.vue'
|
||||
import { set } from 'vue'
|
||||
|
||||
const LOAD_EMOJI_BY = 50
|
||||
const LOAD_EMOJI_INTERVAL = 100
|
||||
const LOAD_EMOJI_SANE_AMOUNT = 500
|
||||
// At widest, approximately 20 emoji are visible in a row,
|
||||
// loading 3 rows, could be overkill for narrow picker
|
||||
const LOAD_EMOJI_BY = 60
|
||||
|
||||
// When to start loading new batch emoji, in pixels
|
||||
const LOAD_EMOJI_MARGIN = 64
|
||||
|
||||
const filterByKeyword = (list, keyword = '') => {
|
||||
return list.filter(x => x.displayText.includes(keyword))
|
||||
|
@ -24,10 +27,8 @@ const EmojiPicker = {
|
|||
showingStickers: false,
|
||||
groupsScrolledClass: 'scrolled-top',
|
||||
keepOpen: false,
|
||||
customEmojiBuffer: (this.$store.state.instance.customEmoji || [])
|
||||
.slice(0, LOAD_EMOJI_BY),
|
||||
customEmojiBufferSlice: LOAD_EMOJI_BY,
|
||||
customEmojiTimeout: null,
|
||||
customEmojiCounter: LOAD_EMOJI_BY,
|
||||
customEmojiLoadAllConfirmed: false
|
||||
}
|
||||
},
|
||||
|
@ -36,10 +37,22 @@ const EmojiPicker = {
|
|||
Checkbox
|
||||
},
|
||||
methods: {
|
||||
onStickerUploaded (e) {
|
||||
this.$emit('sticker-uploaded', e)
|
||||
},
|
||||
onStickerUploadFailed (e) {
|
||||
this.$emit('sticker-upload-failed', e)
|
||||
},
|
||||
onEmoji (emoji) {
|
||||
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
|
||||
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
||||
},
|
||||
onScroll (e) {
|
||||
const target = (e && e.target) || this.$refs['emoji-groups']
|
||||
this.updateScrolledClass(target)
|
||||
this.scrolledGroup(target)
|
||||
this.triggerLoadMore(target)
|
||||
},
|
||||
highlight (key) {
|
||||
const ref = this.$refs['group-' + key]
|
||||
const top = ref[0].offsetTop
|
||||
|
@ -49,9 +62,7 @@ const EmojiPicker = {
|
|||
this.$refs['emoji-groups'].scrollTop = top + 1
|
||||
})
|
||||
},
|
||||
scrolledGroup (e) {
|
||||
const target = (e && e.target) || this.$refs['emoji-groups']
|
||||
const top = target.scrollTop + 5
|
||||
updateScrolledClass (target) {
|
||||
if (target.scrollTop <= 5) {
|
||||
this.groupsScrolledClass = 'scrolled-top'
|
||||
} else if (target.scrollTop >= target.scrollTopMax - 5) {
|
||||
|
@ -59,6 +70,28 @@ const EmojiPicker = {
|
|||
} else {
|
||||
this.groupsScrolledClass = 'scrolled-middle'
|
||||
}
|
||||
},
|
||||
triggerLoadMore (target) {
|
||||
const ref = this.$refs['group-end-custom'][0]
|
||||
if (!ref) return
|
||||
const bottom = ref.offsetTop + ref.offsetHeight
|
||||
|
||||
const scrollerBottom = target.scrollTop + target.clientHeight
|
||||
const scrollerTop = target.scrollTop
|
||||
const scrollerMax = target.scrollHeight
|
||||
|
||||
// Loads more emoji when they come into view
|
||||
const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN
|
||||
// Always load when at the very top in case there's no scroll space yet
|
||||
const atTop = scrollerTop < 5
|
||||
// Don't load when looking at unicode category or at the very bottom
|
||||
const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax
|
||||
if (!bottomAboveViewport && (approachingBottom || atTop)) {
|
||||
this.loadEmoji()
|
||||
}
|
||||
},
|
||||
scrolledGroup (target) {
|
||||
const top = target.scrollTop + 5
|
||||
this.$nextTick(() => {
|
||||
this.emojisView.forEach(group => {
|
||||
const ref = this.$refs['group-' + group.id]
|
||||
|
@ -68,67 +101,40 @@ const EmojiPicker = {
|
|||
})
|
||||
})
|
||||
},
|
||||
loadEmojiInsane () {
|
||||
this.customEmojiLoadAllConfirmed = true
|
||||
this.continueEmojiLoad()
|
||||
},
|
||||
loadEmoji () {
|
||||
const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
|
||||
const saneLoaded = this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT &&
|
||||
!this.customEmojiLoadAllConfirmed
|
||||
|
||||
if (allLoaded || saneLoaded) {
|
||||
if (allLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
this.customEmojiBuffer.push(
|
||||
...this.filteredEmoji.slice(
|
||||
this.customEmojiCounter,
|
||||
this.customEmojiCounter + LOAD_EMOJI_BY
|
||||
)
|
||||
)
|
||||
this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL)
|
||||
this.customEmojiCounter += LOAD_EMOJI_BY
|
||||
this.customEmojiBufferSlice += LOAD_EMOJI_BY
|
||||
},
|
||||
startEmojiLoad (forceUpdate = false) {
|
||||
if (!forceUpdate) {
|
||||
this.keyword = ''
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs['emoji-groups'].scrollTop = 0
|
||||
})
|
||||
const bufferSize = this.customEmojiBuffer.length
|
||||
const bufferPrefilledSane = bufferSize === LOAD_EMOJI_SANE_AMOUNT && !this.customEmojiLoadAllConfirmed
|
||||
const bufferPrefilledAll = bufferSize === this.filteredEmoji.length
|
||||
if (forceUpdate || bufferPrefilledSane || bufferPrefilledAll) {
|
||||
if (bufferPrefilledAll && !forceUpdate) {
|
||||
return
|
||||
}
|
||||
if (this.customEmojiTimeout) {
|
||||
window.clearTimeout(this.customEmojiTimeout)
|
||||
}
|
||||
|
||||
set(
|
||||
this,
|
||||
'customEmojiBuffer',
|
||||
this.filteredEmoji.slice(0, LOAD_EMOJI_BY)
|
||||
)
|
||||
this.customEmojiCounter = LOAD_EMOJI_BY
|
||||
this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL)
|
||||
},
|
||||
continueEmojiLoad () {
|
||||
this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL)
|
||||
this.customEmojiBufferSlice = LOAD_EMOJI_BY
|
||||
},
|
||||
toggleStickers () {
|
||||
this.showingStickers = !this.showingStickers
|
||||
},
|
||||
setShowStickers (value) {
|
||||
this.showingStickers = value
|
||||
},
|
||||
onStickerUploaded (e) {
|
||||
this.$emit('sticker-uploaded', e)
|
||||
},
|
||||
onStickerUploadFailed (e) {
|
||||
this.$emit('sticker-upload-failed', e)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keyword () {
|
||||
this.customEmojiLoadAllConfirmed = false
|
||||
this.scrolledGroup()
|
||||
this.onScroll()
|
||||
this.startEmojiLoad(true)
|
||||
}
|
||||
},
|
||||
|
@ -142,19 +148,14 @@ const EmojiPicker = {
|
|||
}
|
||||
return 0
|
||||
},
|
||||
saneAmount () {
|
||||
// for UI
|
||||
return LOAD_EMOJI_SANE_AMOUNT
|
||||
},
|
||||
filteredEmoji () {
|
||||
return filterByKeyword(
|
||||
this.$store.state.instance.customEmoji || [],
|
||||
this.keyword
|
||||
)
|
||||
},
|
||||
askForSanity () {
|
||||
return this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT &&
|
||||
!this.customEmojiLoadAllConfirmed
|
||||
customEmojiBuffer () {
|
||||
return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)
|
||||
},
|
||||
emojis () {
|
||||
const standardEmojis = this.$store.state.instance.emoji || []
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
ref="emoji-groups"
|
||||
class="emoji-groups"
|
||||
:class="groupsScrolledClass"
|
||||
@scroll="scrolledGroup"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<div
|
||||
v-for="group in emojisView"
|
||||
|
@ -73,6 +73,7 @@
|
|||
:src="emoji.imageUrl"
|
||||
>
|
||||
</span>
|
||||
<span :ref="'group-end-' + group.id" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="keep-open">
|
||||
|
@ -80,20 +81,6 @@
|
|||
{{ $t('emoji.keep_open') }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div
|
||||
v-if="askForSanity"
|
||||
class="too-many-emoji"
|
||||
>
|
||||
<div class="alert warning hint">
|
||||
{{ $t('emoji.load_all_hint', { saneAmount } ) }}
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-default"
|
||||
@click.prevent="loadEmojiInsane"
|
||||
>
|
||||
{{ $t('emoji.load_all', { emojiAmount: filteredEmoji.length } ) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="showingStickers"
|
||||
|
|
|
@ -2,6 +2,7 @@ import StillImage from '../still-image/still-image.vue'
|
|||
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
||||
import Modal from '../modal/modal.vue'
|
||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||
import GestureService from '../../services/gesture_service/gesture_service'
|
||||
|
||||
const MediaModal = {
|
||||
components: {
|
||||
|
@ -29,7 +30,27 @@ const MediaModal = {
|
|||
return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.mediaSwipeGestureRight = GestureService.swipeGesture(
|
||||
GestureService.DIRECTION_RIGHT,
|
||||
this.goPrev,
|
||||
50
|
||||
)
|
||||
this.mediaSwipeGestureLeft = GestureService.swipeGesture(
|
||||
GestureService.DIRECTION_LEFT,
|
||||
this.goNext,
|
||||
50
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
mediaTouchStart (e) {
|
||||
GestureService.beginSwipe(e, this.mediaSwipeGestureRight)
|
||||
GestureService.beginSwipe(e, this.mediaSwipeGestureLeft)
|
||||
},
|
||||
mediaTouchMove (e) {
|
||||
GestureService.updateSwipe(e, this.mediaSwipeGestureRight)
|
||||
GestureService.updateSwipe(e, this.mediaSwipeGestureLeft)
|
||||
},
|
||||
hide () {
|
||||
this.$store.dispatch('closeMediaViewer')
|
||||
},
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
v-if="type === 'image'"
|
||||
class="modal-image"
|
||||
:src="currentMedia.url"
|
||||
@touchstart.stop="mediaTouchStart"
|
||||
@touchmove.stop="mediaTouchMove"
|
||||
>
|
||||
<VideoAttachment
|
||||
v-if="type === 'video'"
|
||||
|
@ -41,18 +43,16 @@
|
|||
.modal-view.media-modal-view {
|
||||
z-index: 1001;
|
||||
|
||||
&:hover {
|
||||
.modal-view-button-arrow {
|
||||
opacity: 0.75;
|
||||
.modal-view-button-arrow {
|
||||
opacity: 0.75;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { mapState } from 'vuex'
|
||||
|
||||
const MRFTransparencyPanel = {
|
||||
computed: mapState({
|
||||
federationPolicy: state => state.instance.federationPolicy,
|
||||
mrfPolicies: state => state.instance.federationPolicy.mrf_policies,
|
||||
acceptInstances: state => state.instance.federationPolicy.mrf_simple.accept,
|
||||
rejectInstances: state => state.instance.federationPolicy.mrf_simple.reject,
|
||||
quarantineInstances: state => state.instance.federationPolicy.quarantined_instances,
|
||||
ftlRemovalInstances: state => state.instance.federationPolicy.mrf_simple.federated_timeline_removal,
|
||||
mediaNsfwInstances: state => state.instance.federationPolicy.mrf_simple.media_nsfw,
|
||||
mediaRemovalInstances: state => state.instance.federationPolicy.mrf_simple.media_removal
|
||||
})
|
||||
}
|
||||
|
||||
export default MRFTransparencyPanel
|
122
src/components/mrf_transparency_panel/mrf_transparency_panel.vue
Normal file
122
src/components/mrf_transparency_panel/mrf_transparency_panel.vue
Normal file
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="federationPolicy"
|
||||
class="mrf-transparency-panel"
|
||||
>
|
||||
<div class="panel panel-default base01-background">
|
||||
<div class="panel-heading timeline-heading base02-background">
|
||||
<div class="title">
|
||||
{{ $t("about.federation") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="mrf-section">
|
||||
<h2>{{ $t("about.mrf_policies") }}</h2>
|
||||
<p>{{ $t("about.mrf_policies_desc") }}</p>
|
||||
|
||||
<ul>
|
||||
<li
|
||||
v-for="policy in mrfPolicies"
|
||||
:key="policy"
|
||||
v-text="policy"
|
||||
/>
|
||||
</ul>
|
||||
|
||||
<h2>{{ $t("about.mrf_policy_simple") }}</h2>
|
||||
|
||||
<div v-if="acceptInstances.length">
|
||||
<h4>{{ $t("about.mrf_policy_simple_accept") }}</h4>
|
||||
|
||||
<p>{{ $t("about.mrf_policy_simple_accept_desc") }}</p>
|
||||
|
||||
<ul>
|
||||
<li
|
||||
v-for="instance in acceptInstances"
|
||||
:key="instance"
|
||||
v-text="instance"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="rejectInstances.length">
|
||||
<h4>{{ $t("about.mrf_policy_simple_reject") }}</h4>
|
||||
|
||||
<p>{{ $t("about.mrf_policy_simple_reject_desc") }}</p>
|
||||
|
||||
<ul>
|
||||
<li
|
||||
v-for="instance in rejectInstances"
|
||||
:key="instance"
|
||||
v-text="instance"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="quarantineInstances.length">
|
||||
<h4>{{ $t("about.mrf_policy_simple_quarantine") }}</h4>
|
||||
|
||||
<p>{{ $t("about.mrf_policy_simple_quarantine_desc") }}</p>
|
||||
|
||||
<ul>
|
||||
<li
|
||||
v-for="instance in quarantineInstances"
|
||||
:key="instance"
|
||||
v-text="instance"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="ftlRemovalInstances.length">
|
||||
<h4>{{ $t("about.mrf_policy_simple_ftl_removal") }}</h4>
|
||||
|
||||
<p>{{ $t("about.mrf_policy_simple_ftl_removal_desc") }}</p>
|
||||
|
||||
<ul>
|
||||
<li
|
||||
v-for="instance in ftlRemovalInstances"
|
||||
:key="instance"
|
||||
v-text="instance"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="mediaNsfwInstances.length">
|
||||
<h4>{{ $t("about.mrf_policy_simple_media_nsfw") }}</h4>
|
||||
|
||||
<p>{{ $t("about.mrf_policy_simple_media_nsfw_desc") }}</p>
|
||||
|
||||
<ul>
|
||||
<li
|
||||
v-for="instance in mediaNsfwInstances"
|
||||
:key="instance"
|
||||
v-text="instance"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="mediaRemovalInstances.length">
|
||||
<h4>{{ $t("about.mrf_policy_simple_media_removal") }}</h4>
|
||||
|
||||
<p>{{ $t("about.mrf_policy_simple_media_removal_desc") }}</p>
|
||||
|
||||
<ul>
|
||||
<li
|
||||
v-for="instance in mediaRemovalInstances"
|
||||
:key="instance"
|
||||
v-text="instance"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./mrf_transparency_panel.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.mrf-section {
|
||||
margin: 1em;
|
||||
}
|
||||
</style>
|
|
@ -38,6 +38,11 @@
|
|||
{{ $t("nav.twkn") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'about' }">
|
||||
{{ $t("nav.about") }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -163,7 +163,7 @@
|
|||
<div
|
||||
ref="bottom"
|
||||
class="form-bottom"
|
||||
>
|
||||
>
|
||||
<div class="form-bottom-left">
|
||||
<media-upload
|
||||
ref="mediaUpload"
|
||||
|
|
31
src/components/remote_user_resolver/remote_user_resolver.js
Normal file
31
src/components/remote_user_resolver/remote_user_resolver.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
const RemoteUserResolver = {
|
||||
data: () => ({
|
||||
error: false
|
||||
}),
|
||||
mounted () {
|
||||
this.redirect()
|
||||
},
|
||||
methods: {
|
||||
redirect () {
|
||||
const acct = this.$route.params.username + '@' + this.$route.params.hostname
|
||||
this.$store.state.api.backendInteractor.fetchUser({ id: acct })
|
||||
.then((externalUser) => {
|
||||
if (externalUser.error) {
|
||||
this.error = true
|
||||
} else {
|
||||
this.$store.commit('addNewUsers', [externalUser])
|
||||
const id = externalUser.id
|
||||
this.$router.replace({
|
||||
name: 'external-user-profile',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.error = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default RemoteUserResolver
|
20
src/components/remote_user_resolver/remote_user_resolver.vue
Normal file
20
src/components/remote_user_resolver/remote_user_resolver.vue
Normal file
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{{ $t('remote_user_resolver.remote_user_resolver') }}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
{{ $t('remote_user_resolver.searching_for') }} @{{ $route.params.username }}@{{ $route.params.hostname }}
|
||||
</p>
|
||||
<p v-if="error">
|
||||
{{ $t('remote_user_resolver.error') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./remote_user_resolver.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
14
src/components/staff_panel/staff_panel.js
Normal file
14
src/components/staff_panel/staff_panel.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||
|
||||
const StaffPanel = {
|
||||
components: {
|
||||
BasicUserCard
|
||||
},
|
||||
computed: {
|
||||
staffAccounts () {
|
||||
return this.$store.state.instance.staffAccounts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default StaffPanel
|
23
src/components/staff_panel/staff_panel.vue
Normal file
23
src/components/staff_panel/staff_panel.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="staff-panel">
|
||||
<div class="panel panel-default base01-background">
|
||||
<div class="panel-heading timeline-heading base02-background">
|
||||
<div class="title">
|
||||
{{ $t("about.staff") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<basic-user-card
|
||||
v-for="user in staffAccounts"
|
||||
:key="user.screen_name"
|
||||
:user="user"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./staff_panel.js" ></script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
|
@ -12,11 +12,13 @@ export default Vue.component('tab-switcher', {
|
|||
},
|
||||
onSwitch: {
|
||||
required: false,
|
||||
type: Function
|
||||
type: Function,
|
||||
default: undefined
|
||||
},
|
||||
activeTab: {
|
||||
required: false,
|
||||
type: String
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
scrollableTabs: {
|
||||
required: false,
|
||||
|
|
|
@ -276,6 +276,8 @@
|
|||
mask-composite: exclude;
|
||||
background-size: cover;
|
||||
mask-size: 100% 60%;
|
||||
border-top-left-radius: calc(var(--panelRadius) - 1px);
|
||||
border-top-right-radius: calc(var(--panelRadius) - 1px);
|
||||
|
||||
&.hide-bio {
|
||||
mask-size: 100% 40px;
|
||||
|
|
|
@ -35,6 +35,7 @@ const MuteList = withSubscription({
|
|||
const UserSettings = {
|
||||
data () {
|
||||
return {
|
||||
newEmail: '',
|
||||
newName: this.$store.state.users.currentUser.name,
|
||||
newBio: unescape(this.$store.state.users.currentUser.description),
|
||||
newLocked: this.$store.state.users.currentUser.locked,
|
||||
|
@ -56,6 +57,9 @@ const UserSettings = {
|
|||
backgroundPreview: null,
|
||||
bannerUploadError: null,
|
||||
backgroundUploadError: null,
|
||||
changeEmailError: false,
|
||||
changeEmailPassword: '',
|
||||
changedEmail: false,
|
||||
deletingAccount: false,
|
||||
deleteAccountConfirmPasswordInput: '',
|
||||
deleteAccountError: false,
|
||||
|
@ -305,6 +309,22 @@ const UserSettings = {
|
|||
}
|
||||
})
|
||||
},
|
||||
changeEmail () {
|
||||
const params = {
|
||||
email: this.newEmail,
|
||||
password: this.changeEmailPassword
|
||||
}
|
||||
this.$store.state.api.backendInteractor.changeEmail(params)
|
||||
.then((res) => {
|
||||
if (res.status === 'success') {
|
||||
this.changedEmail = true
|
||||
this.changeEmailError = false
|
||||
} else {
|
||||
this.changedEmail = false
|
||||
this.changeEmailError = res.error
|
||||
}
|
||||
})
|
||||
},
|
||||
activateTab (tabName) {
|
||||
this.activeTab = tabName
|
||||
},
|
||||
|
|
|
@ -85,14 +85,14 @@
|
|||
<Checkbox
|
||||
v-model="hideFollowsCount"
|
||||
:disabled="!hideFollows"
|
||||
>
|
||||
>
|
||||
{{ $t('settings.hide_follows_count_description') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<p>
|
||||
<Checkbox
|
||||
v-model="hideFollowers"
|
||||
>
|
||||
>
|
||||
{{ $t('settings.hide_followers_description') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
|
@ -233,6 +233,39 @@
|
|||
</div>
|
||||
|
||||
<div :label="$t('settings.security_tab')">
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.change_email') }}</h2>
|
||||
<div>
|
||||
<p>{{ $t('settings.new_email') }}</p>
|
||||
<input
|
||||
v-model="newEmail"
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{ $t('settings.current_password') }}</p>
|
||||
<input
|
||||
v-model="changeEmailPassword"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-default"
|
||||
@click="changeEmail"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
</button>
|
||||
<p v-if="changedEmail">
|
||||
{{ $t('settings.changed_email') }}
|
||||
</p>
|
||||
<template v-if="changeEmailError !== false">
|
||||
<p>{{ $t('settings.change_email_error') }}</p>
|
||||
<p>{{ changeEmailError }}</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.change_password') }}</h2>
|
||||
<div>
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
"features_panel": {
|
||||
"chat": "Chat",
|
||||
"gopher": "Gopher",
|
||||
"media_proxy": "Media Proxy",
|
||||
"media_proxy": "Medienproxy",
|
||||
"scope_options": "Reichweitenoptionen",
|
||||
"text_limit": "Textlimit",
|
||||
"title": "Features",
|
||||
"who_to_follow": "Who to follow"
|
||||
"who_to_follow": "Wem folgen?"
|
||||
},
|
||||
"finder": {
|
||||
"error_fetching_user": "Fehler beim Suchen des Benutzers",
|
||||
|
@ -29,15 +29,18 @@
|
|||
"username": "Benutzername"
|
||||
},
|
||||
"nav": {
|
||||
"about": "Über",
|
||||
"back": "Zurück",
|
||||
"chat": "Lokaler Chat",
|
||||
"friend_requests": "Followanfragen",
|
||||
"mentions": "Erwähnungen",
|
||||
"interactions": "Interaktionen",
|
||||
"dms": "Direktnachrichten",
|
||||
"public_tl": "Öffentliche Zeitleiste",
|
||||
"timeline": "Zeitleiste",
|
||||
"twkn": "Das gesamte bekannte Netzwerk",
|
||||
"user_search": "Benutzersuche",
|
||||
"search": "Suche",
|
||||
"preferences": "Voreinstellungen"
|
||||
},
|
||||
"notifications": {
|
||||
|
@ -115,6 +118,9 @@
|
|||
"delete_account_description": "Lösche deinen Account und alle deine Nachrichten unwiderruflich.",
|
||||
"delete_account_error": "Es ist ein Fehler beim Löschen deines Accounts aufgetreten. Tritt dies weiterhin auf, wende dich an den Administrator der Instanz.",
|
||||
"delete_account_instructions": "Tippe dein Passwort unten in das Feld ein, um die Löschung deines Accounts zu bestätigen.",
|
||||
"discoverable": "Erlaubnis für automatisches Suchen nach diesem Account",
|
||||
"avatar_size_instruction": "Die empfohlene minimale Größe für Avatare ist 150x150 Pixel.",
|
||||
"pad_emoji": "Emojis mit Leerzeichen umrahmen",
|
||||
"export_theme": "Farbschema speichern",
|
||||
"filtering": "Filtern",
|
||||
"filtering_explanation": "Alle Beiträge die diese Wörter enthalten werden ausgeblendet. Ein Wort pro Zeile.",
|
||||
|
@ -128,8 +134,11 @@
|
|||
"general": "Allgemein",
|
||||
"hide_attachments_in_convo": "Anhänge in Unterhaltungen ausblenden",
|
||||
"hide_attachments_in_tl": "Anhänge in der Zeitleiste ausblenden",
|
||||
"hide_muted_posts": "Verberge Beiträge stummgeschalteter Nutzer",
|
||||
"max_thumbnails": "Maximale Anzahl von Vorschaubildern pro Beitrag",
|
||||
"hide_isp": "Instanz-spezifisches Panel ausblenden",
|
||||
"preload_images": "Bilder vorausladen",
|
||||
"use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen",
|
||||
"hide_post_stats": "Beitragsstatistiken verbergen (z.B. die Anzahl der Favoriten)",
|
||||
"hide_user_stats": "Benutzerstatistiken verbergen (z.B. die Anzahl der Follower)",
|
||||
"hide_filtered_statuses": "Gefilterte Beiträge verbergen",
|
||||
|
@ -147,6 +156,9 @@
|
|||
"lock_account_description": "Sperre deinen Account, um neue Follower zu genehmigen oder abzulehnen",
|
||||
"loop_video": "Videos wiederholen",
|
||||
"loop_video_silent_only": "Nur Videos ohne Ton wiederholen (z.B. Mastodons \"gifs\")",
|
||||
"mutes_tab": "Mutes",
|
||||
"play_videos_in_modal": "Videos in größerem Medienfenster abspielen",
|
||||
"use_contain_fit": "Vorschaubilder nicht zuschneiden",
|
||||
"name": "Name",
|
||||
"name_bio": "Name & Bio",
|
||||
"new_password": "Neues Passwort",
|
||||
|
@ -158,6 +170,8 @@
|
|||
"no_rich_text_description": "Rich-Text Formatierungen von allen Beiträgen entfernen",
|
||||
"hide_follows_description": "Zeige nicht, wem ich folge",
|
||||
"hide_followers_description": "Zeige nicht, wer mir folgt",
|
||||
"hide_follows_count_description": "Verberge die Anzahl deiner Gefolgten",
|
||||
"hide_followers_count_description": "Verberge die Anzahl deiner Folgenden",
|
||||
"nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind",
|
||||
"oauth_tokens": "OAuth-Token",
|
||||
"token": "Zeichen",
|
||||
|
@ -176,10 +190,12 @@
|
|||
"reply_visibility_all": "Alle Antworten zeigen",
|
||||
"reply_visibility_following": "Zeige nur Antworten an mich oder an Benutzer, denen ich folge",
|
||||
"reply_visibility_self": "Nur Antworten an mich anzeigen",
|
||||
"autohide_floating_post_button": "Automatisches Verbergen des Knopfs für neue Beiträge (mobil)",
|
||||
"saving_err": "Fehler beim Speichern der Einstellungen",
|
||||
"saving_ok": "Einstellungen gespeichert",
|
||||
"security_tab": "Sicherheit",
|
||||
"scope_copy": "Reichweite beim Antworten übernehmen (Direktnachrichten werden immer kopiert)",
|
||||
"minimal_scopes_mode": "Minimiere Reichweitenoptionen",
|
||||
"set_new_avatar": "Setze einen neuen Avatar",
|
||||
"set_new_profile_background": "Setze einen neuen Hintergrund für dein Profil",
|
||||
"set_new_profile_banner": "Setze einen neuen Banner für dein Profil",
|
||||
|
@ -189,7 +205,8 @@
|
|||
"subject_line_email": "Wie Email: \"re: Betreff\"",
|
||||
"subject_line_mastodon": "Wie Mastodon: unverändert kopieren",
|
||||
"subject_line_noop": "Nicht kopieren",
|
||||
"stop_gifs": "Play-on-hover GIFs",
|
||||
"post_status_content_type": "Beitragsart",
|
||||
"stop_gifs": "Animationen nur beim Darüberfahren abspielen",
|
||||
"streaming": "Aktiviere automatisches Laden (Streaming) von neuen Beiträgen",
|
||||
"text": "Text",
|
||||
"theme": "Farbschema",
|
||||
|
@ -372,5 +389,25 @@
|
|||
"GiB": "GiB",
|
||||
"TiB": "TiB"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"people": "Leute",
|
||||
"hashtags": "Hashtags",
|
||||
"person_talking": "{count} Person spricht darüber",
|
||||
"people_talking": "{count} Leute sprechen darüber",
|
||||
"no_results": "Keine Ergebnisse"
|
||||
},
|
||||
"password_reset": {
|
||||
"forgot_password": "Passwort vergessen?",
|
||||
"password_reset": "Password zurücksetzen",
|
||||
"instruction": "Wenn du hier deinen Benutznamen oder die zugehörige E-Mail-Adresse eingibst, kann dir der Server einen Link zum Passwortzurücksetzen zuschicken.",
|
||||
"placeholder": "Dein Benutzername oder die zugehörige E-Mail-Adresse",
|
||||
"check_email": "Im E-Mail-Posteingang des angebenen Kontos müsste sich jetzt (oder zumindest in Kürze) die E-Mail mit dem Link zum Passwortzurücksetzen befinden.",
|
||||
"return_home": "Zurück zur Heimseite",
|
||||
"not_found": "Benutzername/E-Mail-Adresse nicht gefunden. Vertippt?",
|
||||
"too_many_requests": "Kurze Pause. Zu viele Versuche. Bitte, später nochmal probieren.",
|
||||
"password_reset_disabled": "Passwortzurücksetzen deaktiviert. Bitte Administrator kontaktieren.",
|
||||
"password_reset_required": "Passwortzurücksetzen erforderlich",
|
||||
"password_reset_required_but_mailer_is_disabled": "Passwortzurücksetzen wäre erforderlich, ist aber deaktiviert. Bitte Administrator kontaktieren."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,23 @@
|
|||
{
|
||||
"about": {
|
||||
"staff": "Staff",
|
||||
"federation": "Federation",
|
||||
"mrf_policies": "Enabled MRF Policies",
|
||||
"mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance. The following policies are enabled:",
|
||||
"mrf_policy_simple": "Instance-specific Policies",
|
||||
"mrf_policy_simple_accept": "Accept",
|
||||
"mrf_policy_simple_accept_desc": "This instance only accepts messages from the following instances:",
|
||||
"mrf_policy_simple_reject": "Reject",
|
||||
"mrf_policy_simple_reject_desc": "This instance will not accept messages from the following instances:",
|
||||
"mrf_policy_simple_quarantine": "Quarantine",
|
||||
"mrf_policy_simple_quarantine_desc": "This instance will send only public posts to the following instances:",
|
||||
"mrf_policy_simple_ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
|
||||
"mrf_policy_simple_ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
|
||||
"mrf_policy_simple_media_removal": "Media Removal",
|
||||
"mrf_policy_simple_media_removal_desc": "This instance removes media from posts on the following instances:",
|
||||
"mrf_policy_simple_media_nsfw": "Media Force-set As Sensitive",
|
||||
"mrf_policy_simple_media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
|
||||
},
|
||||
"chat": {
|
||||
"title": "Chat"
|
||||
},
|
||||
|
@ -172,6 +191,11 @@
|
|||
"password_confirmation_match": "should be the same as password"
|
||||
}
|
||||
},
|
||||
"remote_user_resolver": {
|
||||
"remote_user_resolver": "Remote user resolver",
|
||||
"searching_for": "Searching for",
|
||||
"error": "Not found."
|
||||
},
|
||||
"selectable_list": {
|
||||
"select_all": "Select all"
|
||||
},
|
||||
|
@ -219,6 +243,9 @@
|
|||
"cGreen": "Green (Retweet)",
|
||||
"cOrange": "Orange (Favorite)",
|
||||
"cRed": "Red (Cancel)",
|
||||
"change_email": "Change Email",
|
||||
"change_email_error": "There was an issue changing your email.",
|
||||
"changed_email": "Email changed successfully!",
|
||||
"change_password": "Change Password",
|
||||
"change_password_error": "There was an issue changing your password.",
|
||||
"changed_password": "Password changed successfully!",
|
||||
|
@ -277,6 +304,7 @@
|
|||
"use_contain_fit": "Don't crop the attachment in thumbnails",
|
||||
"name": "Name",
|
||||
"name_bio": "Name & Bio",
|
||||
"new_email": "New Email",
|
||||
"new_password": "New password",
|
||||
"notification_visibility": "Types of notifications to show",
|
||||
"notification_visibility_follows": "Follows",
|
||||
|
|
|
@ -558,6 +558,8 @@
|
|||
"unmute": "Isiltasuna kendu",
|
||||
"unmute_progress": "Isiltasuna kentzen...",
|
||||
"mute_progress": "Isiltzen...",
|
||||
"hide_repeats": "Ezkutatu errepikapenak",
|
||||
"show_repeats": "Erakutsi errpekiapenak",
|
||||
"admin_menu": {
|
||||
"moderation": "Moderazioa",
|
||||
"grant_admin": "Administratzaile baimena",
|
||||
|
@ -633,6 +635,8 @@
|
|||
"return_home": "Itzuli hasierara",
|
||||
"not_found": "Ezin izan dugu helbide elektroniko edo erabiltzaile hori aurkitu.",
|
||||
"too_many_requests": "Saiakera gehiegi burutu ditzu, saiatu berriro geroxeago.",
|
||||
"password_reset_disabled": "Pasahitza berrezartzea debekatuta dago. Mesedez, jarri harremanetan instantzia administratzailearekin."
|
||||
"password_reset_disabled": "Pasahitza berrezartzea debekatuta dago. Mesedez, jarri harremanetan instantzia administratzailearekin.",
|
||||
"password_reset_required": "Pasahitza berrezarri behar duzu saioa hasteko.",
|
||||
"password_reset_required_but_mailer_is_disabled": "Pasahitza berrezarri behar duzu, baina pasahitza berrezartzeko aukera desgaituta dago. Mesedez, jarri harremanetan instantziaren administratzailearekin."
|
||||
}
|
||||
}
|
|
@ -127,6 +127,9 @@
|
|||
"cGreen": "Повторить",
|
||||
"cOrange": "Нравится",
|
||||
"cRed": "Отменить",
|
||||
"change_email": "Сменить email",
|
||||
"change_email_error": "Произошла ошибка при попытке изменить email.",
|
||||
"changed_email": "Email изменён успешно.",
|
||||
"change_password": "Сменить пароль",
|
||||
"change_password_error": "Произошла ошибка при попытке изменить пароль.",
|
||||
"changed_password": "Пароль изменён успешно.",
|
||||
|
@ -169,6 +172,7 @@
|
|||
"loop_video_silent_only": "Зацикливать только беззвучные видео (т.е. \"гифки\" с Mastodon)",
|
||||
"name": "Имя",
|
||||
"name_bio": "Имя и описание",
|
||||
"new_email": "Новый email",
|
||||
"new_password": "Новый пароль",
|
||||
"notification_visibility": "Показывать уведомления",
|
||||
"notification_visibility_follows": "Подписки",
|
||||
|
|
|
@ -5,6 +5,7 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
|||
|
||||
export const defaultState = {
|
||||
colors: {},
|
||||
hideISP: false,
|
||||
// bad name: actually hides posts of muted USERS
|
||||
hideMutedPosts: undefined, // instance default
|
||||
collapseMessageWithSubject: undefined, // instance default
|
||||
|
|
|
@ -8,6 +8,7 @@ const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications
|
|||
const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
|
||||
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
||||
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
||||
const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
|
||||
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
||||
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
|
||||
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
|
||||
|
@ -16,12 +17,12 @@ const ADMIN_USERS_URL = '/api/pleroma/admin/users'
|
|||
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
||||
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
|
||||
|
||||
const MFA_SETTINGS_URL = '/api/pleroma/profile/mfa'
|
||||
const MFA_BACKUP_CODES_URL = '/api/pleroma/profile/mfa/backup_codes'
|
||||
const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
|
||||
const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
|
||||
|
||||
const MFA_SETUP_OTP_URL = '/api/pleroma/profile/mfa/setup/totp'
|
||||
const MFA_CONFIRM_OTP_URL = '/api/pleroma/profile/mfa/confirm/totp'
|
||||
const MFA_DISABLE_OTP_URL = '/api/pleroma/profile/mfa/totp'
|
||||
const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
|
||||
const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
|
||||
const MFA_DISABLE_OTP_URL = '/api/pleroma/account/mfa/totp'
|
||||
|
||||
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
|
||||
const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
|
||||
|
@ -691,6 +692,20 @@ const deleteAccount = ({ credentials, password }) => {
|
|||
.then((response) => response.json())
|
||||
}
|
||||
|
||||
const changeEmail = ({ credentials, email, password }) => {
|
||||
const form = new FormData()
|
||||
|
||||
form.append('email', email)
|
||||
form.append('password', password)
|
||||
|
||||
return fetch(CHANGE_EMAIL_URL, {
|
||||
body: form,
|
||||
method: 'POST',
|
||||
headers: authHeaders(credentials)
|
||||
})
|
||||
.then((response) => response.json())
|
||||
}
|
||||
|
||||
const changePassword = ({ credentials, password, newPassword, newPasswordConfirmation }) => {
|
||||
const form = new FormData()
|
||||
|
||||
|
@ -966,6 +981,7 @@ const apiService = {
|
|||
importBlocks,
|
||||
importFollows,
|
||||
deleteAccount,
|
||||
changeEmail,
|
||||
changePassword,
|
||||
settingsMFA,
|
||||
mfaDisableOTP,
|
||||
|
|
|
@ -131,6 +131,7 @@ const backendInteractorService = credentials => {
|
|||
const importFollows = (file) => apiService.importFollows({ file, credentials })
|
||||
|
||||
const deleteAccount = ({ password }) => apiService.deleteAccount({ credentials, password })
|
||||
const changeEmail = ({ email, password }) => apiService.changeEmail({ credentials, email, password })
|
||||
const changePassword = ({ password, newPassword, newPasswordConfirmation }) =>
|
||||
apiService.changePassword({ credentials, password, newPassword, newPasswordConfirmation })
|
||||
|
||||
|
@ -195,6 +196,7 @@ const backendInteractorService = credentials => {
|
|||
importBlocks,
|
||||
importFollows,
|
||||
deleteAccount,
|
||||
changeEmail,
|
||||
changePassword,
|
||||
fetchSettingsMFA,
|
||||
generateMfaBackupCodes,
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
|
||||
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
|
||||
"monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
|
||||
"mammal": [ "Mammal", "#272c37", "#444b5d", "#f8f8f8", "#9bacc8", "#7f3142", "#2bd850", "#2b90d9", "#ca8f04" ],
|
||||
|
||||
"redmond-xx": "/static/themes/redmond-xx.json",
|
||||
"redmond-xx-se": "/static/themes/redmond-xx-se.json",
|
||||
"redmond-xxi": "/static/themes/redmond-xxi.json",
|
||||
"breezy-dark": "/static/themes/breezy-dark.json",
|
||||
"breezy-light": "/static/themes/breezy-light.json"
|
||||
"breezy-light": "/static/themes/breezy-light.json",
|
||||
"mammal": "/static/themes/mammal.json"
|
||||
}
|
||||
|
|
57
static/themes/mammal.json
Normal file
57
static/themes/mammal.json
Normal file
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"_pleroma_theme_version": 2,
|
||||
"name": "Mammal",
|
||||
"theme": {
|
||||
"shadows": {
|
||||
"button": [],
|
||||
"buttonHover": [
|
||||
{
|
||||
"x": "0",
|
||||
"y": "0",
|
||||
"blur": "0",
|
||||
"spread": 1024,
|
||||
"color": "#56a7e1",
|
||||
"alpha": "1",
|
||||
"inset": true
|
||||
}
|
||||
],
|
||||
"buttonPressed": [
|
||||
{
|
||||
"x": "0",
|
||||
"y": "0",
|
||||
"blur": "0",
|
||||
"spread": 1024,
|
||||
"color": "#56a7e1",
|
||||
"alpha": "1",
|
||||
"inset": true
|
||||
}
|
||||
],
|
||||
"panel": [],
|
||||
"panelHeader": [],
|
||||
"topBar": []
|
||||
},
|
||||
"opacity": { "input": "1" },
|
||||
"colors": {
|
||||
"bg": "#282c37",
|
||||
"text": "#f8f8f8",
|
||||
"link": "#9bacc8",
|
||||
"fg": "#444b5d",
|
||||
"input": "#FFFFFF",
|
||||
"inputText": "#282c37",
|
||||
"btn": "#2b90d9",
|
||||
"btnText": "#FFFFFF",
|
||||
"cRed": "#7f3142",
|
||||
"cBlue": "#2b90d9",
|
||||
"cGreen": "#2bd850",
|
||||
"cOrange": "#ca8f04"
|
||||
},
|
||||
"radii": {
|
||||
"btn": 4,
|
||||
"input": 4,
|
||||
"panel": "0",
|
||||
"avatar": "4",
|
||||
"avatarAlt": "4",
|
||||
"attachment": "4"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue