Merge remote-tracking branch 'origin/develop' into shigusegubu-vue3
* origin/develop: (79 commits) Fix display of theme checkboxes Make suggestor ignore users without valid names Give tab switcher a role Remove @touchstart in post status button Update babel monorepo to v7.21.0 Make it possible to auto-select the first candidate in autocomplete Fix poll interaction Fix registration error with email language selected Add changelog for 2.5.1 Translated using Weblate (Chinese (Traditional)) Translated using Weblate (Ukrainian) Translated using Weblate (Japanese (ja_EASY)) Translated using Weblate (Chinese (Simplified)) Translated using Weblate (Chinese (Simplified)) Translated using Weblate (Chinese (Simplified)) Use class to style screenreader-only text Update Font Awesome Update dependency nightwatch to v2.6.11 Update dependency eslint to v8.33.0 Update dependency @vue/test-utils to v2.2.8 ...
This commit is contained in:
commit
085cbc62e7
82 changed files with 2920 additions and 481 deletions
28
CHANGELOG.md
28
CHANGELOG.md
|
@ -3,6 +3,34 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## 2.5.1
|
||||
### Fixed
|
||||
- Checkboxes in settings can now work with screenreaders
|
||||
- Autocomplete in edit boxes can now work with screenreaders
|
||||
- Status interact buttons now have focus indicator for anonymous users
|
||||
- Top bar buttons now correctly have text labels
|
||||
- It is now possible to register if the site admin requires birthday to register
|
||||
- User cards from search results will correctly popup
|
||||
- Fix notification attachment icon overflow
|
||||
- Editing mute words is less laggy
|
||||
- Repeater's name will no longer mess up with the directionality of the text sitting on the same line
|
||||
- Unauthenticated access will give better error messages
|
||||
- It is now easier to close the media viewer with a mouse when there is only one image
|
||||
- Deleting profile fields can work properly
|
||||
- Clicking the react button will correctly focus the search box
|
||||
- Clicking buttons on the top-bar will no longer bring you to the top of the page
|
||||
- Emoji picker is much faster to load
|
||||
- `blockquote`s have a better display style
|
||||
- Announcements posting and editing are now available to everyone with such a privilege, not just admins
|
||||
- Adding or removing list members will actually work
|
||||
- Emojis without a pack are now correctly displayed in emoji picker
|
||||
- Changing notification settings will actually work
|
||||
|
||||
### Added
|
||||
- You can now set and see birthdays
|
||||
- Optional confirmation dialogs when performing various actions
|
||||
- You can now set fallback languages
|
||||
|
||||
## 2.5.0 - 23.12.2022
|
||||
### Fixed
|
||||
- UI no longer lags when switching between mobile and desktop mode
|
||||
|
|
|
@ -25,7 +25,17 @@ This could be a bit trickier, you basically need steps 1-4 from *develop build*
|
|||
|
||||
### Replacing your instance's frontend with custom FE build
|
||||
|
||||
This is the most easiest way to use and test FE build: you just need to copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder.
|
||||
#### New way (via AdminFE, a bit janky but works)
|
||||
|
||||
In backend's [static directory](../backend/configuration/static_dir.md) there should be a folder called `frontends` if you installed any frontends from AdminFE before, otherwise you can create it yourself (ensuring correct permissions). Backend will serve given frontend from path `frontends/{frontend}/{reference}`, where `{frontend}` is name of frontend (`pleroma-fe`) and `{reference}` is version. You could make a production build, move `dist` folder into `frontends/pleroma-fe` and rename it into something like `myCustomVersion`. To actually make backend serve this frontend by default, in AdminFE you'll need to set name/reference in Settings -> Frontend -> Frontends -> Primary.
|
||||
|
||||
You could also install from a zip file (i.e. CI build) but AdminFE UI is a bit buggy and lacking, so this approach is not recommended.
|
||||
|
||||
Take note that frontend management is in early development and currently there's no way for user to change frontend or version for themselves, primary frontend becomes default frontend for all users and visitors.
|
||||
|
||||
#### Old way (replaces everything, hard to maintain, not recommended)
|
||||
|
||||
Copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder, and this could remove emojis, other frontends etc. and therefore this approach is not recommended.
|
||||
|
||||
### Running production build locally or on a separate server
|
||||
|
||||
|
|
|
@ -14,6 +14,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 -->
|
||||
<div id="popovers" />
|
||||
</body>
|
||||
|
|
30
package.json
30
package.json
|
@ -16,12 +16,12 @@
|
|||
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.20.7",
|
||||
"@babel/runtime": "7.21.0",
|
||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
||||
"@fortawesome/vue-fontawesome": "3.0.2",
|
||||
"@fortawesome/fontawesome-svg-core": "6.3.0",
|
||||
"@fortawesome/free-regular-svg-icons": "6.3.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.3.0",
|
||||
"@fortawesome/vue-fontawesome": "3.0.3",
|
||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
||||
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
||||
"@ruffle-rs/ruffle": "0.1.0-nightly.2022.7.12",
|
||||
|
@ -36,7 +36,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",
|
||||
|
@ -49,19 +49,19 @@
|
|||
"vuex": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.20.7",
|
||||
"@babel/core": "7.21.0",
|
||||
"@babel/eslint-parser": "7.19.1",
|
||||
"@babel/plugin-transform-runtime": "7.19.6",
|
||||
"@babel/plugin-transform-runtime": "7.21.0",
|
||||
"@babel/preset-env": "7.20.2",
|
||||
"@babel/register": "7.18.9",
|
||||
"@babel/register": "7.21.0",
|
||||
"@intlify/vue-i18n-loader": "5.0.0",
|
||||
"@ungap/event-target": "0.2.3",
|
||||
"@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",
|
||||
|
@ -72,13 +72,13 @@
|
|||
"css-loader": "6.7.3",
|
||||
"css-minimizer-webpack-plugin": "4.2.2",
|
||||
"custom-event-polyfill": "1.0.7",
|
||||
"eslint": "8.31.0",
|
||||
"eslint": "8.33.0",
|
||||
"eslint-config-standard": "17.0.0",
|
||||
"eslint-formatter-friendly": "7.0.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-n": "15.6.1",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-vue": "9.8.0",
|
||||
"eslint-plugin-vue": "9.9.0",
|
||||
"eslint-webpack-plugin": "3.2.0",
|
||||
"eventsource-polyfill": "0.9.6",
|
||||
"express": "4.18.2",
|
||||
|
@ -99,7 +99,7 @@
|
|||
"lodash": "4.17.21",
|
||||
"mini-css-extract-plugin": "2.7.2",
|
||||
"mocha": "10.2.0",
|
||||
"nightwatch": "2.6.6",
|
||||
"nightwatch": "2.6.11",
|
||||
"opn": "5.5.0",
|
||||
"ora": "0.4.1",
|
||||
"postcss": "8.4.20",
|
||||
|
|
14
src/App.scss
14
src/App.scss
|
@ -580,8 +580,6 @@ textarea,
|
|||
}
|
||||
|
||||
&[type="checkbox"] {
|
||||
display: none;
|
||||
|
||||
&:checked + label::before {
|
||||
color: $fallback--text;
|
||||
color: var(--inputText, $fallback--text);
|
||||
|
@ -887,3 +885,15 @@ option {
|
|||
opacity: 0;
|
||||
}
|
||||
/* stylelint-enable no-descending-specificity */
|
||||
|
||||
.visible-for-screenreader-only {
|
||||
display: block;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
visibility: visible;
|
||||
clip: rect(0 0 0 0);
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -5,12 +5,16 @@
|
|||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="visible-for-screenreader-only"
|
||||
:disabled="disabled"
|
||||
:checked="modelValue"
|
||||
:indeterminate="indeterminate"
|
||||
@change="$emit('update:modelValue', $event.target.checked)"
|
||||
>
|
||||
<i class="checkbox-indicator" />
|
||||
<i
|
||||
class="checkbox-indicator"
|
||||
:aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
v-if="!!$slots.default"
|
||||
class="label"
|
||||
|
@ -33,6 +37,7 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
@import "../../variables";
|
||||
@import "../../mixins";
|
||||
|
||||
.checkbox {
|
||||
position: relative;
|
||||
|
@ -81,8 +86,6 @@ export default {
|
|||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
display: none;
|
||||
|
||||
&:checked + .checkbox-indicator::before {
|
||||
color: $fallback--text;
|
||||
color: var(--inputText, $fallback--text);
|
||||
|
|
37
src/components/confirm_modal/confirm_modal.js
Normal file
37
src/components/confirm_modal/confirm_modal.js
Normal file
|
@ -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
|
29
src/components/confirm_modal/confirm_modal.vue
Normal file
29
src/components/confirm_modal/confirm_modal.vue
Normal file
|
@ -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,4 +1,5 @@
|
|||
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,
|
||||
|
@ -30,7 +31,8 @@ library.add(
|
|||
|
||||
export default {
|
||||
components: {
|
||||
SearchBar
|
||||
SearchBar,
|
||||
ConfirmModal
|
||||
},
|
||||
data: () => ({
|
||||
searchBarHidden: true,
|
||||
|
@ -40,7 +42,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 },
|
||||
|
@ -73,15 +76,32 @@ export default {
|
|||
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
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
class="logo"
|
||||
:to="{ name: 'root' }"
|
||||
:style="logoBgStyle"
|
||||
:title="sitename"
|
||||
>
|
||||
<div
|
||||
class="mask"
|
||||
|
@ -38,13 +39,13 @@
|
|||
/>
|
||||
<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 +53,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,7 +51,7 @@
|
|||
margin: 15vh auto;
|
||||
position: fixed;
|
||||
transform: translateX(-50%);
|
||||
z-index: 999;
|
||||
z-index: 2001;
|
||||
cursor: default;
|
||||
display: block;
|
||||
background-color: $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,12 +127,16 @@ const EmojiInput = {
|
|||
components: {
|
||||
Popover,
|
||||
EmojiPicker,
|
||||
UnicodeDomainIndicator
|
||||
UnicodeDomainIndicator,
|
||||
ScreenReaderNotice
|
||||
},
|
||||
computed: {
|
||||
padEmoji () {
|
||||
return this.$store.getters.mergedConfig.padEmoji
|
||||
},
|
||||
defaultCandidateIndex () {
|
||||
return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1
|
||||
},
|
||||
preText () {
|
||||
return this.modelValue.slice(0, this.caret)
|
||||
},
|
||||
|
@ -203,6 +209,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 +290,11 @@ const EmojiInput = {
|
|||
...rest,
|
||||
img: imageUrl || ''
|
||||
}))
|
||||
this.highlighted = this.defaultCandidateIndex
|
||||
this.$refs.screenReaderNotice.announce(
|
||||
this.$tc('tool_tip.autocomplete_available',
|
||||
this.suggestions.length,
|
||||
{ number: this.suggestions.length }))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -374,26 +391,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 +558,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">
|
||||
|
|
|
@ -94,8 +94,9 @@ export const suggestUsers = ({ dispatch, state }) => {
|
|||
|
||||
const newSuggestions = state.users.users.filter(
|
||||
user =>
|
||||
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
||||
user.name.toLowerCase().startsWith(noPrefix)
|
||||
user.screen_name && user.name && (
|
||||
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
||||
user.name.toLowerCase().startsWith(noPrefix))
|
||||
).slice(0, 20).sort((a, b) => {
|
||||
let aScore = 0
|
||||
let bScore = 0
|
||||
|
|
|
@ -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,22 @@ 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
|
||||
},
|
||||
pinStatus () {
|
||||
this.$store.dispatch('pinStatus', this.status.id)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
|
@ -133,7 +149,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -165,6 +165,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>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
:class="{ custom: isCustom }"
|
||||
>
|
||||
<label
|
||||
:id="name + '-label'"
|
||||
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
||||
class="label"
|
||||
>
|
||||
|
@ -12,7 +13,8 @@
|
|||
<input
|
||||
v-if="typeof fallback !== 'undefined'"
|
||||
:id="name + '-o'"
|
||||
class="opt exlcude-disabled"
|
||||
:aria-labelledby="name + '-label'"
|
||||
class="opt exlcude-disabled visible-for-screenreader-only"
|
||||
type="checkbox"
|
||||
:checked="present"
|
||||
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
|
||||
|
@ -21,6 +23,7 @@
|
|||
v-if="typeof fallback !== 'undefined'"
|
||||
class="opt-l"
|
||||
:for="name + '-o'"
|
||||
:aria-hidden="true"
|
||||
/>
|
||||
{{ ' ' }}
|
||||
<Select
|
||||
|
|
|
@ -1,21 +1,44 @@
|
|||
<template>
|
||||
<div>
|
||||
<label for="interface-language-switcher">
|
||||
<div class="interface-language-switcher">
|
||||
<label>
|
||||
{{ 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,7 +57,7 @@ export default {
|
|||
required: true
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
type: [Array, String],
|
||||
required: true
|
||||
},
|
||||
setLanguage: {
|
||||
|
@ -48,7 +71,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 +83,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>
|
||||
|
|
|
@ -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(
|
||||
|
@ -57,7 +60,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 +95,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()
|
||||
|
|
|
@ -88,6 +88,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 +247,16 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-modal.dark-overlay {
|
||||
&::before {
|
||||
z-index: 3000;
|
||||
}
|
||||
|
||||
.dialog-modal.panel {
|
||||
z-index: 3001;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
choices: []
|
||||
choices: [],
|
||||
randomSeed: `${Math.random()}`.replace('.', '-')
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
|
|
@ -4,53 +4,63 @@
|
|||
:class="containerClass"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in options"
|
||||
:key="index"
|
||||
class="poll-option"
|
||||
:role="showResults ? 'section' : (poll.multiple ? 'group' : 'radiogroup')"
|
||||
>
|
||||
<div
|
||||
v-if="showResults"
|
||||
:title="resultTitle(option)"
|
||||
class="option-result"
|
||||
v-for="(option, index) in options"
|
||||
:key="index"
|
||||
class="poll-option"
|
||||
>
|
||||
<div class="option-result-label">
|
||||
<span class="result-percentage">
|
||||
{{ percentageForOption(option.votes_count) }}%
|
||||
</span>
|
||||
<RichContent
|
||||
:html="option.title_html"
|
||||
:handle-links="false"
|
||||
:emoji="emoji"
|
||||
<div
|
||||
v-if="showResults"
|
||||
:title="resultTitle(option)"
|
||||
class="option-result"
|
||||
>
|
||||
<div class="option-result-label">
|
||||
<span class="result-percentage">
|
||||
{{ percentageForOption(option.votes_count) }}%
|
||||
</span>
|
||||
<RichContent
|
||||
:html="option.title_html"
|
||||
:handle-links="false"
|
||||
:emoji="emoji"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="result-fill"
|
||||
:style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="result-fill"
|
||||
:style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
@click="activateOption(index)"
|
||||
>
|
||||
<input
|
||||
v-if="poll.multiple"
|
||||
type="checkbox"
|
||||
:disabled="loading"
|
||||
:value="index"
|
||||
>
|
||||
<input
|
||||
v-else
|
||||
type="radio"
|
||||
:disabled="loading"
|
||||
:value="index"
|
||||
tabindex="0"
|
||||
:role="poll.multiple ? 'checkbox' : 'radio'"
|
||||
:aria-labelledby="`option-vote-${randomSeed}-${index}`"
|
||||
:aria-checked="choices[index]"
|
||||
@click="activateOption(index)"
|
||||
>
|
||||
<label class="option-vote">
|
||||
<RichContent
|
||||
:html="option.title_html"
|
||||
:handle-links="false"
|
||||
:emoji="emoji"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
v-if="poll.multiple"
|
||||
type="checkbox"
|
||||
class="poll-checkbox"
|
||||
:disabled="loading"
|
||||
:value="index"
|
||||
>
|
||||
<input
|
||||
v-else
|
||||
type="radio"
|
||||
:disabled="loading"
|
||||
:value="index"
|
||||
>
|
||||
<label class="option-vote">
|
||||
<RichContent
|
||||
:id="`option-vote-${randomSeed}-${index}`"
|
||||
:html="option.title_html"
|
||||
:handle-links="false"
|
||||
:emoji="emoji"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer faint">
|
||||
|
@ -161,5 +171,9 @@
|
|||
padding: 0 0.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.poll-checkbox {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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'
|
||||
|
@ -629,6 +630,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"
|
||||
|
@ -265,12 +281,10 @@
|
|||
>
|
||||
{{ $t('post_status.post') }}
|
||||
</button>
|
||||
<!-- touchstart is used to keep the OSK at the same position after a message send -->
|
||||
<button
|
||||
v-else
|
||||
:disabled="uploadingFiles || disableSubmit"
|
||||
class="btn button-default"
|
||||
@touchstart.stop.prevent="postStatus($event, newStatus)"
|
||||
@click.stop.prevent="postStatus($event, newStatus)"
|
||||
>
|
||||
{{ $t('post_status.post') }}
|
||||
|
|
|
@ -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') }}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
:class="{ disabled: !present || disabled }"
|
||||
>
|
||||
<label
|
||||
:id="name + '-label'"
|
||||
:for="name"
|
||||
class="label"
|
||||
>
|
||||
|
@ -12,7 +13,8 @@
|
|||
<input
|
||||
v-if="typeof fallback !== 'undefined'"
|
||||
:id="name + '-o'"
|
||||
class="opt"
|
||||
:aria-labelledby="name + '-label'"
|
||||
class="opt visible-for-screenreader-only"
|
||||
type="checkbox"
|
||||
:checked="present"
|
||||
@change="$emit('update:modelValue', !present ? fallback : undefined)"
|
||||
|
@ -21,6 +23,7 @@
|
|||
v-if="typeof fallback !== 'undefined'"
|
||||
class="opt-l"
|
||||
:for="name + '-o'"
|
||||
:aria-hidden="true"
|
||||
/>
|
||||
<input
|
||||
:id="name"
|
||||
|
@ -34,9 +37,10 @@
|
|||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
>
|
||||
<input
|
||||
:id="name"
|
||||
:id="name + '-numeric'"
|
||||
class="input-number"
|
||||
type="number"
|
||||
:aria-labelledby="name + '-label'"
|
||||
:value="modelValue || fallback"
|
||||
:disabled="!present || disabled"
|
||||
:max="hardMax"
|
||||
|
|
|
@ -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,8 +14,9 @@ const registration = {
|
|||
username: '',
|
||||
password: '',
|
||||
confirm: '',
|
||||
birthday: '',
|
||||
reason: '',
|
||||
language: ''
|
||||
language: ['']
|
||||
},
|
||||
captcha: {}
|
||||
}),
|
||||
|
@ -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: {
|
||||
|
@ -72,7 +100,7 @@ const registration = {
|
|||
this.user.captcha_token = this.captcha.token
|
||||
this.user.captcha_answer_data = this.captcha.answer_data
|
||||
if (this.user.language) {
|
||||
this.user.language = localeService.internalToBackendLocale(this.user.language)
|
||||
this.user.language = localeService.internalToBackendLocaleMulti(this.user.language.filter(k => k))
|
||||
}
|
||||
|
||||
this.v$.$touch()
|
||||
|
|
|
@ -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 }"
|
||||
|
@ -176,6 +210,7 @@
|
|||
:prompt-text="$t('registration.email_language')"
|
||||
:language="v$.user.language.$model"
|
||||
:set-language="val => v$.user.language.$model = val"
|
||||
@click.stop.prevent
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
21
src/components/screen_reader_notice/screen_reader_notice.js
Normal file
21
src/components/screen_reader_notice/screen_reader_notice.js
Normal file
|
@ -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
|
10
src/components/screen_reader_notice/screen_reader_notice.vue
Normal file
10
src/components/screen_reader_notice/screen_reader_notice.vue
Normal file
|
@ -0,0 +1,10 @@
|
|||
<template>
|
||||
<div
|
||||
class="visible-for-screenreader-only"
|
||||
:aria-live="ariaLive"
|
||||
>
|
||||
{{ currentText }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./screen_reader_notice.js"></script>
|
|
@ -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 />
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
>
|
||||
<Popover
|
||||
trigger="hover"
|
||||
:trigger-attrs="{ 'aria-label': $t('settings.setting_changed') }"
|
||||
>
|
||||
<template #trigger>
|
||||
|
||||
<FAIcon
|
||||
icon="wrench"
|
||||
:aria-label="$t('settings.setting_changed')"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
|
|
|
@ -148,6 +148,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">
|
||||
|
@ -451,6 +501,14 @@
|
|||
{{ $t('settings.pad_emoji') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="autocompleteSelect"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.autocomplete_select_first') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -129,12 +129,13 @@
|
|||
v-model="selected.inset"
|
||||
:disabled="!present"
|
||||
name="inset"
|
||||
class="input-inset"
|
||||
class="input-inset visible-for-screenreader-only"
|
||||
type="checkbox"
|
||||
>
|
||||
<label
|
||||
class="checkbox-label"
|
||||
for="inset"
|
||||
:aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -117,6 +117,7 @@ export default {
|
|||
onClick={this.clickTab(index)}
|
||||
class={classesTab.join(' ')}
|
||||
type="button"
|
||||
role="tab"
|
||||
>
|
||||
<img src={props.image} title={props['image-tooltip']}/>
|
||||
{props.label ? '' : props.label}
|
||||
|
@ -131,6 +132,7 @@ export default {
|
|||
onClick={this.clickTab(index)}
|
||||
class={classesTab.join(' ')}
|
||||
type="button"
|
||||
role="tab"
|
||||
>
|
||||
{!props.icon ? '' : (<FAIcon class="tab-icon" size="2x" fixed-width icon={props.icon}/>)}
|
||||
<span class="text">
|
||||
|
@ -167,11 +169,15 @@ export default {
|
|||
|
||||
return (
|
||||
<div class={'tab-switcher ' + (this.sideTabBar ? 'side-tabs' : 'top-tabs')}>
|
||||
<div class="tabs">
|
||||
<div
|
||||
class="tabs"
|
||||
role="tablist"
|
||||
>
|
||||
{tabs}
|
||||
</div>
|
||||
<div
|
||||
ref="contents"
|
||||
role="tabpanel"
|
||||
class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}
|
||||
v-body-scroll-lock={this.bodyScrollLock}
|
||||
>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -355,3 +355,8 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mute-expiry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
|
|
@ -314,6 +314,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;
|
||||
|
||||
|
|
|
@ -137,6 +137,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",
|
||||
|
@ -172,6 +176,7 @@
|
|||
"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 +271,7 @@
|
|||
"text/markdown": "Markdown",
|
||||
"text/bbcode": "BBCode"
|
||||
},
|
||||
"content_type_selection": "Post format",
|
||||
"content_warning": "Subject (optional)",
|
||||
"default": "Just landed in L.A.",
|
||||
"direct_warning_to_all": "This post will be visible to all the mentioned users.",
|
||||
|
@ -283,6 +289,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 +319,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 +346,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 +431,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",
|
||||
|
@ -440,6 +465,7 @@
|
|||
"domain_mutes": "Domains",
|
||||
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
|
||||
"pad_emoji": "Pad emoji with spaces when adding from picker",
|
||||
"autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available",
|
||||
"emoji_reactions_on_timeline": "Show emoji reactions on timeline",
|
||||
"export_theme": "Save preset",
|
||||
"filtering": "Filtering",
|
||||
|
@ -510,6 +536,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",
|
||||
|
@ -843,6 +873,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})",
|
||||
|
@ -852,6 +886,9 @@
|
|||
"bookmark": "Bookmark",
|
||||
"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:",
|
||||
|
@ -898,10 +935,22 @@
|
|||
},
|
||||
"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",
|
||||
"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",
|
||||
|
@ -909,6 +958,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!",
|
||||
|
@ -920,9 +973,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",
|
||||
|
@ -936,6 +998,7 @@
|
|||
"hide_repeats": "Hide repeats",
|
||||
"show_repeats": "Show repeats",
|
||||
"bot": "Bot",
|
||||
"birthday": "Born {birthday}",
|
||||
"admin_menu": {
|
||||
"moderation": "Moderation",
|
||||
"grant_admin": "Grant Admin",
|
||||
|
@ -996,7 +1059,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": {
|
||||
|
|
|
@ -603,7 +603,7 @@
|
|||
"use_websockets": "Uzi teĥnikaron «websockets» (tuja ĝisdatigo)",
|
||||
"mention_link_display_full_for_remote": "plene nur je uzantoj foraj (ekz. {'@'}zozo{'@'}ekzemplo.org)",
|
||||
"expert_mode": "Montri altnivelajn",
|
||||
"setting_server_side": "Ĉi tiu agordo estas ligita al via profilo, kaj efektiviĝon en ĉiuj viaj salutoj kaj klientoj",
|
||||
"setting_server_side": "Ĉi tiu agordo estas ligita al via profilo, kaj efektiviĝos en ĉiuj viaj salutoj kaj klientoj",
|
||||
"post_look_feel": "Aspekto de afiŝoj",
|
||||
"mention_links": "Menciaj ligiloj",
|
||||
"email_language": "Lingvo de leteroj ricevotaj de la servilo",
|
||||
|
|
|
@ -17,7 +17,17 @@
|
|||
"media_removal": "メディアをのぞく",
|
||||
"media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:",
|
||||
"media_nsfw": "メディアをすべてセンシティブにする",
|
||||
"media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:"
|
||||
"media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:",
|
||||
"reason": "りゆう",
|
||||
"instance": "インスタンス",
|
||||
"not_applicable": "なし"
|
||||
},
|
||||
"keyword": {
|
||||
"keyword_policies": "キーワードポリシー",
|
||||
"reject": "おことわり",
|
||||
"replace": "おきかえ",
|
||||
"ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく",
|
||||
"is_replaced_by": "→"
|
||||
}
|
||||
},
|
||||
"staff": "スタッフ"
|
||||
|
@ -36,7 +46,10 @@
|
|||
"scope_options": "こうかいはんいせんたく",
|
||||
"text_limit": "もじのかず",
|
||||
"title": "ゆうこうなきのう",
|
||||
"who_to_follow": "おすすめユーザー"
|
||||
"who_to_follow": "おすすめユーザー",
|
||||
"pleroma_chat_messages": "Pleroma チャット",
|
||||
"upload_limit": "アップロードできるファイルのおおきさ",
|
||||
"shout": "Shoutbox"
|
||||
},
|
||||
"finder": {
|
||||
"error_fetching_user": "ユーザーけんさくがエラーになりました",
|
||||
|
@ -54,7 +67,34 @@
|
|||
"disable": "なし",
|
||||
"enable": "あり",
|
||||
"confirm": "たしかめる",
|
||||
"verify": "たしかめる"
|
||||
"verify": "たしかめる",
|
||||
"retry": "もういちど、ためしてください",
|
||||
"loading": "よみこんでいます…",
|
||||
"undo": "もとにもどす",
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"unpin": "ピンどめするのをやめる",
|
||||
"scroll_to_top": "いちばんうえにもどる",
|
||||
"role": {
|
||||
"moderator": "モデレーター",
|
||||
"admin": "かんりするひと"
|
||||
},
|
||||
"flash_security": "Flash コンテンツはどんなコードでもじっこうできるので、あぶないかもしれません。",
|
||||
"flash_fail": "Flash コンテンツをよみこむことに、しっぱいしました。コンソールで、くわしいないようを、よむことができます。",
|
||||
"scope_in_timeline": {
|
||||
"private": "フォロワーげんてい",
|
||||
"public": "パブリック",
|
||||
"unlisted": "アンリステッド",
|
||||
"direct": "ダイレクト"
|
||||
},
|
||||
"pin": "ピンどめする",
|
||||
"flash_content": "Flash コンテンツを、 Ruffle をつかってひょうじする (うごかないかもしれません)。",
|
||||
"generic_error_message": "エラーになりました: {0}",
|
||||
"error_retry": "もういちど、ためしてください",
|
||||
"never_show_again": "にどとひょうじしない",
|
||||
"close": "とじる",
|
||||
"dismiss": "むしする",
|
||||
"peek": "かくす"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop_picture": "がぞうをきりぬく",
|
||||
|
@ -83,11 +123,17 @@
|
|||
"heading": {
|
||||
"totp": "2-ファクターにんしょう",
|
||||
"recovery": "2-ファクターリカバリー"
|
||||
}
|
||||
},
|
||||
"logout_confirm_title": "ログアウトのかくにん",
|
||||
"logout_confirm": "ほんとうに、ログアウトしますか?",
|
||||
"logout_confirm_accept_button": "ログアウトする",
|
||||
"logout_confirm_cancel_button": "ログアウトしない"
|
||||
},
|
||||
"media_modal": {
|
||||
"previous": "まえ",
|
||||
"next": "つぎ"
|
||||
"next": "つぎ",
|
||||
"counter": "{current} / {total}",
|
||||
"hide": "メディアビューアーをとじる"
|
||||
},
|
||||
"nav": {
|
||||
"about": "これはなに?",
|
||||
|
@ -104,7 +150,20 @@
|
|||
"user_search": "ユーザーをさがす",
|
||||
"search": "さがす",
|
||||
"who_to_follow": "おすすめユーザー",
|
||||
"preferences": "せってい"
|
||||
"preferences": "せってい",
|
||||
"home_timeline": "ホームタイムライン",
|
||||
"bookmarks": "ブックマーク",
|
||||
"timelines": "タイムライン",
|
||||
"chats": "チャット",
|
||||
"lists": "リスト",
|
||||
"mobile_notifications": "つうちをひらく (よんでないものがあります)",
|
||||
"mobile_notifications_close": "つうちをとじる",
|
||||
"announcements": "おしらせ",
|
||||
"edit_pinned": "ピンどめをへんしゅう",
|
||||
"search_close": "けんさくバーをとじる",
|
||||
"edit_nav_mobile": "ナビゲーションバーのせっていをかえる",
|
||||
"mobile_sidebar": "モバイルのサイドバーをきりかえる",
|
||||
"edit_finish": "へんしゅうをおわりにする"
|
||||
},
|
||||
"notifications": {
|
||||
"broken_favorite": "ステータスがみつかりません。さがしています…",
|
||||
|
@ -114,21 +173,29 @@
|
|||
"notifications": "つうち",
|
||||
"read": "よんだ!",
|
||||
"repeated_you": "あなたのステータスがリピートされました",
|
||||
"no_more_notifications": "つうちはありません"
|
||||
"no_more_notifications": "つうちはありません",
|
||||
"error": "つうちをとりにいくことに、しっぱいしました: {0}",
|
||||
"follow_request": "あなたをフォローしたいです",
|
||||
"migrated_to": "インスタンスを、ひっこしました",
|
||||
"reacted_with": "{0} でリアクションしました",
|
||||
"poll_ended": "とうひょうが、おわりました",
|
||||
"submitted_report": "つうほうしました"
|
||||
},
|
||||
"polls": {
|
||||
"add_poll": "いれふだをはじめる",
|
||||
"add_poll": "とうひょうをはじめる",
|
||||
"add_option": "オプションをふやす",
|
||||
"option": "オプション",
|
||||
"votes": "いれふだ",
|
||||
"vote": "ふだをいれる",
|
||||
"type": "いれふだのかた",
|
||||
"votes": "ひょう",
|
||||
"vote": "とうひょうする",
|
||||
"type": "とうひょうのけいしき",
|
||||
"single_choice": "ひとつえらぶ",
|
||||
"multiple_choices": "いくつでもえらべる",
|
||||
"expiry": "いれふだのながさ",
|
||||
"expires_in": "いれふだは {0} で、おわります",
|
||||
"expired": "いれふだは {0} まえに、おわりました",
|
||||
"not_enough_options": "ユニークなオプションが、たりません"
|
||||
"expiry": "とうひょうのながさ",
|
||||
"expires_in": "とうひょうは {0} で、おわります",
|
||||
"expired": "とうひょうは {0} まえに、おわりました",
|
||||
"not_enough_options": "ユニークなオプションが、たりません",
|
||||
"people_voted_count": "{count} にんが、とうひょうしました",
|
||||
"votes_count": "{count} ひょう"
|
||||
},
|
||||
"emoji": {
|
||||
"stickers": "ステッカー",
|
||||
|
@ -139,7 +206,19 @@
|
|||
"custom": "カスタムえもじ",
|
||||
"unicode": "ユニコードえもじ",
|
||||
"load_all_hint": "はじめの {saneAmount} このえもじだけがロードされています。すべてのえもじをロードすると、パフォーマンスがわるくなるかもしれません。",
|
||||
"load_all": "すべてのえもじをロード ({emojiAmount} こあります)"
|
||||
"load_all": "すべてのえもじをロード ({emojiAmount} こあります)",
|
||||
"unicode_groups": {
|
||||
"flags": "はた",
|
||||
"activities": "かつどう",
|
||||
"animals-and-nature": "どうぶつ・しぜん",
|
||||
"food-and-drink": "たべもの・のみもの",
|
||||
"objects": "もの",
|
||||
"people-and-body": "ひと・からだ",
|
||||
"smileys-and-emotion": "えがお・きもち",
|
||||
"symbols": "きごう",
|
||||
"travel-and-places": "りょこう・ばしょ"
|
||||
},
|
||||
"regional_indicator": "ばしょをしめすきごう {letter}"
|
||||
},
|
||||
"stickers": {
|
||||
"add_sticker": "ステッカーをふやす"
|
||||
|
@ -147,7 +226,10 @@
|
|||
"interactions": {
|
||||
"favs_repeats": "リピートとおきにいり",
|
||||
"follows": "あたらしいフォロー",
|
||||
"load_older": "ふるいやりとりをみる"
|
||||
"load_older": "ふるいやりとりをみる",
|
||||
"emoji_reactions": "えもじリアクション",
|
||||
"moves": "ユーザーのひっこし",
|
||||
"reports": "つうほう"
|
||||
},
|
||||
"post_status": {
|
||||
"new_status": "とうこうする",
|
||||
|
@ -176,7 +258,18 @@
|
|||
"private": "フォロワーげんてい: フォロワーのみにとどきます",
|
||||
"public": "パブリック: パブリックタイムラインにとどきます",
|
||||
"unlisted": "アンリステッド: パブリックタイムラインにとどきません"
|
||||
}
|
||||
},
|
||||
"media_description_error": "メディアのアップロードにしっぱいしました。もういちどためしてください",
|
||||
"edit_status": "ステータスをへんしゅうする",
|
||||
"media_description": "メディアのせつめい",
|
||||
"content_type_selection": "とうこうのけいしき",
|
||||
"edit_remote_warning": "ほかのリモートインスタンスは、へんしゅうをサポートしていないかもしれません。そして、へんしゅうされたとうこうをうけとることができないかもしれません。",
|
||||
"post": "とうこう",
|
||||
"edit_unsupported_warning": "Pleroma は、メンションやとうひょうのへんしゅうを、サポートしていません。",
|
||||
"preview": "プレビュー",
|
||||
"preview_empty": "なにもありません",
|
||||
"empty_status_error": "とうこうないようを、にゅうりょくしてください",
|
||||
"scope_notice_dismiss": "このつうちをとじる"
|
||||
},
|
||||
"registration": {
|
||||
"bio": "プロフィール",
|
||||
|
@ -196,8 +289,18 @@
|
|||
"email_required": "なにかかいてください",
|
||||
"password_required": "なにかかいてください",
|
||||
"password_confirmation_required": "なにかかいてください",
|
||||
"password_confirmation_match": "パスワードがちがいます"
|
||||
}
|
||||
"password_confirmation_match": "パスワードがちがいます",
|
||||
"birthday_required": "なにかかいてください",
|
||||
"birthday_min_age": "{date} か、それよりまえにしてください"
|
||||
},
|
||||
"reason_placeholder": "このインスタンスでは、ひとがかくにんして、とうろくをうけいれています。\nなぜあなたがとうろくしたいのかを、かんりしているひとに、おしえてください。",
|
||||
"bio_optional": "プロフィール (かかなくてもよい)",
|
||||
"reason": "とうろくするりゆう",
|
||||
"email_optional": "Eメール (かかなくてもよい)",
|
||||
"register": "とうろくする",
|
||||
"email_language": "サーバーからのメールは、なにご(どのことば)がいいですか?",
|
||||
"birthday": "たんじょうび:",
|
||||
"birthday_optional": "たんじょうび (かかなくてもよい):"
|
||||
},
|
||||
"remote_user_resolver": {
|
||||
"remote_user_resolver": "リモートユーザーリゾルバー",
|
||||
|
@ -393,7 +496,24 @@
|
|||
"save_load_hint": "「のこす」オプションをONにすると、テーマをえらんだときとロードしたとき、いまのせっていをのこします。また、テーマをエクスポートするとき、これらのオプションをストアします。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべてのせっていをセーブします。",
|
||||
"reset": "リセット",
|
||||
"clear_all": "すべてクリア",
|
||||
"clear_opacity": "とうめいどをクリア"
|
||||
"clear_opacity": "とうめいどをクリア",
|
||||
"help": {
|
||||
"older_version_imported": "ふるいバージョンのフロントエンドでつくられたファイルをインポートしました。",
|
||||
"snapshot_missing": "ファイルにはテーマのスナップショットがありません。おもっていたみためと、ちがうかもしれません。",
|
||||
"migration_snapshot_ok": "あんぜんのため、テーマのスナップショットがよみこまれました。テーマのデータをよみこむことができます。",
|
||||
"snapshot_source_mismatch": "バージョンがただしくないです。フロントエンドのバージョンをもとにもどしたあと、あたらしくしたことが、りゆうかもしれません。ふるいフロントエンドでテーマをへんこうしていたばあい、ふるいバージョンをつかうのがいいです。そうでないばあい、あたらしいバージョンをつかってください。",
|
||||
"snapshot_present": "テーマのスナップショットをよみこみました。せっていはうわがきされました。かわりに、テーマのじっさいのデータをよみこむことができます。",
|
||||
"fe_upgraded": "フロントエンドといっしょに、テーマエンジンもあたらしくなりました。",
|
||||
"fe_downgraded": "フロントエンドが、まえのバージョンにもどりました。",
|
||||
"migration_napshot_gone": "スナップショットがありません。おぼえているみためと、ちがうかもしれません。",
|
||||
"upgraded_from_v2": "PleromaFEがあたらしくなったので、いままでのみためとすこしちがうかもしれません。",
|
||||
"v2_imported": "ふるいフロントエンドのためのファイルをインポートしました。せっていしたのとは、すこしちがうかもしれません。",
|
||||
"future_version_imported": "あたらしいフロントエンドでつくられたファイルをインポートしました。"
|
||||
},
|
||||
"load_theme": "テーマをよみこむ",
|
||||
"keep_as_is": "そのままにする",
|
||||
"use_snapshot": "ふるいバージョン",
|
||||
"use_source": "あたらしいバージョン"
|
||||
},
|
||||
"common": {
|
||||
"color": "いろ",
|
||||
|
@ -429,7 +549,26 @@
|
|||
"borders": "さかいめ",
|
||||
"buttons": "ボタン",
|
||||
"inputs": "インプットフィールド",
|
||||
"faint_text": "うすいテキスト"
|
||||
"faint_text": "うすいテキスト",
|
||||
"post": "とうこう / プロフィール",
|
||||
"wallpaper": "かべがみ",
|
||||
"icons": "アイコン",
|
||||
"highlight": "よくみえるようにした、ようそ",
|
||||
"pressed": "おしたとき",
|
||||
"chat": {
|
||||
"border": "さかいめ",
|
||||
"incoming": "うけとったもの",
|
||||
"outgoing": "おくったもの"
|
||||
},
|
||||
"underlay": "アンダーレイ",
|
||||
"alert_neutral": "それいがい",
|
||||
"popover": "ツールチップ、メニュー、ポップオーバー",
|
||||
"poll": "とうひょうのグラフ",
|
||||
"selectedPost": "えらんだとうこう",
|
||||
"selectedMenu": "えらんだメニューアイテム",
|
||||
"disabled": "つかえないとき",
|
||||
"toggled": "きりかえたとき",
|
||||
"tabs": "タブ"
|
||||
},
|
||||
"radii": {
|
||||
"_tab_label": "まるさ"
|
||||
|
@ -462,7 +601,8 @@
|
|||
"buttonPressed": "ボタン (おされているとき)",
|
||||
"buttonPressedHover": "ボタン (ホバー、かつ、おされているとき)",
|
||||
"input": "インプットフィールド"
|
||||
}
|
||||
},
|
||||
"hintV3": "かげのばあいは、 {0} というかきかたをつかうことができます。そうすると、ほかのいろのスロットをつかうことができます。"
|
||||
},
|
||||
"fonts": {
|
||||
"_tab_label": "フォント",
|
||||
|
@ -497,7 +637,167 @@
|
|||
"title": "バージョン",
|
||||
"backend_version": "バックエンドのバージョン",
|
||||
"frontend_version": "フロントエンドのバージョン"
|
||||
}
|
||||
},
|
||||
"notification_visibility_polls": "あなたがさんかしたとうひょうが、おわりました",
|
||||
"setting_server_side": "このせっていは、あなたのプロフィールについてのものです。へんこうすると、すべてのセッションとクライアントにえいきょうします",
|
||||
"mute_import_error": "ミュートのインポートが、エラーになりました",
|
||||
"account_backup_description": "あなたのアカウントじょうほうや、とうこうのアーカイブを、ダウンロードすることができます。しかし、 Pleroma アカウントにインポートすることはまだできません。",
|
||||
"list_backups_error": "バックアップリストをとりにいくことが、エラーになりました: {error}",
|
||||
"list_aliases_error": "エイリアスをとりにいくときに、エラーになりました: {error}",
|
||||
"added_alias": "エイリアスをつくりました。",
|
||||
"move_account_notes": "もしあなたがアカウントをほかのインスタンスにひっこしたいのなら、ひっこすさきのアカウントからここへのエイリアスをつくってください。",
|
||||
"file_export_import": {
|
||||
"backup_settings_theme": "せっていとテーマをファイルにバックアップする",
|
||||
"restore_settings": "ファイルからせっていをもとにもどす",
|
||||
"errors": {
|
||||
"file_too_new": "メジャーバージョン({fileMajor})がちがいます。この PleromaFE (せっていのバージョン {feMajor}) はふるいので、つかうことができません",
|
||||
"file_slightly_new": "ファイルのマイナーバージョンがちがっています。いくつかのせっていは、よみこまれないかもしれません",
|
||||
"invalid_file": "これは Pleroma のせっていをバックアップしたファイルではありません。",
|
||||
"file_too_old": "メジャーバージョン({fileMajor})がちがいます。ファイルのバージョンが古いので、使うことができません(バージョン {feMajor} いじょうのせっていバージョンをつかってください)"
|
||||
},
|
||||
"backup_settings": "せっていをファイルにバックアップする",
|
||||
"backup_restore": "せっていのバックアップ"
|
||||
},
|
||||
"hide_wallpaper": "このインスタンスのバックグラウンドをかくす",
|
||||
"reply_visibility_following_short": "わたしのフォローしているひとにあてられたリプライをみる",
|
||||
"reply_visibility_self_short": "じぶんにあてられたリプライだけをみる",
|
||||
"save": "へんこうをほぞんする",
|
||||
"reset_banner_confirm": "ほんとうに、バナーをリセットしますか?",
|
||||
"tree_advanced": "ツリービューで、ナビゲーションをもっとじゅうなんにする",
|
||||
"third_column_mode": "じゅうぶんなくうかんがあれば、3ばんめのれつをひょうじする",
|
||||
"conversation_other_replies_button": "「ほかのリプライ」ボタンをひょうじするばしょ",
|
||||
"user_popover_avatar_action_open": "プロフィールをひらく",
|
||||
"notification_setting_filters": "フィルター",
|
||||
"notification_setting_hide_notification_contents": "おくったひとと、ないようを、プッシュつうちにひょうじしない",
|
||||
"backup_running": "バックアップしています。{number}このデータをしょりしました。",
|
||||
"word_filter_and_more": "ことばのフィルターと、そのほか…",
|
||||
"account_privacy": "プライバシー",
|
||||
"posts": "とうこう",
|
||||
"move_account": "アカウントをひっこす",
|
||||
"move_account_target": "ひっこしさきのアカウント (れい: {example})",
|
||||
"mute_bot_posts": "Bot のとうこうをミュートする",
|
||||
"hide_bot_indication": "Bot によるとうこうであることを、とうこうにひょうじしない",
|
||||
"hide_all_muted_posts": "ミュートしたとうこうをかくす",
|
||||
"hide_shoutbox": "Shoutbox をかくす",
|
||||
"conversation_display_tree": "ツリーけいしき",
|
||||
"mention_link_display_full_for_remote": "リモートユーザーだけ、ながいなまえでひょうじする (れい: {'@'}hoge{'@'}example.org)",
|
||||
"mention_link_bolden_you": "あなたがメンションされたとき、あなたへのメンションを、よくみえるようにする",
|
||||
"user_popover_avatar_action": "ポップオーバーのアバターをクリックしたとき",
|
||||
"user_popover_avatar_action_zoom": "アバターをおおきくする",
|
||||
"user_popover_avatar_action_close": "ポップオーバーをとじる",
|
||||
"always_show_post_button": "とうこうボタンをいつもひょうじする",
|
||||
"auto_update": "あたらしいとうこうを、じどうてきにみせる",
|
||||
"user_mutes": "ユーザー",
|
||||
"useStreamingApi": "とうこうとつうちを、リアルタイムにうけとる",
|
||||
"use_websockets": "Websockets をつかう (リアルタイムアップデート)",
|
||||
"mutes_and_blocks": "ミュートとブロック",
|
||||
"emoji_reactions_on_timeline": "えもじリアクションをタイムラインにひょうじする",
|
||||
"accent": "アクセント",
|
||||
"domain_mutes": "ドメイン",
|
||||
"import_mutes_from_a_csv_file": "CSVファイルからミュートをインポートする",
|
||||
"reset_avatar": "アバターをリセットする",
|
||||
"remove_language": "とりのぞく",
|
||||
"primary_language": "いちばんわかることば:",
|
||||
"add_language": "よびとしてつかうことばを、ついかする",
|
||||
"fallback_language": "よびとしてつかうことば {index}:",
|
||||
"lists_navigation": "ナビゲーションにリストをひょうじする",
|
||||
"account_alias": "アカウントのエイリアス",
|
||||
"mention_link_display_full": "いつも、ながいなまえをひょうじする (れい: {'@'}hoge{'@'}example.org)",
|
||||
"setting_changed": "せっていは、デフォルトとちがっています",
|
||||
"email_language": "サーバーからうけとるEメールのことば",
|
||||
"mute_export": "ミュートのエクスポート",
|
||||
"mute_export_button": "あなたのミュートを、 CSV ファイルにエクスポートします",
|
||||
"mute_import": "ミュートのインポート",
|
||||
"mutes_imported": "ミュートをインポートしました!すこしじかんがかかるかもしれません。",
|
||||
"account_backup": "アカウントのバックアップ",
|
||||
"account_backup_table_head": "バックアップ",
|
||||
"download_backup": "ダウンロード",
|
||||
"backup_not_ready": "バックアップのじゅんびが、まだできていません。",
|
||||
"backup_failed": "バックアップにしっぱいしました。",
|
||||
"remove_backup": "とりのぞく",
|
||||
"add_backup": "あたらしいバックアップをつくる",
|
||||
"added_backup": "あたらしいバックアップをつくりました。",
|
||||
"add_backup_error": "あたらしいバックアップをつくるときに、エラーになりました: {error}",
|
||||
"bot": "これは bot アカウントです",
|
||||
"account_alias_table_head": "エイリアス",
|
||||
"hide_list_aliases_error_action": "とじる",
|
||||
"remove_alias": "このエイリアスをけす",
|
||||
"add_alias_error": "エイリアスをつくるときに、エラーになりました: {error}",
|
||||
"new_alias_target": "あたらしいエイリアスをつくる (れい: {example})",
|
||||
"moved_account": "アカウントをひっこしました。",
|
||||
"move_account_error": "アカウントをひっこしているときに、エラーになりました: {error}",
|
||||
"wordfilter": "ことばのフィルター",
|
||||
"hide_media_previews": "メディアのプレビューをかくす",
|
||||
"right_sidebar": "サイドバーをみぎにひょうじする",
|
||||
"hide_wordfiltered_statuses": "ことばのフィルターでフィルターされたステータスをかくす",
|
||||
"hide_muted_threads": "ミュートされたスレッドをかくす",
|
||||
"navbar_column_stretch": "ナビゲーションバーをれつのはばまでのばす",
|
||||
"birthday": {
|
||||
"label": "たんじょうび",
|
||||
"show_birthday": "たんじょうびを、ひょうじする"
|
||||
},
|
||||
"profile_fields": {
|
||||
"label": "プロフィールのメタデータ",
|
||||
"add_field": "フィールドをふやす",
|
||||
"name": "ラベル",
|
||||
"value": "ないよう"
|
||||
},
|
||||
"user_profiles": "ユーザープロフィール",
|
||||
"notification_visibility_moves": "ユーザーのひっこし",
|
||||
"notification_visibility_emoji_reactions": "リアクション",
|
||||
"hide_favorites_description": "おきにいりのリストをみせない (つうちはおくられます)",
|
||||
"reset_profile_background": "プロフィールバックグラウンドをリセットする",
|
||||
"reset_profile_banner": "プロフィールバナーをリセットする",
|
||||
"reset_avatar_confirm": "ほんとうに、アバターをリセットしますか?",
|
||||
"reset_background_confirm": "ほんとうに、バックグラウンドをリセットしますか?",
|
||||
"column_sizes_sidebar": "サイドバー",
|
||||
"column_sizes_notifs": "つうち",
|
||||
"columns": "れつ",
|
||||
"column_sizes": "れつのおおきさ",
|
||||
"column_sizes_content": "コンテンツ",
|
||||
"conversation_display": "スレッドのひょうじけいしき",
|
||||
"conversation_display_linear": "リニアけいしき",
|
||||
"conversation_display_linear_quick": "リニアビュー",
|
||||
"show_scrollbars": "よこのれつにスクロールバーをひょうじする",
|
||||
"third_column_mode_none": "3ばんめのれつをひょうじしない",
|
||||
"third_column_mode_postform": "とうこうフォームとナビゲーション",
|
||||
"third_column_mode_notifications": "つうちのれつをひょうじする",
|
||||
"tree_fade_ancestors": "げんざいのステータスのおやを、うすいいろのもじでひょうじする",
|
||||
"conversation_other_replies_button_below": "ステータスのした",
|
||||
"conversation_other_replies_button_inside": "ステータスのなか",
|
||||
"max_depth_in_thread": "デフォルトでひょうじするスレッドのふかさ",
|
||||
"sensitive_by_default": "デフォルトで、とうこうをNSFWにする",
|
||||
"type_domains_to_mute": "ミュートしたいドメインを、ここでけんさくできます",
|
||||
"mention_link_use_tooltip": "メンションのリンクをクリックしたとき、ユーザーカードをみせる",
|
||||
"mention_link_show_avatar": "ユーザーのアバターをリンクのよこにひょうじする",
|
||||
"mention_link_show_avatar_quick": "ユーザーのアバターをメンションのとなりにひょうじする",
|
||||
"mention_link_fade_domain": "ドメイン(れい: {'@'}hoge{'@'}example.org のなかの {'@'}example.org)を、うすいいろにする",
|
||||
"user_popover_avatar_overlay": "ユーザーのポップオーバーを、ユーザーのアバターのうえにひょうじする",
|
||||
"show_yous": "(あなた)をひょうじする",
|
||||
"notification_setting_block_from_strangers": "フォローしていないユーザーからのつうちをブロックする",
|
||||
"notification_setting_privacy": "プライバシー",
|
||||
"more_settings": "そのたのせってい",
|
||||
"expert_mode": "くわしいせっていを、ひょうじする",
|
||||
"mention_links": "メンションのリンク",
|
||||
"post_look_feel": "とうこうのみためとかんかく",
|
||||
"allow_following_move": "フォローしているアカウントがインスタンスをひっこしたばあい、じどうでフォローしてもよい",
|
||||
"chatMessageRadius": "チャットメッセージ",
|
||||
"confirm_dialogs": "つぎのばあいに、かくにんをする",
|
||||
"confirm_dialogs_repeat": "ステータスをリピートするとき",
|
||||
"confirm_dialogs_unfollow": "ユーザーのフォローをはずすとき",
|
||||
"confirm_dialogs_block": "ユーザーをブロックするとき",
|
||||
"confirm_dialogs_mute": "ユーザーをミュートするとき",
|
||||
"confirm_dialogs_delete": "ステータスをけすとき",
|
||||
"confirm_dialogs_logout": "ログアウトするとき",
|
||||
"confirm_dialogs_approve_follow": "フォローをうけいれるとき",
|
||||
"confirm_dialogs_deny_follow": "フォローをことわるとき",
|
||||
"confirm_dialogs_remove_follower": "フォロワーをとりのぞくとき",
|
||||
"conversation_display_tree_quick": "ツリービュー",
|
||||
"disable_sticky_headers": "れつのヘッダーを、がめんのいちばんうえにこていしない",
|
||||
"virtual_scrolling": "タイムラインのレンダリングをよくする",
|
||||
"use_at_icon": "{'@'} きごうを、もじのかわりに、アイコンでひょうじする",
|
||||
"mention_link_display_short": "いつも、みじかいなまえにする (れい: {'@'}hoge)",
|
||||
"mention_link_display": "メンションのリンクをひょうじするけいしき"
|
||||
},
|
||||
"time": {
|
||||
"day": "{0}日",
|
||||
|
@ -531,7 +831,23 @@
|
|||
"year": "{0}年",
|
||||
"years": "{0}年",
|
||||
"year_short": "{0}年",
|
||||
"years_short": "{0}年"
|
||||
"years_short": "{0}年",
|
||||
"unit": {
|
||||
"minutes": "{0}ふん",
|
||||
"seconds_short": "{0}びょう",
|
||||
"weeks": "{0}しゅうかん",
|
||||
"weeks_short": "{0}しゅう",
|
||||
"years": "{0}ねん",
|
||||
"years_short": "{0}ねん",
|
||||
"days": "{0}にち",
|
||||
"days_short": "{0}にち",
|
||||
"hours": "{0}じかん",
|
||||
"hours_short": "{0}じかん",
|
||||
"minutes_short": "{0}ふん",
|
||||
"months": "{0}かげつ",
|
||||
"months_short": "{0}かげつ",
|
||||
"seconds": "{0}びょう"
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"collapse": "たたむ",
|
||||
|
@ -543,7 +859,11 @@
|
|||
"show_new": "よみこみ",
|
||||
"up_to_date": "さいしん",
|
||||
"no_more_statuses": "これでおわりです",
|
||||
"no_statuses": "ありません"
|
||||
"no_statuses": "ありません",
|
||||
"socket_broke": "コード{0}により、リアルタイムでつながることがなくなりました",
|
||||
"socket_reconnected": "リアルタイムでつながることを、つくりました",
|
||||
"reload": "もういちど、よみこむ",
|
||||
"error": "タイムラインをとりにいくときに、エラーになりました: {0}"
|
||||
},
|
||||
"status": {
|
||||
"favorites": "おきにいり",
|
||||
|
@ -556,7 +876,57 @@
|
|||
"reply_to": "へんしん:",
|
||||
"replies_list": "へんしん:",
|
||||
"mute_conversation": "スレッドをミュートする",
|
||||
"unmute_conversation": "スレッドをミュートするのをやめる"
|
||||
"unmute_conversation": "スレッドをミュートするのをやめる",
|
||||
"repeat_confirm_title": "リピートのかくにん",
|
||||
"mentions": "メンション",
|
||||
"thread_muted": "ミュートされたスレッド",
|
||||
"collapse_attachments": "ファイルをかくす",
|
||||
"remove_attachment": "ファイルをとりのぞく",
|
||||
"thread_show_full": "このスレッドのすべてのとうこうをみる (ぜんぶで{numStatus}このステータス、ふかさ{depth})",
|
||||
"show_all_attachments": "すべてのファイルをみる",
|
||||
"hide_full_subject": "かくす",
|
||||
"nsfw": "NSFW",
|
||||
"hide_content": "かくす",
|
||||
"status_deleted": "このとうこうは、けされました",
|
||||
"you": "(あなた)",
|
||||
"expand": "ひろげる",
|
||||
"repeat_confirm_accept_button": "リピートする",
|
||||
"repeat_confirm_cancel_button": "リピートしない",
|
||||
"edited_at": "({time} まえにへんしゅう)",
|
||||
"delete_confirm_title": "けすことのかくにん",
|
||||
"delete_confirm_accept_button": "けす",
|
||||
"delete_confirm_cancel_button": "のこす",
|
||||
"edit": "ステータスをへんしゅうする",
|
||||
"bookmark": "ブックマークする",
|
||||
"unbookmark": "ブックマークをはずす",
|
||||
"replies_list_with_others": "へんしん (ほかに +{numReplies}こ):",
|
||||
"status_unavailable": "ステータスがありません",
|
||||
"copy_link": "リンクをコピー",
|
||||
"external_source": "そとにあるソース",
|
||||
"thread_muted_and_words": "つぎのことばをふくむので:",
|
||||
"show_content": "みる",
|
||||
"plus_more": "あと {number}こ",
|
||||
"many_attachments": "とうこうには、{number}このファイルがついています",
|
||||
"show_attachment_in_modal": "メディアモーダルでみる",
|
||||
"show_attachment_description": "せつめいのプレビュー (ぜんぶみるには、ファイルをひらいてください)",
|
||||
"hide_attachment": "ファイルをかくす",
|
||||
"attachment_stop_flash": "Flash プレーヤーをとめる",
|
||||
"move_up": "ファイルをひだりにうごかす",
|
||||
"move_down": "ファイルをみぎにうごかす",
|
||||
"open_gallery": "ギャラリーをひらく",
|
||||
"thread_hide": "スレッドをかくす",
|
||||
"thread_show": "スレッドをみる",
|
||||
"show_full_subject": "すべてをみる",
|
||||
"repeat_confirm": "ほんとうに、このステータスをリピートしますか?",
|
||||
"show_all_conversation": "このスレッドをぜんぶみる (あと {numStatus}このステータス)",
|
||||
"show_only_conversation_under_this": "このステータスへのへんしんだけをみる",
|
||||
"status_history": "ステータスのれきし",
|
||||
"thread_show_full_with_icon": "{icon} {text}",
|
||||
"thread_follow": "のこりのとうこうをみる (ぜんぶで {numStatus}このステータス)",
|
||||
"thread_follow_with_icon": "{icon} {text}",
|
||||
"ancestor_follow": "このステータスよりしたの、{numReplies}このへんしんをみる",
|
||||
"ancestor_follow_with_icon": "{icon} {text}",
|
||||
"show_all_conversation_with_icon": "{icon} {text}"
|
||||
},
|
||||
"user_card": {
|
||||
"approve": "うけいれ",
|
||||
|
@ -577,7 +947,7 @@
|
|||
"media": "メディア",
|
||||
"mention": "メンション",
|
||||
"mute": "ミュート",
|
||||
"muted": "ミュートしています!",
|
||||
"muted": "ミュートしています",
|
||||
"per_day": "/日",
|
||||
"remote_follow": "リモートフォロー",
|
||||
"report": "つうほう",
|
||||
|
@ -608,8 +978,52 @@
|
|||
"disable_remote_subscription": "ほかのインスタンスからフォローされないようにする",
|
||||
"disable_any_subscription": "フォローされないようにする",
|
||||
"quarantine": "ほかのインスタンスのユーザーのとうこうをとめる",
|
||||
"delete_user": "ユーザーをけす"
|
||||
}
|
||||
"delete_user": "ユーザーをけす",
|
||||
"delete_user_data_and_deactivate_confirmation": "これをすると、このアカウントのデータがきえて、にどとつかえなくなります。ほんとうに、していいですか?"
|
||||
},
|
||||
"mute_confirm_accept_button": "ミュートする",
|
||||
"unfollow_confirm_title": "フォローをやめることのかくにん",
|
||||
"mute_confirm": "ほんとうに、 {user} をミュートしますか?",
|
||||
"mute_duration_prompt": "このユーザーをつぎのじかんだけミュートする (0にすると、おわりがありません):",
|
||||
"edit_note_apply": "てきよう",
|
||||
"block_confirm": "ほんとうに、 {user} をブロックしますか?",
|
||||
"deactivated": "つかえない",
|
||||
"remove_follower": "フォロワーをとりのぞく",
|
||||
"highlight": {
|
||||
"solid": "バッググラウンドをひとつのいろにする",
|
||||
"striped": "しまもようのバックグラウンドにする",
|
||||
"side": "はじにせんをつける",
|
||||
"disabled": "めだたせない"
|
||||
},
|
||||
"mute_confirm_cancel_button": "ミュートしない",
|
||||
"unfollow_confirm_accept_button": "フォローをやめる",
|
||||
"unfollow_confirm": "ほんとうに、 {user} のフォローをやめますか?",
|
||||
"unfollow_confirm_cancel_button": "フォローしたままにする",
|
||||
"mute_confirm_title": "ミュートのかくにん",
|
||||
"block_confirm_accept_button": "ブロックする",
|
||||
"block_confirm_cancel_button": "ブロックしない",
|
||||
"deny_confirm_title": "おことわりのかくにん",
|
||||
"deny_confirm_accept_button": "ことわる",
|
||||
"deny_confirm_cancel_button": "ことわらない",
|
||||
"deny_confirm": "{user} のフォローリクエストをことわりますか?",
|
||||
"follow_cancel": "リクエストをキャンセル",
|
||||
"birthday": "{birthday} に、うまれました",
|
||||
"remove_follower_confirm_title": "フォロワーをとりのぞくことのかくにん",
|
||||
"remove_follower_confirm_accept_button": "とりのぞく",
|
||||
"remove_follower_confirm_cancel_button": "のこす",
|
||||
"remove_follower_confirm": "ほんとうに、 {user} をあなたのフォロワーからとりのぞきますか?",
|
||||
"edit_note": "メモをへんしゅうする",
|
||||
"edit_note_cancel": "キャンセル",
|
||||
"message": "メッセージ",
|
||||
"bot": "bot",
|
||||
"approve_confirm_title": "うけいれのかくにん",
|
||||
"approve_confirm_accept_button": "うけいれる",
|
||||
"approve_confirm_cancel_button": "うけいれない",
|
||||
"approve_confirm": "{user} のフォローリクエストをうけいれますか?",
|
||||
"edit_profile": "プロフィールをへんしゅう",
|
||||
"block_confirm_title": "ブロックのかくにん",
|
||||
"note_blank": "(なし)",
|
||||
"note": "メモ"
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "ユーザータイムライン",
|
||||
|
@ -634,13 +1048,21 @@
|
|||
"repeat": "リピート",
|
||||
"reply": "リプライ",
|
||||
"favorite": "おきにいり",
|
||||
"user_settings": "ユーザーせってい"
|
||||
"user_settings": "ユーザーせってい",
|
||||
"accept_follow_request": "フォローのおねがいを、うけいれる",
|
||||
"toggle_mute": "ミュートされたないようをみるために、つうちをひらくか、とじる",
|
||||
"autocomplete_available": "{number}このけっかが、あります。うえとしたのキーをつかって、けっかをみることができます。",
|
||||
"add_reaction": "リアクションをつける",
|
||||
"reject_follow_request": "フォローのおねがいを、ことわる",
|
||||
"bookmark": "ブックマーク",
|
||||
"toggle_expand": "とうこうをすべてみるために、つうちをひらくか、とじる"
|
||||
},
|
||||
"upload": {
|
||||
"error": {
|
||||
"base": "アップロードにしっぱいしました。",
|
||||
"file_too_big": "ファイルがおおきすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
|
||||
"default": "しばらくしてから、ためしてください"
|
||||
"default": "しばらくしてから、ためしてください",
|
||||
"message": "アップロードにしっぱいしました: {0}"
|
||||
},
|
||||
"file_size_units": {
|
||||
"B": "B",
|
||||
|
@ -655,7 +1077,9 @@
|
|||
"hashtags": "ハッシュタグ",
|
||||
"person_talking": "{count} にんが、はなしています",
|
||||
"people_talking": "{count} にんが、はなしています",
|
||||
"no_results": "みつかりませんでした"
|
||||
"no_results": "みつかりませんでした",
|
||||
"no_more_results": "これでおわりです",
|
||||
"load_more": "もっとみる"
|
||||
},
|
||||
"password_reset": {
|
||||
"forgot_password": "パスワードを、わすれましたか?",
|
||||
|
@ -668,5 +1092,103 @@
|
|||
"password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。",
|
||||
"password_reset_required": "ログインするには、パスワードをリセットしてください。",
|
||||
"password_reset_required_but_mailer_is_disabled": "あなたはパスワードのリセットがひつようです。しかし、まずいことに、このインスタンスでは、パスワードのリセットができなくなっています。このインスタンスのアドミニストレーターに、おといあわせください。"
|
||||
},
|
||||
"announcements": {
|
||||
"post_placeholder": "おしらせのないようを、にゅうりょくしてください。",
|
||||
"end_time_prompt": "おわるじかん: ",
|
||||
"inactive_message": "このおしらせは、つかわれていません",
|
||||
"page_header": "おしらせ",
|
||||
"title": "おしらせ",
|
||||
"post_action": "とうこう",
|
||||
"post_form_header": "おしらせをとうこう",
|
||||
"mark_as_read_action": "よんだことにする",
|
||||
"post_error": "エラー: {error}",
|
||||
"close_error": "とじる",
|
||||
"delete_action": "けす",
|
||||
"start_time_display": "{time}にはじまります",
|
||||
"end_time_display": "{time}におわります",
|
||||
"edit_action": "へんしゅう",
|
||||
"start_time_prompt": "はじまるじかん: ",
|
||||
"all_day_prompt": "このイベントはいちにちじゅうやります",
|
||||
"published_time_display": "{time}にこうかいされました",
|
||||
"submit_edit_action": "そうしん",
|
||||
"cancel_edit_action": "キャンセル"
|
||||
},
|
||||
"report": {
|
||||
"reported_statuses": "つうほうされたステータス:",
|
||||
"reporter": "つうほうしたひと:",
|
||||
"state_closed": "クローズ",
|
||||
"state_resolved": "かいけつしました",
|
||||
"reported_user": "つうほうされたユーザー:",
|
||||
"notes": "メモ:",
|
||||
"state": "じょうたい:",
|
||||
"state_open": "オープン"
|
||||
},
|
||||
"update": {
|
||||
"update_bugs": "もんだいや、バグがあれば、 {pleromaGitlab} でおしえてください。ちゃんとテストはしているのですが、たくさんのことをかえているので、そしてかいはつバージョンをつかっているので、もんだいやバグに、きづかないことがあります。あなたがきづいたもんだいについての、フィードバックやていあんを、まっています。 Pleroma や Pleroma-FE をよくするやりかたについても、おしえてください。",
|
||||
"update_changelog_here": "すべてのかわったことのきろく",
|
||||
"art_by": "{linkToArtist}によるさくひん",
|
||||
"big_update_title": "すこし、まってください",
|
||||
"big_update_content": "しばらくリリースがありませんでした。おもっていたみためと、ちがうかもしれません。",
|
||||
"update_bugs_gitlab": "Pleroma GitLab",
|
||||
"update_changelog": "かわったことをすべてみるには、{theFullChangelog}をみてください。"
|
||||
},
|
||||
"chats": {
|
||||
"new": "あたらしいチャット",
|
||||
"chats": "チャット",
|
||||
"you": "あなた:",
|
||||
"message_user": "{nickname} にメッセージ",
|
||||
"delete": "けす",
|
||||
"empty_message_error": "なにかかいてください",
|
||||
"more": "もっとみる",
|
||||
"delete_confirm": "ほんとうに、このメッセージをけしますか?",
|
||||
"error_loading_chat": "チャットをよみこむことに、しっぱいしました。",
|
||||
"error_sending_message": "メッセージをおくることに、しっぱいしました。",
|
||||
"empty_chat_list_placeholder": "チャットがありません。あたらしいチャットボタンをおして、はじめてください!"
|
||||
},
|
||||
"shoutbox": {
|
||||
"title": "Shoutbox"
|
||||
},
|
||||
"errors": {
|
||||
"storage_unavailable": "Pleroma はブラウザーのストレージにアクセスすることができません。あなたがログインしたことと、あなたのローカルのせっていは、ほぞんされません。ほかにももんだいがおきるかもしれません。 Cookie をゆうこうにしてください。"
|
||||
},
|
||||
"lists": {
|
||||
"lists": "リスト",
|
||||
"new": "あたらしいリスト",
|
||||
"search": "ユーザーをさがす",
|
||||
"title": "リストのなまえ",
|
||||
"create": "つくる",
|
||||
"save": "へんこうをほぞんする",
|
||||
"delete": "リストをけす",
|
||||
"following_only": "フォローしているひとげんていにする",
|
||||
"manage_lists": "リストをかんりする",
|
||||
"manage_members": "リストにふくまれるひとを、かんりする",
|
||||
"add_members": "もっとユーザーをさがす",
|
||||
"remove_from_list": "リストからとりのぞく",
|
||||
"add_to_list": "リストにいれる",
|
||||
"editing_list": "リスト {listTitle} をへんしゅうしています",
|
||||
"creating_list": "あたらしいリストをつくっています",
|
||||
"update_title": "なまえをほぞんする",
|
||||
"really_delete": "ほんとうに、リストをけしますか?",
|
||||
"is_in_list": "すでにリストのなかにあります",
|
||||
"error": "リストをへんしゅうするときに、エラーになりました: {0}"
|
||||
},
|
||||
"file_type": {
|
||||
"audio": "オーディオ",
|
||||
"video": "ビデオ",
|
||||
"image": "がぞう",
|
||||
"file": "ファイル"
|
||||
},
|
||||
"display_date": {
|
||||
"today": "きょう"
|
||||
},
|
||||
"unicode_domain_indicator": {
|
||||
"tooltip": "このドメインは、ASCIIいがいのもじをふくんでいます。"
|
||||
},
|
||||
"domain_mute_card": {
|
||||
"mute": "ミュート",
|
||||
"mute_progress": "ミュートしています…",
|
||||
"unmute": "ミュートをやめる",
|
||||
"unmute_progress": "ミュートをやめています…"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -118,7 +118,10 @@
|
|||
"totp": "Двофакторна автентифікація"
|
||||
},
|
||||
"enter_two_factor_code": "Введіть двофакторний код автентифікації",
|
||||
"placeholder": "напр. stepan"
|
||||
"placeholder": "напр. stepan",
|
||||
"logout_confirm": "Ви дійсно хочете вийти?",
|
||||
"logout_confirm_accept_button": "Вийти",
|
||||
"logout_confirm_cancel_button": "Ні, хочу назад!"
|
||||
},
|
||||
"importer": {
|
||||
"error": "Під час імпортування файлу сталася помилка.",
|
||||
|
@ -189,7 +192,8 @@
|
|||
"mobile_notifications": "Відкрити сповіщення (є непрочитані)",
|
||||
"mobile_notifications_close": "Закрити сповіщення",
|
||||
"edit_nav_mobile": "Редагувати панель навігації",
|
||||
"announcements": "Анонси"
|
||||
"announcements": "Анонси",
|
||||
"search_close": "Закрити панель пошуку"
|
||||
},
|
||||
"media_modal": {
|
||||
"next": "Наступна",
|
||||
|
|
284
src/i18n/zh.json
284
src/i18n/zh.json
|
@ -53,7 +53,15 @@
|
|||
"direct": "私讯",
|
||||
"private": "仅关注者",
|
||||
"unlisted": "列外"
|
||||
}
|
||||
},
|
||||
"scroll_to_top": "滚动至顶",
|
||||
"generic_error_message": "发生一个错误:{0}",
|
||||
"never_show_again": "不再显示",
|
||||
"undo": "撤销",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"unpin": "取消固定该项",
|
||||
"pin": "固定该项"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop_picture": "裁剪图片",
|
||||
|
@ -82,7 +90,11 @@
|
|||
"heading": {
|
||||
"totp": "双重因素验证",
|
||||
"recovery": "双重因素恢复"
|
||||
}
|
||||
},
|
||||
"logout_confirm_cancel_button": "不要登出",
|
||||
"logout_confirm_title": "确认登出",
|
||||
"logout_confirm_accept_button": "登出",
|
||||
"logout_confirm": "您确定要登出吗?"
|
||||
},
|
||||
"media_modal": {
|
||||
"previous": "往前",
|
||||
|
@ -109,7 +121,16 @@
|
|||
"chats": "聊天",
|
||||
"timelines": "时间线",
|
||||
"bookmarks": "书签",
|
||||
"home_timeline": "主页时间线"
|
||||
"home_timeline": "主页时间线",
|
||||
"lists": "列表",
|
||||
"edit_finish": "完成编辑",
|
||||
"mobile_notifications": "打开通知(有未读的)",
|
||||
"mobile_notifications_close": "关闭通知",
|
||||
"announcements": "公告",
|
||||
"edit_nav_mobile": "自定义导航栏",
|
||||
"edit_pinned": "编辑固定的项目",
|
||||
"mobile_sidebar": "切换移动设备侧栏",
|
||||
"search_close": "关闭搜索栏"
|
||||
},
|
||||
"notifications": {
|
||||
"broken_favorite": "未知的状态,正在搜索中…",
|
||||
|
@ -124,7 +145,8 @@
|
|||
"migrated_to": "迁移到了",
|
||||
"follow_request": "想要关注你",
|
||||
"error": "取得通知时发生错误:{0}",
|
||||
"poll_ended": "投票结束了"
|
||||
"poll_ended": "投票结束了",
|
||||
"submitted_report": "提交举报"
|
||||
},
|
||||
"polls": {
|
||||
"add_poll": "增加投票",
|
||||
|
@ -149,7 +171,9 @@
|
|||
"favs_repeats": "转发和喜欢",
|
||||
"follows": "新的关注者",
|
||||
"load_older": "加载更早的互动",
|
||||
"moves": "用户迁移"
|
||||
"moves": "用户迁移",
|
||||
"reports": "举报",
|
||||
"emoji_reactions": "表情回应"
|
||||
},
|
||||
"post_status": {
|
||||
"new_status": "发布新状态",
|
||||
|
@ -183,7 +207,12 @@
|
|||
"media_description": "媒体描述",
|
||||
"media_description_error": "更新媒体失败,请重试",
|
||||
"empty_status_error": "不能发布没有内容、没有附件的发文",
|
||||
"post": "发送"
|
||||
"post": "发送",
|
||||
"edit_remote_warning": "其它远程实例可能不支持编辑并且无法接收您的帖子的最新版本。",
|
||||
"edit_unsupported_warning": "Pleroma 不支持对提及或投票进行编辑。",
|
||||
"edit_status": "编辑状态",
|
||||
"content_type_selection": "发帖格式",
|
||||
"scope_notice_dismiss": "关闭此提示"
|
||||
},
|
||||
"registration": {
|
||||
"bio": "简介",
|
||||
|
@ -203,12 +232,18 @@
|
|||
"email_required": "不能留空",
|
||||
"password_required": "不能留空",
|
||||
"password_confirmation_required": "不能留空",
|
||||
"password_confirmation_match": "密码不一致"
|
||||
"password_confirmation_match": "密码不一致",
|
||||
"birthday_required": "不能为空",
|
||||
"birthday_min_age": "必须在 {date} 或之前"
|
||||
},
|
||||
"reason_placeholder": "此实例的注册需要手动批准。\n请让管理员知道您为什么想要注册。",
|
||||
"reason": "注册理由",
|
||||
"register": "注册",
|
||||
"email_language": "你想从服务器收到什么语言的邮件?"
|
||||
"email_language": "你想从服务器收到什么语言的邮件?",
|
||||
"bio_optional": "介绍(可选)",
|
||||
"email_optional": "电子邮件(可选)",
|
||||
"birthday": "生日:",
|
||||
"birthday_optional": "生日(可选):"
|
||||
},
|
||||
"selectable_list": {
|
||||
"select_all": "选择全部"
|
||||
|
@ -599,7 +634,7 @@
|
|||
"backup_settings": "备份设置到文件",
|
||||
"backup_restore": "设置备份"
|
||||
},
|
||||
"right_sidebar": "在右侧显示侧边栏",
|
||||
"right_sidebar": "反转分栏的顺序",
|
||||
"hide_shoutbox": "隐藏实例留言板",
|
||||
"expert_mode": "显示高级",
|
||||
"download_backup": "下载",
|
||||
|
@ -631,7 +666,78 @@
|
|||
"move_account_notes": "如果你想把账号移动到别的地方,你必须去目标账号,然后加一个指向这里的别名。",
|
||||
"wordfilter": "词语过滤器",
|
||||
"user_profiles": "用户资料",
|
||||
"third_column_mode_notifications": "消息栏"
|
||||
"third_column_mode_notifications": "通知栏",
|
||||
"backup_running": "此备份正在进行,已处理 {number} 条记录。 |此备份正在进行,已处理 {number} 条记录。",
|
||||
"lists_navigation": "在导航中显示列表",
|
||||
"word_filter_and_more": "词过滤器及其它...",
|
||||
"backup_failed": "此备份已失败。",
|
||||
"birthday": {
|
||||
"label": "生日",
|
||||
"show_birthday": "展示我的生日"
|
||||
},
|
||||
"hide_favorites_description": "不显示我的喜欢列表(人们仍然会收到通知)",
|
||||
"third_column_mode": "当有足够的空间时,显示第三栏包含",
|
||||
"third_column_mode_postform": "主要的发文形式和导航",
|
||||
"columns": "分栏",
|
||||
"user_popover_avatar_overlay": "在用户头像上显示用户弹出窗口",
|
||||
"navbar_column_stretch": "延伸导航栏至分栏宽度",
|
||||
"posts": "帖子",
|
||||
"conversation_display_linear_quick": "线性视图",
|
||||
"conversation_other_replies_button": "显示 “其它回复” 按钮",
|
||||
"confirm_dialogs_delete": "删除状态",
|
||||
"confirm_dialogs_mute": "隐藏用户",
|
||||
"column_sizes": "分栏大小",
|
||||
"column_sizes_sidebar": "侧栏",
|
||||
"column_sizes_content": "內容",
|
||||
"column_sizes_notifs": "通知",
|
||||
"conversation_other_replies_button_below": "在状态下方",
|
||||
"conversation_other_replies_button_inside": "在状态中",
|
||||
"auto_update": "自动显示新的帖子",
|
||||
"use_websockets": "使用 websockets(实时更新)",
|
||||
"max_depth_in_thread": "默认显示同主题帖子中的最大层数",
|
||||
"hide_wordfiltered_statuses": "隐藏经过词语过滤的状态",
|
||||
"hide_muted_threads": "不显示已隐藏的同主题帖子",
|
||||
"notification_visibility_polls": "你所投的投票的结束于",
|
||||
"tree_advanced": "允许在树状视图中进行更灵活的导航",
|
||||
"tree_fade_ancestors": "以模糊的文字显示当前状态的原型",
|
||||
"conversation_display_linear": "线性样式",
|
||||
"mention_link_fade_domain": "淡化域名(例如:{'@'}example.org 中的 {'@'}foo{'@'}example.org)",
|
||||
"mention_link_bolden_you": "当你被提及时突出显示提及你",
|
||||
"user_popover_avatar_action": "弹出式头像点击动作",
|
||||
"user_popover_avatar_action_zoom": "缩放头像",
|
||||
"user_popover_avatar_action_close": "关闭弹出窗口",
|
||||
"show_yous": "显示 (You)s",
|
||||
"add_language": "添加备用语言",
|
||||
"remove_language": "移除",
|
||||
"primary_language": "主要语言:",
|
||||
"fallback_language": "备用语言 {index}:",
|
||||
"account_privacy": "隐私",
|
||||
"conversation_display": "对话显示样式",
|
||||
"conversation_display_tree": "树状样式",
|
||||
"conversation_display_tree_quick": "树状视图",
|
||||
"disable_sticky_headers": "不要把分栏的顶栏固定在屏幕的顶部",
|
||||
"confirm_dialogs": "请求确认于",
|
||||
"confirm_dialogs_logout": "登出",
|
||||
"confirm_dialogs_deny_follow": "拒绝关注请求",
|
||||
"confirm_dialogs_approve_follow": "批准关注请求",
|
||||
"confirm_dialogs_block": "屏蔽用户",
|
||||
"confirm_dialogs_unfollow": "取消关注用户",
|
||||
"confirm_dialogs_repeat": "转发状态",
|
||||
"confirm_dialogs_remove_follower": "移除关注者",
|
||||
"mute_bot_posts": "隐藏机器人的帖子",
|
||||
"hide_bot_indication": "隐藏帖子中的机器人提示",
|
||||
"always_show_post_button": "始终显示浮动的新帖子按钮",
|
||||
"show_scrollbars": "显示侧栏的滚动条",
|
||||
"third_column_mode_none": "完全不显示第三栏",
|
||||
"use_at_icon": "将 {'@'} 符号显示为图标而不是文本",
|
||||
"mention_link_display": "显示提及链接",
|
||||
"mention_link_display_short": "始终以简称的形式出现(例如:{'@'}foo)",
|
||||
"mention_link_display_full_for_remote": "仅远程实例用户以全名的形式出现(例如:{'@'}foo{'@'}example.org)",
|
||||
"mention_link_display_full": "始终以全名的形式出现(例如:{'@'}foo{'@'}example.org)",
|
||||
"mention_link_use_tooltip": "点击提及链接时显示用户卡片",
|
||||
"mention_link_show_avatar": "在链接旁边显示用户头像",
|
||||
"mention_link_show_avatar_quick": "在提及内容旁边显示用户头像",
|
||||
"user_popover_avatar_action_open": "打开个人资料"
|
||||
},
|
||||
"time": {
|
||||
"day": "{0} 天",
|
||||
|
@ -697,7 +803,9 @@
|
|||
"reload": "重新载入",
|
||||
"error": "取得时间轴时发生错误:{0}",
|
||||
"socket_broke": "丢失实时连接:CloseEvent code {0}",
|
||||
"socket_reconnected": "已建立实时连接"
|
||||
"socket_reconnected": "已建立实时连接",
|
||||
"quick_view_settings": "快速视图设置",
|
||||
"quick_filter_settings": "快速过滤设置"
|
||||
},
|
||||
"status": {
|
||||
"favorites": "喜欢",
|
||||
|
@ -706,7 +814,7 @@
|
|||
"pin": "在个人资料置顶",
|
||||
"unpin": "取消在个人资料置顶",
|
||||
"pinned": "置顶",
|
||||
"delete_confirm": "你真的想要删除这条状态吗?",
|
||||
"delete_confirm": "您确定要删除这条状态吗?",
|
||||
"reply_to": "回复",
|
||||
"replies_list": "回复:",
|
||||
"mute_conversation": "隐藏对话",
|
||||
|
@ -715,7 +823,7 @@
|
|||
"show_content": "显示内容",
|
||||
"hide_full_subject": "隐藏此部分标题",
|
||||
"show_full_subject": "显示全部标题",
|
||||
"thread_muted": "此系列消息已被隐藏",
|
||||
"thread_muted": "同主题帖子已被隐藏",
|
||||
"copy_link": "复制状态链接",
|
||||
"status_unavailable": "状态不可取得",
|
||||
"unbookmark": "取消书签",
|
||||
|
@ -736,10 +844,10 @@
|
|||
"attachment_stop_flash": "停止 Flash 播放器",
|
||||
"move_up": "把附件左移",
|
||||
"open_gallery": "打开图库",
|
||||
"thread_hide": "隐藏这个线索",
|
||||
"thread_show": "显示这个线索",
|
||||
"thread_hide": "隐藏这个同主题帖子",
|
||||
"thread_show": "显示这个同主题帖子",
|
||||
"thread_show_full_with_icon": "{icon} {text}",
|
||||
"thread_follow": "查看这个线索的剩余部分(一共有 {numStatus} 个状态)",
|
||||
"thread_follow": "查看这个同主题帖子的剩余部分(一共有 {numStatus} 个状态)",
|
||||
"thread_follow_with_icon": "{icon} {text}",
|
||||
"ancestor_follow": "查看这个状态下的别的 {numReplies} 个回复",
|
||||
"ancestor_follow_with_icon": "{icon} {text}",
|
||||
|
@ -748,8 +856,19 @@
|
|||
"mentions": "提及",
|
||||
"replies_list_with_others": "回复(另外 +{numReplies} 个):",
|
||||
"move_down": "把附件右移",
|
||||
"thread_show_full": "显示这个线索下的所有东西(一共有 {numStatus} 个状态,最大深度 {depth})",
|
||||
"show_only_conversation_under_this": "只显示这个状态的回复"
|
||||
"thread_show_full": "显示这个同主题帖子下的所有东西(一共有 {numStatus} 个状态,最大深度 {depth})",
|
||||
"show_only_conversation_under_this": "只显示这个状态的回复",
|
||||
"repeat_confirm": "您确定要转发这条状态吗?",
|
||||
"repeat_confirm_title": "确认转发",
|
||||
"repeat_confirm_accept_button": "转发",
|
||||
"repeat_confirm_cancel_button": "不要转发",
|
||||
"edit": "编辑状态",
|
||||
"edited_at": "(最后编辑于 {time})",
|
||||
"delete_confirm_title": "确认删除",
|
||||
"delete_confirm_accept_button": "删除",
|
||||
"delete_confirm_cancel_button": "保留",
|
||||
"show_attachment_in_modal": "在媒体模式中显示",
|
||||
"status_history": "状态历史"
|
||||
},
|
||||
"user_card": {
|
||||
"approve": "核准",
|
||||
|
@ -797,7 +916,8 @@
|
|||
"disable_remote_subscription": "禁止从远程实例关注用户",
|
||||
"disable_any_subscription": "完全禁止关注用户",
|
||||
"quarantine": "从联合实例中禁止用户帖子",
|
||||
"delete_user": "删除用户"
|
||||
"delete_user": "删除用户",
|
||||
"delete_user_data_and_deactivate_confirmation": "这将永久删除该账户的数据并停用该账户。你完全确定吗?"
|
||||
},
|
||||
"hidden": "已隐藏",
|
||||
"show_repeats": "显示转发",
|
||||
|
@ -811,7 +931,41 @@
|
|||
"solid": "单一颜色背景",
|
||||
"disabled": "不突出显示"
|
||||
},
|
||||
"edit_profile": "编辑个人资料"
|
||||
"edit_profile": "编辑个人资料",
|
||||
"approve_confirm_title": "确认批准",
|
||||
"approve_confirm_accept_button": "批准",
|
||||
"block_confirm_accept_button": "屏蔽",
|
||||
"block_confirm_cancel_button": "不要屏蔽",
|
||||
"deactivated": "已停用",
|
||||
"deny_confirm_title": "确认拒绝",
|
||||
"deny_confirm_accept_button": "拒绝",
|
||||
"deny_confirm_cancel_button": "不要拒绝",
|
||||
"deny_confirm": "您是否要拒绝 {user} 的关注请求?",
|
||||
"follow_cancel": "取消请求",
|
||||
"unfollow_confirm_title": "确认取消关注",
|
||||
"unfollow_confirm": "您确定要取消关注 {user} 吗?",
|
||||
"unfollow_confirm_accept_button": "取消关注",
|
||||
"unfollow_confirm_cancel_button": "不要取消关注",
|
||||
"mute_confirm_title": "确认隐藏",
|
||||
"mute_confirm_accept_button": "隐藏",
|
||||
"mute_confirm_cancel_button": "不要隐藏",
|
||||
"mute_duration_prompt": "让这个用户隐藏(0表示无限期):",
|
||||
"remove_follower": "移除关注者",
|
||||
"remove_follower_confirm_title": "确认移除关注者",
|
||||
"remove_follower_confirm_cancel_button": "保留",
|
||||
"remove_follower_confirm": "您确定要将 {user} 从您的关注者里移除吗?",
|
||||
"birthday": "生于 {birthday}",
|
||||
"note": "备注",
|
||||
"approve_confirm_cancel_button": "不要批准",
|
||||
"approve_confirm": "您是否要批准 {user} 的关注请求?",
|
||||
"block_confirm_title": "确认屏蔽",
|
||||
"block_confirm": "您确定要屏蔽 {user} 吗?",
|
||||
"mute_confirm": "您确定要隐藏 {user} 吗?",
|
||||
"remove_follower_confirm_accept_button": "移除",
|
||||
"note_blank": "(空)",
|
||||
"edit_note": "编辑备注",
|
||||
"edit_note_apply": "应用",
|
||||
"edit_note_cancel": "取消"
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "用户时间线",
|
||||
|
@ -840,7 +994,10 @@
|
|||
"reject_follow_request": "拒绝关注请求",
|
||||
"add_reaction": "添加互动",
|
||||
"bookmark": "书签",
|
||||
"accept_follow_request": "接受关注请求"
|
||||
"accept_follow_request": "接受关注请求",
|
||||
"toggle_expand": "展开或折叠通知以显示帖子全文",
|
||||
"toggle_mute": "展开或折叠通知以显示已隐藏的内容",
|
||||
"autocomplete_available": "共有 {number} 个结果可用。使用向上和向下键浏览它们。"
|
||||
},
|
||||
"upload": {
|
||||
"error": {
|
||||
|
@ -862,7 +1019,9 @@
|
|||
"hashtags": "话题标签",
|
||||
"person_talking": "{count} 人正在讨论",
|
||||
"people_talking": "{count} 人正在讨论",
|
||||
"no_results": "没有搜索结果"
|
||||
"no_results": "没有搜索结果",
|
||||
"no_more_results": "没有更多结果",
|
||||
"load_more": "加载更多结果"
|
||||
},
|
||||
"password_reset": {
|
||||
"forgot_password": "忘记密码了?",
|
||||
|
@ -890,7 +1049,20 @@
|
|||
"search_emoji": "搜索表情符号",
|
||||
"emoji": "表情符号",
|
||||
"load_all": "加载所有表情符号(共 {emojiAmount} 个)",
|
||||
"load_all_hint": "最先加载的 {saneAmount} 表情符号,加载全部表情符号可能会带来性能问题。"
|
||||
"load_all_hint": "最先加载的 {saneAmount} 表情符号,加载全部表情符号可能会带来性能问题。",
|
||||
"unicode_groups": {
|
||||
"flags": "旗帜",
|
||||
"food-and-drink": "饮食",
|
||||
"objects": "物件",
|
||||
"people-and-body": "人和身体",
|
||||
"symbols": "符号",
|
||||
"travel-and-places": "旅行和地点",
|
||||
"activities": "活动",
|
||||
"animals-and-nature": "动物和自然",
|
||||
"smileys-and-emotion": "表情与情感"
|
||||
},
|
||||
"regional_indicator": "地区指示符 {letter}",
|
||||
"unpacked": "拆分的表情符号"
|
||||
},
|
||||
"about": {
|
||||
"mrf": {
|
||||
|
@ -950,7 +1122,7 @@
|
|||
"empty_chat_list_placeholder": "您还没有任何聊天记录。开始聊天吧!",
|
||||
"error_sending_message": "发送消息时出了点问题。",
|
||||
"error_loading_chat": "加载聊天时出了点问题。",
|
||||
"delete_confirm": "您确实要删除此消息吗?",
|
||||
"delete_confirm": "您确定要删除此消息吗?",
|
||||
"more": "更多",
|
||||
"empty_message_error": "无法发布空消息",
|
||||
"new": "新聊天",
|
||||
|
@ -958,5 +1130,69 @@
|
|||
"delete": "删除",
|
||||
"message_user": "发消息给 {nickname}",
|
||||
"you": "你:"
|
||||
},
|
||||
"announcements": {
|
||||
"page_header": "公告",
|
||||
"title": "公告",
|
||||
"mark_as_read_action": "标为已读",
|
||||
"post_form_header": "发布公告",
|
||||
"post_placeholder": "在这里输入公告内容...",
|
||||
"post_action": "发布",
|
||||
"post_error": "错误:{error}",
|
||||
"close_error": "关闭",
|
||||
"delete_action": "删除",
|
||||
"start_time_prompt": "起始时间: ",
|
||||
"end_time_prompt": "终止时间: ",
|
||||
"all_day_prompt": "这是全天的事件",
|
||||
"published_time_display": "发表于 {time}",
|
||||
"start_time_display": "开始于 {time}",
|
||||
"end_time_display": "结束于 {time}",
|
||||
"edit_action": "编辑",
|
||||
"submit_edit_action": "提交",
|
||||
"cancel_edit_action": "取消",
|
||||
"inactive_message": "这个公告不活跃"
|
||||
},
|
||||
"report": {
|
||||
"reported_user": "被举报者:",
|
||||
"state_closed": "已关闭",
|
||||
"state_resolved": "已解决",
|
||||
"reporter": "举报者:",
|
||||
"state_open": "开启",
|
||||
"reported_statuses": "已举报的状态:",
|
||||
"notes": "备注:",
|
||||
"state": "状态:"
|
||||
},
|
||||
"unicode_domain_indicator": {
|
||||
"tooltip": "此域名包含非 ascii 字符。"
|
||||
},
|
||||
"update": {
|
||||
"update_bugs_gitlab": "Pleroma GitLab",
|
||||
"update_changelog": "关于变化的更多细节,请参见 {theFullChangelog} 。",
|
||||
"update_changelog_here": "完整的更新日志",
|
||||
"big_update_title": "请忍耐一下",
|
||||
"big_update_content": "我们已经有一段时间没有发布发行版,所以事情的外观和感觉可能与你习惯的不一样。",
|
||||
"update_bugs": "请在 {pleromaGitlab} 上报告任何问题和bug,因为我们已经改变了很多,虽然我们进行了彻底的测试,并且自己使用了开发版本,但我们可能错过了一些东西。我们欢迎你对你可能遇到的问题或如何改进Pleroma和Pleroma-FE提出反馈和建议。",
|
||||
"art_by": "Art by {linkToArtist}"
|
||||
},
|
||||
"lists": {
|
||||
"search": "搜索用户",
|
||||
"create": "创建",
|
||||
"save": "保存更改",
|
||||
"delete": "删除列表",
|
||||
"following_only": "限制于正在关注",
|
||||
"manage_lists": "管理列表",
|
||||
"manage_members": "管理列表成员",
|
||||
"add_members": "搜索更多用户",
|
||||
"remove_from_list": "从列表中移除",
|
||||
"add_to_list": "添加到列表",
|
||||
"is_in_list": "已在列表中",
|
||||
"editing_list": "正在编辑列表 {listTitle}",
|
||||
"creating_list": "正在创建新的列表",
|
||||
"update_title": "保存标题",
|
||||
"really_delete": "真的要删除列表吗?",
|
||||
"error": "操作列表时出错:{0}",
|
||||
"lists": "列表",
|
||||
"new": "新的列表",
|
||||
"title": "列表标题"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,12 +113,23 @@
|
|||
"submit": "提交",
|
||||
"apply": "應用",
|
||||
"role": {
|
||||
"moderator": "主持人",
|
||||
"moderator": "審查者",
|
||||
"admin": "管理員"
|
||||
},
|
||||
"flash_content": "點擊以使用 Ruffle 顯示 Flash 內容(實驗性,可能無效)。",
|
||||
"flash_security": "請注意,這可能有潜在的危險,因為Flash內容仍然是武斷的程式碼。",
|
||||
"flash_fail": "無法加載flash內容,請參閱控制台瞭解詳細資訊。"
|
||||
"flash_fail": "無法加載flash內容,請參閱控制台瞭解詳細資訊。",
|
||||
"no": "否",
|
||||
"generic_error_message": "發生了一個錯誤: {0}",
|
||||
"never_show_again": "不再顯示",
|
||||
"yes": "是",
|
||||
"undo": "復原",
|
||||
"scroll_to_top": "滾動至頂部",
|
||||
"pin": "置頂",
|
||||
"scope_in_timeline": {
|
||||
"private": "僅關注者"
|
||||
},
|
||||
"unpin": "停止置頂"
|
||||
},
|
||||
"finder": {
|
||||
"find_user": "尋找用戶",
|
||||
|
@ -133,7 +144,8 @@
|
|||
"pleroma_chat_messages": "Pleroma 聊天",
|
||||
"chat": "聊天",
|
||||
"gopher": "Gopher",
|
||||
"upload_limit": "上傳限制"
|
||||
"upload_limit": "上傳限制",
|
||||
"shout": "留言板"
|
||||
},
|
||||
"exporter": {
|
||||
"processing": "正在處理,稍後會提示您下載文件",
|
||||
|
@ -164,11 +176,14 @@
|
|||
"reject": "拒絕",
|
||||
"accept_desc": "本實例只接收來自下列實例的消息:",
|
||||
"simple_policies": "站規",
|
||||
"accept": "接受"
|
||||
"accept": "接受",
|
||||
"instance": "實例",
|
||||
"reason": "原因",
|
||||
"not_applicable": "N/A"
|
||||
},
|
||||
"mrf_policies_desc": "MRF 策略會影響本實例的互通行為。以下策略已啟用:",
|
||||
"keyword": {
|
||||
"ftl_removal": "從“全部已知網絡”時間線上移除",
|
||||
"ftl_removal": "從「全部已知網絡」時間線上移除",
|
||||
"replace": "取代",
|
||||
"reject": "拒絕",
|
||||
"is_replaced_by": "→",
|
||||
|
@ -865,5 +880,26 @@
|
|||
"password_reset_disabled": "密碼重置已經被禁用。請聯繫你的實例管理員。",
|
||||
"password_reset_required": "您必須重置密碼才能登陸。",
|
||||
"password_reset_required_but_mailer_is_disabled": "您必須重置密碼,但是密碼重置被禁用了。請聯繫您所在實例的管理員。"
|
||||
},
|
||||
"announcements": {
|
||||
"post_error": "錯誤: {error}",
|
||||
"close_error": "關閉",
|
||||
"delete_action": "刪除",
|
||||
"start_time_prompt": "開始時間: ",
|
||||
"end_time_prompt": "結束時間: ",
|
||||
"all_day_prompt": "這是全日活動",
|
||||
"start_time_display": "{time} 開始",
|
||||
"end_time_display": "{time} 結束",
|
||||
"published_time_display": "{time} 發布",
|
||||
"edit_action": "編輯",
|
||||
"submit_edit_action": "送出",
|
||||
"cancel_edit_action": "取消",
|
||||
"inactive_message": "此公告無效",
|
||||
"page_header": "公告",
|
||||
"title": "公告",
|
||||
"mark_as_read_action": "標示為以閱讀",
|
||||
"post_placeholder": "在此輸入您的公告內容……",
|
||||
"post_form_header": "發布公告",
|
||||
"post_action": "發布"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,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,
|
||||
|
@ -106,7 +115,8 @@ export const defaultState = {
|
|||
conversationTreeAdvanced: undefined, // instance default
|
||||
conversationOtherRepliesButton: undefined, // instance default
|
||||
conversationTreeFadeAncestors: undefined, // instance default
|
||||
maxDepthInThread: undefined // instance default
|
||||
maxDepthInThread: undefined, // instance default
|
||||
autocompleteSelect: undefined // instance default
|
||||
}
|
||||
|
||||
// caching the instance default properties
|
||||
|
@ -184,7 +194,10 @@ 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)
|
||||
)
|
||||
break
|
||||
case 'thirdColumnMode':
|
||||
dispatch('setLayoutWidth', undefined)
|
||||
|
|
|
@ -71,6 +71,15 @@ const defaultState = {
|
|||
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',
|
||||
|
@ -95,6 +104,7 @@ const defaultState = {
|
|||
conversationOtherRepliesButton: 'below',
|
||||
conversationTreeFadeAncestors: false,
|
||||
maxDepthInThread: 6,
|
||||
autocompleteSelect: false,
|
||||
|
||||
// Nasty stuff
|
||||
customEmoji: [],
|
||||
|
@ -107,6 +117,8 @@ const defaultState = {
|
|||
restrictedNicknames: [],
|
||||
safeDM: true,
|
||||
knownDomains: [],
|
||||
birthdayRequired: false,
|
||||
birthdayMinAge: 0,
|
||||
|
||||
// Feature-set, apparently, not everything here is reported...
|
||||
shoutAvailable: false,
|
||||
|
@ -286,8 +298,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
|
||||
}
|
||||
}
|
||||
}))
|
||||
},
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1118,8 +1118,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 }) => {
|
||||
|
|
|
@ -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 }
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ const specialLanguageCodes = {
|
|||
const internalToBrowserLocale = code => specialLanguageCodes[code] || code
|
||||
|
||||
const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')
|
||||
const internalToBackendLocaleMulti = codes => {
|
||||
const langs = Array.isArray(codes) ? codes : [codes]
|
||||
return langs.map(internalToBackendLocale).join(',')
|
||||
}
|
||||
|
||||
const getLanguageName = (code) => {
|
||||
const specialLanguageNames = {
|
||||
|
@ -28,6 +32,7 @@ const languages = _.map(languagesObject.languages, (code) => ({ code, name: getL
|
|||
const localeService = {
|
||||
internalToBrowserLocale,
|
||||
internalToBackendLocale,
|
||||
internalToBackendLocaleMulti,
|
||||
languages,
|
||||
getLanguageName
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ const generateInput = (value, padEmoji = true) => {
|
|||
padEmoji
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
$t: (msg) => msg
|
||||
},
|
||||
stubs: {
|
||||
FAIcon: true
|
||||
|
|
Loading…
Add table
Reference in a new issue