Compare commits
97 Commits
f14169546d
...
44c1f43bd8
Author | SHA1 | Date |
---|---|---|
FloatingGhost | 44c1f43bd8 | |
FloatingGhost | 5ee4f76de3 | |
FloatingGhost | 11f24727c7 | |
FloatingGhost | 4126a1d95e | |
Sam Therapy | 089b80f00f | |
FloatingGhost | 91703a2eee | |
Sam Therapy | c48c23f406 | |
eris | 73362ef2be | |
eris | d30d684602 | |
eris | 06b306473b | |
eris | 058b6b6182 | |
eris | 20064ee6f7 | |
eris | 78382a27ea | |
eris | 79e3dc099b | |
eris | 28c6412f03 | |
FloatingGhost | c452695a01 | |
FloatingGhost | 0613a4d285 | |
Sam Therapy | 430fc8607a | |
eris | 7bb303726f | |
Sol Fisher Romanoff | 2936e2efee | |
Sol Fisher Romanoff | 0468aad584 | |
Sam Therapy | a184a562f7 | |
Sol Fisher Romanoff | 0888b9462f | |
Sol Fisher Romanoff | c372d05b25 | |
FloatingGhost | f3712e1152 | |
FloatingGhost | d2f20b3323 | |
FloatingGhost | b4846be5fc | |
FloatingGhost | 473faa6b57 | |
FloatingGhost | 1ac46ce7ac | |
FloatingGhost | ce479c5e12 | |
FloatingGhost | 169dec5048 | |
FloatingGhost | a2212b4396 | |
tusooa | 04f5a448e4 | |
Pleroma Renovate Bot | d2716341cb | |
HJ | f229c4a106 | |
HJ | af22092472 | |
HJ | a08378253f | |
tusooa | 2635e24679 | |
tusooa | 93e01aefad | |
Sam Therapy | 63d8c83e3d | |
Pleroma Renovate Bot | ec0b239496 | |
Pleroma Renovate Bot | 31c9b6885b | |
tusooa | bb1c60fa93 | |
tusooa | 817432bd62 | |
HJ | 22c3012e1c | |
HJ | 65e10f07de | |
HJ | a9716701be | |
tusooa | 03d5c2e140 | |
tusooa | dd97a23ce7 | |
tusooa | b1e75c25bd | |
tusooa | c7c68340f1 | |
Tusooa Zhu | ce8101e60a | |
tusooa | 5f12c3ae76 | |
tusooa | d159031121 | |
Tusooa Zhu | 68110ce825 | |
tusooa | 1ab958ba6b | |
tusooa | 5243632678 | |
tusooa | 5478192e20 | |
tusooa | 246593970b | |
tusooa | 72cb9e8bdb | |
tusooa | 6235af4592 | |
tusooa | 4db7f07421 | |
Tusooa Zhu | 5359633c74 | |
Tusooa Zhu | 8a99d129dc | |
Tusooa Zhu | 1856eeda40 | |
Tusooa Zhu | 041bbb1622 | |
Tusooa Zhu | 547e85c7c6 | |
Tusooa Zhu | b7af37fce8 | |
Tusooa Zhu | 228a9afdf5 | |
Tusooa Zhu | 95c15fca22 | |
Tusooa Zhu | ec957d4162 | |
Tusooa Zhu | 91c4a57fe5 | |
Tusooa Zhu | 0b914d7815 | |
Tusooa Zhu | 76d99c08d6 | |
Tusooa Zhu | dc04c8cbd4 | |
Tusooa Zhu | 39e4746f61 | |
Tusooa Zhu | 51ade26066 | |
Tusooa Zhu | 8c8a8232c8 | |
Tusooa Zhu | 5c048321e7 | |
Tusooa Zhu | c202c89ca0 | |
Tusooa Zhu | 0bfe100ef7 | |
Tusooa Zhu | 3b7aaae2b3 | |
Tusooa Zhu | c032b48219 | |
Tusooa Zhu | e7e35ead09 | |
Tusooa Zhu | a0c6d642af | |
Tusooa Zhu | 0684f19d1b | |
Tusooa Zhu | a0b886459b | |
Tusooa Zhu | 4d175235f1 | |
Tusooa Zhu | f8b522e36d | |
Tusooa Zhu | 1ff2948aeb | |
Tusooa Zhu | 1e352fbfac | |
Tusooa Zhu | 3ad5df805e | |
tusooa | 7e2ae2ba95 | |
Tusooa Zhu | 52eef2eed1 | |
Tusooa Zhu | b7e9373965 | |
marcin mikołajczak | 6649baaac9 | |
marcin mikołajczak | 79d02bddbe |
Binary file not shown.
|
@ -9,6 +9,7 @@
|
|||
<body class="hidden">
|
||||
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
||||
<div id="app"></div>
|
||||
<div id="modal"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<script type="text/javascript" src="/instance/pleroma-mod-loader.js"></script>
|
||||
<div id="popovers" />
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"localforage": "1.10.0",
|
||||
"parse-link-header": "2.0.0",
|
||||
"phoenix": "1.6.2",
|
||||
"punycode.js": "2.1.0",
|
||||
"punycode.js": "2.3.0",
|
||||
"qrcode": "1.5.0",
|
||||
"querystring-es3": "0.2.1",
|
||||
"url": "0.11.0",
|
||||
|
@ -57,9 +57,9 @@
|
|||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||
"@vue/babel-plugin-jsx": "1.1.1",
|
||||
"@vue/compiler-sfc": "3.2.45",
|
||||
"@vue/test-utils": "2.2.7",
|
||||
"@vue/test-utils": "2.2.8",
|
||||
"autoprefixer": "10.4.13",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-loader": "9.1.2",
|
||||
"babel-plugin-lodash": "3.3.4",
|
||||
"chai": "4.3.7",
|
||||
"chalk": "1.1.3",
|
||||
|
|
|
@ -71,7 +71,6 @@
|
|||
<StatusHistoryModal v-if="editingAvailable" />
|
||||
<SettingsModal />
|
||||
<UpdateNotification />
|
||||
<div id="modal" />
|
||||
<GlobalNoticeList />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -60,6 +60,8 @@ const getInstanceConfig = async ({ store }) => {
|
|||
|
||||
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
|
||||
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
|
||||
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
|
||||
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
|
||||
|
||||
if (vapidPublicKey) {
|
||||
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
|
||||
|
@ -256,6 +258,7 @@ const getNodeInfo = async ({ store }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
||||
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
||||
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
||||
store.dispatch('setInstanceOption', { name: 'translationEnabled', value: features.includes('akkoma:machine_translation') })
|
||||
|
||||
const uploadLimits = metadata.uploadLimits
|
||||
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
|
||||
|
@ -291,6 +294,7 @@ const getNodeInfo = async ({ store }) => {
|
|||
})
|
||||
|
||||
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
|
||||
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances })
|
||||
store.dispatch('setInstanceOption', {
|
||||
name: 'federating',
|
||||
value: typeof federation.enabled === 'undefined'
|
||||
|
@ -379,6 +383,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
store.dispatch('startFetchingAnnouncements')
|
||||
getTOS({ store })
|
||||
getStickers({ store })
|
||||
store.dispatch('getSupportedTranslationlanguages')
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
|
|
|
@ -2,6 +2,7 @@ import PublicTimeline from 'components/public_timeline/public_timeline.vue'
|
|||
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
|
||||
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
|
||||
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
|
||||
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
|
||||
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
|
||||
import ConversationPage from 'components/conversation-page/conversation-page.vue'
|
||||
import Interactions from 'components/interactions/interactions.vue'
|
||||
|
@ -47,6 +48,7 @@ export default (store) => {
|
|||
},
|
||||
{ name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },
|
||||
{ name: 'public-timeline', path: '/main/public', component: PublicTimeline },
|
||||
{ name: 'bubble-timeline', path: '/main/bubble', component: BubbleTimeline },
|
||||
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
||||
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
|
||||
|
|
|
@ -3,6 +3,7 @@ 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'
|
||||
import LocalBubblePanel from '../local_bubble_panel/local_bubble_panel.vue'
|
||||
|
||||
const About = {
|
||||
components: {
|
||||
|
@ -10,7 +11,8 @@ const About = {
|
|||
FeaturesPanel,
|
||||
TermsOfServicePanel,
|
||||
StaffPanel,
|
||||
MRFTransparencyPanel
|
||||
MRFTransparencyPanel,
|
||||
LocalBubblePanel
|
||||
},
|
||||
computed: {
|
||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
||||
<staff-panel />
|
||||
<terms-of-service-panel />
|
||||
<LocalBubblePanel />
|
||||
<MRFTransparencyPanel />
|
||||
<features-panel v-if="showFeaturesPanel" />
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { mapState } from 'vuex'
|
|||
import ProgressButton from '../progress_button/progress_button.vue'
|
||||
import Popover from '../popover/popover.vue'
|
||||
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faEllipsisV
|
||||
|
@ -16,14 +17,30 @@ const AccountActions = {
|
|||
'user', 'relationship'
|
||||
],
|
||||
data () {
|
||||
return { }
|
||||
return {
|
||||
showingConfirmBlock: false,
|
||||
showingConfirmRemoveFollower: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ProgressButton,
|
||||
Popover,
|
||||
UserListMenu
|
||||
UserListMenu,
|
||||
ConfirmModal
|
||||
},
|
||||
methods: {
|
||||
showConfirmBlock () {
|
||||
this.showingConfirmBlock = true
|
||||
},
|
||||
hideConfirmBlock () {
|
||||
this.showingConfirmBlock = false
|
||||
},
|
||||
showConfirmRemoveUserFromFollowers () {
|
||||
this.showingConfirmRemoveFollower = true
|
||||
},
|
||||
hideConfirmRemoveUserFromFollowers () {
|
||||
this.showingConfirmRemoveFollower = false
|
||||
},
|
||||
showRepeats () {
|
||||
this.$store.dispatch('showReblogs', this.user.id)
|
||||
},
|
||||
|
@ -31,13 +48,29 @@ const AccountActions = {
|
|||
this.$store.dispatch('hideReblogs', this.user.id)
|
||||
},
|
||||
blockUser () {
|
||||
if (!this.shouldConfirmBlock) {
|
||||
this.doBlockUser()
|
||||
} else {
|
||||
this.showConfirmBlock()
|
||||
}
|
||||
},
|
||||
doBlockUser () {
|
||||
this.$store.dispatch('blockUser', this.user.id)
|
||||
this.hideConfirmBlock()
|
||||
},
|
||||
unblockUser () {
|
||||
this.$store.dispatch('unblockUser', this.user.id)
|
||||
},
|
||||
removeUserFromFollowers () {
|
||||
if (!this.shouldConfirmRemoveUserFromFollowers) {
|
||||
this.doRemoveUserFromFollowers()
|
||||
} else {
|
||||
this.showConfirmRemoveUserFromFollowers()
|
||||
}
|
||||
},
|
||||
doRemoveUserFromFollowers () {
|
||||
this.$store.dispatch('removeUserFromFollowers', this.user.id)
|
||||
this.hideConfirmRemoveUserFromFollowers()
|
||||
},
|
||||
reportUser () {
|
||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
||||
|
@ -50,6 +83,12 @@ const AccountActions = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
shouldConfirmBlock () {
|
||||
return this.$store.getters.mergedConfig.modalOnBlock
|
||||
},
|
||||
shouldConfirmRemoveUserFromFollowers () {
|
||||
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
|
||||
},
|
||||
...mapState({
|
||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
||||
})
|
||||
|
|
|
@ -74,6 +74,48 @@
|
|||
</button>
|
||||
</template>
|
||||
</Popover>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmBlock"
|
||||
:title="$t('user_card.block_confirm_title')"
|
||||
:confirm-text="$t('user_card.block_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.block_confirm_cancel_button')"
|
||||
@accepted="doBlockUser"
|
||||
@cancelled="hideConfirmBlock"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="user_card.block_confirm"
|
||||
tag="span"
|
||||
>
|
||||
<template #user>
|
||||
<span
|
||||
v-text="user.screen_name_ui"
|
||||
/>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmRemoveFollower"
|
||||
:title="$t('user_card.remove_follower_confirm_title')"
|
||||
:confirm-text="$t('user_card.remove_follower_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.remove_follower_confirm_cancel_button')"
|
||||
@accepted="doRemoveUserFromFollowers"
|
||||
@cancelled="hideConfirmRemoveUserFromFollowers"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="user_card.remove_follower_confirm"
|
||||
tag="span"
|
||||
>
|
||||
<template #user>
|
||||
<span
|
||||
v-text="user.screen_name_ui"
|
||||
/>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import Timeline from '../timeline/timeline.vue'
|
||||
const PublicTimeline = {
|
||||
components: {
|
||||
Timeline
|
||||
},
|
||||
computed: {
|
||||
timeline () { return this.$store.state.statuses.timelines.bubble }
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' })
|
||||
},
|
||||
unmounted () {
|
||||
this.$store.dispatch('stopFetchingTimeline', 'bubble')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PublicTimeline
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<Timeline
|
||||
:title="$t('nav.bubble_timeline')"
|
||||
:timeline="timeline"
|
||||
:timeline-name="'bubble'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script src="./bubble_timeline.js"></script>
|
|
@ -0,0 +1,37 @@
|
|||
import DialogModal from '../dialog_modal/dialog_modal.vue'
|
||||
|
||||
/**
|
||||
* This component emits the following events:
|
||||
* cancelled, emitted when the action should not be performed;
|
||||
* accepted, emitted when the action should be performed;
|
||||
*
|
||||
* The caller should close this dialog after receiving any of the two events.
|
||||
*/
|
||||
const ConfirmModal = {
|
||||
components: {
|
||||
DialogModal
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
cancelText: {
|
||||
type: String
|
||||
},
|
||||
confirmText: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
onCancel () {
|
||||
this.$emit('cancelled')
|
||||
},
|
||||
onAccept () {
|
||||
this.$emit('accepted')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfirmModal
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<dialog-modal
|
||||
v-body-scroll-lock="true"
|
||||
class="confirm-modal"
|
||||
:on-cancel="onCancel"
|
||||
>
|
||||
<template #header>
|
||||
<span v-text="title" />
|
||||
</template>
|
||||
|
||||
<slot />
|
||||
|
||||
<template #footer>
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click.prevent="onAccept"
|
||||
v-text="confirmText"
|
||||
/>
|
||||
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click.prevent="onCancel"
|
||||
v-text="cancelText"
|
||||
/>
|
||||
</template>
|
||||
</dialog-modal>
|
||||
</template>
|
||||
|
||||
<script src="./confirm_modal.js"></script>
|
|
@ -1,16 +1,21 @@
|
|||
import SearchBar from 'components/search_bar/search_bar.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faSignInAlt,
|
||||
faSignOutAlt,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faUserPlus,
|
||||
faBullhorn,
|
||||
faSearch,
|
||||
faTachometerAlt,
|
||||
faCog,
|
||||
faGlobe,
|
||||
faBolt,
|
||||
faUsers,
|
||||
faCommentMedical,
|
||||
faBookmark,
|
||||
faInfoCircle
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
@ -19,18 +24,23 @@ library.add(
|
|||
faSignOutAlt,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faUserPlus,
|
||||
faBullhorn,
|
||||
faSearch,
|
||||
faTachometerAlt,
|
||||
faCog,
|
||||
faGlobe,
|
||||
faBolt,
|
||||
faUsers,
|
||||
faCommentMedical,
|
||||
faBookmark,
|
||||
faInfoCircle
|
||||
)
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SearchBar
|
||||
SearchBar,
|
||||
ConfirmModal
|
||||
},
|
||||
data: () => ({
|
||||
searchBarHidden: true,
|
||||
|
@ -40,7 +50,8 @@ export default {
|
|||
window.CSS.supports('-moz-mask-size', 'contain') ||
|
||||
window.CSS.supports('-ms-mask-size', 'contain') ||
|
||||
window.CSS.supports('-o-mask-size', 'contain')
|
||||
)
|
||||
),
|
||||
showingConfirmLogout: false
|
||||
}),
|
||||
computed: {
|
||||
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
|
||||
|
@ -69,19 +80,51 @@ export default {
|
|||
})
|
||||
},
|
||||
logo () { return this.$store.state.instance.logo },
|
||||
mergedConfig () {
|
||||
return this.$store.getters.mergedConfig
|
||||
},
|
||||
sitename () { return this.$store.state.instance.name },
|
||||
showNavShortcuts () {
|
||||
return this.mergedConfig.showNavShortcuts
|
||||
},
|
||||
showWiderShortcuts () {
|
||||
return this.mergedConfig.showWiderShortcuts
|
||||
},
|
||||
hideSiteFavicon () {
|
||||
return this.mergedConfig.hideSiteFavicon
|
||||
},
|
||||
hideSiteName () {
|
||||
return this.mergedConfig.hideSiteName
|
||||
},
|
||||
hideSitename () { return this.$store.state.instance.hideSitename },
|
||||
logoLeft () { return this.$store.state.instance.logoLeft },
|
||||
currentUser () { return this.$store.state.users.currentUser },
|
||||
privateMode () { return this.$store.state.instance.private }
|
||||
privateMode () { return this.$store.state.instance.private },
|
||||
shouldConfirmLogout () {
|
||||
return this.$store.getters.mergedConfig.modalOnLogout
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
scrollToTop () {
|
||||
window.scrollTo(0, 0)
|
||||
},
|
||||
showConfirmLogout () {
|
||||
this.showingConfirmLogout = true
|
||||
},
|
||||
hideConfirmLogout () {
|
||||
this.showingConfirmLogout = false
|
||||
},
|
||||
logout () {
|
||||
if (!this.shouldConfirmLogout) {
|
||||
this.doLogout()
|
||||
} else {
|
||||
this.showConfirmLogout()
|
||||
}
|
||||
},
|
||||
doLogout () {
|
||||
this.$router.replace('/main/public')
|
||||
this.$store.dispatch('logout')
|
||||
this.hideConfirmLogout()
|
||||
},
|
||||
onSearchBarToggled (hidden) {
|
||||
this.searchBarHidden = hidden
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
display: grid;
|
||||
grid-template-rows: var(--navbar-height);
|
||||
grid-template-columns: 2fr auto 2fr;
|
||||
grid-template-areas: "sitename logo actions";
|
||||
grid-template-areas: "nav-left logo actions";
|
||||
box-sizing: border-box;
|
||||
padding: 0 1.2em;
|
||||
margin: auto;
|
||||
max-width: 980px;
|
||||
max-width: 1110px;
|
||||
}
|
||||
|
||||
&.-column-stretch .inner-nav {
|
||||
|
@ -38,7 +38,7 @@
|
|||
|
||||
&.-logoLeft .inner-nav {
|
||||
grid-template-columns: auto 2fr 2fr;
|
||||
grid-template-areas: "logo sitename actions";
|
||||
grid-template-areas: "logo nav-left actions";
|
||||
}
|
||||
|
||||
&.-column-stretch.-wide .inner-nav {
|
||||
|
@ -109,20 +109,54 @@
|
|||
}
|
||||
}
|
||||
|
||||
.svg-inline--fa {
|
||||
color: $fallback--link;
|
||||
color: var(--topBarLink, $fallback--link);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
margin-left: 0.2em;
|
||||
width: 2em;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
.svg-inline--fa {
|
||||
color: $fallback--link;
|
||||
color: var(--topBarLink, $fallback--link);
|
||||
&.router-link-active {
|
||||
font-size: 1.2em;
|
||||
margin-top: 0.05em;
|
||||
|
||||
.svg-inline--fa {
|
||||
font-weight: bolder;
|
||||
color: $fallback--text;
|
||||
color: var(--selectedMenuText, $fallback--text);
|
||||
|
||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||
}
|
||||
}
|
||||
|
||||
&-logout {
|
||||
margin-left: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.sitename {
|
||||
grid-area: sitename;
|
||||
.-wide {
|
||||
.nav-icon:not(.nav-icon-logout) {
|
||||
margin-left: 0.7em;
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
padding-left: 5px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nav-left-wrapper {
|
||||
grid-area: nav-left;
|
||||
|
||||
.favicon {
|
||||
height: 28px;
|
||||
vertical-align: middle;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
|
|
@ -5,21 +5,85 @@
|
|||
:class="{ '-logoLeft': logoLeft }"
|
||||
@click="scrollToTop()"
|
||||
>
|
||||
<div class="inner-nav">
|
||||
<div class="item sitename">
|
||||
<div
|
||||
class="inner-nav"
|
||||
:class="{ '-wide': showWiderShortcuts }"
|
||||
>
|
||||
<div class="item nav-left-wrapper">
|
||||
<router-link
|
||||
v-if="!hideSitename"
|
||||
class="site-name"
|
||||
class="site-brand"
|
||||
:to="{ name: 'root' }"
|
||||
active-class="home"
|
||||
>
|
||||
{{ sitename }}
|
||||
<img
|
||||
v-if="!hideSiteFavicon"
|
||||
class="favicon"
|
||||
src="/favicon.png"
|
||||
>
|
||||
<span
|
||||
v-if="!hideSiteName"
|
||||
class="site-name"
|
||||
>
|
||||
{{ sitename }}
|
||||
</span>
|
||||
</router-link>
|
||||
<div
|
||||
v-if="(currentUser || !privateMode) && showNavShortcuts"
|
||||
class="nav-items left"
|
||||
>
|
||||
<router-link
|
||||
v-if="currentUser"
|
||||
:to="{ name: 'friends' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="home"
|
||||
:title="$t('nav.home_timeline')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'public-timeline' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="users"
|
||||
:title="$t('nav.public_tl')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'public-external-timeline' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="globe"
|
||||
:title="$t('nav.twkn')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="currentUser"
|
||||
:to="{ name: 'bubble-timeline' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="circle"
|
||||
:title="$t('nav.bubble_timeline')"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<router-link
|
||||
class="logo"
|
||||
:to="{ name: 'root' }"
|
||||
:style="logoBgStyle"
|
||||
:title="sitename"
|
||||
>
|
||||
<div
|
||||
class="mask"
|
||||
|
@ -36,15 +100,55 @@
|
|||
@toggled="onSearchBarToggled"
|
||||
@click.stop
|
||||
/>
|
||||
<div
|
||||
v-if="(currentUser || !privateMode) && showNavShortcuts"
|
||||
class="nav-items right"
|
||||
>
|
||||
<router-link
|
||||
class="nav-icon"
|
||||
:to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="bolt"
|
||||
:title="$t('nav.interactions')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="currentUser"
|
||||
:to="{ name: 'lists' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="list"
|
||||
:title="$t('nav.lists')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="currentUser"
|
||||
:to="{ name: 'bookmarks' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="bookmark"
|
||||
:title="$t('nav.bookmarks')"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
<button
|
||||
class="button-unstyled nav-icon"
|
||||
:title="$t('nav.preferences')"
|
||||
@click.stop="openSettingsModal"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="cog"
|
||||
:title="$t('nav.preferences')"
|
||||
/>
|
||||
</button>
|
||||
<a
|
||||
|
@ -52,30 +156,42 @@
|
|||
href="/pleroma/admin/#/login-pleroma"
|
||||
class="nav-icon"
|
||||
target="_blank"
|
||||
:title="$t('nav.administration')"
|
||||
@click.stop
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="tachometer-alt"
|
||||
:title="$t('nav.administration')"
|
||||
/>
|
||||
</a>
|
||||
<span class="spacer" />
|
||||
<button
|
||||
v-if="currentUser"
|
||||
class="button-unstyled nav-icon"
|
||||
:title="$t('login.logout')"
|
||||
@click.stop.prevent="logout"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="sign-out-alt"
|
||||
:title="$t('login.logout')"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmLogout"
|
||||
:title="$t('login.logout_confirm_title')"
|
||||
:confirm-text="$t('login.logout_confirm_accept_button')"
|
||||
:cancel-text="$t('login.logout_confirm_cancel_button')"
|
||||
@accepted="doLogout"
|
||||
@cancelled="hideConfirmLogout"
|
||||
>
|
||||
{{ $t('login.logout_confirm') }}
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</nav>
|
||||
</template>
|
||||
<script src="./desktop_nav.js"></script>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
right: 0;
|
||||
top: 0;
|
||||
background: rgb(27 31 35 / 50%);
|
||||
z-index: 99;
|
||||
z-index: 2000;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,9 +51,10 @@
|
|||
margin: 15vh auto;
|
||||
position: fixed;
|
||||
transform: translateX(-50%);
|
||||
z-index: 999;
|
||||
z-index: 2001;
|
||||
cursor: default;
|
||||
display: block;
|
||||
width: max-content;
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Completion from '../../services/completion/completion.js'
|
||||
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
||||
import Popover from 'src/components/popover/popover.vue'
|
||||
import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue'
|
||||
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
|
||||
import { take } from 'lodash'
|
||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
||||
|
@ -109,9 +110,10 @@ const EmojiInput = {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
randomSeed: `${Math.random()}`.replace('.', '-'),
|
||||
input: undefined,
|
||||
caretEl: undefined,
|
||||
highlighted: 0,
|
||||
highlighted: -1,
|
||||
caret: 0,
|
||||
focused: false,
|
||||
blurTimeout: null,
|
||||
|
@ -125,7 +127,8 @@ const EmojiInput = {
|
|||
components: {
|
||||
Popover,
|
||||
EmojiPicker,
|
||||
UnicodeDomainIndicator
|
||||
UnicodeDomainIndicator,
|
||||
ScreenReaderNotice
|
||||
},
|
||||
computed: {
|
||||
padEmoji () {
|
||||
|
@ -203,6 +206,12 @@ const EmojiInput = {
|
|||
top: this.input.scrollTop,
|
||||
left: this.input.scrollLeft
|
||||
})
|
||||
},
|
||||
suggestionListId () {
|
||||
return `suggestions-${this.randomSeed}`
|
||||
},
|
||||
suggestionItemId () {
|
||||
return (index) => `suggestion-item-${index}-${this.randomSeed}`
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
@ -278,6 +287,11 @@ const EmojiInput = {
|
|||
...rest,
|
||||
img: imageUrl || ''
|
||||
}))
|
||||
this.highlighted = -1
|
||||
this.$refs.screenReaderNotice.announce(
|
||||
this.$tc('tool_tip.autocomplete_available',
|
||||
this.suggestions.length,
|
||||
{ number: this.suggestions.length }))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -374,26 +388,27 @@ const EmojiInput = {
|
|||
},
|
||||
cycleBackward (e) {
|
||||
const len = this.suggestions.length || 0
|
||||
if (len > 1) {
|
||||
this.highlighted -= 1
|
||||
if (this.highlighted < 0) {
|
||||
this.highlighted = this.suggestions.length - 1
|
||||
}
|
||||
|
||||
this.highlighted -= 1
|
||||
if (this.highlighted === -1) {
|
||||
this.input.focus()
|
||||
} else if (this.highlighted < -1) {
|
||||
this.highlighted = len - 1
|
||||
}
|
||||
if (len > 0) {
|
||||
e.preventDefault()
|
||||
} else {
|
||||
this.highlighted = 0
|
||||
}
|
||||
},
|
||||
cycleForward (e) {
|
||||
const len = this.suggestions.length || 0
|
||||
if (len > 1) {
|
||||
this.highlighted += 1
|
||||
if (this.highlighted >= len) {
|
||||
this.highlighted = 0
|
||||
}
|
||||
|
||||
this.highlighted += 1
|
||||
if (this.highlighted >= len) {
|
||||
this.highlighted = -1
|
||||
this.input.focus()
|
||||
}
|
||||
if (len > 0) {
|
||||
e.preventDefault()
|
||||
} else {
|
||||
this.highlighted = 0
|
||||
}
|
||||
},
|
||||
scrollIntoView () {
|
||||
|
@ -540,6 +555,13 @@ const EmojiInput = {
|
|||
})
|
||||
},
|
||||
resize () {
|
||||
},
|
||||
autoCompleteItemLabel (suggestion) {
|
||||
if (suggestion.user) {
|
||||
return suggestion.displayText + ' ' + suggestion.detailText
|
||||
} else {
|
||||
return this.maybeLocalizedEmojiName(suggestion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,19 @@
|
|||
class="emoji-input"
|
||||
:class="{ 'with-picker': !hideEmojiButton }"
|
||||
>
|
||||
<slot />
|
||||
<slot
|
||||
:id="'textbox-' + randomSeed"
|
||||
:aria-owns="suggestionListId"
|
||||
aria-autocomplete="both"
|
||||
:aria-expanded="showSuggestions"
|
||||
:aria-activedescendant="(!showSuggestions || highlighted === -1) ? '' : suggestionItemId(highlighted)"
|
||||
/>
|
||||
<!-- TODO: make the 'x' disappear if at the end maybe? -->
|
||||
<div
|
||||
ref="hiddenOverlay"
|
||||
class="hidden-overlay"
|
||||
:style="overlayStyle"
|
||||
:aria-hidden="true"
|
||||
>
|
||||
<span>{{ preText }}</span>
|
||||
<span
|
||||
|
@ -18,11 +25,16 @@
|
|||
>x</span>
|
||||
<span>{{ postText }}</span>
|
||||
</div>
|
||||
<screen-reader-notice
|
||||
ref="screenReaderNotice"
|
||||
aria-live="assertive"
|
||||
/>
|
||||
<template v-if="enableEmojiPicker">
|
||||
<button
|
||||
v-if="!hideEmojiButton"
|
||||
class="button-unstyled emoji-picker-icon"
|
||||
type="button"
|
||||
:title="$t('emoji.add_emoji')"
|
||||
@click.prevent="togglePicker"
|
||||
>
|
||||
<FAIcon :icon="['far', 'smile-beam']" />
|
||||
|
@ -43,17 +55,24 @@
|
|||
ref="suggestorPopover"
|
||||
class="autocomplete-panel"
|
||||
placement="bottom"
|
||||
:trigger-attrs="{ 'aria-hidden': true }"
|
||||
>
|
||||
<template #content>
|
||||
<div
|
||||
:id="suggestionListId"
|
||||
ref="panel-body"
|
||||
class="autocomplete-panel-body"
|
||||
role="listbox"
|
||||
>
|
||||
<div
|
||||
v-for="(suggestion, index) in suggestions"
|
||||
:id="suggestionItemId(index)"
|
||||
:key="index"
|
||||
class="autocomplete-item"
|
||||
role="option"
|
||||
:class="{ highlighted: index === highlighted }"
|
||||
:aria-label="autoCompleteItemLabel(suggestion)"
|
||||
:aria-selected="index === highlighted"
|
||||
@click.stop.prevent="onClick($event, suggestion)"
|
||||
>
|
||||
<span class="image">
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
ref="popover"
|
||||
trigger="click"
|
||||
popover-class="emoji-picker popover-default"
|
||||
:trigger-attrs="{ 'aria-hidden': true }"
|
||||
@show="onPopoverShown"
|
||||
@close="onPopoverClosed"
|
||||
>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Popover from '../popover/popover.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faEllipsisH,
|
||||
|
@ -32,10 +33,14 @@ library.add(
|
|||
|
||||
const ExtraButtons = {
|
||||
props: ['status'],
|
||||
components: { Popover },
|
||||
components: {
|
||||
Popover,
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
expanded: false
|
||||
expanded: false,
|
||||
showingDeleteDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -46,11 +51,29 @@ const ExtraButtons = {
|
|||
this.expanded = false
|
||||
},
|
||||
deleteStatus () {
|
||||
const confirmed = window.confirm(this.$t('status.delete_confirm'))
|
||||
if (confirmed) {
|
||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||
if (this.shouldConfirmDelete) {
|
||||
this.showDeleteStatusConfirmDialog()
|
||||
} else {
|
||||
this.doDeleteStatus()
|
||||
}
|
||||
},
|
||||
doDeleteStatus () {
|
||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||
this.hideDeleteStatusConfirmDialog()
|
||||
},
|
||||
showDeleteStatusConfirmDialog () {
|
||||
this.showingDeleteDialog = true
|
||||
},
|
||||
hideDeleteStatusConfirmDialog () {
|
||||
this.showingDeleteDialog = false
|
||||
},
|
||||
|
||||
translateStatus () {
|
||||
const translateTo = this.$store.getters.mergedConfig.translationLanguage || this.$store.state.instance.interfaceLanguage
|
||||
this.$store.dispatch('translateStatus', { id: this.status.id, language: translateTo })
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
.catch(err => this.$emit('onError', err.error.error))
|
||||
},
|
||||
pinStatus () {
|
||||
this.$store.dispatch('pinStatus', this.status.id)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
|
@ -124,6 +147,9 @@ const ExtraButtons = {
|
|||
canMute () {
|
||||
return !!this.currentUser
|
||||
},
|
||||
canTranslate () {
|
||||
return this.$store.state.instance.translationEnabled === true
|
||||
},
|
||||
canBookmark () {
|
||||
return !!this.currentUser
|
||||
},
|
||||
|
@ -133,7 +159,10 @@ const ExtraButtons = {
|
|||
isEdited () {
|
||||
return this.status.edited_at !== null
|
||||
},
|
||||
editingAvailable () { return this.$store.state.instance.editingAvailable }
|
||||
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
||||
shouldConfirmDelete () {
|
||||
return this.$store.getters.mergedConfig.modalOnDelete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -142,6 +142,17 @@
|
|||
:icon="['far', 'flag']"
|
||||
/><span>{{ $t("user_card.report") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="canTranslate"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="translateStatus"
|
||||
@click="close"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="globe"
|
||||
/><span>{{ $t("status.translate") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #trigger>
|
||||
|
@ -165,6 +176,18 @@
|
|||
/>
|
||||
</FALayers>
|
||||
</span>
|
||||
<teleport to="#modal">
|
||||
<ConfirmModal
|
||||
v-if="showingDeleteDialog"
|
||||
:title="$t('status.delete_confirm_title')"
|
||||
:cancel-text="$t('status.delete_confirm_cancel_button')"
|
||||
:confirm-text="$t('status.delete_confirm_accept_button')"
|
||||
@cancelled="hideDeleteStatusConfirmDialog"
|
||||
@accepted="doDeleteStatus"
|
||||
>
|
||||
{{ $t('status.delete_confirm') }}
|
||||
</ConfirmModal>
|
||||
</teleport>
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
|
|
|
@ -38,13 +38,20 @@
|
|||
class="button-unstyled interactive"
|
||||
target="_blank"
|
||||
role="button"
|
||||
:title="$t('tool_tip.favorite')"
|
||||
:href="remoteInteractionLink"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
:title="$t('tool_tip.favorite')"
|
||||
:icon="['far', 'star']"
|
||||
/>
|
||||
<FALayers class="fa-scale-110 fa-old-padding-layer">
|
||||
<FAIcon
|
||||
class="fa-scale-110"
|
||||
:icon="['far', 'star']"
|
||||
/>
|
||||
<FAIcon
|
||||
class="focus-marker"
|
||||
transform="shrink-6 up-9 right-12"
|
||||
icon="plus"
|
||||
/>
|
||||
</FALayers>
|
||||
</a>
|
||||
<span
|
||||
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
|
||||
export default {
|
||||
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
inProgress: false
|
||||
inProgress: false,
|
||||
showingConfirmUnfollow: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
shouldConfirmUnfollow () {
|
||||
return this.$store.getters.mergedConfig.modalOnUnfollow
|
||||
},
|
||||
isPressed () {
|
||||
return this.inProgress || this.relationship.following
|
||||
},
|
||||
|
@ -35,6 +43,12 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
showConfirmUnfollow () {
|
||||
this.showingConfirmUnfollow = true
|
||||
},
|
||||
hideConfirmUnfollow () {
|
||||
this.showingConfirmUnfollow = false
|
||||
},
|
||||
onClick () {
|
||||
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
|
||||
},
|
||||
|
@ -45,12 +59,21 @@ export default {
|
|||
})
|
||||
},
|
||||
unfollow () {
|
||||
if (this.shouldConfirmUnfollow) {
|
||||
this.showConfirmUnfollow()
|
||||
} else {
|
||||
this.doUnfollow()
|
||||
}
|
||||
},
|
||||
doUnfollow () {
|
||||
const store = this.$store
|
||||
this.inProgress = true
|
||||
requestUnfollow(this.relationship.id, store).then(() => {
|
||||
this.inProgress = false
|
||||
store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
|
||||
})
|
||||
|
||||
this.hideConfirmUnfollow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,27 @@
|
|||
@click="onClick"
|
||||
>
|
||||
{{ label }}
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmUnfollow"
|
||||
:title="$t('user_card.unfollow_confirm_title')"
|
||||
:confirm-text="$t('user_card.unfollow_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.unfollow_confirm_cancel_button')"
|
||||
@accepted="doUnfollow"
|
||||
@cancelled="hideConfirmUnfollow"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="user_card.unfollow_confirm"
|
||||
tag="span"
|
||||
>
|
||||
<template #user>
|
||||
<span
|
||||
v-text="user.screen_name_ui"
|
||||
/>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
/>
|
||||
<RemoveFollowerButton
|
||||
v-if="noFollowsYou && relationship.followed_by"
|
||||
:user="user"
|
||||
:relationship="relationship"
|
||||
class="follow-card-button"
|
||||
/>
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
|
||||
|
||||
const FollowRequestCard = {
|
||||
props: ['user'],
|
||||
components: {
|
||||
BasicUserCard
|
||||
BasicUserCard,
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showingApproveConfirmDialog: false,
|
||||
showingDenyConfirmDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
findFollowRequestNotificationId () {
|
||||
|
@ -13,7 +21,26 @@ const FollowRequestCard = {
|
|||
)
|
||||
return notif && notif.id
|
||||
},
|
||||
showApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = true
|
||||
},
|
||||
hideApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = false
|
||||
},
|
||||
showDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = true
|
||||
},
|
||||
hideDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = false
|
||||
},
|
||||
approveUser () {
|
||||
if (this.shouldConfirmApprove) {
|
||||
this.showApproveConfirmDialog()
|
||||
} else {
|
||||
this.doApprove()
|
||||
}
|
||||
},
|
||||
doApprove () {
|
||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
|
||||
|
@ -25,14 +52,34 @@ const FollowRequestCard = {
|
|||
notification.type = 'follow'
|
||||
}
|
||||
})
|
||||
this.hideApproveConfirmDialog()
|
||||
},
|
||||
denyUser () {
|
||||
if (this.shouldConfirmDeny) {
|
||||
this.showDenyConfirmDialog()
|
||||
} else {
|
||||
this.doDeny()
|
||||
}
|
||||
},
|
||||
doDeny () {
|
||||
const notifId = this.findFollowRequestNotificationId()
|
||||
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
|
||||
.then(() => {
|
||||
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
})
|
||||
this.hideDenyConfirmDialog()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mergedConfig () {
|
||||
return this.$store.getters.mergedConfig
|
||||
},
|
||||
shouldConfirmApprove () {
|
||||
return this.mergedConfig.modalOnApproveFollow
|
||||
},
|
||||
shouldConfirmDeny () {
|
||||
return this.mergedConfig.modalOnDenyFollow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,28 @@
|
|||
{{ $t('user_card.deny') }}
|
||||
</button>
|
||||
</div>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingApproveConfirmDialog"
|
||||
:title="$t('user_card.approve_confirm_title')"
|
||||
:confirm-text="$t('user_card.approve_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
|
||||
@accepted="doApprove"
|
||||
@cancelled="hideApproveConfirmDialog"
|
||||
>
|
||||
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
|
||||
</confirm-modal>
|
||||
<confirm-modal
|
||||
v-if="showingDenyConfirmDialog"
|
||||
:title="$t('user_card.deny_confirm_title')"
|
||||
:confirm-text="$t('user_card.deny_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
|
||||
@accepted="doDeny"
|
||||
@cancelled="hideDenyConfirmDialog"
|
||||
>
|
||||
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</basic-user-card>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,21 +1,51 @@
|
|||
<template>
|
||||
<div>
|
||||
<FAIcon
|
||||
v-if="globeIcon"
|
||||
icon="globe"
|
||||
/>
|
||||
{{ ' ' }}
|
||||
<label for="interface-language-switcher">
|
||||
{{ promptText }}
|
||||
</label>
|
||||
{{ ' ' }}
|
||||
<Select
|
||||
id="interface-language-switcher"
|
||||
v-model="controlledLanguage"
|
||||
>
|
||||
<option
|
||||
v-for="lang in languages"
|
||||
:key="lang.code"
|
||||
:value="lang.code"
|
||||
<ul class="setting-list">
|
||||
<li
|
||||
v-for="index of controlledLanguage.keys()"
|
||||
:key="index"
|
||||
>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
</Select>
|
||||
<label>
|
||||
{{ index === 0 ? $t('settings.primary_language') : $tc('settings.fallback_language', index, { index }) }}
|
||||
<Select
|
||||
class="language-select"
|
||||
:model-value="controlledLanguage[index]"
|
||||
@update:modelValue="val => setLanguageAt(index, val)"
|
||||
>
|
||||
<option
|
||||
v-for="lang in languages"
|
||||
:key="lang.code"
|
||||
:value="lang.code"
|
||||
>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
</Select>
|
||||
</label>
|
||||
<button
|
||||
v-if="controlledLanguage.length > 1 && index !== 0"
|
||||
class="button-default btn"
|
||||
@click="() => removeLanguageAt(index)"
|
||||
>
|
||||
{{ $t('settings.remove_language') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class="button-default btn"
|
||||
@click="addLanguage"
|
||||
>
|
||||
{{ $t('settings.add_language') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -34,12 +64,16 @@ export default {
|
|||
required: true
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
type: [Array, String],
|
||||
required: true
|
||||
},
|
||||
setLanguage: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
globeIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -48,7 +82,9 @@ export default {
|
|||
},
|
||||
|
||||
controlledLanguage: {
|
||||
get: function () { return this.language },
|
||||
get: function () {
|
||||
return Array.isArray(this.language) ? this.language : [this.language]
|
||||
},
|
||||
set: function (val) {
|
||||
this.setLanguage(val)
|
||||
}
|
||||
|
@ -58,7 +94,30 @@ export default {
|
|||
methods: {
|
||||
getLanguageName (code) {
|
||||
return localeService.getLanguageName(code)
|
||||
},
|
||||
addLanguage () {
|
||||
this.controlledLanguage = [...this.controlledLanguage, '']
|
||||
},
|
||||
setLanguageAt (index, val) {
|
||||
const lang = [...this.controlledLanguage]
|
||||
lang[index] = val
|
||||
this.controlledLanguage = lang
|
||||
},
|
||||
removeLanguageAt (index) {
|
||||
const lang = [...this.controlledLanguage]
|
||||
lang.splice(index, 1)
|
||||
this.controlledLanguage = lang
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../variables";
|
||||
|
||||
.interface-language-switcher {
|
||||
.language-select {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { mapState } from 'vuex'
|
||||
import { get } from 'lodash'
|
||||
|
||||
const LocalBubblePanel = {
|
||||
computed: {
|
||||
...mapState({
|
||||
bubbleInstances: state => get(state, 'instance.localBubbleInstances')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default LocalBubblePanel
|
|
@ -0,0 +1,23 @@
|
|||
.mrf-section {
|
||||
margin: 1em;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
th,
|
||||
td {
|
||||
width: 180px;
|
||||
max-width: 360px;
|
||||
overflow: hidden;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
th + th,
|
||||
td + td {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="bubbleInstances"
|
||||
class="bubble-panel"
|
||||
>
|
||||
<div class="panel panel-default base01-background">
|
||||
<div class="panel-heading timeline-heading base02-background">
|
||||
<div class="title">
|
||||
{{ $t("about.bubble_instances") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>{{ $t("about.bubble_instances_description") }}:</p>
|
||||
<ul>
|
||||
<li
|
||||
v-for="instance in bubbleInstances"
|
||||
:key="instance"
|
||||
v-text="instance"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./local_bubble_panel.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import './local_bubble_panel.scss';
|
||||
</style>
|
|
@ -1,5 +1,6 @@
|
|||
import SideDrawer from '../side_drawer/side_drawer.vue'
|
||||
import Notifications from '../notifications/notifications.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
|
||||
import GestureService from '../../services/gesture_service/gesture_service'
|
||||
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
|
||||
|
@ -25,12 +26,14 @@ const MobileNav = {
|
|||
components: {
|
||||
SideDrawer,
|
||||
Notifications,
|
||||
NavigationPins
|
||||
NavigationPins,
|
||||
ConfirmModal
|
||||
},
|
||||
data: () => ({
|
||||
notificationsCloseGesture: undefined,
|
||||
notificationsOpen: false,
|
||||
notificationsAtTop: true
|
||||
notificationsAtTop: true,
|
||||
showingConfirmLogout: false
|
||||
}),
|
||||
created () {
|
||||
this.notificationsCloseGesture = GestureService.swipeGesture(
|
||||
|
@ -49,7 +52,12 @@ const MobileNav = {
|
|||
unseenNotificationsCount () {
|
||||
return this.unseenNotifications.length
|
||||
},
|
||||
hideSitename () { return this.$store.state.instance.hideSitename },
|
||||
mergedConfig () {
|
||||
return this.$store.getters.mergedConfig
|
||||
},
|
||||
hideSiteName () {
|
||||
return this.mergedConfig.hideSiteName
|
||||
},
|
||||
sitename () { return this.$store.state.instance.name },
|
||||
isChat () {
|
||||
return this.$route.name === 'chat'
|
||||
|
@ -57,7 +65,11 @@ const MobileNav = {
|
|||
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']),
|
||||
chatsPinned () {
|
||||
return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
|
||||
}
|
||||
},
|
||||
shouldConfirmLogout () {
|
||||
return this.$store.getters.mergedConfig.modalOnLogout
|
||||
},
|
||||
...mapGetters(['unreadChatCount'])
|
||||
},
|
||||
methods: {
|
||||
toggleMobileSidebar () {
|
||||
|
@ -88,9 +100,23 @@ const MobileNav = {
|
|||
scrollMobileNotificationsToTop () {
|
||||
this.$refs.mobileNotifications.scrollTo(0, 0)
|
||||
},
|
||||
showConfirmLogout () {
|
||||
this.showingConfirmLogout = true
|
||||
},
|
||||
hideConfirmLogout () {
|
||||
this.showingConfirmLogout = false
|
||||
},
|
||||
logout () {
|
||||
if (!this.shouldConfirmLogout) {
|
||||
this.doLogout()
|
||||
} else {
|
||||
this.showConfirmLogout()
|
||||
}
|
||||
},
|
||||
doLogout () {
|
||||
this.$router.replace('/main/public')
|
||||
this.$store.dispatch('logout')
|
||||
this.hideConfirmLogout()
|
||||
},
|
||||
markNotificationsAsSeen () {
|
||||
// this.$refs.notifications.markAsSeen()
|
||||
|
|
|
@ -23,8 +23,16 @@
|
|||
class="alert-dot"
|
||||
/>
|
||||
</button>
|
||||
<NavigationPins class="pins" />
|
||||
</div> <div class="item right">
|
||||
<router-link
|
||||
v-if="!hideSiteName"
|
||||
class="site-name"
|
||||
:to="{ name: 'root' }"
|
||||
active-class="home"
|
||||
>
|
||||
{{ sitename }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="item right">
|
||||
<button
|
||||
v-if="currentUser"
|
||||
class="button-unstyled mobile-nav-button"
|
||||
|
@ -88,6 +96,18 @@
|
|||
ref="sideDrawer"
|
||||
:logout="logout"
|
||||
/>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmLogout"
|
||||
:title="$t('login.logout_confirm_title')"
|
||||
:confirm-text="$t('login.logout_confirm_accept_button')"
|
||||
:cancel-text="$t('login.logout_confirm_cancel_button')"
|
||||
@accepted="doLogout"
|
||||
@cancelled="hideConfirmLogout"
|
||||
>
|
||||
{{ $t('login.logout_confirm') }}
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -235,6 +255,16 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-modal.dark-overlay {
|
||||
&::before {
|
||||
z-index: 3000;
|
||||
}
|
||||
|
||||
.dialog-modal.panel {
|
||||
z-index: 3001;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -15,11 +15,12 @@ import {
|
|||
faChevronDown,
|
||||
faChevronUp,
|
||||
faComments,
|
||||
faBell,
|
||||
faBolt,
|
||||
faInfoCircle,
|
||||
faStream,
|
||||
faList,
|
||||
faBullhorn
|
||||
faBullhorn,
|
||||
faCircle
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
|
@ -30,11 +31,12 @@ library.add(
|
|||
faChevronDown,
|
||||
faChevronUp,
|
||||
faComments,
|
||||
faBell,
|
||||
faBolt,
|
||||
faInfoCircle,
|
||||
faStream,
|
||||
faList,
|
||||
faBullhorn
|
||||
faBullhorn,
|
||||
faCircle
|
||||
)
|
||||
const NavPanel = {
|
||||
props: ['forceExpand', 'forceEditMode'],
|
||||
|
|
|
@ -22,6 +22,13 @@ export const TIMELINES = {
|
|||
label: 'nav.public_tl',
|
||||
criteria: ['!private']
|
||||
},
|
||||
bubble: {
|
||||
route: 'bubble-timeline',
|
||||
anon: true,
|
||||
icon: 'circle',
|
||||
label: 'nav.bubble_timeline',
|
||||
criteria: ['!private', 'federating']
|
||||
},
|
||||
twkn: {
|
||||
route: 'public-external-timeline',
|
||||
anon: true,
|
||||
|
|
|
@ -58,6 +58,7 @@ const NavPanel = {
|
|||
if (!this.currentUser) {
|
||||
return filterNavigation([
|
||||
{ ...TIMELINES.public, name: 'public' },
|
||||
{ ...TIMELINES.bubble, name: 'bubble' },
|
||||
{ ...TIMELINES.twkn, name: 'twkn' },
|
||||
{ ...ROOT_ITEMS.about, name: 'about' }
|
||||
],
|
||||
|
|
|
@ -8,6 +8,7 @@ import Report from '../report/report.vue'
|
|||
import UserLink from '../user_link/user_link.vue'
|
||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||
import UserPopover from '../user_popover/user_popover.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
|
||||
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
@ -43,7 +44,9 @@ const Notification = {
|
|||
return {
|
||||
statusExpanded: false,
|
||||
betterShadow: this.$store.state.interface.browserSupport.cssFilter,
|
||||
unmuted: false
|
||||
unmuted: false,
|
||||
showingApproveConfirmDialog: false,
|
||||
showingDenyConfirmDialog: false
|
||||
}
|
||||
},
|
||||
props: ['notification'],
|
||||
|
@ -56,7 +59,8 @@ const Notification = {
|
|||
Report,
|
||||
RichContent,
|
||||
UserPopover,
|
||||
UserLink
|
||||
UserLink,
|
||||
ConfirmModal
|
||||
},
|
||||
methods: {
|
||||
toggleStatusExpanded () {
|
||||
|
@ -71,7 +75,26 @@ const Notification = {
|
|||
toggleMute () {
|
||||
this.unmuted = !this.unmuted
|
||||
},
|
||||
showApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = true
|
||||
},
|
||||
hideApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = false
|
||||
},
|
||||
showDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = true
|
||||
},
|
||||
hideDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = false
|
||||
},
|
||||
approveUser () {
|
||||
if (this.shouldConfirmApprove) {
|
||||
this.showApproveConfirmDialog()
|
||||
} else {
|
||||
this.doApprove()
|
||||
}
|
||||
},
|
||||
doApprove () {
|
||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
|
||||
|
@ -81,13 +104,22 @@ const Notification = {
|
|||
notification.type = 'follow'
|
||||
}
|
||||
})
|
||||
this.hideApproveConfirmDialog()
|
||||
},
|
||||
denyUser () {
|
||||
if (this.shouldConfirmDeny) {
|
||||
this.showDenyConfirmDialog()
|
||||
} else {
|
||||
this.doDeny()
|
||||
}
|
||||
},
|
||||
doDeny () {
|
||||
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
|
||||
.then(() => {
|
||||
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
})
|
||||
this.hideDenyConfirmDialog()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -117,6 +149,15 @@ const Notification = {
|
|||
isStatusNotification () {
|
||||
return isStatusNotification(this.notification.type)
|
||||
},
|
||||
mergedConfig () {
|
||||
return this.$store.getters.mergedConfig
|
||||
},
|
||||
shouldConfirmApprove () {
|
||||
return this.mergedConfig.modalOnApproveFollow
|
||||
},
|
||||
shouldConfirmDeny () {
|
||||
return this.mergedConfig.modalOnDenyFollow
|
||||
},
|
||||
...mapState({
|
||||
currentUser: state => state.users.currentUser
|
||||
})
|
||||
|
|
|
@ -243,6 +243,28 @@
|
|||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingApproveConfirmDialog"
|
||||
:title="$t('user_card.approve_confirm_title')"
|
||||
:confirm-text="$t('user_card.approve_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
|
||||
@accepted="doApprove"
|
||||
@cancelled="hideApproveConfirmDialog"
|
||||
>
|
||||
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
|
||||
</confirm-modal>
|
||||
<confirm-modal
|
||||
v-if="showingDenyConfirmDialog"
|
||||
:title="$t('user_card.deny_confirm_title')"
|
||||
:confirm-text="$t('user_card.deny_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
|
||||
@accepted="doDeny"
|
||||
@cancelled="hideDenyConfirmDialog"
|
||||
>
|
||||
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -94,19 +94,10 @@ export default {
|
|||
},
|
||||
convertExpiryToUnit (unit, amount) {
|
||||
// Note: we want seconds and not milliseconds
|
||||
switch (unit) {
|
||||
case 'minutes': return (1000 * amount) / DateUtils.MINUTE
|
||||
case 'hours': return (1000 * amount) / DateUtils.HOUR
|
||||
case 'days': return (1000 * amount) / DateUtils.DAY
|
||||
}
|
||||
return DateUtils.secondsToUnit(unit, amount)
|
||||
},
|
||||
convertExpiryFromUnit (unit, amount) {
|
||||
// Note: we want seconds and not milliseconds
|
||||
switch (unit) {
|
||||
case 'minutes': return 0.001 * amount * DateUtils.MINUTE
|
||||
case 'hours': return 0.001 * amount * DateUtils.HOUR
|
||||
case 'days': return 0.001 * amount * DateUtils.DAY
|
||||
}
|
||||
return DateUtils.unitToSeconds(unit, amount)
|
||||
},
|
||||
expiryAmountChange () {
|
||||
this.expiryAmount =
|
||||
|
|
|
@ -8,6 +8,7 @@ import Gallery from 'src/components/gallery/gallery.vue'
|
|||
import StatusContent from '../status_content/status_content.vue'
|
||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
||||
import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js'
|
||||
import { reject, map, uniqBy, debounce } from 'lodash'
|
||||
import suggestor from '../emoji_input/suggestor.js'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
|
@ -64,6 +65,7 @@ const PostStatusForm = {
|
|||
'statusScope',
|
||||
'statusContentType',
|
||||
'replyTo',
|
||||
'quoteId',
|
||||
'repliedUser',
|
||||
'attentions',
|
||||
'copyMessageScope',
|
||||
|
@ -108,12 +110,12 @@ const PostStatusForm = {
|
|||
this.updateIdempotencyKey()
|
||||
this.resize(this.$refs.textarea)
|
||||
|
||||
if (this.replyTo) {
|
||||
if (this.replyTo || this.quoteId) {
|
||||
const textLength = this.$refs.textarea.value.length
|
||||
this.$refs.textarea.setSelectionRange(textLength, textLength)
|
||||
}
|
||||
|
||||
if (this.replyTo || this.autoFocus) {
|
||||
if (this.replyTo || this.quoteId || this.autoFocus) {
|
||||
this.$refs.textarea.focus()
|
||||
}
|
||||
},
|
||||
|
@ -123,7 +125,7 @@ const PostStatusForm = {
|
|||
|
||||
const { scopeCopy } = this.$store.getters.mergedConfig
|
||||
|
||||
if (this.replyTo) {
|
||||
if (this.replyTo || this.quoteId) {
|
||||
const currentUser = this.$store.state.users.currentUser
|
||||
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
|
||||
}
|
||||
|
@ -347,6 +349,7 @@ const PostStatusForm = {
|
|||
media: newStatus.files,
|
||||
store: this.$store,
|
||||
inReplyToStatusId: this.replyTo,
|
||||
quoteId: this.quoteId,
|
||||
contentType: newStatus.contentType,
|
||||
poll,
|
||||
idempotencyKey: this.idempotencyKey
|
||||
|
@ -380,6 +383,7 @@ const PostStatusForm = {
|
|||
media: [],
|
||||
store: this.$store,
|
||||
inReplyToStatusId: this.replyTo,
|
||||
quoteId: this.quoteId,
|
||||
contentType: newStatus.contentType,
|
||||
poll: {},
|
||||
preview: true
|
||||
|
@ -630,6 +634,9 @@ const PostStatusForm = {
|
|||
},
|
||||
openProfileTab () {
|
||||
this.$store.dispatch('openSettingsModalTab', 'profile')
|
||||
},
|
||||
propsToNative (props) {
|
||||
return propsToNative(props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
<span>{{ $t('post_status.scope_notice.public') }}</span>
|
||||
<a
|
||||
class="fa-scale-110 fa-old-padding dismiss"
|
||||
:title="$t('post_status.scope_notice_dismiss')"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<FAIcon icon="times" />
|
||||
|
@ -42,6 +45,9 @@
|
|||
<span>{{ $t('post_status.scope_notice.unlisted') }}</span>
|
||||
<a
|
||||
class="fa-scale-110 fa-old-padding dismiss"
|
||||
:title="$t('post_status.scope_notice_dismiss')"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<FAIcon icon="times" />
|
||||
|
@ -54,6 +60,9 @@
|
|||
<span>{{ $t('post_status.scope_notice.private') }}</span>
|
||||
<a
|
||||
class="fa-scale-110 fa-old-padding dismiss"
|
||||
:title="$t('post_status.scope_notice_dismiss')"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<FAIcon icon="times" />
|
||||
|
@ -124,14 +133,17 @@
|
|||
:suggest="emojiSuggestor"
|
||||
class="form-control"
|
||||
>
|
||||
<input
|
||||
v-model="newStatus.spoilerText"
|
||||
type="text"
|
||||
:placeholder="$t('post_status.content_warning')"
|
||||
:disabled="posting && !optimisticPosting"
|
||||
size="1"
|
||||
class="form-post-subject"
|
||||
>
|
||||
<template #default="inputProps">
|
||||
<input
|
||||
v-model="newStatus.spoilerText"
|
||||
type="text"
|
||||
:placeholder="$t('post_status.content_warning')"
|
||||
:disabled="posting && !optimisticPosting"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
size="1"
|
||||
class="form-post-subject"
|
||||
>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<EmojiInput
|
||||
ref="emoji-input"
|
||||
|
@ -148,29 +160,32 @@
|
|||
@sticker-upload-failed="uploadFailed"
|
||||
@shown="handleEmojiInputShow"
|
||||
>
|
||||
<textarea
|
||||
ref="textarea"
|
||||
v-model="newStatus.status"
|
||||
:placeholder="placeholder || $t('post_status.default')"
|
||||
rows="1"
|
||||
cols="1"
|
||||
:disabled="posting && !optimisticPosting"
|
||||
class="form-post-body"
|
||||
:class="{ 'scrollable-form': !!maxHeight }"
|
||||
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
|
||||
@keydown.meta.enter="postStatus($event, newStatus)"
|
||||
@keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
|
||||
@input="resize"
|
||||
@compositionupdate="resize"
|
||||
@paste="paste"
|
||||
/>
|
||||
<p
|
||||
v-if="hasStatusLengthLimit"
|
||||
class="character-counter faint"
|
||||
:class="{ error: isOverLengthLimit }"
|
||||
>
|
||||
{{ charactersLeft }}
|
||||
</p>
|
||||
<template #default="inputProps">
|
||||
<textarea
|
||||
ref="textarea"
|
||||
v-model="newStatus.status"
|
||||
:placeholder="placeholder || $t('post_status.default')"
|
||||
rows="1"
|
||||
cols="1"
|
||||
:disabled="posting && !optimisticPosting"
|
||||
class="form-post-body"
|
||||
:class="{ 'scrollable-form': !!maxHeight }"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
|
||||
@keydown.meta.enter="postStatus($event, newStatus)"
|
||||
@keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
|
||||
@input="resize"
|
||||
@compositionupdate="resize"
|
||||
@paste="paste"
|
||||
/>
|
||||
<p
|
||||
v-if="hasStatusLengthLimit"
|
||||
class="character-counter faint"
|
||||
:class="{ error: isOverLengthLimit }"
|
||||
>
|
||||
{{ charactersLeft }}
|
||||
</p>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<div
|
||||
v-if="!disableScopeSelector"
|
||||
|
@ -193,6 +208,7 @@
|
|||
id="post-content-type"
|
||||
v-model="newStatus.contentType"
|
||||
class="form-control"
|
||||
:attrs="{ 'aria-label': $t('post_status.content_type_selection') }"
|
||||
>
|
||||
<option
|
||||
v-for="postFormat in postFormats"
|
||||
|
|
|
@ -6,36 +6,51 @@
|
|||
:trigger-attrs="{ title: $t('timeline.quick_filter_settings') }"
|
||||
>
|
||||
<template #content>
|
||||
<div class="dropdown-menu">
|
||||
<div v-if="loggedIn">
|
||||
<div
|
||||
class="dropdown-menu"
|
||||
role="menu"
|
||||
>
|
||||
<div
|
||||
v-if="loggedIn"
|
||||
role="group"
|
||||
>
|
||||
<button
|
||||
v-if="!conversation"
|
||||
class="button-default dropdown-item"
|
||||
:aria-checked="replyVisibilityAll"
|
||||
role="menuitemradio"
|
||||
@click="replyVisibilityAll = true"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox -radio"
|
||||
:class="{ 'menu-checkbox-checked': replyVisibilityAll }"
|
||||
:aria-hidden="true"
|
||||
/>{{ $t('settings.reply_visibility_all') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!conversation"
|
||||
class="button-default dropdown-item"
|
||||
:aria-checked="replyVisibilityFollowing"
|
||||
role="menuitemradio"
|
||||
@click="replyVisibilityFollowing = true"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox -radio"
|
||||
:class="{ 'menu-checkbox-checked': replyVisibilityFollowing }"
|
||||
:aria-hidden="true"
|
||||
/>{{ $t('settings.reply_visibility_following_short') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!conversation"
|
||||
class="button-default dropdown-item"
|
||||
:aria-checked="replyVisibilitySelf"
|
||||
role="menuitemradio"
|
||||
@click="replyVisibilitySelf = true"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox -radio"
|
||||
:class="{ 'menu-checkbox-checked': replyVisibilitySelf }"
|
||||
:aria-hidden="true"
|
||||
/>{{ $t('settings.reply_visibility_self_short') }}
|
||||
</button>
|
||||
<div
|
||||
|
@ -46,33 +61,43 @@
|
|||
</div>
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
role="menuitemcheckbox"
|
||||
:aria-checked="muteBotStatuses"
|
||||
@click="muteBotStatuses = !muteBotStatuses"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox"
|
||||
:class="{ 'menu-checkbox-checked': muteBotStatuses }"
|
||||
:aria-hidden="true"
|
||||
/>{{ $t('settings.mute_bot_posts') }}
|
||||
</button>
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
role="menuitemcheckbox"
|
||||
:aria-checked="hideMedia"
|
||||
@click="hideMedia = !hideMedia"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox"
|
||||
:class="{ 'menu-checkbox-checked': hideMedia }"
|
||||
:aria-hidden="true"
|
||||
/>{{ $t('settings.hide_media_previews') }}
|
||||
</button>
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
role="menuitemcheckbox"
|
||||
:aria-checked="hideMutedPosts"
|
||||
@click="hideMutedPosts = !hideMutedPosts"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox"
|
||||
:class="{ 'menu-checkbox-checked': hideMutedPosts }"
|
||||
:aria-hidden="true"
|
||||
/>{{ $t('settings.hide_all_muted_posts') }}
|
||||
</button>
|
||||
<button
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
role="menuitem"
|
||||
@click="openTab('filtering')"
|
||||
>
|
||||
<FAIcon icon="font" />{{ $t('settings.word_filter_and_more') }}
|
||||
|
|
|
@ -6,60 +6,87 @@
|
|||
:trigger-attrs="{ title: $t('timeline.quick_view_settings') }"
|
||||
>
|
||||
<template #content>
|
||||
<div class="dropdown-menu">
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
@click="conversationDisplay = 'tree'"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox -radio"
|
||||
:class="{ 'menu-checkbox-checked': conversationDisplay === 'tree' }"
|
||||
/><FAIcon icon="folder-tree" /> {{ $t('settings.conversation_display_tree_quick') }}
|
||||
</button>
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
@click="conversationDisplay = 'linear'"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox -radio"
|
||||
:class="{ 'menu-checkbox-checked': conversationDisplay === 'linear' }"
|
||||
/><FAIcon icon="list" /> {{ $t('settings.conversation_display_linear_quick') }}
|
||||
</button>
|
||||
<div
|
||||
class="dropdown-menu"
|
||||
role="menu"
|
||||
>
|
||||
<div role="group">
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
:aria-checked="conversationDisplay === 'tree'"
|
||||
role="menuitemradio"
|
||||
@click="conversationDisplay = 'tree'"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox -radio"
|
||||
:aria-hidden="true"
|
||||
:class="{ 'menu-checkbox-checked': conversationDisplay === 'tree' }"
|
||||
/><FAIcon
|
||||
icon="folder-tree"
|
||||
:aria-hidden="true"
|
||||
/> {{ $t('settings.conversation_display_tree_quick') }}
|
||||
</button>
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
:aria-checked="conversationDisplay === 'linear'"
|
||||
role="menuitemradio"
|
||||
@click="conversationDisplay = 'linear'"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox -radio"
|
||||
:class="{ 'menu-checkbox-checked': conversationDisplay === 'linear' }"
|
||||
:aria-hidden="true"
|
||||
/><FAIcon
|
||||
icon="list"
|
||||
:aria-hidden="true"
|
||||
/> {{ $t('settings.conversation_display_linear_quick') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
role="separator"
|
||||
class="dropdown-divider"
|
||||
/>
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
role="menuitemcheckbox"
|
||||
:aria-checked="showUserAvatars"
|
||||
@click="showUserAvatars = !showUserAvatars"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox"
|
||||
:class="{ 'menu-checkbox-checked': showUserAvatars }"
|
||||
:aria-hidden="true"
|
||||
/>{{ $t('settings.mention_link_show_avatar_quick') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!conversation"
|
||||
class="button-default dropdown-item"
|
||||
role="menuitemcheckbox"
|
||||
:aria-checked="autoUpdate"
|
||||
@click="autoUpdate = !autoUpdate"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox"
|
||||
:class="{ 'menu-checkbox-checked': autoUpdate }"
|
||||
:aria-hidden="true"
|
||||
/>{{ $t('settings.auto_update') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!conversation"
|
||||
class="button-default dropdown-item"
|
||||
role="menuitemcheckbox"
|
||||
:aria-checked="collapseWithSubjects"
|
||||
@click="collapseWithSubjects = !collapseWithSubjects"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox"
|
||||
:class="{ 'menu-checkbox-checked': collapseWithSubjects }"
|
||||
:aria-hidden="true"
|
||||
/>{{ $t('settings.collapse_subject') }}
|
||||
</button>
|
||||
<button
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
role="menuitem"
|
||||
@click="openTab('general')"
|
||||
>
|
||||
<FAIcon icon="wrench" />{{ $t('settings.more_settings') }}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faQuoteLeft } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(faQuoteLeft)
|
||||
|
||||
const QuoteButton = {
|
||||
name: 'QuoteButton',
|
||||
props: ['status', 'quoting', 'visibility'],
|
||||
computed: {
|
||||
loggedIn () {
|
||||
return !!this.$store.state.users.currentUser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default QuoteButton
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="(visibility === 'public' || visibility === 'unlisted') && loggedIn"
|
||||
class="QuoteButton"
|
||||
>
|
||||
<button
|
||||
class="button-unstyled interactive"
|
||||
:class="{'-active': quoting}"
|
||||
:title="$t('tool_tip.quote')"
|
||||
@click.prevent="$emit('toggle')"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="quote-left"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./quote_button.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.QuoteButton {
|
||||
display: flex;
|
||||
|
||||
> :first-child {
|
||||
padding: 10px;
|
||||
margin: -10px -8px -10px -10px;
|
||||
}
|
||||
|
||||
.action-counter {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.interactive {
|
||||
&:hover .svg-inline--fa,
|
||||
&.-active .svg-inline--fa {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,24 @@
|
|||
import { mapGetters } from 'vuex'
|
||||
import QuoteCardContent from '../quote_card_content/quote_card_content.vue'
|
||||
|
||||
const QuoteCard = {
|
||||
name: 'QuoteCard',
|
||||
props: [
|
||||
'status'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
imageLoaded: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'mergedConfig'
|
||||
])
|
||||
},
|
||||
components: {
|
||||
QuoteCardContent
|
||||
}
|
||||
}
|
||||
|
||||
export default QuoteCard
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div>
|
||||
<router-link
|
||||
class="quote-card"
|
||||
:to="{ name: 'conversation', params: { id: status.id } }"
|
||||
>
|
||||
<QuoteCardContent
|
||||
:status="status"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./quote_card"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.quote-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
margin-top: 0.5em;
|
||||
|
||||
.card-image {
|
||||
flex-shrink: 0;
|
||||
width: 120px;
|
||||
max-width: 25%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: $fallback--attachmentRadius;
|
||||
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
max-height: 100%;
|
||||
margin: 0.5em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-host {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
margin: 0.5em 0 0 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
line-height: 1.2em;
|
||||
// cap description at 3 lines, the 1px is to clean up some stray pixels
|
||||
// TODO: fancier fade-out at the bottom to show off that it's too long?
|
||||
max-height: calc(1.2em * 3 - 1px);
|
||||
}
|
||||
|
||||
.nsfw-alert {
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: $fallback--attachmentRadius;
|
||||
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<Status
|
||||
v-if="status"
|
||||
:is-preview="true"
|
||||
:statusoid="status"
|
||||
:compact="true"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'QuoteCardContent',
|
||||
components: {
|
||||
Status: defineAsyncComponent(() => import('../status/status.vue'))
|
||||
},
|
||||
props: [
|
||||
'status'
|
||||
]
|
||||
}
|
||||
</script>
|
|
@ -3,6 +3,7 @@ import { required, requiredIf, sameAs } from '@vuelidate/validators'
|
|||
import { mapActions, mapState } from 'vuex'
|
||||
import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
|
||||
import localeService from '../../services/locale/locale.service.js'
|
||||
import { DAY } from 'src/services/date_utils/date_utils.js'
|
||||
|
||||
const registration = {
|
||||
setup () { return { v$: useVuelidate() } },
|
||||
|
@ -13,6 +14,7 @@ const registration = {
|
|||
username: '',
|
||||
password: '',
|
||||
confirm: '',
|
||||
birthday: '',
|
||||
reason: '',
|
||||
language: ''
|
||||
},
|
||||
|
@ -32,6 +34,12 @@ const registration = {
|
|||
required,
|
||||
sameAs: sameAs(this.user.password)
|
||||
},
|
||||
birthday: {
|
||||
required: requiredIf(() => this.birthdayRequired),
|
||||
maxValue: value => {
|
||||
return !this.birthdayRequired || new Date(value).getTime() <= this.birthdayMin.getTime()
|
||||
}
|
||||
},
|
||||
reason: { required: requiredIf(() => this.accountApprovalRequired) },
|
||||
language: {}
|
||||
}
|
||||
|
@ -52,6 +60,24 @@ const registration = {
|
|||
reasonPlaceholder () {
|
||||
return this.replaceNewlines(this.$t('registration.reason_placeholder'))
|
||||
},
|
||||
birthdayMin () {
|
||||
const minAge = this.birthdayMinAge
|
||||
const today = new Date()
|
||||
today.setUTCMilliseconds(0)
|
||||
today.setUTCSeconds(0)
|
||||
today.setUTCMinutes(0)
|
||||
today.setUTCHours(0)
|
||||
const minDate = new Date()
|
||||
minDate.setTime(today.getTime() - minAge * DAY)
|
||||
return minDate
|
||||
},
|
||||
birthdayMinAttr () {
|
||||
return this.birthdayMin.toJSON().replace(/T.+$/, '')
|
||||
},
|
||||
birthdayMinFormatted () {
|
||||
const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
|
||||
return this.user.birthday && new Date(Date.parse(this.birthdayMin)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' })
|
||||
},
|
||||
...mapState({
|
||||
registrationOpen: (state) => state.instance.registrationOpen,
|
||||
signedIn: (state) => !!state.users.currentUser,
|
||||
|
@ -59,7 +85,9 @@ const registration = {
|
|||
serverValidationErrors: (state) => state.users.signUpErrors,
|
||||
termsOfService: (state) => state.instance.tos,
|
||||
accountActivationRequired: (state) => state.instance.accountActivationRequired,
|
||||
accountApprovalRequired: (state) => state.instance.accountApprovalRequired
|
||||
accountApprovalRequired: (state) => state.instance.accountApprovalRequired,
|
||||
birthdayRequired: (state) => state.instance.birthdayRequired,
|
||||
birthdayMinAge: (state) => state.instance.birthdayMinAge
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -167,6 +167,40 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="form-group"
|
||||
:class="{ 'form-group--error': v$.user.birthday.$error }"
|
||||
>
|
||||
<label
|
||||
class="form--label"
|
||||
for="sign-up-birthday"
|
||||
>
|
||||
{{ birthdayRequired ? $t('registration.birthday') : $t('registration.birthday_optional') }}
|
||||
</label>
|
||||
<input
|
||||
id="sign-up-birthday"
|
||||
v-model="user.birthday"
|
||||
:disabled="isPending"
|
||||
class="form-control"
|
||||
type="date"
|
||||
:max="birthdayRequired ? birthdayMinAttr : undefined"
|
||||
:aria-required="birthdayRequired"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="v$.user.birthday.$dirty"
|
||||
class="form-error"
|
||||
>
|
||||
<ul>
|
||||
<li v-if="v$.user.birthday.required.$invalid">
|
||||
<span>{{ $t('registration.validations.birthday_required') }}</span>
|
||||
</li>
|
||||
<li v-if="v$.user.birthday.maxValue.$invalid">
|
||||
<span>{{ $tc('registration.validations.birthday_min_age', { date: birthdayMinFormatted }) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="form-group"
|
||||
:class="{ 'form-group--error': v$.user.language.$error }"
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
|
||||
export default {
|
||||
props: ['relationship'],
|
||||
props: ['user', 'relationship'],
|
||||
data () {
|
||||
return {
|
||||
inProgress: false
|
||||
inProgress: false,
|
||||
showingConfirmRemoveFollower: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
computed: {
|
||||
label () {
|
||||
if (this.inProgress) {
|
||||
|
@ -12,14 +18,31 @@ export default {
|
|||
} else {
|
||||
return this.$t('user_card.remove_follower')
|
||||
}
|
||||
},
|
||||
shouldConfirmRemoveUserFromFollowers () {
|
||||
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showConfirmRemoveUserFromFollowers () {
|
||||
this.showingConfirmRemoveFollower = true
|
||||
},
|
||||
hideConfirmRemoveUserFromFollowers () {
|
||||
this.showingConfirmRemoveFollower = false
|
||||
},
|
||||
onClick () {
|
||||
if (!this.shouldConfirmRemoveUserFromFollowers) {
|
||||
this.doRemoveUserFromFollowers()
|
||||
} else {
|
||||
this.showConfirmRemoveUserFromFollowers()
|
||||
}
|
||||
},
|
||||
doRemoveUserFromFollowers () {
|
||||
this.inProgress = true
|
||||
this.$store.dispatch('removeUserFromFollowers', this.relationship.id).then(() => {
|
||||
this.inProgress = false
|
||||
})
|
||||
this.hideConfirmRemoveUserFromFollowers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,27 @@
|
|||
@click="onClick"
|
||||
>
|
||||
{{ label }}
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmRemoveFollower"
|
||||
:title="$t('user_card.remove_follower_confirm_title')"
|
||||
:confirm-text="$t('user_card.remove_follower_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.remove_follower_confirm_cancel_button')"
|
||||
@accepted="doRemoveUserFromFollowers"
|
||||
@cancelled="hideConfirmRemoveUserFromFollowers"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="user_card.remove_follower_confirm"
|
||||
tag="span"
|
||||
>
|
||||
<template #user>
|
||||
<span
|
||||
v-text="user.screen_name_ui"
|
||||
/>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -32,12 +32,20 @@
|
|||
target="_blank"
|
||||
role="button"
|
||||
:href="remoteInteractionLink"
|
||||
:title="$t('tool_tip.reply')"
|
||||
>
|
||||
<FAIcon
|
||||
icon="reply"
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
:title="$t('tool_tip.reply')"
|
||||
/>
|
||||
<FALayers class="fa-old-padding-layer">
|
||||
<FAIcon
|
||||
class="fa-scale-110"
|
||||
icon="reply"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="!replying"
|
||||
class="focus-marker"
|
||||
transform="shrink-6 up-8 right-16"
|
||||
icon="plus"
|
||||
/>
|
||||
</FALayers>
|
||||
</a>
|
||||
<span
|
||||
v-if="status.replies_count > 0"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faRetweet,
|
||||
|
@ -15,13 +16,24 @@ library.add(
|
|||
|
||||
const RetweetButton = {
|
||||
props: ['status', 'loggedIn', 'visibility'],
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
animated: false
|
||||
animated: false,
|
||||
showingConfirmDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
retweet () {
|
||||
if (!this.status.repeated && this.shouldConfirmRepeat) {
|
||||
this.showConfirmDialog()
|
||||
} else {
|
||||
this.doRetweet()
|
||||
}
|
||||
},
|
||||
doRetweet () {
|
||||
if (!this.status.repeated) {
|
||||
this.$store.dispatch('retweet', { id: this.status.id })
|
||||
} else {
|
||||
|
@ -31,6 +43,13 @@ const RetweetButton = {
|
|||
setTimeout(() => {
|
||||
this.animated = false
|
||||
}, 500)
|
||||
this.hideConfirmDialog()
|
||||
},
|
||||
showConfirmDialog () {
|
||||
this.showingConfirmDialog = true
|
||||
},
|
||||
hideConfirmDialog () {
|
||||
this.showingConfirmDialog = false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -39,6 +58,9 @@ const RetweetButton = {
|
|||
},
|
||||
remoteInteractionLink () {
|
||||
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
|
||||
},
|
||||
shouldConfirmRepeat () {
|
||||
return this.mergedConfig.modalOnRepeat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,13 +45,20 @@
|
|||
class="button-unstyled interactive"
|
||||
target="_blank"
|
||||
role="button"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
:href="remoteInteractionLink"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="retweet"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
/>
|
||||
<FALayers class="fa-old-padding-layer">
|
||||
<FAIcon
|
||||
class="fa-scale-110"
|
||||
icon="retweet"
|
||||
/>
|
||||
<FAIcon
|
||||
class="focus-marker"
|
||||
transform="shrink-6 up-9 right-12"
|
||||
icon="plus"
|
||||
/>
|
||||
</FALayers>
|
||||
</a>
|
||||
<span
|
||||
v-if="!mergedConfig.hidePostStats && status.repeat_num > 0"
|
||||
|
@ -59,6 +66,18 @@
|
|||
>
|
||||
{{ status.repeat_num }}
|
||||
</span>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmDialog"
|
||||
:title="$t('status.repeat_confirm_title')"
|
||||
:confirm-text="$t('status.repeat_confirm_accept_button')"
|
||||
:cancel-text="$t('status.repeat_confirm_cancel_button')"
|
||||
@accepted="doRetweet"
|
||||
@cancelled="hideConfirmDialog"
|
||||
>
|
||||
{{ $t('status.repeat_confirm') }}
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
const ScreenReaderNotice = {
|
||||
props: {
|
||||
ariaLive: {
|
||||
type: String,
|
||||
defualt: 'assertive'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
currentText: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
announce (text) {
|
||||
this.currentText = text
|
||||
setTimeout(() => { this.currentText = '' }, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ScreenReaderNotice
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<div
|
||||
class="screen-reader-text"
|
||||
:aria-live="ariaLive"
|
||||
>
|
||||
{{ currentText }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./screen_reader_notice.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.screen-reader-text {
|
||||
display: block;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
|
@ -8,6 +8,7 @@
|
|||
class="button-unstyled nav-icon"
|
||||
:title="$t('nav.search')"
|
||||
type="button"
|
||||
:aria-expanded="!hidden"
|
||||
@click.prevent.stop="toggleHidden"
|
||||
>
|
||||
<FAIcon
|
||||
|
@ -29,6 +30,7 @@
|
|||
<button
|
||||
class="button-default search-button"
|
||||
type="submit"
|
||||
:title="$t('nav.search')"
|
||||
@click="find(searchTerm)"
|
||||
>
|
||||
<FAIcon
|
||||
|
@ -39,6 +41,8 @@
|
|||
<button
|
||||
class="button-unstyled cancel-search"
|
||||
type="button"
|
||||
:title="$t('nav.search_close')"
|
||||
:aria-expanded="!hidden"
|
||||
@click.prevent.stop="toggleHidden"
|
||||
>
|
||||
<FAIcon
|
||||
|
|
|
@ -13,6 +13,7 @@ export default {
|
|||
'modelValue',
|
||||
'disabled',
|
||||
'unstyled',
|
||||
'kind'
|
||||
'kind',
|
||||
'attrs'
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<select
|
||||
:disabled="disabled"
|
||||
:value="modelValue"
|
||||
v-bind="attrs"
|
||||
@change="$emit('update:modelValue', $event.target.value)"
|
||||
>
|
||||
<slot />
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
faTimes,
|
||||
faFileUpload,
|
||||
faFileDownload,
|
||||
faSignOutAlt,
|
||||
faChevronDown
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
|
@ -28,6 +29,7 @@ library.add(
|
|||
faWindowMinimize,
|
||||
faFileUpload,
|
||||
faFileDownload,
|
||||
faSignOutAlt,
|
||||
faChevronDown
|
||||
)
|
||||
|
||||
|
@ -66,6 +68,11 @@ const SettingsModal = {
|
|||
closeModal () {
|
||||
this.$store.dispatch('closeSettingsModal')
|
||||
},
|
||||
logout () {
|
||||
this.$router.replace('/main/public')
|
||||
this.$store.dispatch('closeSettingsModal')
|
||||
this.$store.dispatch('logout')
|
||||
},
|
||||
peekModal () {
|
||||
this.$store.dispatch('togglePeekSettingsModal')
|
||||
},
|
||||
|
@ -150,6 +157,7 @@ const SettingsModal = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
currentUser () { return this.$store.state.users.currentUser },
|
||||
currentSaveStateNotice () {
|
||||
return this.$store.state.interface.settings.currentSaveStateNotice
|
||||
},
|
||||
|
|
|
@ -76,5 +76,11 @@
|
|||
transform: translateY(calc(100% - 50px));
|
||||
}
|
||||
}
|
||||
|
||||
.logout-button {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,20 @@
|
|||
id="unscrolled-content"
|
||||
class="extra-content"
|
||||
/>
|
||||
<button
|
||||
v-if="currentUser"
|
||||
class="button-default logout-button"
|
||||
:title="$t('login.logout')"
|
||||
:aria-label="$t('login.logout')"
|
||||
@click.prevent="logout"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="sign-out-alt"
|
||||
/>
|
||||
<span>{{ $t('login.logout') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -56,6 +56,7 @@ const GeneralTab = {
|
|||
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
|
||||
// Future spec, still not supported in Nightly 63 as of 08/2018
|
||||
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks')
|
||||
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -104,11 +105,23 @@ const GeneralTab = {
|
|||
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
||||
}
|
||||
},
|
||||
translationLanguages () {
|
||||
return (this.$store.getters.mergedConfig.supportedTranslationLanguages || []).map(lang => ({ key: lang.code, value: lang.code, label: lang.name }))
|
||||
},
|
||||
translationLanguage: {
|
||||
get: function () { return this.$store.getters.mergedConfig.translationLanguage },
|
||||
set: function (val) {
|
||||
this.$store.dispatch('setOption', { name: 'translationLanguage', value: val })
|
||||
}
|
||||
},
|
||||
...SharedComputedObject()
|
||||
},
|
||||
methods: {
|
||||
changeDefaultScope (value) {
|
||||
this.$store.dispatch('setServerSideOption', { name: 'defaultScope', value })
|
||||
},
|
||||
setTranslationLanguage (value) {
|
||||
this.$store.dispatch('setOption', { name: 'translationLanguage', value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,38 @@
|
|||
<div class="setting-item">
|
||||
<h2>{{ $t('nav.timeline') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="hideSiteFavicon"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.hide_site_favicon') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="hideSiteName"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.hide_site_name') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="showNavShortcuts"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.show_nav_shortcuts') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="showWiderShortcuts"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.show_wider_shortcuts') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="stopGifs">
|
||||
{{ $t('settings.stop_gifs') }}
|
||||
|
@ -82,6 +114,16 @@
|
|||
{{ $t('settings.user_popover_avatar_overlay') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<ChoiceSetting
|
||||
v-if="user && (translationLanguages.length > 0)"
|
||||
id="translationLanguage"
|
||||
path="translationLanguage"
|
||||
:options="translationLanguages"
|
||||
>
|
||||
{{ $t('settings.translation_language') }}
|
||||
</ChoiceSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="alwaysShowNewPostButton"
|
||||
|
@ -153,6 +195,56 @@
|
|||
</SizeSetting>
|
||||
</div>
|
||||
</li>
|
||||
<li class="select-multiple">
|
||||
<span class="label">{{ $t('settings.confirm_dialogs') }}</span>
|
||||
<ul class="option-list">
|
||||
<li>
|
||||
<BooleanSetting path="modalOnRepeat">
|
||||
{{ $t('settings.confirm_dialogs_repeat') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="modalOnUnfollow">
|
||||
{{ $t('settings.confirm_dialogs_unfollow') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="modalOnBlock">
|
||||
{{ $t('settings.confirm_dialogs_block') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="modalOnMute">
|
||||
{{ $t('settings.confirm_dialogs_mute') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="modalOnDelete">
|
||||
{{ $t('settings.confirm_dialogs_delete') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="modalOnLogout">
|
||||
{{ $t('settings.confirm_dialogs_logout') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="modalOnApproveFollow">
|
||||
{{ $t('settings.confirm_dialogs_approve_follow') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="modalOnDenyFollow">
|
||||
{{ $t('settings.confirm_dialogs_deny_follow') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="modalOnRemoveUserFromFollowers">
|
||||
{{ $t('settings.confirm_dialogs_remove_follower') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
|
|
|
@ -12,6 +12,7 @@ import InterfaceLanguageSwitcher from 'src/components/interface_language_switche
|
|||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
import { propsToNative } from 'src/services/attributes_helper/attributes_helper.service.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
@ -32,6 +33,8 @@ const ProfileTab = {
|
|||
newName: this.$store.state.users.currentUser.name_unescaped,
|
||||
newBio: unescape(this.$store.state.users.currentUser.description),
|
||||
newLocked: this.$store.state.users.currentUser.locked,
|
||||
newBirthday: this.$store.state.users.currentUser.birthday,
|
||||
showBirthday: this.$store.state.users.currentUser.show_birthday,
|
||||
newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
|
||||
showRole: this.$store.state.users.currentUser.show_role,
|
||||
role: this.$store.state.users.currentUser.role,
|
||||
|
@ -43,7 +46,7 @@ const ProfileTab = {
|
|||
bannerPreview: null,
|
||||
background: null,
|
||||
backgroundPreview: null,
|
||||
emailLanguage: this.$store.state.users.currentUser.language || ''
|
||||
emailLanguage: this.$store.state.users.currentUser.language || ['']
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -125,12 +128,14 @@ const ProfileTab = {
|
|||
display_name: this.newName,
|
||||
fields_attributes: this.newFields.filter(el => el != null),
|
||||
bot: this.bot,
|
||||
show_role: this.showRole
|
||||
show_role: this.showRole,
|
||||
birthday: this.newBirthday || '',
|
||||
show_birthday: this.showBirthday
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
|
||||
if (this.emailLanguage) {
|
||||
params.language = localeService.internalToBackendLocale(this.emailLanguage)
|
||||
params.language = localeService.internalToBackendLocaleMulti(this.emailLanguage)
|
||||
}
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
|
@ -257,6 +262,9 @@ const ProfileTab = {
|
|||
messageArgs: [error.message],
|
||||
level: 'error'
|
||||
})
|
||||
},
|
||||
propsToNative (props) {
|
||||
return propsToNative(props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,4 +129,9 @@
|
|||
padding: 0 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.birthday-input {
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,14 @@
|
|||
enable-emoji-picker
|
||||
:suggest="emojiSuggestor"
|
||||
>
|
||||
<input
|
||||
id="username"
|
||||
v-model="newName"
|
||||
class="name-changer"
|
||||
>
|
||||
<template #default="inputProps">
|
||||
<input
|
||||
id="username"
|
||||
v-model="newName"
|
||||
class="name-changer"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<p>{{ $t('settings.bio') }}</p>
|
||||
<EmojiInput
|
||||
|
@ -20,10 +23,13 @@
|
|||
enable-emoji-picker
|
||||
:suggest="emojiUserSuggestor"
|
||||
>
|
||||
<textarea
|
||||
v-model="newBio"
|
||||
class="bio resize-height"
|
||||
/>
|
||||
<template #default="inputProps">
|
||||
<textarea
|
||||
v-model="newBio"
|
||||
class="bio resize-height"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
/>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<p v-if="role === 'admin' || role === 'moderator'">
|
||||
<Checkbox v-model="showRole">
|
||||
|
@ -35,6 +41,18 @@
|
|||
</template>
|
||||
</Checkbox>
|
||||
</p>
|
||||
<div>
|
||||
<p>{{ $t('settings.birthday.label') }}</p>
|
||||
<input
|
||||
id="birthday"
|
||||
v-model="newBirthday"
|
||||
type="date"
|
||||
class="birthday-input"
|
||||
>
|
||||
<Checkbox v-model="showBirthday">
|
||||
{{ $t('settings.birthday.show_birthday') }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div v-if="maxFields > 0">
|
||||
<p>{{ $t('settings.profile_fields.label') }}</p>
|
||||
<div
|
||||
|
@ -48,10 +66,13 @@
|
|||
hide-emoji-button
|
||||
:suggest="userSuggestor"
|
||||
>
|
||||
<input
|
||||
v-model="newFields[i].name"
|
||||
:placeholder="$t('settings.profile_fields.name')"
|
||||
>
|
||||
<template #default="inputProps">
|
||||
<input
|
||||
v-model="newFields[i].name"
|
||||
:placeholder="$t('settings.profile_fields.name')"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<EmojiInput
|
||||
v-model="newFields[i].value"
|
||||
|
@ -59,10 +80,13 @@
|
|||
hide-emoji-button
|
||||
:suggest="userSuggestor"
|
||||
>
|
||||
<input
|
||||
v-model="newFields[i].value"
|
||||
:placeholder="$t('settings.profile_fields.value')"
|
||||
>
|
||||
<template #default="inputProps">
|
||||
<input
|
||||
v-model="newFields[i].value"
|
||||
:placeholder="$t('settings.profile_fields.value')"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<button
|
||||
class="delete-field button-unstyled -hover-highlight"
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
faSignOutAlt,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faBolt,
|
||||
faUserPlus,
|
||||
faBullhorn,
|
||||
faSearch,
|
||||
|
@ -25,7 +25,7 @@ library.add(
|
|||
faSignOutAlt,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faBolt,
|
||||
faUserPlus,
|
||||
faBullhorn,
|
||||
faSearch,
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="bell"
|
||||
icon="bolt"
|
||||
/> {{ $t("nav.interactions") }}
|
||||
</router-link>
|
||||
</li>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import ReplyButton from '../reply_button/reply_button.vue'
|
||||
import QuoteButton from '../quote_button/quote_button.vue'
|
||||
import FavoriteButton from '../favorite_button/favorite_button.vue'
|
||||
import ReactButton from '../react_button/react_button.vue'
|
||||
import RetweetButton from '../retweet_button/retweet_button.vue'
|
||||
|
@ -117,7 +118,8 @@ const Status = {
|
|||
MentionLink,
|
||||
MentionsLine,
|
||||
UserPopover,
|
||||
UserLink
|
||||
UserLink,
|
||||
QuoteButton
|
||||
},
|
||||
props: [
|
||||
'statusoid',
|
||||
|
@ -147,6 +149,8 @@ const Status = {
|
|||
'controlledToggleShowingLongSubject',
|
||||
'controlledReplying',
|
||||
'controlledToggleReplying',
|
||||
'controlledQuoting',
|
||||
'controlledToggleQuoting',
|
||||
'controlledMediaPlaying',
|
||||
'controlledSetMediaPlaying',
|
||||
'dive'
|
||||
|
@ -154,6 +158,7 @@ const Status = {
|
|||
data () {
|
||||
return {
|
||||
uncontrolledReplying: false,
|
||||
uncontrolledQuoting: false,
|
||||
unmuted: false,
|
||||
userExpanded: false,
|
||||
uncontrolledMediaPlaying: [],
|
||||
|
@ -166,7 +171,7 @@ const Status = {
|
|||
swapReacts () {
|
||||
return this.mergedConfig.swapReacts
|
||||
},
|
||||
...controlledOrUncontrolledGetters(['replying', 'mediaPlaying']),
|
||||
...controlledOrUncontrolledGetters(['replying', 'quoting', 'mediaPlaying']),
|
||||
muteWords () {
|
||||
return this.mergedConfig.muteWords
|
||||
},
|
||||
|
@ -428,6 +433,9 @@ const Status = {
|
|||
toggleReplying () {
|
||||
controlledOrUncontrolledToggle(this, 'replying')
|
||||
},
|
||||
toggleQuoting () {
|
||||
controlledOrUncontrolledToggle(this, 'quoting')
|
||||
},
|
||||
gotoOriginal (id) {
|
||||
if (this.inConversation) {
|
||||
this.$emit('goto', id)
|
||||
|
|
|
@ -101,6 +101,10 @@
|
|||
|
||||
.status-heading {
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
.emoji {
|
||||
--emoji-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.heading-name-row {
|
||||
|
@ -357,6 +361,15 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.quote-form {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.quote-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.favs-repeated-users {
|
||||
margin-top: var(--status-margin, $status-margin);
|
||||
}
|
||||
|
|
|
@ -447,6 +447,12 @@
|
|||
:status="status"
|
||||
@toggle="toggleReplying"
|
||||
/>
|
||||
<quote-button
|
||||
:visibility="status.visibility"
|
||||
:quoting="quoting"
|
||||
:status="status"
|
||||
@toggle="toggleQuoting"
|
||||
/>
|
||||
<retweet-button
|
||||
:visibility="status.visibility"
|
||||
:logged-in="loggedIn"
|
||||
|
@ -509,6 +515,20 @@
|
|||
@posted="toggleReplying"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="quoting"
|
||||
class="status-container quote-form"
|
||||
>
|
||||
<PostStatusForm
|
||||
class="quote-body"
|
||||
:quote-id="status.id"
|
||||
:attentions="[status.user]"
|
||||
:replied-user="status.user"
|
||||
:copy-message-scope="status.visibility"
|
||||
:subject="replySubject"
|
||||
@posted="toggleQuoting"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
faLink,
|
||||
faPollH
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import Select from 'src/components/select/select.vue'
|
||||
|
||||
library.add(
|
||||
faFile,
|
||||
|
@ -37,7 +38,8 @@ const StatusContent = {
|
|||
data () {
|
||||
return {
|
||||
postLength: this.status.text.length,
|
||||
parseReadyDone: false
|
||||
parseReadyDone: false,
|
||||
translateFrom: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -78,10 +80,14 @@ const StatusContent = {
|
|||
attachmentTypes () {
|
||||
return this.status.attachments.map(file => fileType.fileType(file.mimetype))
|
||||
},
|
||||
translationLanguages () {
|
||||
return (this.$store.getters.mergedConfig.supportedTranslationLanguages || []).map(lang => ({ key: lang.code, value: lang.code, label: lang.name }))
|
||||
},
|
||||
...mapGetters(['mergedConfig'])
|
||||
},
|
||||
components: {
|
||||
RichContent
|
||||
RichContent,
|
||||
Select
|
||||
},
|
||||
mounted () {
|
||||
this.status.attentions && this.status.attentions.forEach(attn => {
|
||||
|
@ -124,6 +130,10 @@ const StatusContent = {
|
|||
},
|
||||
generateTagLink (tag) {
|
||||
return `/tag/${tag}`
|
||||
},
|
||||
translateStatus () {
|
||||
const translateTo = this.$store.getters.mergedConfig.translationLanguage || this.$store.state.instance.interfaceLanguage
|
||||
this.$store.dispatch('translateStatus', { id: this.status.id, language: translateTo, from: this.translateFrom })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,13 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.translation {
|
||||
border: 1px solid var(--accent, $fallback--link);
|
||||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
margin-top: 1em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
--_still_image-label-scale: 0.5;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,44 @@
|
|||
:attentions="status.attentions"
|
||||
@parseReady="onParseReady"
|
||||
/>
|
||||
<div
|
||||
v-if="status.translation"
|
||||
class="translation"
|
||||
>
|
||||
<h4>{{ $t('status.translated_from', { language: status.translation.detected_language }) }}</h4>
|
||||
<RichContent
|
||||
:class="{ '-single-line': singleLine }"
|
||||
class="text media-body"
|
||||
:html="status.translation.text"
|
||||
:emoji="status.emojis"
|
||||
:handle-links="true"
|
||||
:mfm="renderMisskeyMarkdown && (status.media_type === 'text/x.misskeymarkdown')"
|
||||
:greentext="mergedConfig.greentext"
|
||||
:attentions="status.attentions"
|
||||
@parseReady="onParseReady"
|
||||
/>
|
||||
<div>
|
||||
<label class="label">{{ $t('status.override_translation_source_language') }}</label>
|
||||
{{ ' ' }}
|
||||
<Select
|
||||
id="source-language-switcher"
|
||||
v-model="translateFrom"
|
||||
class="preset-switcher"
|
||||
>
|
||||
<option
|
||||
v-for="language in translationLanguages"
|
||||
:key="language.key"
|
||||
:value="language.value"
|
||||
>
|
||||
{{ language.label }}
|
||||
</option>
|
||||
</Select>
|
||||
{{ ' ' }}
|
||||
<button @click="translateStatus" class="btn button-default">
|
||||
{{ $t('status.translate') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-show="hideSubjectStatus"
|
||||
|
|
|
@ -3,6 +3,7 @@ import Poll from '../poll/poll.vue'
|
|||
import Gallery from '../gallery/gallery.vue'
|
||||
import StatusBody from 'src/components/status_body/status_body.vue'
|
||||
import LinkPreview from '../link-preview/link-preview.vue'
|
||||
import QuoteCard from '../quote_card/quote_card.vue'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
@ -109,7 +110,8 @@ const StatusContent = {
|
|||
Poll,
|
||||
Gallery,
|
||||
LinkPreview,
|
||||
StatusBody
|
||||
StatusBody,
|
||||
QuoteCard
|
||||
},
|
||||
methods: {
|
||||
toggleShowingTall () {
|
||||
|
|
|
@ -41,7 +41,14 @@
|
|||
@play="$emit('mediaplay', attachment.id)"
|
||||
@pause="$emit('mediapause', attachment.id)"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="status.quote && !compact"
|
||||
class="quote"
|
||||
>
|
||||
<QuoteCard
|
||||
:status="status.quote"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="status.card && !noHeading && !compact"
|
||||
class="link-preview media-body"
|
||||
|
@ -63,4 +70,10 @@
|
|||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.quote-inline,
|
||||
.quote + .link-preview {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -19,7 +19,8 @@ export const timelineNames = () => {
|
|||
bookmarks: 'nav.bookmarks',
|
||||
dms: 'nav.dms',
|
||||
'public-timeline': 'nav.public_tl',
|
||||
'public-external-timeline': 'nav.twkn'
|
||||
'public-external-timeline': 'nav.twkn',
|
||||
'bubble-timeline': 'nav.bubble_timeline'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
padding: 0 0.65em;
|
||||
height: 3.5em;
|
||||
line-height: 3.5em;
|
||||
padding-bottom: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: $fallback--lightBg;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { unitToSeconds } from 'src/services/date_utils/date_utils.js'
|
||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||
import RemoteFollow from '../remote_follow/remote_follow.vue'
|
||||
import ProgressButton from '../progress_button/progress_button.vue'
|
||||
|
@ -8,6 +9,7 @@ import UserNote from '../user_note/user_note.vue'
|
|||
import Select from '../select/select.vue'
|
||||
import UserLink from '../user_link/user_link.vue'
|
||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
|
@ -46,7 +48,10 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
followRequestInProgress: false,
|
||||
betterShadow: this.$store.state.interface.browserSupport.cssFilter
|
||||
betterShadow: this.$store.state.interface.browserSupport.cssFilter,
|
||||
showingConfirmMute: false,
|
||||
muteExpiryAmount: 0,
|
||||
muteExpiryUnit: 'minutes'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -137,6 +142,12 @@ export default {
|
|||
supportsNote () {
|
||||
return 'note' in this.relationship
|
||||
},
|
||||
shouldConfirmMute () {
|
||||
return this.mergedConfig.modalOnMute
|
||||
},
|
||||
muteExpiryUnits () {
|
||||
return ['minutes', 'hours', 'days']
|
||||
},
|
||||
...mapGetters(['mergedConfig'])
|
||||
},
|
||||
components: {
|
||||
|
@ -149,11 +160,29 @@ export default {
|
|||
Select,
|
||||
RichContent,
|
||||
UserLink,
|
||||
UserNote
|
||||
UserNote,
|
||||
ConfirmModal
|
||||
},
|
||||
methods: {
|
||||
showConfirmMute () {
|
||||
this.showingConfirmMute = true
|
||||
},
|
||||
hideConfirmMute () {
|
||||
this.showingConfirmMute = false
|
||||
},
|
||||
muteUser () {
|
||||
this.$store.dispatch('muteUser', this.user.id)
|
||||
if (!this.shouldConfirmMute) {
|
||||
this.doMuteUser()
|
||||
} else {
|
||||
this.showConfirmMute()
|
||||
}
|
||||
},
|
||||
doMuteUser () {
|
||||
this.$store.dispatch('muteUser', {
|
||||
id: this.user.id,
|
||||
expiresIn: this.shouldConfirmMute ? unitToSeconds(this.muteExpiryUnit, this.muteExpiryAmount) : 0
|
||||
})
|
||||
this.hideConfirmMute()
|
||||
},
|
||||
unmuteUser () {
|
||||
this.$store.dispatch('unmuteUser', this.user.id)
|
||||
|
|
|
@ -357,3 +357,8 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mute-expiry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
|
|
@ -321,6 +321,53 @@
|
|||
:handle-links="true"
|
||||
/>
|
||||
</div>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmMute"
|
||||
:title="$t('user_card.mute_confirm_title')"
|
||||
:confirm-text="$t('user_card.mute_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.mute_confirm_cancel_button')"
|
||||
@accepted="doMuteUser"
|
||||
@cancelled="hideConfirmMute"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="user_card.mute_confirm"
|
||||
tag="div"
|
||||
>
|
||||
<template #user>
|
||||
<span
|
||||
v-text="user.screen_name_ui"
|
||||
/>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<div
|
||||
class="mute-expiry"
|
||||
>
|
||||
<label>
|
||||
{{ $t('user_card.mute_duration_prompt') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="muteExpiryAmount"
|
||||
type="number"
|
||||
class="expiry-amount hide-number-spinner"
|
||||
:min="0"
|
||||
>
|
||||
<Select
|
||||
v-model="muteExpiryUnit"
|
||||
unstyled="true"
|
||||
class="expiry-unit"
|
||||
>
|
||||
<option
|
||||
v-for="unit in muteExpiryUnits"
|
||||
:key="unit"
|
||||
:value="unit"
|
||||
>
|
||||
{{ $t(`time.${unit}_short`, ['']) }}
|
||||
</option>
|
||||
</Select>
|
||||
</div>
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -7,13 +7,16 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
|||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||
import List from '../list/list.vue'
|
||||
import withLoadMore from '../../hocs/with_load_more/with_load_more'
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faCircleNotch
|
||||
faCircleNotch,
|
||||
faBirthdayCake
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faCircleNotch
|
||||
faCircleNotch,
|
||||
faBirthdayCake
|
||||
)
|
||||
|
||||
const FollowerList = withLoadMore({
|
||||
|
@ -76,6 +79,10 @@ const UserProfile = {
|
|||
},
|
||||
followersTabVisible () {
|
||||
return this.isUs || !this.user.hide_followers
|
||||
},
|
||||
formattedBirthday () {
|
||||
const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
|
||||
return this.user.birthday && new Date(Date.parse(this.user.birthday)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -12,6 +12,16 @@
|
|||
rounded="top"
|
||||
:has-note-editor="true"
|
||||
/>
|
||||
<span
|
||||
v-if="!!user.birthday"
|
||||
class="user-birthday"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-old-padding"
|
||||
icon="birthday-cake"
|
||||
/>
|
||||
{{ $t('user_card.birthday', { birthday: formattedBirthday }) }}
|
||||
</span>
|
||||
<div
|
||||
v-if="user.fields_html && user.fields_html.length > 0"
|
||||
class="user-profile-fields"
|
||||
|
@ -149,6 +159,10 @@
|
|||
// No sticky header on user profile
|
||||
--currentPanelStack: 1;
|
||||
|
||||
.user-birthday {
|
||||
margin: 0 0.75em 0.5em;
|
||||
}
|
||||
|
||||
.user-profile-fields {
|
||||
margin: 0 0.5em;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"about": {
|
||||
"bubble_instances": "Local Bubble Instances",
|
||||
"bubble_instances_description": "Instances chosen by the admins to represent the local area of this instance",
|
||||
"mrf": {
|
||||
"federation": "Federation",
|
||||
"keyword": {
|
||||
|
@ -137,6 +139,10 @@
|
|||
"login": "Log in",
|
||||
"description": "Log in with OAuth",
|
||||
"logout": "Log out",
|
||||
"logout_confirm_title": "Logout confirmation",
|
||||
"logout_confirm": "Do you really want to logout?",
|
||||
"logout_confirm_accept_button": "Logout",
|
||||
"logout_confirm_cancel_button": "Do not logout",
|
||||
"password": "Password",
|
||||
"placeholder": "e.g. lain",
|
||||
"register": "Register",
|
||||
|
@ -166,12 +172,18 @@
|
|||
"interactions": "Interactions",
|
||||
"dms": "Direct messages",
|
||||
"public_tl": "Public timeline",
|
||||
"public_timeline_description": "Public posts from this instance",
|
||||
"timeline": "Timeline",
|
||||
"home_timeline": "Home timeline",
|
||||
"home_timeline_description": "Posts from people you follow",
|
||||
"bubble_timeline": "Bubble timeline",
|
||||
"bubble_timeline_description": "Posts from instances close to yours, as recommended by the admin(s)",
|
||||
"twkn": "Known Network",
|
||||
"twkn_timeline_description": "Posts from the entire network",
|
||||
"bookmarks": "Bookmarks",
|
||||
"user_search": "User Search",
|
||||
"search": "Search",
|
||||
"search_close": "Close search bar",
|
||||
"who_to_follow": "Who to follow",
|
||||
"preferences": "Preferences",
|
||||
"timelines": "Timelines",
|
||||
|
@ -266,6 +278,7 @@
|
|||
"text/markdown": "Markdown",
|
||||
"text/bbcode": "BBCode"
|
||||
},
|
||||
"content_type_selection": "Post format",
|
||||
"content_warning": "Subject (optional)",
|
||||
"default": "Garbage Inserted",
|
||||
"direct_warning_to_all": "This post will be visible to all the mentioned users.",
|
||||
|
@ -283,6 +296,7 @@
|
|||
"private": "This post will be visible to your followers only",
|
||||
"unlisted": "This post will not be visible in Public Timeline and The Whole Known Network"
|
||||
},
|
||||
"scope_notice_dismiss": "Close this notice",
|
||||
"scope": {
|
||||
"direct": "Direct - post to mentioned users only",
|
||||
"private": "Followers-only - post to followers only",
|
||||
|
@ -312,9 +326,13 @@
|
|||
"email_required": "cannot be left blank",
|
||||
"password_required": "cannot be left blank",
|
||||
"password_confirmation_required": "cannot be left blank",
|
||||
"password_confirmation_match": "should be the same as password"
|
||||
"password_confirmation_match": "should be the same as password",
|
||||
"birthday_required": "cannot be left blank",
|
||||
"birthday_min_age": "must be on or before {date}"
|
||||
},
|
||||
"email_language": "In which language do you want to receive emails from the server?"
|
||||
"email_language": "In which language do you want to receive emails from the server?",
|
||||
"birthday": "Birthday:",
|
||||
"birthday_optional": "Birthday (optional):"
|
||||
},
|
||||
"remote_user_resolver": {
|
||||
"remote_user_resolver": "Remote user resolver",
|
||||
|
@ -335,6 +353,10 @@
|
|||
"select_all": "Select all"
|
||||
},
|
||||
"settings": {
|
||||
"add_language": "Add fallback language",
|
||||
"remove_language": "Remove",
|
||||
"primary_language": "Primary language:",
|
||||
"fallback_language": "Fallback language {index}:",
|
||||
"app_name": "App name",
|
||||
"expert_mode": "Show advanced",
|
||||
"save": "Save changes",
|
||||
|
@ -416,6 +438,16 @@
|
|||
"composing": "Composing",
|
||||
"confirm_new_password": "Confirm new password",
|
||||
"current_password": "Current password",
|
||||
"confirm_dialogs": "Ask for confirmation when",
|
||||
"confirm_dialogs_repeat": "repeating a status",
|
||||
"confirm_dialogs_unfollow": "unfollowing a user",
|
||||
"confirm_dialogs_block": "blocking a user",
|
||||
"confirm_dialogs_mute": "muting a user",
|
||||
"confirm_dialogs_delete": "deleting a status",
|
||||
"confirm_dialogs_logout": "logging out",
|
||||
"confirm_dialogs_approve_follow": "approving a follower",
|
||||
"confirm_dialogs_deny_follow": "denying a follower",
|
||||
"confirm_dialogs_remove_follower": "removing a follower",
|
||||
"mutes_and_blocks": "Mutes and Blocks",
|
||||
"data_import_export_tab": "Data import / export",
|
||||
"default_vis": "Default visibility scope",
|
||||
|
@ -464,7 +496,6 @@
|
|||
"hide_all_muted_posts": "Hide muted posts",
|
||||
"max_thumbnails": "Maximum amount of thumbnails per post (empty = no limit)",
|
||||
"hide_isp": "Hide instance-specific panel",
|
||||
"show_third_column": "Move Notifications to a seperate column",
|
||||
"hide_shoutbox": "Hide instance frothbox",
|
||||
"compact_nav_panel": "Compact navigation panel",
|
||||
"compact_user_panel": "Compact user panel",
|
||||
|
@ -475,6 +506,8 @@
|
|||
"preload_images": "Preload images",
|
||||
"use_one_click_nsfw": "Open NSFW attachments with just one click",
|
||||
"hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
|
||||
"hide_site_favicon": "Hide instance favicon in top panel",
|
||||
"hide_site_name": "Hide instance name in top panel",
|
||||
"hide_user_stats": "Hide user statistics (e.g. the number of followers)",
|
||||
"hide_filtered_statuses": "Hide all filtered posts",
|
||||
"hide_wordfiltered_statuses": "Hide word-filtered statuses",
|
||||
|
@ -515,6 +548,10 @@
|
|||
"name": "Label",
|
||||
"value": "Content"
|
||||
},
|
||||
"birthday": {
|
||||
"label": "Birthday",
|
||||
"show_birthday": "Show my birthday"
|
||||
},
|
||||
"account_privacy": "Privacy",
|
||||
"use_contain_fit": "Don't crop the attachment in thumbnails",
|
||||
"name": "Name",
|
||||
|
@ -541,6 +578,8 @@
|
|||
"hide_followers_count_description": "Don't show follower count",
|
||||
"show_admin_badge": "Show \"Admin\" badge in my profile",
|
||||
"show_moderator_badge": "Show \"Moderator\" badge in my profile",
|
||||
"show_nav_shortcuts": "Show extra navigation shortcuts in top panel",
|
||||
"show_wider_shortcuts": "Show wider gap between top panel shortcuts",
|
||||
"nsfw_clickthrough": "Hide sensitive/NSFW media",
|
||||
"oauth_tokens": "OAuth tokens",
|
||||
"token": "Token",
|
||||
|
@ -619,6 +658,7 @@
|
|||
"theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
|
||||
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
|
||||
"tooltipRadius": "Tooltips/alerts",
|
||||
"translation_language": "Automatic Translation Language",
|
||||
"type_domains_to_mute": "Search domains to mute",
|
||||
"upload_a_photo": "Upload a photo",
|
||||
"user_settings": "User Settings",
|
||||
|
@ -836,7 +876,7 @@
|
|||
"conversation": "Conversation",
|
||||
"error": "Error fetching timeline: {0}",
|
||||
"load_older": "Load older statuses",
|
||||
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
|
||||
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated or quoted",
|
||||
"repeated": "repeated",
|
||||
"show_new": "Show new",
|
||||
"reload": "Reload",
|
||||
|
@ -851,6 +891,10 @@
|
|||
"status": {
|
||||
"favorites": "Favorites",
|
||||
"repeats": "Repeats",
|
||||
"repeat_confirm": "Do you really want to repeat this status?",
|
||||
"repeat_confirm_title": "Repeat confirmation",
|
||||
"repeat_confirm_accept_button": "Repeat",
|
||||
"repeat_confirm_cancel_button": "Do not repeat",
|
||||
"delete": "Delete status",
|
||||
"edit": "Edit status",
|
||||
"edited_at": "(last edited {time})",
|
||||
|
@ -858,8 +902,13 @@
|
|||
"unpin": "Unpin from profile",
|
||||
"pinned": "Pinned",
|
||||
"bookmark": "Bookmark",
|
||||
"translate": "Translate",
|
||||
"translated_from": "Translated from {language}",
|
||||
"unbookmark": "Unbookmark",
|
||||
"delete_confirm": "Do you really want to delete this status?",
|
||||
"delete_confirm_title": "Delete confirmation",
|
||||
"delete_confirm_accept_button": "Delete",
|
||||
"delete_confirm_cancel_button": "Keep",
|
||||
"reply_to": "Reply to",
|
||||
"mentions": "Mentions",
|
||||
"replies_list": "Replies:",
|
||||
|
@ -906,11 +955,23 @@
|
|||
},
|
||||
"user_card": {
|
||||
"approve": "Approve",
|
||||
"approve_confirm_title": "Approve confirmation",
|
||||
"approve_confirm_accept_button": "Approve",
|
||||
"approve_confirm_cancel_button": "Do not approve",
|
||||
"approve_confirm": "Do you want to approve {user}'s follow request?",
|
||||
"block": "Block",
|
||||
"blocked": "Blocked!",
|
||||
"block_confirm_title": "Block confirmation",
|
||||
"block_confirm": "Do you really want to block {user}?",
|
||||
"block_confirm_accept_button": "Block",
|
||||
"block_confirm_cancel_button": "Do not block",
|
||||
"blocks_you": "Blocks you!",
|
||||
"deactivated": "Deactivated",
|
||||
"deny": "Deny",
|
||||
"deny_confirm_title": "Deny confirmation",
|
||||
"deny_confirm_accept_button": "Deny",
|
||||
"deny_confirm_cancel_button": "Do not deny",
|
||||
"deny_confirm": "Do you want to deny {user}'s follow request?",
|
||||
"edit_profile": "Edit profile",
|
||||
"favorites": "Favorites",
|
||||
"follow": "Follow",
|
||||
|
@ -918,6 +979,10 @@
|
|||
"follow_sent": "Request sent!",
|
||||
"follow_progress": "Requesting…",
|
||||
"follow_unfollow": "Unfollow",
|
||||
"unfollow_confirm_title": "Unfollow confirmation",
|
||||
"unfollow_confirm": "Do you really want to unfollow {user}?",
|
||||
"unfollow_confirm_accept_button": "Unfollow",
|
||||
"unfollow_confirm_cancel_button": "Do not unfollow",
|
||||
"followees": "Following",
|
||||
"followers": "Followers",
|
||||
"following": "Following!",
|
||||
|
@ -929,9 +994,18 @@
|
|||
"message": "Message",
|
||||
"mute": "Mute",
|
||||
"muted": "Muted",
|
||||
"mute_confirm_title": "Mute confirmation",
|
||||
"mute_confirm": "Do you really want to mute {user}?",
|
||||
"mute_confirm_accept_button": "Mute",
|
||||
"mute_confirm_cancel_button": "Do not mute",
|
||||
"mute_duration_prompt": "Mute this user for (0 for indefinite time):",
|
||||
"per_day": "per day",
|
||||
"remote_follow": "Remote follow",
|
||||
"remove_follower": "Remove follower",
|
||||
"remove_follower_confirm_title": "Remove follower confirmation",
|
||||
"remove_follower_confirm_accept_button": "Remove",
|
||||
"remove_follower_confirm_cancel_button": "Keep",
|
||||
"remove_follower_confirm": "Do you really want to remove {user} from your followers?",
|
||||
"report": "Report",
|
||||
"statuses": "Statuses",
|
||||
"subscribe": "Subscribe",
|
||||
|
@ -945,6 +1019,7 @@
|
|||
"hide_repeats": "Hide repeats",
|
||||
"show_repeats": "Show repeats",
|
||||
"bot": "Bot",
|
||||
"birthday": "Born {birthday}",
|
||||
"admin_menu": {
|
||||
"moderation": "Moderation",
|
||||
"grant_admin": "Grant Admin",
|
||||
|
@ -996,6 +1071,7 @@
|
|||
},
|
||||
"tool_tip": {
|
||||
"media_upload": "Upload media",
|
||||
"quote": "Quote",
|
||||
"repeat": "Repeat",
|
||||
"reply": "Reply",
|
||||
"favorite": "Favorite",
|
||||
|
@ -1005,7 +1081,8 @@
|
|||
"reject_follow_request": "Reject follow request",
|
||||
"bookmark": "Bookmark",
|
||||
"toggle_expand": "Expand or collapse notification to show post in full",
|
||||
"toggle_mute": "Expand or collapse notification to reveal muted content"
|
||||
"toggle_mute": "Expand or collapse notification to reveal muted content",
|
||||
"autocomplete_available": "{number} result is available. Use up and down keys to navigate through them. | {number} results are available. Use up and down keys to navigate through them."
|
||||
},
|
||||
"upload": {
|
||||
"error": {
|
||||
|
|
|
@ -90,8 +90,12 @@
|
|||
"interactions": "インタラクション",
|
||||
"dms": "ダイレクトメッセージ",
|
||||
"public_tl": "公開タイムライン",
|
||||
"public_timeline_description": "このインスタンスからの公開投稿",
|
||||
"timeline": "タイムライン",
|
||||
"twkn": "すべてのネットワーク",
|
||||
"twkn_timeline_description": "全連合からの投稿",
|
||||
"bubble_timeline": "バブルタイムライン",
|
||||
"bubble_timeline_description": "管理者がおすすめしているインスタンスからの投稿",
|
||||
"user_search": "ユーザーを探す",
|
||||
"search": "検索",
|
||||
"who_to_follow": "おすすめユーザー",
|
||||
|
@ -100,7 +104,9 @@
|
|||
"bookmarks": "ブックマーク",
|
||||
"timelines": "タイムライン",
|
||||
"chats": "チャット",
|
||||
"home_timeline": "ホームタイムライン"
|
||||
"home_timeline": "ホームタイムライン",
|
||||
"home_timeline_description": "フォローしているユーザーからの投稿",
|
||||
"announcements": "お知らせ"
|
||||
},
|
||||
"notifications": {
|
||||
"broken_favorite": "ステータスが見つかりません。探しています…",
|
||||
|
@ -597,199 +603,179 @@
|
|||
"backup_settings": "設定をファイルにバックアップする",
|
||||
"backup_restore": "設定をバックアップ"
|
||||
},
|
||||
"save": "変更を保存",
|
||||
"hide_shoutbox": "Shoutboxを表示しない",
|
||||
"always_show_post_button": "投稿ボタンを常に表示",
|
||||
"right_sidebar": "サイドバーを右に表示"
|
||||
},
|
||||
"time": {
|
||||
"day": "{0}日",
|
||||
"days": "{0}日",
|
||||
"day_short": "{0}日",
|
||||
"days_short": "{0}日",
|
||||
"hour": "{0}時間",
|
||||
"hours": "{0}時間",
|
||||
"hour_short": "{0}時間",
|
||||
"hours_short": "{0}時間",
|
||||
"in_future": "{0}で",
|
||||
"in_past": "{0}前",
|
||||
"minute": "{0}分",
|
||||
"minutes": "{0}分",
|
||||
"minute_short": "{0}分",
|
||||
"minutes_short": "{0}分",
|
||||
"month": "{0}ヶ月前",
|
||||
"months": "{0}ヶ月前",
|
||||
"month_short": "{0}ヶ月前",
|
||||
"months_short": "{0}ヶ月前",
|
||||
"now": "たった今",
|
||||
"now_short": "たった今",
|
||||
"second": "{0}秒",
|
||||
"seconds": "{0}秒",
|
||||
"second_short": "{0}秒",
|
||||
"seconds_short": "{0}秒",
|
||||
"week": "{0}週間",
|
||||
"weeks": "{0}週間",
|
||||
"week_short": "{0}週間",
|
||||
"weeks_short": "{0}週間",
|
||||
"year": "{0}年",
|
||||
"years": "{0}年",
|
||||
"year_short": "{0}年",
|
||||
"years_short": "{0}年"
|
||||
},
|
||||
"timeline": {
|
||||
"collapse": "たたむ",
|
||||
"conversation": "スレッド",
|
||||
"error_fetching": "読み込みがエラーになりました",
|
||||
"load_older": "古いステータス",
|
||||
"no_retweet_hint": "投稿を「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
|
||||
"repeated": "リピート",
|
||||
"show_new": "読み込み",
|
||||
"up_to_date": "最新",
|
||||
"no_more_statuses": "これで終わりです",
|
||||
"no_statuses": "ステータスはありません",
|
||||
"reload": "再読み込み",
|
||||
"error": "タイムラインの読み込みに失敗しました: {0}",
|
||||
"socket_reconnected": "リアルタイム接続が確立されました",
|
||||
"socket_broke": "コード{0}によりリアルタイム接続が切断されました"
|
||||
},
|
||||
"status": {
|
||||
"favorites": "お気に入り",
|
||||
"repeats": "リピート",
|
||||
"delete": "ステータスを削除",
|
||||
"pin": "プロフィールにピン留め",
|
||||
"unpin": "プロフィールのピン留めを外す",
|
||||
"pinned": "ピン留め",
|
||||
"delete_confirm": "本当にこのステータスを削除してもよろしいですか?",
|
||||
"reply_to": "返信",
|
||||
"replies_list": "返信:",
|
||||
"mute_conversation": "スレッドをミュート",
|
||||
"unmute_conversation": "スレッドのミュートを解除",
|
||||
"nsfw": "閲覧注意",
|
||||
"expand": "広げる",
|
||||
"status_deleted": "この投稿は削除されました",
|
||||
"hide_content": "隠す",
|
||||
"show_content": "見る",
|
||||
"hide_full_subject": "隠す",
|
||||
"show_full_subject": "全部見る",
|
||||
"thread_muted_and_words": "以下の単語を含むため:",
|
||||
"thread_muted": "ミュートされたスレッド",
|
||||
"external_source": "外部ソース",
|
||||
"copy_link": "リンクをコピー",
|
||||
"status_unavailable": "利用できません",
|
||||
"unbookmark": "ブックマーク解除",
|
||||
"bookmark": "ブックマーク",
|
||||
"mentions": "メンション",
|
||||
"you": "(あなた)",
|
||||
"plus_more": "ほか{number}件"
|
||||
},
|
||||
"user_card": {
|
||||
"approve": "受け入れ",
|
||||
"block": "ブロック",
|
||||
"blocked": "ブロックしています!",
|
||||
"deny": "お断り",
|
||||
"favorites": "お気に入り",
|
||||
"follow": "フォロー",
|
||||
"follow_sent": "リクエストを送りました!",
|
||||
"follow_progress": "リクエストしています…",
|
||||
"follow_unfollow": "フォローをやめる",
|
||||
"followees": "フォロー",
|
||||
"followers": "フォロワー",
|
||||
"following": "フォローしています!",
|
||||
"follows_you": "フォローされました!",
|
||||
"its_you": "これはあなたです!",
|
||||
"media": "メディア",
|
||||
"mention": "メンション",
|
||||
"mute": "ミュート",
|
||||
"muted": "ミュートしています",
|
||||
"per_day": "/日",
|
||||
"remote_follow": "リモートフォロー",
|
||||
"report": "通報",
|
||||
"statuses": "ステータス",
|
||||
"subscribe": "購読",
|
||||
"unsubscribe": "購読を解除",
|
||||
"unblock": "ブロック解除",
|
||||
"unblock_progress": "ブロックを解除しています…",
|
||||
"block_progress": "ブロックしています…",
|
||||
"unmute": "ミュート解除",
|
||||
"unmute_progress": "ミュートを解除しています…",
|
||||
"mute_progress": "ミュートしています…",
|
||||
"admin_menu": {
|
||||
"moderation": "モデレーション",
|
||||
"grant_admin": "管理者権限を付与",
|
||||
"revoke_admin": "管理者権限を解除",
|
||||
"grant_moderator": "モデレーター権限を付与",
|
||||
"revoke_moderator": "モデレーター権限を解除",
|
||||
"activate_account": "アカウントをアクティブにする",
|
||||
"deactivate_account": "アカウントをアクティブでなくする",
|
||||
"delete_account": "アカウントを削除",
|
||||
"force_nsfw": "すべての投稿をNSFWにする",
|
||||
"strip_media": "投稿からメディアを除去する",
|
||||
"force_unlisted": "投稿を未収載にする",
|
||||
"sandbox": "投稿をフォロワーのみにする",
|
||||
"disable_remote_subscription": "他のインスタンスからフォローされないようにする",
|
||||
"disable_any_subscription": "フォローされないようにする",
|
||||
"quarantine": "他のインスタンスからの投稿を止める",
|
||||
"delete_user": "ユーザーを削除"
|
||||
"status": {
|
||||
"bookmark": "ブックマーク",
|
||||
"copy_link": "リンクをコピー",
|
||||
"delete": "ステータスを削除",
|
||||
"delete_confirm": "本当にこのステータスを削除してもよろしいですか?",
|
||||
"expand": "広げる",
|
||||
"external_source": "外部ソース",
|
||||
"favorites": "お気に入り",
|
||||
"hide_content": "隠す",
|
||||
"hide_full_subject": "隠す",
|
||||
"mentions": "メンション",
|
||||
"mute_conversation": "スレッドをミュート",
|
||||
"nsfw": "閲覧注意",
|
||||
"pin": "プロフィールにピン留め",
|
||||
"pinned": "ピン留め",
|
||||
"plus_more": "ほか{number}件",
|
||||
"repeats": "リピート",
|
||||
"replies_list": "返信:",
|
||||
"reply_to": "返信",
|
||||
"show_content": "見る",
|
||||
"show_full_subject": "全部見る",
|
||||
"status_deleted": "この投稿は削除されました",
|
||||
"status_unavailable": "利用できません",
|
||||
"thread_muted": "ミュートされたスレッド",
|
||||
"thread_muted_and_words": "以下の単語を含むため:",
|
||||
"unbookmark": "ブックマーク解除",
|
||||
"unmute_conversation": "スレッドのミュートを解除",
|
||||
"unpin": "プロフィールのピン留めを外す",
|
||||
"you": "(あなた)"
|
||||
},
|
||||
"roles": {
|
||||
"moderator": "モデレーター",
|
||||
"admin": "管理者"
|
||||
"time": {
|
||||
"in_future": "{0}で",
|
||||
"in_past": "{0}前",
|
||||
"now": "たった今",
|
||||
"now_short": "たった今",
|
||||
"unit": {
|
||||
"days": "{0}日",
|
||||
"days_short": "{0}日",
|
||||
"hours": "{0}時間",
|
||||
"hours_short": "{0}時間",
|
||||
"minutes": "{0}分",
|
||||
"minutes_short": "{0}分",
|
||||
"months": "{0}ヶ月",
|
||||
"months_short": "{0}ヶ月",
|
||||
"seconds": "{0}秒",
|
||||
"seconds_short": "{0}秒",
|
||||
"weeks": "{0}週間",
|
||||
"weeks_short": "{0}週間",
|
||||
"years": "{0}年",
|
||||
"years_short": "{0}年"
|
||||
}
|
||||
},
|
||||
"show_repeats": "リピートを見る",
|
||||
"hide_repeats": "リピートを隠す",
|
||||
"message": "メッセージ",
|
||||
"hidden": "隠す",
|
||||
"bot": "bot",
|
||||
"highlight": {
|
||||
"solid": "背景を単色にする",
|
||||
"striped": "背景を縞模様にする",
|
||||
"side": "端に線を付ける",
|
||||
"disabled": "強調しない"
|
||||
"timeline": {
|
||||
"collapse": "たたむ",
|
||||
"conversation": "スレッド",
|
||||
"error": "タイムラインの読み込みに失敗しました: {0}",
|
||||
"load_older": "古いステータス",
|
||||
"no_more_statuses": "これで終わりです",
|
||||
"no_retweet_hint": "投稿を「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります",
|
||||
"no_statuses": "ステータスはありません",
|
||||
"reload": "再読み込み",
|
||||
"repeated": "リピート",
|
||||
"show_new": "読み込み",
|
||||
"socket_broke": "コード{0}によりリアルタイム接続が切断されました",
|
||||
"socket_reconnected": "リアルタイム接続が確立されました",
|
||||
"up_to_date": "最新"
|
||||
},
|
||||
"edit_profile": "プロフィールを編集"
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "ユーザータイムライン",
|
||||
"profile_does_not_exist": "申し訳ない。このプロフィールは存在しません。",
|
||||
"profile_loading_error": "申し訳ない。プロフィールの読み込みがエラーになりました。"
|
||||
},
|
||||
"user_reporting": {
|
||||
"title": "通報する: {0}",
|
||||
"add_comment_description": "この通報は、あなたのインスタンスのモデレーターに送られます。このアカウントを通報する理由を説明することができます:",
|
||||
"additional_comments": "追加のコメント",
|
||||
"forward_description": "このアカウントは他のサーバーに置かれています。この通報のコピーをリモートのサーバーに送りますか?",
|
||||
"forward_to": "転送する: {0}",
|
||||
"submit": "送信",
|
||||
"generic_error": "あなたのリクエストを処理しようとしましたが、エラーになりました。"
|
||||
},
|
||||
"who_to_follow": {
|
||||
"more": "詳細",
|
||||
"who_to_follow": "おすすめユーザー"
|
||||
},
|
||||
"tool_tip": {
|
||||
"media_upload": "メディアをアップロード",
|
||||
"repeat": "リピート",
|
||||
"reply": "返信",
|
||||
"favorite": "お気に入り",
|
||||
"user_settings": "ユーザー設定",
|
||||
"bookmark": "ブックマーク",
|
||||
"reject_follow_request": "フォローリクエストを拒否",
|
||||
"accept_follow_request": "フォローリクエストを許可",
|
||||
"add_reaction": "リアクションを追加"
|
||||
},
|
||||
"upload": {
|
||||
"error": {
|
||||
"base": "アップロードに失敗しました。",
|
||||
"file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
|
||||
"default": "しばらくしてから試してください",
|
||||
"message": "アップロードに失敗: {0}"
|
||||
"tool_tip": {
|
||||
"accept_follow_request": "フォローリクエストを許可",
|
||||
"add_reaction": "リアクションを追加",
|
||||
"bookmark": "ブックマーク",
|
||||
"favorite": "お気に入り",
|
||||
"media_upload": "メディアをアップロード",
|
||||
"reject_follow_request": "フォローリクエストを拒否",
|
||||
"repeat": "リピート",
|
||||
"reply": "返信",
|
||||
"user_settings": "ユーザー設定"
|
||||
},
|
||||
"file_size_units": {
|
||||
"B": "B",
|
||||
"KiB": "KiB",
|
||||
"MiB": "MiB",
|
||||
"GiB": "GiB",
|
||||
"TiB": "TiB"
|
||||
"upload": {
|
||||
"error": {
|
||||
"base": "アップロードに失敗しました。",
|
||||
"default": "しばらくしてから試してください",
|
||||
"file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
|
||||
"message": "アップロードに失敗: {0}"
|
||||
},
|
||||
"file_size_units": {
|
||||
"B": "B",
|
||||
"GiB": "GiB",
|
||||
"KiB": "KiB",
|
||||
"MiB": "MiB",
|
||||
"TiB": "TiB"
|
||||
}
|
||||
},
|
||||
"user_card": {
|
||||
"admin_menu": {
|
||||
"activate_account": "アカウントをアクティブにする",
|
||||
"deactivate_account": "アカウントをアクティブでなくする",
|
||||
"delete_account": "アカウントを削除",
|
||||
"delete_user": "ユーザーを削除",
|
||||
"disable_any_subscription": "フォローされないようにする",
|
||||
"disable_remote_subscription": "他のインスタンスからフォローされないようにする",
|
||||
"force_nsfw": "すべての投稿をNSFWにする",
|
||||
"force_unlisted": "投稿を未収載にする",
|
||||
"grant_admin": "管理者権限を付与",
|
||||
"grant_moderator": "モデレーター権限を付与",
|
||||
"moderation": "モデレーション",
|
||||
"quarantine": "他のインスタンスからの投稿を止める",
|
||||
"revoke_admin": "管理者権限を解除",
|
||||
"revoke_moderator": "モデレーター権限を解除",
|
||||
"sandbox": "投稿をフォロワーのみにする",
|
||||
"strip_media": "投稿からメディアを除去する"
|
||||
},
|
||||
"approve": "受け入れ",
|
||||
"block": "ブロック",
|
||||
"block_progress": "ブロックしています…",
|
||||
"blocked": "ブロックしています!",
|
||||
"bot": "bot",
|
||||
"deny": "お断り",
|
||||
"edit_profile": "プロフィールを編集",
|
||||
"favorites": "お気に入り",
|
||||
"follow": "フォロー",
|
||||
"follow_progress": "リクエストしています…",
|
||||
"follow_sent": "リクエストを送りました!",
|
||||
"follow_unfollow": "フォローをやめる",
|
||||
"followees": "フォロー",
|
||||
"followers": "フォロワー",
|
||||
"following": "フォローしています!",
|
||||
"follows_you": "フォローされました!",
|
||||
"hidden": "隠す",
|
||||
"hide_repeats": "リピートを隠す",
|
||||
"highlight": {
|
||||
"disabled": "強調しない",
|
||||
"side": "端に線を付ける",
|
||||
"solid": "背景を単色にする",
|
||||
"striped": "背景を縞模様にする"
|
||||
},
|
||||
"its_you": "これはあなたです!",
|
||||
"media": "メディア",
|
||||
"mention": "メンション",
|
||||
"message": "メッセージ",
|
||||
"mute": "ミュート",
|
||||
"mute_progress": "ミュートしています…",
|
||||
"muted": "ミュートしています",
|
||||
"note": "私的なメモ",
|
||||
"per_day": "/日",
|
||||
"remote_follow": "リモートフォロー",
|
||||
"report": "通報",
|
||||
"show_repeats": "リピートを見る",
|
||||
"statuses": "ステータス",
|
||||
"subscribe": "購読",
|
||||
"unblock": "ブロック解除",
|
||||
"unblock_progress": "ブロックを解除しています…",
|
||||
"unmute": "ミュート解除",
|
||||
"unmute_progress": "ミュートを解除しています…",
|
||||
"unsubscribe": "購読を解除"
|
||||
},
|
||||
"user_profile": {
|
||||
"profile_does_not_exist": "申し訳ない。このプロフィールは存在しません。",
|
||||
"profile_loading_error": "申し訳ない。プロフィールの読み込みがエラーになりました。",
|
||||
"timeline_title": "ユーザータイムライン"
|
||||
},
|
||||
"user_reporting": {
|
||||
"add_comment_description": "この通報は、あなたのインスタンスのモデレーターに送られます。このアカウントを通報する理由を説明することができます:",
|
||||
"additional_comments": "追加のコメント",
|
||||
"forward_description": "このアカウントは他のサーバーに置かれています。この通報のコピーをリモートのサーバーに送りますか?",
|
||||
"forward_to": "転送する: {0}",
|
||||
"generic_error": "あなたのリクエストを処理しようとしましたが、エラーになりました。",
|
||||
"submit": "送信",
|
||||
"title": "通報する: {0}"
|
||||
},
|
||||
"who_to_follow": {
|
||||
"more": "詳細",
|
||||
"who_to_follow": "おすすめユーザー"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
// sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json
|
||||
// There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
|
||||
|
||||
import { isEqual } from 'lodash'
|
||||
import { languages, langCodeToJsonName } from './languages.js'
|
||||
|
||||
const ULTIMATE_FALLBACK_LOCALE = 'en'
|
||||
|
||||
const hasLanguageFile = (code) => languages.includes(code)
|
||||
|
||||
const loadLanguageFile = (code) => {
|
||||
|
@ -25,11 +28,26 @@ const messages = {
|
|||
en: require('./en.json').default
|
||||
},
|
||||
setLanguage: async (i18n, language) => {
|
||||
if (hasLanguageFile(language)) {
|
||||
const messages = await loadLanguageFile(language)
|
||||
i18n.setLocaleMessage(language, messages.default)
|
||||
const languages = (Array.isArray(language) ? language : [language]).filter(k => k)
|
||||
|
||||
if (!languages.includes(ULTIMATE_FALLBACK_LOCALE)) {
|
||||
languages.push(ULTIMATE_FALLBACK_LOCALE)
|
||||
}
|
||||
i18n.locale = language
|
||||
const [first, ...rest] = languages
|
||||
|
||||
if (first === i18n.locale && isEqual(rest, i18n.fallbackLocale)) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const lang of languages) {
|
||||
if (hasLanguageFile(lang)) {
|
||||
const messages = await loadLanguageFile(lang)
|
||||
i18n.setLocaleMessage(lang, messages.default)
|
||||
}
|
||||
}
|
||||
|
||||
i18n.fallbackLocale = rest
|
||||
i18n.locale = first
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -270,6 +270,12 @@ const api = {
|
|||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: 'lists', fetcher })
|
||||
},
|
||||
getSupportedTranslationlanguages (store) {
|
||||
store.state.backendInteractor.getSupportedTranslationlanguages({ store })
|
||||
.then((data) => {
|
||||
store.dispatch('setOption', { name: 'supportedTranslationLanguages', value: data })
|
||||
})
|
||||
},
|
||||
|
||||
// Pleroma websocket
|
||||
setWsToken (store, token) {
|
||||
|
|
|
@ -40,6 +40,10 @@ export const defaultState = {
|
|||
muteBotStatuses: undefined, // instance default
|
||||
collapseMessageWithSubject: undefined, // instance default
|
||||
padEmoji: true,
|
||||
showNavShortcuts: undefined, // instance default
|
||||
showWiderShortcuts: undefined, // instance default
|
||||
hideSiteFavicon: undefined, // instance default
|
||||
hideSiteName: undefined, // instance default
|
||||
swapReacts: true,
|
||||
hideAttachments: false,
|
||||
hideAttachmentsInConv: false,
|
||||
|
@ -82,6 +86,15 @@ export const defaultState = {
|
|||
minimalScopesMode: undefined, // instance default
|
||||
// This hides statuses filtered via a word filter
|
||||
hideFilteredStatuses: undefined, // instance default
|
||||
modalOnRepeat: undefined, // instance default
|
||||
modalOnUnfollow: undefined, // instance default
|
||||
modalOnBlock: undefined, // instance default
|
||||
modalOnMute: undefined, // instance default
|
||||
modalOnDelete: undefined, // instance default
|
||||
modalOnLogout: undefined, // instance default
|
||||
modalOnApproveFollow: undefined, // instance default
|
||||
modalOnDenyFollow: undefined, // instance default
|
||||
modalOnRemoveUserFromFollowers: undefined, // instance default
|
||||
playVideosInModal: false,
|
||||
useOneClickNsfw: false,
|
||||
useContainFit: true,
|
||||
|
@ -110,7 +123,9 @@ export const defaultState = {
|
|||
conversationTreeAdvanced: undefined, // instance default
|
||||
conversationOtherRepliesButton: undefined, // instance default
|
||||
conversationTreeFadeAncestors: undefined, // instance default
|
||||
maxDepthInThread: undefined // instance default
|
||||
maxDepthInThread: undefined, // instance default
|
||||
translationLanguage: undefined, // instance default,
|
||||
supportedTranslationLanguages: [] // instance default
|
||||
}
|
||||
|
||||
// caching the instance default properties
|
||||
|
@ -188,7 +203,11 @@ const config = {
|
|||
case 'interfaceLanguage':
|
||||
messages.setLanguage(this.getters.i18n, value)
|
||||
dispatch('loadUnicodeEmojiData', value)
|
||||
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
|
||||
Cookies.set(
|
||||
BACKEND_LANGUAGE_COOKIE_NAME,
|
||||
localeService.internalToBackendLocaleMulti(value)
|
||||
)
|
||||
dispatch('setInstanceOption', { name: 'interfaceLanguage', value })
|
||||
break
|
||||
case 'thirdColumnMode':
|
||||
dispatch('setLayoutWidth', undefined)
|
||||
|
|
|
@ -68,9 +68,19 @@ const defaultState = {
|
|||
hideWordFilteredPosts: false,
|
||||
hidePostStats: false,
|
||||
hideBotIndication: false,
|
||||
hideSitename: false,
|
||||
hideSiteFavicon: false,
|
||||
hideSiteName: false,
|
||||
hideUserStats: false,
|
||||
muteBotStatuses: false,
|
||||
modalOnRepeat: false,
|
||||
modalOnUnfollow: false,
|
||||
modalOnBlock: true,
|
||||
modalOnMute: false,
|
||||
modalOnDelete: true,
|
||||
modalOnLogout: true,
|
||||
modalOnApproveFollow: false,
|
||||
modalOnDenyFollow: false,
|
||||
modalOnRemoveUserFromFollowers: false,
|
||||
loginMethod: 'password',
|
||||
logo: '/static/logo.svg',
|
||||
logoMargin: '.2em',
|
||||
|
@ -85,6 +95,8 @@ const defaultState = {
|
|||
scopeCopy: true,
|
||||
showFeaturesPanel: true,
|
||||
showInstanceSpecificPanel: false,
|
||||
showNavShortcuts: true,
|
||||
showWiderShortcuts: true,
|
||||
sidebarRight: false,
|
||||
subjectLineBehavior: 'email',
|
||||
theme: 'pleroma-dark',
|
||||
|
@ -107,6 +119,8 @@ const defaultState = {
|
|||
restrictedNicknames: [],
|
||||
safeDM: true,
|
||||
knownDomains: [],
|
||||
birthdayRequired: false,
|
||||
birthdayMinAge: 0,
|
||||
|
||||
// Feature-set, apparently, not everything here is reported...
|
||||
shoutAvailable: false,
|
||||
|
@ -286,8 +300,13 @@ const instance = {
|
|||
langList
|
||||
.map(async lang => {
|
||||
if (!state.unicodeEmojiAnnotations[lang]) {
|
||||
const annotations = await loadAnnotations(lang)
|
||||
commit('setUnicodeEmojiAnnotations', { lang, annotations })
|
||||
try {
|
||||
const annotations = await loadAnnotations(lang)
|
||||
commit('setUnicodeEmojiAnnotations', { lang, annotations })
|
||||
} catch (e) {
|
||||
console.warn(`Error loading unicode emoji annotations for ${lang}: `, e)
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}))
|
||||
},
|
||||
|
|
|
@ -63,7 +63,8 @@ export const defaultState = () => ({
|
|||
tag: emptyTl(),
|
||||
dms: emptyTl(),
|
||||
bookmarks: emptyTl(),
|
||||
list: emptyTl()
|
||||
list: emptyTl(),
|
||||
bubble: emptyTl()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -432,6 +433,10 @@ export const mutations = {
|
|||
state.conversationsObject[newStatus.statusnet_conversation_id].forEach(status => { status.thread_muted = newStatus.thread_muted })
|
||||
}
|
||||
},
|
||||
setTranslatedStatus (state, { id, translation }) {
|
||||
const newStatus = state.allStatusesObject[id]
|
||||
newStatus.translation = translation
|
||||
},
|
||||
setRetweeted (state, { status, value }) {
|
||||
const newStatus = state.allStatusesObject[status.id]
|
||||
|
||||
|
@ -650,6 +655,10 @@ const statuses = {
|
|||
rootState.api.backendInteractor.unpinOwnStatus({ id: statusId })
|
||||
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
||||
},
|
||||
translateStatus ({ rootState, commit }, { id, translation, language, from }) {
|
||||
return rootState.api.backendInteractor.translateStatus({ id: id, translation, language, from })
|
||||
.then((translation) => commit('setTranslatedStatus', { id, translation }))
|
||||
},
|
||||
muteConversation ({ rootState, commit }, statusId) {
|
||||
return rootState.api.backendInteractor.muteConversation({ id: statusId })
|
||||
.then((status) => commit('setMutedStatus', status))
|
||||
|
|
|
@ -61,13 +61,16 @@ const editUserNote = (store, { id, comment }) => {
|
|||
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
||||
}
|
||||
|
||||
const muteUser = (store, id) => {
|
||||
const muteUser = (store, args) => {
|
||||
const id = typeof args === 'object' ? args.id : args
|
||||
const expiresIn = typeof args === 'object' ? args.expiresIn : 0
|
||||
|
||||
const predictedRelationship = store.state.relationships[id] || { id }
|
||||
predictedRelationship.muting = true
|
||||
store.commit('updateUserRelationship', [predictedRelationship])
|
||||
store.commit('addMuteId', id)
|
||||
|
||||
return store.rootState.api.backendInteractor.muteUser({ id })
|
||||
return store.rootState.api.backendInteractor.muteUser({ id, expiresIn })
|
||||
.then((relationship) => {
|
||||
store.commit('updateUserRelationship', [relationship])
|
||||
store.commit('addMuteId', id)
|
||||
|
|
|
@ -11,11 +11,11 @@ const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
|
|||
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
||||
const MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
|
||||
const ALIASES_URL = '/api/pleroma/aliases'
|
||||
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
|
||||
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
|
||||
const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
|
||||
const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
|
||||
const ADMIN_USERS_URL = '/api/pleroma/admin/users'
|
||||
const TAG_USER_URL = '/api/v1/pleroma/admin/users/tag'
|
||||
const PERMISSION_GROUP_URL = (screenName, right) => `/api/v1/pleroma/admin/users/${screenName}/permission_group/${right}`
|
||||
const ACTIVATE_USER_URL = '/api/v1/pleroma/admin/users/activate'
|
||||
const DEACTIVATE_USER_URL = '/api/v1/pleroma/admin/users/deactivate'
|
||||
const ADMIN_USERS_URL = '/api/v1/pleroma/admin/users'
|
||||
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
||||
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
|
||||
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
|
||||
|
@ -31,6 +31,8 @@ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
|
|||
const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
|
||||
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
|
||||
const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
|
||||
const AKKOMA_LANGUAGES_URL = '/api/v1/akkoma/translation/languages'
|
||||
const AKKOMA_TRANSLATE_URL = (id, lang) => `/api/v1/statuses/${id}/translations/${lang}`
|
||||
const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss`
|
||||
const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
|
||||
const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
|
||||
|
@ -47,6 +49,7 @@ const MASTODON_DENY_USER_URL = id => `/api/v1/follow_requests/${id}/reject`
|
|||
const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct'
|
||||
const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public'
|
||||
const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home'
|
||||
const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble'
|
||||
const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}`
|
||||
const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context`
|
||||
const MASTODON_STATUS_SOURCE_URL = id => `/api/v1/statuses/${id}/source`
|
||||
|
@ -101,7 +104,7 @@ const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}`
|
|||
const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages`
|
||||
const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
|
||||
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
|
||||
const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports'
|
||||
const PLEROMA_ADMIN_REPORTS = '/api/v1/pleroma/admin/reports'
|
||||
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
|
||||
const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
|
||||
const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
|
||||
|
@ -675,6 +678,7 @@ const fetchTimeline = ({
|
|||
}) => {
|
||||
const timelineUrls = {
|
||||
public: MASTODON_PUBLIC_TIMELINE,
|
||||
bubble: AKKOMA_BUBBLE_TIMELINE_URL,
|
||||
friends: MASTODON_USER_HOME_TIMELINE_URL,
|
||||
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
|
||||
notifications: MASTODON_USER_NOTIFICATIONS_URL,
|
||||
|
@ -797,6 +801,18 @@ const unretweet = ({ id, credentials }) => {
|
|||
.then((data) => parseStatus(data))
|
||||
}
|
||||
|
||||
const getSupportedTranslationlanguages = ({ credentials }) => {
|
||||
return promisedRequest({ url: AKKOMA_LANGUAGES_URL, credentials })
|
||||
}
|
||||
|
||||
const translateStatus = ({ id, credentials, language, from }) => {
|
||||
const queryString = from ? `?from=${from}` : ''
|
||||
return promisedRequest({ url: AKKOMA_TRANSLATE_URL(id, language) + queryString, method: 'GET', credentials })
|
||||
.then((data) => {
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
const bookmarkStatus = ({ id, credentials }) => {
|
||||
return promisedRequest({
|
||||
url: MASTODON_BOOKMARK_STATUS_URL(id),
|
||||
|
@ -822,6 +838,7 @@ const postStatus = ({
|
|||
poll,
|
||||
mediaIds = [],
|
||||
inReplyToStatusId,
|
||||
quoteId,
|
||||
contentType,
|
||||
preview,
|
||||
idempotencyKey
|
||||
|
@ -854,6 +871,9 @@ const postStatus = ({
|
|||
if (inReplyToStatusId) {
|
||||
form.append('in_reply_to_id', inReplyToStatusId)
|
||||
}
|
||||
if (quoteId) {
|
||||
form.append('quote_id', quoteId)
|
||||
}
|
||||
if (preview) {
|
||||
form.append('preview', 'true')
|
||||
}
|
||||
|
@ -1118,8 +1138,12 @@ const fetchMutes = ({ credentials }) => {
|
|||
.then((users) => users.map(parseUser))
|
||||
}
|
||||
|
||||
const muteUser = ({ id, credentials }) => {
|
||||
return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST' })
|
||||
const muteUser = ({ id, expiresIn, credentials }) => {
|
||||
const payload = {}
|
||||
if (expiresIn) {
|
||||
payload.expires_in = expiresIn
|
||||
}
|
||||
return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST', payload })
|
||||
}
|
||||
|
||||
const unmuteUser = ({ id, credentials }) => {
|
||||
|
@ -1480,7 +1504,15 @@ export const ProcessedWS = ({
|
|||
}
|
||||
socket.addEventListener('open', (wsEvent) => {
|
||||
console.debug(`[WS][${id}] Socket connected`, wsEvent)
|
||||
setInterval(() => {
|
||||
try {
|
||||
socket.send('ping')
|
||||
} catch (e) {
|
||||
clearInterval(this)
|
||||
}
|
||||
}, 30000)
|
||||
})
|
||||
|
||||
socket.addEventListener('error', (wsEvent) => {
|
||||
console.debug(`[WS][${id}] Socket errored`, wsEvent)
|
||||
})
|
||||
|
@ -1768,7 +1800,9 @@ const apiService = {
|
|||
postAnnouncement,
|
||||
editAnnouncement,
|
||||
deleteAnnouncement,
|
||||
adminFetchAnnouncements
|
||||
adminFetchAnnouncements,
|
||||
translateStatus,
|
||||
getSupportedTranslationlanguages
|
||||
}
|
||||
|
||||
export default apiService
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { kebabCase } from 'lodash'
|
||||
|
||||
const propsToNative = props => Object.keys(props).reduce((acc, cur) => {
|
||||
acc[kebabCase(cur)] = props[cur]
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
export { propsToNative }
|
|
@ -35,6 +35,10 @@ const backendInteractorService = credentials => ({
|
|||
return ProcessedWS({ url, id: 'User' })
|
||||
},
|
||||
|
||||
getSupportedTranslationlanguages ({ store }) {
|
||||
return apiService.getSupportedTranslationlanguages({ store, credentials })
|
||||
},
|
||||
|
||||
...Object.entries(apiService).reduce((acc, [key, func]) => {
|
||||
return {
|
||||
...acc,
|
||||
|
|
|
@ -41,3 +41,19 @@ export const relativeTimeShort = (date, nowThreshold = 1) => {
|
|||
r.key += '_short'
|
||||
return r
|
||||
}
|
||||
|
||||
export const unitToSeconds = (unit, amount) => {
|
||||
switch (unit) {
|
||||
case 'minutes': return 0.001 * amount * MINUTE
|
||||
case 'hours': return 0.001 * amount * HOUR
|
||||
case 'days': return 0.001 * amount * DAY
|
||||
}
|
||||
}
|
||||
|
||||
export const secondsToUnit = (unit, amount) => {
|
||||
switch (unit) {
|
||||
case 'minutes': return (1000 * amount) / MINUTE
|
||||
case 'hours': return (1000 * amount) / HOUR
|
||||
case 'days': return (1000 * amount) / DAY
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,8 @@ export const parseUser = (data) => {
|
|||
output.role = 'member'
|
||||
}
|
||||
|
||||
output.birthday = data.pleroma.birthday
|
||||
|
||||
if (data.pleroma.privileges) {
|
||||
output.privileges = data.pleroma.privileges
|
||||
} else if (data.pleroma.is_admin) {
|
||||
|
@ -162,6 +164,7 @@ export const parseUser = (data) => {
|
|||
output.no_rich_text = data.source.pleroma.no_rich_text
|
||||
output.show_role = data.source.pleroma.show_role
|
||||
output.discoverable = data.source.pleroma.discoverable
|
||||
output.show_birthday = data.pleroma.show_birthday
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,6 +392,9 @@ export const parseStatus = (data) => {
|
|||
output.visibility = data.visibility
|
||||
output.card = data.card
|
||||
output.created_at = new Date(data.created_at)
|
||||
if (data.quote) {
|
||||
output.quote = parseStatus(data.quote)
|
||||
}
|
||||
|
||||
// Converting to string, the right way.
|
||||
output.in_reply_to_status_id = output.in_reply_to_status_id
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue