Compare commits

...

94 commits

Author SHA1 Message Date
Sean King
78e9fff730 Change "Remove this follower" to "Remove Follower" and add a button to remove a follower in the followers tab for the logged in user 2022-12-15 21:57:57 +00:00
Sean King
6d430d53d8 Added support for removing users from followers 2022-12-15 21:57:44 +00:00
e853b56eb9 use v1 urls 2022-12-15 21:45:34 +00:00
a84c7a8152 Redo editing placement 2022-09-09 23:40:23 +00:00
8c70bb4301 Refactor reply tab disabling 2022-09-09 20:03:53 +00:00
d01e1e16b0 use with-direction in edited timeago
Fixes #159
2022-09-08 20:28:28 +00:00
dd1a949590 Disable replies and media tabs if not logged in 2022-09-08 20:24:07 +00:00
6774b0d468 Revert "Obfuscate domain names in MRF panel"
This reverts commit 2dc954ebfa.
2022-09-08 19:54:04 +00:00
7f09a45b99 Revert "remove unused method"
This reverts commit 2b6d3ff42b.
2022-09-07 09:29:35 +00:00
d60c629a86 Better edited_at styling 2022-09-07 08:40:43 +00:00
22ceb1772a only include direction on timestamps when required (#108)
Reviewed-on: AkkomaGang/pleroma-fe#108
2022-09-07 08:28:03 +00:00
ba9634ee41 editing (#158)
Co-authored-by: Sean King <seanking2919@protonmail.com>
Co-authored-by: Tusooa Zhu <tusooa@kazv.moe>
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#158
2022-09-07 08:04:41 +00:00
2b6d3ff42b remove unused method 2022-09-07 07:54:54 +00:00
David
edd42664ca Jump to Top on New *only* in non-profiles 2022-09-07 07:54:26 +00:00
David
b7a2b111c3 Fix Announcements Title Style 2022-09-07 07:53:56 +00:00
a6051f49cf only clear timelines on new profile 2022-08-31 22:46:57 +00:00
5be3326f22 ensure timelines only start fetching on click (#150)
no more parallel fetching

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#150
2022-08-31 20:40:21 +00:00
David
fb55bb2113 Potential Fix for (#113) on show new 2022-08-31 20:40:04 +00:00
a6d8550c83 somewhat fix scrolling behaviour (#127)
Reviewed-on: AkkomaGang/pleroma-fe#127
2022-08-31 20:39:32 +00:00
35d32524b0 Separated Posts & Replies (#145) (#148)
Co-authored-by: David <dmgf2008@hotmail.com>
Reviewed-on: AkkomaGang/pleroma-fe#148
Co-authored-by: Mergan <mergan@noreply.akkoma>
Co-committed-by: Mergan <mergan@noreply.akkoma>
2022-08-31 20:35:37 +00:00
0e86f707ff Disable streaming API option by default to fix reply filtering 2022-08-30 10:55:04 +00:00
bb6ee79e3e fix "who reacted" emoji display
fixes #80
2022-08-30 10:14:44 +00:00
cffcdd04bd allow selecting languages for translation 2022-08-30 09:50:31 +00:00
c23964b7a8 add translation options 2022-08-30 09:47:54 +00:00
6e31816c7d Update pleromafe version link 2022-08-30 09:28:27 +00:00
e39e7d5395 Dont copy local URL if external post 2022-08-30 08:51:07 +00:00
fa80d2627d my son 2022-08-30 02:49:55 +00:00
5c9c92669c Add privateMode error image 2022-08-30 02:42:24 +00:00
7a60b3a455 Hide staff panel if private mode which is empty anyway 2022-08-30 01:15:05 +00:00
311b38f9fb Fix i18n 2022-08-28 10:02:17 +00:00
a98d63fa00 Confirmation dialogs (#140)
supercedes #135

adapted from https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1431

Co-authored-by: Tusooa Zhu <tusooa@kazv.moe>
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#140
2022-08-28 09:49:04 +00:00
acdb03f494 Revert "New option: Require confirmation for boosts"
This reverts commit e69dddee69.
2022-08-28 09:39:38 +00:00
296ea3c993 Fix MRF transparency panel showing without transparency 2022-08-28 09:27:48 +00:00
dd42d8e92c Add PWA option 2022-08-28 09:21:31 +00:00
Eris
e69dddee69 New option: Require confirmation for boosts 2022-08-25 05:13:47 +00:00
5abce529ca Revert "Remove unnecessary code from reply filtering"
This reverts commit 69fb3c9a87.
2022-08-10 04:18:07 +00:00
30a5c092b2 Fix reply filtering on bubble timeline 2022-08-10 02:15:10 +00:00
69fb3c9a87 Remove unnecessary code from reply filtering 2022-08-10 02:14:43 +00:00
ca0e15f7a8 Misskey-like quote styling 2022-08-08 03:53:20 +00:00
6bf8513433 Make all new nav options expert/advanced options 2022-08-07 07:12:09 +00:00
20c4cbfd92 Better padding for logout button 2022-08-07 06:53:38 +00:00
b2b5c81e25 Move logout button to settings modal 2022-08-07 06:53:25 +00:00
2a34a63c01 Yeet unnecessary code 2022-08-07 06:53:08 +00:00
738f8a3b9a Disable wider margins 2022-08-07 06:52:10 +00:00
783c198125 Make nav panel icon margin optional 2022-08-07 06:51:22 +00:00
0b0e49606a Make new top nav links optional 2022-08-07 06:49:59 +00:00
7751d9b62b Fix image description weirdness, render alt text properly 2022-08-07 00:53:34 +00:00
6ff06ceaf5 New option: Pause MFM until status hover 2022-08-06 20:02:05 +00:00
c876b54bee Hide quote-inline RE: links 2022-08-06 18:33:44 +00:00
98ba191c28 Fix emoji picker issues, add header 2022-08-05 23:53:05 +00:00
aa4fd497a3 Emoji Pack Picker (#102)
Reviewed-on: AkkomaGang/pleroma-fe#102
Co-authored-by: Mergan <mergan@noreply.akkoma>
Co-committed-by: Mergan <mergan@noreply.akkoma>
2022-08-05 21:20:24 +00:00
1f5ae0543c Change locales for MRF panel 2022-08-03 21:02:11 +00:00
f57f1b7c7a Update local only with biohazard icon and locales 2022-08-03 07:21:05 +00:00
a06be20eac Undo changes to bring code back to upstream 2022-08-03 06:15:53 +00:00
60f9da14dc Fix emoji rendering 2022-08-03 05:05:33 +00:00
fd8a2413ca Change defaults, add warning to pause GIFs option 2022-08-03 04:43:22 +00:00
Sol Fisher Romanoff
5d8cba4ca2 Fix code blocks not working in MFM 2022-08-02 08:30:43 +00:00
c4f5c5aac2 Fix timeago warning applying to polls 2022-08-02 06:13:25 +00:00
d0dbe0c921 Remove cyan text because wtf why is this here 2022-08-02 04:53:11 +00:00
1800cb94e8 Change instance defaults 2022-08-02 04:47:07 +00:00
f57e977c41 Add user options to hide instance favicon and name 2022-08-01 23:13:32 +00:00
2dc954ebfa Obfuscate domain names in MRF panel 2022-08-01 20:59:10 +00:00
24bded2509 Strip displaying MRF features that dont respect MRF transparency settings 2022-08-01 19:24:00 +00:00
42a13c0822 Hide muted replies if muted threads enabled 2022-08-01 05:59:37 +00:00
897728189e Hide replies to muted users 2022-08-01 02:50:58 +00:00
e8160c8403 typo fix 2022-08-01 01:53:38 +00:00
f9ac714caa Update README.md with updated translation instructions 2022-08-01 01:53:38 +00:00
Tusooa Zhu
acdfa056d1 Fix poll duration i18n 2022-08-01 01:52:37 +00:00
sola
5b95f182c5 Translated using Weblate (Catalan)
Currently translated at 100.0% (850 of 850 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ca/
2022-08-01 01:52:37 +00:00
Weblate Admin
c29080a141 Translated using Weblate (English)
Currently translated at 100.0% (850 of 850 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/en/
2022-08-01 01:52:35 +00:00
Weblate Admin
a57f8d026d Translated using Weblate (English)
Currently translated at 100.0% (853 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/en/
2022-08-01 01:50:45 +00:00
sfr
097823d790 Add toggle to hide posts mentioning blocked users (#78)
Reviewed-on: AkkomaGang/pleroma-fe#78
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-08-01 00:34:12 +00:00
5135720adc Adjust user mention avatars and enable by default 2022-07-31 09:36:39 +00:00
54192564b9 Fix avatar mention links option 2022-07-31 01:12:20 +00:00
bebd5575b6 Bell to bolt icon, and no home icon if not logged in 2022-07-30 22:58:14 +00:00
cd72d1370c Don't show home icon if not logged in 2022-07-30 22:49:27 +00:00
baaec9d14b Restructure names and move lists to right side 2022-07-30 21:27:11 +00:00
f5e29ff52e Add home icon 2022-07-30 21:07:56 +00:00
f3de693f0e Indicator for active timeline in desktop nav 2022-07-30 20:38:47 +00:00
26033e62b8 Notifications -> Interactions (not in the column) 2022-07-30 19:24:20 +00:00
57aa0d8da8 Redo top bar with site favicon and more shortcuts 2022-07-30 18:54:19 +00:00
b792655f7a Remove link preview after quote for InlineQuotePolicy 2022-07-29 10:02:20 +00:00
945d4f633e Fix tall status gradient 2022-07-29 03:32:45 +00:00
1964193000 Add ability to click to expand collapsed notifications 2022-07-28 23:42:16 +00:00
64f8d854c8 Locale changes 2022-07-28 23:30:09 +00:00
18f817d73d Adjust status button padding, add emoji hovering 2022-07-28 23:23:45 +00:00
7eed382ea9 Better formatting for emoji react notifications 2022-07-28 06:58:34 +00:00
2b945a2c0c Locale changes and change bubble icon 2022-07-28 06:12:38 +00:00
b93a3c8ab0 Redo about page and staff panel 2022-07-28 00:20:18 +00:00
488cd43080 Only hide quote button to overflow menu if mobile 2022-07-27 23:08:34 +00:00
6b2322d81c Adjust sizes and padding for top nav and emojis 2022-07-27 22:34:58 +00:00
f803393219 Remove descriptions in timeline menu, change to title 2022-07-27 20:23:34 +00:00
68ef914e15 Change default options 2022-07-27 19:58:45 +00:00
6711b197ef Move quote button to extra buttons menu 2022-07-27 19:32:06 +00:00
108 changed files with 2525 additions and 541 deletions

View file

@ -10,3 +10,5 @@ Contributors of this project.
- shpuld (shpuld@shitposter.club): CSS and styling
- Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images.
- hj (hj@shigusegubu.club): Code
- Sean King (seanking@freespeechextremist.com): Code
- Tusooa Zhu (tusooa@kazv.moe): Code

View file

@ -6,7 +6,11 @@ This is a fork of Pleroma-FE from the Pleroma project, with support for new Akko
# For Translators
To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/i18n/messages.js). Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Pleroma-FE.
Alternatively, edit/create `src/i18n/$LANGUAGE_CODE.json` (where `$LANGUAGE_CODE` is the [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language), then add your language to [src/i18n/messages.js](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/i18n/messages.js) if it doesn't already exist there.
Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
# FOR ADMINS

View file

@ -10,11 +10,21 @@
<link rel="stylesheet" href="/static/font/css/lato.css">
<link rel="stylesheet" href="/static/mfm.css">
<!--server-generated-meta-->
<link rel="manifest" href="/static/manifest.json" />
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar" content="black-translucent" />
<link rel="apple-touch-icon" href="/images/icons/icon-512x512.png"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="theme-color" content="#000000" />
<meta name="theme-color-orig" content="#000000" />
<link rel="icon" type="image/png" href="/favicon.png">
</head>
<body class="hidden">
<noscript>To use Pleroma, please enable JavaScript.</noscript>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/static/service-worker.js');} else {console.log("Service worker is not supported");}</script>
<div id="app"></div>
<div id="modal"></div>
<!-- built files will be auto injected -->
</body>
</html>

View file

@ -10,7 +10,9 @@ import MobilePostStatusButton from './components/mobile_post_status_button/mobil
import MobileNav from './components/mobile_nav/mobile_nav.vue'
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex'
@ -33,6 +35,8 @@ export default {
SettingsModal,
UserReportingModal,
PostStatusModal,
EditStatusModal,
StatusHistoryModal,
GlobalNoticeList
},
data: () => ({
@ -83,6 +87,7 @@ export default {
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
editingAvailable () { return this.$store.state.instance.editingAvailable },
layoutType () { return this.$store.state.interface.layoutType },
privateMode () { return this.$store.state.instance.private },
reverseLayout () {

View file

@ -58,8 +58,10 @@
<MobilePostStatusButton />
<UserReportingModal />
<PostStatusModal />
<EditStatusModal v-if="editingAvailable" />
<StatusHistoryModal v-if="editingAvailable" />
<SettingsModal />
<div id="modal" />
<UpdateNotification />
<GlobalNoticeList />
</div>
</template>

View file

@ -248,8 +248,10 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'translationEnabled', value: features.includes('akkoma:machine_translation') })
const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
@ -376,8 +378,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => {
if (to.matched.some(m => m.meta.dontScroll)) {
return false
return {}
}
return savedPosition || { left: 0, top: 0 }
}
})

View file

@ -58,7 +58,7 @@ export default (store) => {
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
},
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile },
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile, meta: { dontScroll: true } },
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration },
@ -75,7 +75,7 @@ export default (store) => {
{ name: 'list-timeline', path: '/lists/:id', component: ListTimeline },
{ name: 'list-edit', path: '/lists/:id/edit', component: ListEdit },
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage },
{ name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile }
{ name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile, meta: { dontScroll: true } }
]
return routes

View file

@ -13,6 +13,8 @@ const About = {
MRFTransparencyPanel
},
computed: {
currentUser () { return this.$store.state.users.currentUser },
privateMode () { return this.$store.state.instance.private },
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&

View file

@ -1,7 +1,7 @@
<template>
<div class="column-inner">
<instance-specific-panel v-if="showInstanceSpecificPanel" />
<staff-panel />
<!--<instance-specific-panel v-if="showInstanceSpecificPanel" />-->
<staff-panel v-if="currentUser || !privateMode" />
<terms-of-service-panel />
<MRFTransparencyPanel />
<features-panel v-if="showFeaturesPanel" />

View file

@ -1,6 +1,8 @@
import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { mapState } from 'vuex'
import {
faEllipsisV
} from '@fortawesome/free-solid-svg-icons'
@ -14,13 +16,22 @@ const AccountActions = {
'user', 'relationship'
],
data () {
return { }
return {
showingConfirmBlock: false
}
},
components: {
ProgressButton,
Popover
Popover,
ConfirmModal
},
methods: {
showConfirmBlock () {
this.showingConfirmBlock = true
},
hideConfirmBlock () {
this.showingConfirmBlock = false
},
showRepeats () {
this.$store.dispatch('showReblogs', this.user.id)
},
@ -28,14 +39,33 @@ 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 () {
this.$store.dispatch('removeUserFromFollowers', this.user.id)
},
reportUser () {
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
}
},
computed: {
shouldConfirmBlock () {
return this.$store.getters.mergedConfig.modalOnBlock
},
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
})
}
}

View file

@ -28,6 +28,13 @@
class="dropdown-divider"
/>
</template>
<button
v-if="relationship.followed_by"
class="btn button-default btn-block dropdown-item"
@click="removeUserFromFollowers"
>
{{ $t('user_card.remove_follower') }}
</button>
<button
v-if="relationship.blocking"
class="btn button-default btn-block dropdown-item"
@ -59,6 +66,27 @@
</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 v-slot:user>
<span
v-text="user.screen_name_ui"
/>
</template>
</i18n-t>
</confirm-modal>
</teleport>
</div>
</template>

View file

@ -1,9 +1,9 @@
<template>
<div class="panel panel-default announcements-page">
<div class="panel-heading">
<span>
<div class="title">
{{ $t('announcements.page_header') }}
</span>
</div>
</div>
<div class="panel-body">
<section

View file

@ -132,6 +132,9 @@ const Attachment = {
...mapGetters(['mergedConfig'])
},
watch: {
'attachment.description' (newVal) {
this.localDescription = newVal
},
localDescription (newVal) {
this.onEdit(newVal)
}

View 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

View file

@ -0,0 +1,39 @@
<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="onCancel"
v-text="cancelText"
/>
<button
class="btn button-default button-positive"
@click.prevent="onAccept"
v-text="confirmText"
/>
</template>
</dialog-modal>
</template>
<style lang="scss" scoped>
@import '../../_variables';
.confirm-modal {
.button-positive {
border: 3px solid var(--accent, $fallback--link);
border-radius: var(--btnRadius, $fallback--btnRadius);
}
}
</style>
<script src="./confirm_modal.js"></script>

View file

@ -7,7 +7,11 @@ const conversationPage = {
computed: {
statusId () {
return this.$route.params.id
}
},
currentUser () {
return this.$store.state.users.currentUser
},
privateMode () { return this.$store.state.instance.private }
}
}

View file

@ -1,5 +1,22 @@
<template>
<div
v-if="!currentUser && privateMode"
class="panel error-placeholder"
>
<div class="panel-heading">
<div class="title">
???
</div>
</div>
<div class="panel-body error">
<img
class="error-img"
src="/static/error.gif"
>
</div>
</div>
<conversation
v-else
:collapsable="false"
is-page="true"
:status-id="statusId"
@ -7,3 +24,23 @@
</template>
<script src="./conversation-page.js"></script>
<style lang="scss">
.error-placeholder {
.panel-body {
display: flex;
justify-content: center;
align-items: middle;
padding: 7em;
&.error {
padding: 0em;
}
.error-img {
width: 100%;
border-radius: 0px 0px 10px 10px;
}
}
}
</style>

View file

@ -1,6 +1,8 @@
import { reduce, filter, findIndex, clone, get } from 'lodash'
import Status from '../status/status.vue'
import ThreadTree from '../thread_tree/thread_tree.vue'
import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -77,6 +79,9 @@ const conversation = {
const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2
return maxDepth >= 1 ? maxDepth : 1
},
streamingEnabled () {
return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
},
displayStyle () {
return this.$store.getters.mergedConfig.conversationDisplay
},
@ -339,7 +344,11 @@ const conversation = {
},
maybeHighlight () {
return this.isExpanded ? this.highlight : null
}
},
...mapGetters(['mergedConfig']),
...mapState({
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus
})
},
components: {
Status,
@ -395,6 +404,11 @@ const conversation = {
setHighlight (id) {
if (!id) return
this.highlight = id
if (!this.streamingEnabled) {
this.$store.dispatch('fetchStatus', id)
}
this.$store.dispatch('fetchFavsAndRepeats', id)
this.$store.dispatch('fetchEmojiReactionsBy', id)
},

View file

@ -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,
@ -11,6 +12,11 @@ import {
faSearch,
faTachometerAlt,
faCog,
faGlobe,
faBolt,
faUsers,
faCommentMedical,
faBookmark,
faInfoCircle
} from '@fortawesome/free-solid-svg-icons'
@ -25,12 +31,18 @@ library.add(
faSearch,
faTachometerAlt,
faCog,
faGlobe,
faBolt,
faUsers,
faCommentMedical,
faBookmark,
faInfoCircle
)
export default {
components: {
SearchBar
SearchBar,
ConfirmModal
},
data: () => ({
searchBarHidden: true,
@ -40,7 +52,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 },
@ -65,20 +78,34 @@ export default {
})
},
logo () { return this.$store.state.instance.logo },
mergedConfig () {
return this.$store.getters.mergedConfig
},
sitename () { return this.$store.state.instance.name },
showNavShortcuts () {
return this.mergedConfig.showNavShortcuts
},
showWiderShortcuts () {
return this.mergedConfig.showWiderShortcuts
},
hideSiteFavicon () {
return this.mergedConfig.hideSiteFavicon
},
hideSiteName () {
return this.mergedConfig.hideSiteName
},
hideSitename () { return this.$store.state.instance.hideSitename },
logoLeft () { return this.$store.state.instance.logoLeft },
currentUser () { return this.$store.state.users.currentUser },
privateMode () { return this.$store.state.instance.private }
privateMode () { return this.$store.state.instance.private },
shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout
}
},
methods: {
scrollToTop () {
window.scrollTo(0, 0)
},
logout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
},
onSearchBarToggled (hidden) {
this.searchBarHidden = hidden
},

View file

@ -15,16 +15,16 @@
display: grid;
grid-template-rows: var(--navbar-height);
grid-template-columns: 2fr auto 2fr;
grid-template-areas: "sitename logo actions";
grid-template-areas: "nav-left logo actions";
box-sizing: border-box;
padding: 0 1.2em;
margin: auto;
max-width: 980px;
max-width: 1110px;
}
&.-logoLeft .inner-nav {
grid-template-columns: auto 2fr 2fr;
grid-template-areas: "logo sitename actions";
grid-template-areas: "logo nav-left actions";
}
.button-default {
@ -84,24 +84,52 @@
}
.nav-icon {
margin-left: 1em;
margin-left: 0.2em;
width: 2em;
height: 100%;
font-size: 130%;
text-align: center;
&-logout {
margin-left: 2em;
}
&.router-link-active {
font-size: 1.2em;
margin-top: 0.05em;
.svg-inline--fa {
font-weight: bolder;
color: $fallback--text;
color: var(--selectedMenuText, $fallback--text);
--lightText: var(--selectedMenuLightText, $fallback--lightText);
}
}
.svg-inline--fa {
color: $fallback--link;
color: var(--topBarLink, $fallback--link);
}
}
.sitename {
grid-area: sitename;
.-wide {
.nav-icon {
margin-left: 0.7em;
}
}
.left {
padding-left: 5px;
display: flex;
}
.nav-left-wrapper {
grid-area: nav-left;
img {
height: 28px;
vertical-align: middle;
padding-right: 5px;
}
}
.actions {
@ -120,5 +148,10 @@
justify-content: flex-end;
text-align: right;
}
&.left {
justify-content: flex-start;
text-alignt: left;
}
}
}

View file

@ -5,16 +5,79 @@
:class="{ '-logoLeft': logoLeft }"
@click="scrollToTop()"
>
<div class="inner-nav">
<div class="item sitename">
<div
class="inner-nav"
:class="{ '-wide': showWiderShortcuts }"
>
<div class="item nav-left-wrapper">
<router-link
v-if="!hideSitename"
class="site-name"
class="site-brand"
:to="{ name: 'root' }"
active-class="home"
>
<img
v-if="!hideSiteFavicon"
class="favicon"
src="/favicon.png"
>
<span
v-if="!hideSiteName"
class="site-name"
>
{{ sitename }}
</span>
</router-link>
<div
v-if="(currentUser || !privateMode) && showNavShortcuts"
class="nav-items left"
>
<router-link
v-if="currentUser"
:to="{ name: 'friends' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="home"
:title="$t('nav.home_timeline')"
/>
</router-link>
<router-link
:to="{ name: 'public-timeline' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="users"
:title="$t('nav.public_tl')"
/>
</router-link>
<router-link
:to="{ name: 'public-external-timeline' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="globe"
:title="$t('nav.twkn')"
/>
</router-link>
<router-link
v-if="currentUser"
:to="{ name: 'bubble-timeline' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="comment-medical"
:title="$t('nav.bubble_timeline')"
/>
</router-link>
</div>
</div>
<router-link
class="logo"
@ -36,6 +99,46 @@
@toggled="onSearchBarToggled"
@click.stop
/>
<div
v-if="(currentUser || !privateMode) && showNavShortcuts"
class="nav-items right"
>
<router-link
class="nav-icon"
:to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="bolt"
:title="$t('nav.interactions')"
/>
</router-link>
<router-link
v-if="currentUser"
:to="{ name: 'lists' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="list"
:title="$t('nav.lists')"
/>
</router-link>
<router-link
v-if="currentUser"
:to="{ name: 'bookmarks' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="bookmark"
:title="$t('nav.bookmarks')"
/>
</router-link>
</div>
<button
class="button-unstyled nav-icon"
@click.stop="openSettingsModal"
@ -61,20 +164,20 @@
:title="$t('nav.administration')"
/>
</a>
<button
v-if="currentUser"
class="button-unstyled nav-icon nav-icon-logout"
@click.prevent="logout"
</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"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="sign-out-alt"
:title="$t('login.logout')"
/>
</button>
</div>
</div>
{{ $t('login.logout_confirm') }}
</confirm-modal>
</teleport>
</nav>
</template>
<script src="./desktop_nav.js"></script>

View file

@ -39,7 +39,7 @@
right: 0;
top: 0;
background: rgba(27,31,35,.5);
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;

View file

@ -0,0 +1,75 @@
import PostStatusForm from '../post_status_form/post_status_form.vue'
import Modal from '../modal/modal.vue'
import statusPosterService from '../../services/status_poster/status_poster.service.js'
import get from 'lodash/get'
const EditStatusModal = {
components: {
PostStatusForm,
Modal
},
data () {
return {
resettingForm: false
}
},
computed: {
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
modalActivated () {
return this.$store.state.editStatus.modalActivated
},
isFormVisible () {
return this.isLoggedIn && !this.resettingForm && this.modalActivated
},
params () {
return this.$store.state.editStatus.params || {}
}
},
watch: {
params (newVal, oldVal) {
if (get(newVal, 'statusId') !== get(oldVal, 'statusId')) {
this.resettingForm = true
this.$nextTick(() => {
this.resettingForm = false
})
}
},
isFormVisible (val) {
if (val) {
this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus())
}
}
},
methods: {
doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) {
const params = {
store: this.$store,
statusId: this.$store.state.editStatus.params.statusId,
status,
spoilerText,
sensitive,
poll,
media,
contentType
}
return statusPosterService.editStatus(params)
.then((data) => {
return data
})
.catch((err) => {
console.error('Error editing status', err)
return {
error: err.message
}
})
},
closeModal () {
this.$store.dispatch('closeEditStatusModal')
}
}
}
export default EditStatusModal

View file

@ -0,0 +1,48 @@
<template>
<Modal
v-if="isFormVisible"
class="edit-form-modal-view"
@backdropClicked="closeModal"
>
<div class="edit-form-modal-panel panel">
<div class="panel-heading">
{{ $t('post_status.edit_status') }}
</div>
<PostStatusForm
class="panel-body"
v-bind="params"
@posted="closeModal"
:disablePolls="true"
:disableVisibilitySelector="true"
:post-handler="doEditStatus"
/>
</div>
</Modal>
</template>
<script src="./edit_status_modal.js"></script>
<style lang="scss">
.modal-view.edit-form-modal-view {
align-items: flex-start;
}
.edit-form-modal-panel {
flex-shrink: 0;
margin-top: 25%;
margin-bottom: 2em;
width: 100%;
max-width: 700px;
@media (orientation: landscape) {
margin-top: 8%;
}
.form-bottom-left {
max-width: 6.5em;
.emoji-icon {
justify-content: right;
}
}
}
</style>

View file

@ -88,9 +88,10 @@
}
}
.emoji-picker-panel {
position: absolute;
position: relative;
z-index: 20;
margin-top: 2px;
top: 0px !important;
&.hide {
display: none

View file

@ -6,7 +6,7 @@ import {
faStickyNote,
faSmileBeam
} from '@fortawesome/free-solid-svg-icons'
import { trim } from 'lodash'
import { trim, escapeRegExp, startCase } from 'lodash'
library.add(
faBoxOpen,
@ -21,23 +21,6 @@ const LOAD_EMOJI_BY = 60
// When to start loading new batch emoji, in pixels
const LOAD_EMOJI_MARGIN = 64
const filterByKeyword = (list, keyword = '') => {
if (keyword === '') return list
const keywordLowercase = keyword.toLowerCase()
let orderedEmojiList = []
for (const emoji of list) {
const indexOfKeyword = emoji.displayText.toLowerCase().indexOf(keywordLowercase)
if (indexOfKeyword > -1) {
if (!Array.isArray(orderedEmojiList[indexOfKeyword])) {
orderedEmojiList[indexOfKeyword] = []
}
orderedEmojiList[indexOfKeyword].push(emoji)
}
}
return orderedEmojiList.flat()
}
const EmojiPicker = {
props: {
enableStickerPicker: {
@ -49,7 +32,7 @@ const EmojiPicker = {
data () {
return {
keyword: '',
activeGroup: 'custom',
activeGroup: 'standard',
showingStickers: false,
groupsScrolledClass: 'scrolled-top',
keepOpen: false,
@ -80,13 +63,8 @@ const EmojiPicker = {
this.triggerLoadMore(target)
},
highlight (key) {
const ref = this.$refs['group-' + key]
const top = ref.offsetTop
this.setShowStickers(false)
this.activeGroup = key
this.$nextTick(() => {
this.$refs['emoji-groups'].scrollTop = top + 1
})
},
updateScrolledClass (target) {
if (target.scrollTop <= 5) {
@ -155,6 +133,13 @@ const EmojiPicker = {
},
setShowStickers (value) {
this.showingStickers = value
},
filterByKeyword (list) {
if (this.keyword === '') return list
const regex = new RegExp(escapeRegExp(trim(this.keyword)), 'i')
return list.filter(emoji => {
return regex.test(emoji.displayText)
})
}
},
watch: {
@ -175,9 +160,8 @@ const EmojiPicker = {
return 0
},
filteredEmoji () {
return filterByKeyword(
this.$store.state.instance.customEmoji || [],
trim(this.keyword)
return this.filterByKeyword(
this.$store.state.instance.customEmoji || []
)
},
customEmojiBuffer () {
@ -185,25 +169,50 @@ const EmojiPicker = {
},
emojis () {
const standardEmojis = this.$store.state.instance.emoji || []
const customEmojis = this.customEmojiBuffer
const customEmojis = this.sortedEmoji
const emojiPacks = []
customEmojis.forEach((pack, id) => {
emojiPacks.push({
id: id.replace(/^pack:/, ''),
text: startCase(id.replace(/^pack:/, '')),
first: pack[0],
emojis: this.filterByKeyword(pack)
})
})
return [
{
id: 'custom',
text: this.$t('emoji.custom'),
icon: 'smile-beam',
emojis: customEmojis
},
{
id: 'standard',
text: this.$t('emoji.unicode'),
icon: 'box-open',
emojis: filterByKeyword(standardEmojis, trim(this.keyword))
first: {
imageUrl: '',
replacement: '🥴'
},
emojis: this.filterByKeyword(standardEmojis)
}
]
].concat(emojiPacks)
},
sortedEmoji () {
const customEmojis = this.$store.state.instance.customEmoji || []
const sortedEmojiGroups = new Map()
customEmojis.forEach((emoji) => {
if (!sortedEmojiGroups.has(emoji.tags[0])) {
sortedEmojiGroups.set(emoji.tags[0], [emoji])
} else {
sortedEmojiGroups.get(emoji.tags[0]).push(emoji)
}
})
return new Map([...sortedEmojiGroups.entries()].sort())
},
emojisView () {
return this.emojis.filter(value => value.emojis.length > 0)
if (this.keyword === '') {
return this.emojis.filter(pack => {
return pack.id === this.activeGroup
})
} else {
return this.emojis.filter(pack => {
return pack.emojis.length > 0
})
}
},
stickerPickerEnabled () {
return (this.$store.state.instance.stickers || []).length !== 0

View file

@ -35,9 +35,12 @@
}
.heading {
display: flex;
height: 32px;
padding: 10px 7px 5px;
margin-top: 10px;
height: 5.8em;
}
.emoji-header {
margin-left: 5px;
}
.content {
@ -65,15 +68,34 @@
.additional-tabs,
.emoji-tabs {
position: absolute;
display: block;
min-width: 0;
flex-basis: auto;
flex-shrink: 1;
flex-wrap: nowrap;
overflow: auto;
width: 100%;
white-space: nowrap;
&-item {
padding: 0 7px;
vertical-align: top;
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: .4em;
cursor: pointer;
font-size: 1.85em;
img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
span {
font-size: 1.9em;
}
&.disabled {
opacity: 0.5;

View file

@ -1,6 +1,7 @@
<template>
<div class="emoji-picker panel panel-default panel-body">
<div class="heading">
<span class="emoji-header">Emoji Packs</span>
<span class="emoji-tabs">
<span
v-for="group in emojis"
@ -13,10 +14,11 @@
:title="group.text"
@click.prevent="highlight(group.id)"
>
<FAIcon
:icon="group.icon"
fixed-width
/>
<span v-if="!group.first.imageUrl">{{ group.first.replacement }}</span>
<img
v-else
:src="group.first.imageUrl"
>
</span>
</span>
<span

View file

@ -27,7 +27,11 @@ const EmojiReactions = {
},
accountsForEmoji () {
return this.status.emoji_reactions.reduce((acc, reaction) => {
if (reaction.url) {
acc[reaction.url] = reaction.accounts || []
} else {
acc[reaction.name] = reaction.accounts || []
}
return acc
}, {})
},
@ -42,6 +46,14 @@ const EmojiReactions = {
reactedWith (emoji) {
return this.status.emoji_reactions.find(r => r.name === emoji).me
},
isLocalReaction (emojiUrl) {
if (!emojiUrl) return true
const reacted = this.accountsForEmoji[emojiUrl]
if (reacted.length === 0) {
return true
}
return reacted[0].is_local
},
fetchEmojiReactionsByIfMissing () {
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
if (hasNoAccounts) {

View file

@ -2,12 +2,13 @@
<div class="emoji-reactions">
<UserListPopover
v-for="(reaction) in emojiReactions"
:key="reaction.name"
:users="accountsForEmoji[reaction.name]"
:key="reaction.url || reaction.name"
:users="accountsForEmoji[reaction.url || reaction.name]"
>
<button
class="emoji-reaction btn button-default"
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
:disabled="!isLocalReaction(reaction.url)"
@click="emojiOnClick(reaction.name, $event)"
@mouseenter="fetchEmojiReactionsByIfMissing()"
>
@ -56,13 +57,15 @@
}
.emoji-reaction {
padding: 0 0.5em;
padding: 1px 6px;
margin-right: 0.5em;
margin-top: 0.5em;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
font-size: 0.7em !important;
height: 30px;
.reaction-emoji {
width: 2.55em !important;
margin-right: 0.25em;

View file

@ -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,
@ -6,7 +7,9 @@ import {
faEyeSlash,
faThumbtack,
faShareAlt,
faExternalLinkAlt
faQuoteLeft,
faExternalLinkAlt,
faHistory
} from '@fortawesome/free-solid-svg-icons'
import {
faBookmark as faBookmarkReg,
@ -20,20 +23,49 @@ library.add(
faEyeSlash,
faThumbtack,
faShareAlt,
faQuoteLeft,
faExternalLinkAlt,
faFlag
faFlag,
faHistory
)
const ExtraButtons = {
props: ['status'],
components: { Popover },
components: {
Popover,
ConfirmModal
},
data () {
return {
expanded: false,
showingDeleteDialog: false
}
},
methods: {
deleteStatus () {
const confirmed = window.confirm(this.$t('status.delete_confirm'))
if (confirmed) {
this.$store.dispatch('deleteStatus', { id: this.status.id })
if (this.shouldConfirmDelete) {
this.showDeleteStatusConfirmDialog()
} else {
this.doDeleteStatus()
}
},
doDeleteStatus () {
this.$store.dispatch('deleteStatus', { id: this.status.id })
this.hideDeleteStatusConfirmDialog()
},
showDeleteStatusConfirmDialog () {
this.showingDeleteDialog = true
},
hideDeleteStatusConfirmDialog () {
this.showingDeleteDialog = false
},
translateStatus () {
const translateTo = this.$store.getters.mergedConfig.translationLanguage || this.$store.state.instance.interfaceLanguage
this.$store.dispatch('translateStatus', { id: this.status.id, language: translateTo })
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
pinStatus () {
this.$store.dispatch('pinStatus', this.status.id)
.then(() => this.$emit('onSuccess'))
@ -71,6 +103,25 @@ const ExtraButtons = {
},
reportStatus () {
this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] })
},
editStatus () {
this.$store.dispatch('fetchStatusSource', { id: this.status.id })
.then(data => this.$store.dispatch('openEditStatusModal', {
statusId: this.status.id,
subject: data.spoiler_text,
statusText: data.text,
statusIsSensitive: this.status.nsfw,
statusPoll: this.status.poll,
statusFiles: [...this.status.attachments],
visibility: this.status.visibility,
statusContentType: data.content_type
}))
},
showStatusHistory () {
const originalStatus = { ...this.status }
const stripFieldsList = ['attachments', 'created_at', 'emojis', 'text', 'raw_html', 'nsfw', 'poll', 'summary', 'summary_raw_html']
stripFieldsList.forEach(p => delete originalStatus[p])
this.$store.dispatch('openStatusHistoryModal', originalStatus)
}
},
computed: {
@ -89,9 +140,23 @@ const ExtraButtons = {
canMute () {
return !!this.currentUser
},
canTranslate () {
return this.$store.state.instance.translationEnabled === true
},
statusLink () {
if (this.status.is_local) {
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
} else {
return this.status.external_url
}
},
shouldConfirmDelete () {
return this.$store.getters.mergedConfig.modalOnDelete
},
isEdited () {
return this.status.edited_at !== null
},
editingAvailable () { return this.$store.state.instance.editingAvailable }
}
}

View file

@ -73,6 +73,28 @@
icon="bookmark"
/><span>{{ $t("status.unbookmark") }}</span>
</button>
<button
v-if="ownStatus && editingAvailable"
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="editStatus"
@click="close"
>
<FAIcon
fixed-width
icon="pen"
/><span>{{ $t("status.edit") }}</span>
</button>
<button
v-if="isEdited && editingAvailable"
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="showStatusHistory"
@click="close"
>
<FAIcon
fixed-width
icon="history"
/><span>{{ $t("status.edit_history") }}</span>
</button>
<button
v-if="canDelete"
class="button-default dropdown-item dropdown-item-icon"
@ -116,6 +138,28 @@
:icon="['far', 'flag']"
/><span>{{ $t("user_card.report") }}</span>
</button>
<button
v-if="(status.visibility === 'public' || status.visibility === 'unlisted')"
class="button-default dropdown-item dropdown-item-icon extra-quote"
@click.prevent="$emit('quote-toggle')"
@click="close"
>
<FAIcon
fixed-width
icon="quote-left"
/><span>{{ $t("tool_tip.quote") }}</span>
</button>
<button
v-if="canTranslate"
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="translateStatus"
@click="close"
>
<FAIcon
fixed-width
icon="globe"
/><span>{{ $t("status.translate") }}</span>
</button>
</div>
</template>
<template v-slot:trigger>
@ -125,6 +169,18 @@
icon="ellipsis-h"
/>
</button>
<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>
@ -151,4 +207,11 @@
}
}
}
@media all and (min-width: 801px) {
.extra-quote {
display: none !important;
}
}
</style>

View file

@ -36,6 +36,7 @@
.FavoriteButton {
display: flex;
margin-right: 5px;
> :first-child {
padding: 10px;

View file

@ -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()
}
}
}

View file

@ -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>

View file

@ -1,6 +1,7 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import RemoteFollow from '../remote_follow/remote_follow.vue'
import FollowButton from '../follow_button/follow_button.vue'
import RemoveFollowerButton from '../remove_follower_button/remove_follower_button.vue'
const FollowCard = {
props: [
@ -10,7 +11,8 @@ const FollowCard = {
components: {
BasicUserCard,
RemoteFollow,
FollowButton
FollowButton,
RemoveFollowerButton
},
computed: {
isMe () {

View file

@ -22,6 +22,11 @@
class="follow-card-follow-button"
:user="user"
/>
<RemoveFollowerButton
v-if="noFollowsYou && relationship.followed_by"
:relationship="relationship"
class="follow-card-button"
/>
</template>
</div>
</basic-user-card>
@ -40,6 +45,12 @@
line-height: 1.5em;
}
&-button {
margin-top: 0.5em;
padding: 0 1.5em;
margin-left: 1em;
}
&-follow-button {
margin-top: 0.5em;
margin-left: auto;

View file

@ -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
}
}
}

View file

@ -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>

View file

@ -1,5 +1,10 @@
<template>
<div>
<FAIcon
v-if="globeIcon"
icon="globe"
/>
{{ ' ' }}
<label for="interface-language-switcher">
{{ promptText }}
</label>
@ -39,6 +44,10 @@ export default {
setLanguage: {
type: Function,
required: true
},
globeIcon: {
type: Boolean,
default: true
}
},
computed: {

View file

@ -188,9 +188,21 @@ $modal-view-button-icon-margin: 0.5em;
overflow-y: auto;
min-height: 1em;
max-width: 500px;
max-height: 9.5em;
word-break: break-word;
white-space: pre-line;
background: rgba(0,0,0,0.6);
padding: 10px;
border-radius: 10px;
transition: 0.5s;
&:not(:hover) {
max-height: 1em;
}
&:hover {
max-height: 9.5em;
transition: 1s;
}
}
.modal-image {

View file

@ -15,11 +15,15 @@
.mention-avatar {
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
width: 1.5em;
height: 1.5em;
width: 2em;
height: 2em;
vertical-align: middle;
user-select: none;
margin-right: 0.2em;
.still-image.avatar {
border-radius: 14px;
}
}
.full {
@ -113,3 +117,11 @@
color: var(--faint, $fallback--faint);
}
}
.reply-glued-label {
.mention-avatar {
width: 1.4em;
height: 1.4em;
}
}

View file

@ -22,6 +22,11 @@
@click.prevent="onClick"
>
<!-- eslint-disable vue/no-v-html -->
<UserAvatar
v-if="shouldShowAvatar"
class="mention-avatar"
:user="user"
/>
<span class="shortName">@<span
class="userName"
v-html="userName"

View file

@ -1,7 +1,9 @@
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 { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
@ -18,11 +20,13 @@ library.add(
const MobileNav = {
components: {
SideDrawer,
Notifications
Notifications,
ConfirmModal
},
data: () => ({
notificationsCloseGesture: undefined,
notificationsOpen: false
notificationsOpen: false,
showingConfirmLogout: false
}),
created () {
this.notificationsCloseGesture = GestureService.swipeGesture(
@ -41,8 +45,17 @@ const MobileNav = {
unseenNotificationsCount () {
return this.unseenNotifications.length
},
hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name }
mergedConfig () {
return this.$store.getters.mergedConfig
},
hideSiteName () {
return this.mergedConfig.hideSiteName
},
sitename () { return this.$store.state.instance.name },
shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout
},
...mapGetters(['unreadChatCount'])
},
methods: {
toggleMobileSidebar () {
@ -68,9 +81,23 @@ const MobileNav = {
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()
},
markNotificationsAsSeen () {
// this.$refs.notifications.markAsSeen()

View file

@ -22,7 +22,7 @@
/>
</button>
<router-link
v-if="!hideSitename"
v-if="!hideSiteName"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
@ -76,6 +76,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>
@ -206,6 +218,14 @@
}
}
}
.confirm-modal.dark-overlay {
&::before {
z-index: 3000;
}
.dialog-modal.panel {
z-index: 3001;
}
}
}
</style>

View file

@ -1,6 +1,6 @@
<template>
<div
v-if="federationPolicy"
v-if="hasInstanceSpecificPolicies"
class="mrf-transparency-panel"
>
<div class="panel panel-default base01-background">
@ -76,7 +76,7 @@
</table>
</div>
<div v-if="quarantineInstances.length">
<!--<div v-if="quarantineInstances.length">
<h4>{{ $t("about.mrf.simple.quarantine") }}</h4>
<p>{{ $t("about.mrf.simple.quarantine_desc") }}</p>
@ -99,7 +99,7 @@
</td>
</tr>
</table>
</div>
</div>-->
<div v-if="ftlRemovalInstances.length">
<h4>{{ $t("about.mrf.simple.ftl_removal") }}</h4>
@ -176,7 +176,7 @@
</table>
</div>
<h2 v-if="hasKeywordPolicies">
<!--<h2 v-if="hasKeywordPolicies">
{{ $t("about.mrf.keyword.keyword_policies") }}
</h2>
@ -217,7 +217,7 @@
{{ keyword.replacement }}
</li>
</ul>
</div>
</div>-->
</div>
</div>
</div>

View file

@ -10,7 +10,7 @@ import {
faChevronDown,
faChevronUp,
faComments,
faBell,
faBolt,
faInfoCircle,
faStream,
faList,
@ -25,7 +25,7 @@ library.add(
faChevronDown,
faChevronUp,
faComments,
faBell,
faBolt,
faInfoCircle,
faStream,
faList,

View file

@ -45,7 +45,7 @@
<FAIcon
fixed-width
class="fa-scale-110"
icon="bell"
icon="bolt"
/>{{ $t("nav.interactions") }}
</router-link>
</li>

View file

@ -5,6 +5,7 @@ import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
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'
@ -36,7 +37,9 @@ const Notification = {
return {
userExpanded: false,
betterShadow: this.$store.state.interface.browserSupport.cssFilter,
unmuted: false
unmuted: false,
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false
}
},
props: [ 'notification' ],
@ -46,7 +49,8 @@ const Notification = {
UserCard,
Timeago,
Status,
RichContent
RichContent,
ConfirmModal
},
methods: {
toggleUserExpanded () {
@ -61,7 +65,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 })
@ -71,13 +94,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: {
@ -107,6 +139,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
})

View file

@ -1,9 +1,10 @@
@import '../../_variables.scss';
.notification-reaction-emoji {
width: 40px;
display: flex;
width: 32px;
display: inline-flex;
flex-direction: column;
vertical-align: middle;
}
// TODO Copypaste from Status, should unify it somehow

View file

@ -151,6 +151,7 @@
>
<Timeago
:time="notification.created_at"
:with-direction="true"
:auto-update="240"
/>
</router-link>
@ -230,6 +231,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>
</div>
</template>

View file

@ -84,7 +84,7 @@
:key="unit"
:value="unit"
>
{{ $t(`time.unit.${unit}_short`, ['']) }}
{{ $tc(`time.unit.${unit}_short`, expiryAmount, ['']) }}
</option>
</Select>
</div>

View file

@ -55,6 +55,14 @@ const pxStringToNumber = (str) => {
const PostStatusForm = {
props: [
'statusId',
'statusText',
'statusIsSensitive',
'statusPoll',
'statusFiles',
'statusMediaDescriptions',
'statusScope',
'statusContentType',
'replyTo',
'quoteId',
'repliedUser',
@ -63,6 +71,7 @@ const PostStatusForm = {
'subject',
'disableSubject',
'disableScopeSelector',
'disableVisibilitySelector',
'disableNotice',
'disableLockWarning',
'disablePolls',
@ -120,23 +129,40 @@ const PostStatusForm = {
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject } = this.$store.getters.mergedConfig
return {
dropFiles: [],
uploadingFiles: false,
error: null,
posting: false,
highlighted: 0,
newStatus: {
let statusParams = {
spoilerText: this.subject || '',
status: statusText,
sensitiveIfSubject,
sensitiveByDefault,
nsfw: !!sensitiveByDefault,
files: [],
poll: {},
mediaDescriptions: {},
visibility: this.suggestedVisibility(),
contentType
},
}
if (this.statusId) {
const statusContentType = this.statusContentType || contentType
statusParams = {
spoilerText: this.subject || '',
status: this.statusText || '',
sensitiveIfSubject,
nsfw: this.statusIsSensitive || !!sensitiveByDefault,
files: this.statusFiles || [],
poll: this.statusPoll || {},
mediaDescriptions: this.statusMediaDescriptions || {},
visibility: this.statusScope || this.suggestedVisibility(),
contentType: statusContentType
}
}
return {
dropFiles: [],
uploadingFiles: false,
error: null,
posting: false,
highlighted: 0,
newStatus: statusParams,
caret: 0,
pollFormVisible: false,
showDropIcon: 'hide',
@ -232,6 +258,9 @@ const PostStatusForm = {
uploadFileLimitReached () {
return this.newStatus.files.length >= this.fileLimit
},
isEdit () {
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
},
...mapGetters(['mergedConfig']),
...mapState({
mobileLayout: state => state.interface.mobileLayout

View file

@ -66,6 +66,13 @@
<span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
</p>
<div
v-if="isEdit"
class="visibility-notice edit-warning"
>
<p>{{ $t('post_status.edit_remote_warning') }}</p>
<p>{{ $t('post_status.edit_unsupported_warning') }}</p>
</div>
<div
v-if="!disablePreview"
class="preview-heading faint"
@ -180,6 +187,7 @@
class="visibility-tray"
>
<scope-selector
v-if="!disableVisibilitySelector"
:show-all="showAllScopes"
:user-default="userDefaultScope"
:original-scope="copyMessageScope"
@ -420,6 +428,16 @@
align-items: baseline;
}
.visibility-notice.edit-warning {
> :first-child {
margin-top: 0;
}
> :last-child {
margin-bottom: 0;
}
}
.media-upload-icon, .poll-icon, .emoji-icon {
font-size: 1.85em;
line-height: 1.1;

View file

@ -24,6 +24,7 @@
.QuoteButton {
display: flex;
margin-left: -5px;
> :first-child {
padding: 10px;
@ -44,4 +45,10 @@
}
}
@media all and (max-width: 800px) {
.QuoteButton {
display: none;
}
}
</style>

View file

@ -64,11 +64,11 @@
color: $fallback--text;
color: var(--text, $fallback--text);
border-style: solid;
border-style: dashed;
border-width: 1px;
border-radius: $fallback--attachmentRadius;
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-color: $fallback--cBlue;
border-color: var(--cBlue, $fallback--cBlue);
}
</style>

View file

@ -0,0 +1,25 @@
export default {
props: ['relationship'],
data () {
return {
inProgress: false
}
},
computed: {
label () {
if (this.inProgress) {
return this.$t('user_card.follow_progress')
} else {
return this.$t('user_card.remove_follower')
}
}
},
methods: {
onClick () {
this.inProgress = true
this.$store.dispatch('removeUserFromFollowers', this.relationship.id).then(() => {
this.inProgress = false
})
}
}
}

View file

@ -0,0 +1,13 @@
<template>
<button
class="btn button-default follow-button"
:class="{ toggled: inProgress }"
:disabled="inProgress"
:title="$t('user_card.remove_follower')"
@click="onClick"
>
{{ label }}
</button>
</template>
<script src="./remove_follower_button.js"></script>

View file

@ -1,3 +1,4 @@
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faRetweet } from '@fortawesome/free-solid-svg-icons'
@ -5,13 +6,24 @@ library.add(faRetweet)
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 {
@ -21,6 +33,13 @@ const RetweetButton = {
setTimeout(() => {
this.animated = false
}, 500)
this.hideConfirmDialog()
},
showConfirmDialog () {
this.showingConfirmDialog = true
},
hideConfirmDialog () {
this.showingConfirmDialog = false
}
},
computed: {
@ -29,6 +48,9 @@ const RetweetButton = {
},
mergedConfig () {
return this.$store.getters.mergedConfig
},
shouldConfirmRepeat () {
return this.mergedConfig.modalOnRepeat
}
}
}

View file

@ -33,6 +33,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>

View file

@ -124,6 +124,14 @@ export default {
}
const renderMisskeyMarkdown = (content) => {
// Untangle code blocks from <br> tags
const codeblocks = content.match(/(<br\/>)?(~~~|```)\w*<br\/>.+?<br\/>\2\1?/g)
if (codeblocks) {
codeblocks.forEach((pre) => {
content = content.replace(pre, pre.replaceAll('<br/>', '\n'))
})
}
marked.use(markedMfm, {
mangle: false,
gfm: false,
@ -133,6 +141,7 @@ export default {
mfmHtml.innerHTML = marked.parse(content)
// Add options with set values to CSS
if (mfmHtml.content.firstChild) {
Array.from(mfmHtml.content.firstChild.getElementsByClassName('mfm')).map((el) => {
if (el.dataset.speed) {
el.style.animationDuration = el.dataset.speed
@ -147,6 +156,7 @@ export default {
}
}
})
}
return mfmHtml.innerHTML
}
@ -359,8 +369,6 @@ export const preProcessPerLine = (html, greentext) => {
.trim()
if (cleanedString.startsWith('&gt;')) {
return `<span class='greentext'>${string}</span>`
} else if (cleanedString.startsWith('&lt;')) {
return `<span class='cyantext'>${string}</span>`
}
}

View file

@ -2,6 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEnvelope,
faLock,
faBiohazard,
faLockOpen,
faGlobe
} from '@fortawesome/free-solid-svg-icons'
@ -9,6 +10,7 @@ import {
library.add(
faEnvelope,
faGlobe,
faBiohazard,
faLock,
faLockOpen
)

View file

@ -67,7 +67,7 @@
@click="changeVis('local')"
>
<FAIcon
icon="users"
icon="biohazard"
class="fa-scale-110 fa-old-padding"
/>
</button>

View file

@ -14,6 +14,7 @@ import {
faTimes,
faFileUpload,
faFileDownload,
faSignOutAlt,
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
import {
@ -28,6 +29,7 @@ library.add(
faWindowMinimize,
faFileUpload,
faFileDownload,
faSignOutAlt,
faChevronDown
)
@ -66,6 +68,11 @@ const SettingsModal = {
closeModal () {
this.$store.dispatch('closeSettingsModal')
},
logout () {
this.$router.replace('/main/public')
this.$store.dispatch('closeSettingsModal')
this.$store.dispatch('logout')
},
peekModal () {
this.$store.dispatch('togglePeekSettingsModal')
},
@ -150,6 +157,7 @@ const SettingsModal = {
}
},
computed: {
currentUser () { return this.$store.state.users.currentUser },
currentSaveStateNotice () {
return this.$store.state.interface.settings.currentSaveStateNotice
},

View file

@ -71,5 +71,11 @@
display: flex;
flex-grow: 1;
}
.logout-button {
position: absolute;
right: 20px;
padding-right: 10px;
}
}
}

View file

@ -111,6 +111,20 @@
id="unscrolled-content"
class="extra-content"
/>
<button
v-if="currentUser"
class="button-default logout-button"
:title="$t('login.logout')"
:aria-label="$t('login.logout')"
@click.prevent="logout"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="sign-out-alt"
/>
<span>{{ $t('login.logout') }}</span>
</button>
</div>
</div>
</Modal>

View file

@ -37,6 +37,15 @@
{{ $t('settings.hide_muted_posts') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
v-if="user"
:disabled="hideFilteredStatuses"
path="hideThreadsWithBlockedUsers"
>
{{ $t('settings.hide_threads_with_blocked_users') }}
</BooleanSetting>
</li>
</ul>
</li>
<li>

View file

@ -50,6 +50,7 @@ const GeneralTab = {
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
// Future spec, still not supported in Nightly 63 as of 08/2018
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks')
}
},
components: {
@ -82,11 +83,20 @@ const GeneralTab = {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
}
},
translationLanguage: {
get: function () { return this.$store.getters.mergedConfig.translationLanguage },
set: function (val) {
this.$store.dispatch('setOption', { name: 'translationLanguage', value: val })
}
},
...SharedComputedObject()
},
methods: {
changeDefaultScope (value) {
this.$store.dispatch('setServerSideOption', { name: 'defaultScope', value })
},
setTranslationLanguage (value) {
this.$store.dispatch('setOption', { name: 'translationLanguage', value })
}
}
}

View file

@ -25,6 +25,38 @@
{{ $t('settings.hide_wallpaper') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="hideSiteFavicon"
expert="1"
>
{{ $t('settings.hide_site_favicon') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="hideSiteName"
expert="1"
>
{{ $t('settings.hide_site_name') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="showNavShortcuts"
expert="1"
>
{{ $t('settings.show_nav_shortcuts') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="showWiderShortcuts"
expert="1"
>
{{ $t('settings.show_wider_shortcuts') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }}
@ -103,6 +135,28 @@
<BooleanSetting path="renderMisskeyMarkdown">
{{ $t('settings.render_mfm') }}
</BooleanSetting>
<ul
class="setting-list suboptions"
>
<li>
<BooleanSetting
path="mfmOnHover"
:disabled="!renderMisskeyMarkdown"
>
{{ $t('settings.render_mfm_on_hover') }}
</BooleanSetting>
</li>
</ul>
</li>
<li>
<p>
<interface-language-switcher
:globe-icon="false"
:prompt-text="$t('settings.translation_language')"
:language="translationLanguage"
:set-language="setTranslationLanguage"
/>
</p>
</li>
<li>
<BooleanSetting
@ -120,6 +174,77 @@
{{ $t('settings.autohide_floating_post_button') }}
</BooleanSetting>
</li>
<li>
<h3>{{ $t('settings.columns') }}</h3>
</li>
<li>
<BooleanSetting path="disableStickyHeaders">
{{ $t('settings.disable_sticky_headers') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="showScrollbars">
{{ $t('settings.show_scrollbars') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="sidebarRight">
{{ $t('settings.right_sidebar') }}
</BooleanSetting>
</li>
<li>
<ChoiceSetting
v-if="user"
id="thirdColumnMode"
path="thirdColumnMode"
:options="thirdColumnModeOptions"
>
{{ $t('settings.third_column_mode') }}
</ChoiceSetting>
</li>
<li>
<h3>{{ $t('settings.confirmation_dialogs') }}</h3>
</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="modalOnApproveFollow">
{{ $t('settings.confirm_dialogs_approve_follow') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="modalOnDenyFollow">
{{ $t('settings.confirm_dialogs_deny_follow') }}
</BooleanSetting>
</li>
</ul>
</li>
</ul>
</div>
<div class="setting-item">

View file

@ -1,6 +1,6 @@
import { extractCommit } from 'src/services/version/version.service'
const pleromaFeCommitUrl = 'https://akkoma.dev/AkkomaGang/pleroma-fe/commit/'
const pleromaFeCommitUrl = 'https://akkoma.dev/eris/pleroma-fe/commit/'
const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commit/'
const VersionTab = {

View file

@ -8,7 +8,7 @@ import {
faSignOutAlt,
faHome,
faComments,
faBell,
faBolt,
faUserPlus,
faBullhorn,
faSearch,
@ -23,7 +23,7 @@ library.add(
faSignOutAlt,
faHome,
faComments,
faBell,
faBolt,
faUserPlus,
faBullhorn,
faSearch,

View file

@ -74,7 +74,7 @@
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="bell"
icon="bolt"
/> {{ $t("nav.interactions") }}
</router-link>
</li>

View file

@ -12,7 +12,7 @@
:key="group.role"
class="staff-group"
>
<h4>{{ $t('general.role.' + group.role) }}</h4>
<h4>The Admin Team</h4>
<basic-user-card
v-for="user in group.users"
:key="user.screen_name"
@ -30,10 +30,21 @@
.staff-group {
padding-left: 1em;
padding-top: 1em;
padding-top: 0.2em;
padding-bottom: 2px;
.basic-user-card {
padding: 0.6em 0.4em;
padding-left: 0;
display: inline-block;
}
.basic-user-card-collapsed-content {
display: none;
}
.still-image {
border-radius: 15px;
}
}

View file

@ -36,6 +36,7 @@ import {
faStar,
faEyeSlash,
faEye,
faBiohazard,
faThumbtack,
faChevronUp,
faChevronDown,
@ -56,6 +57,7 @@ library.add(
faEllipsisH,
faEyeSlash,
faEye,
faBiohazard,
faThumbtack,
faChevronUp,
faChevronDown,
@ -261,6 +263,38 @@ const Status = {
hasMentionsLine () {
return this.mentionsLine.length > 0
},
mentionsBlockedUser () {
// XXX: doesn't work on domain blocks, because users from blocked domains
// don't appear in `attentions' and therefore cannot be filtered.
let mentions = false
// find if user in mentions list is blocked
this.status.attentions.forEach((attn) => {
if (attn.id === this.currentUser.id) return
const relationship = this.$store.getters.relationship(attn.id)
if (relationship.blocking) {
mentions = true
}
})
return mentions
},
mentionsMutedUser () {
// XXX: doesn't work on domain blocks, because users from blocked domains
// don't appear in `attentions' and therefore cannot be filtered.
let mentions = false
// find if user in mentions list is blocked
this.status.attentions.forEach((attn) => {
if (attn.id === this.currentUser.id) return
const relationship = this.$store.getters.relationship(attn.id)
if (relationship.muting) {
mentions = true
}
})
return mentions
},
muted () {
if (this.statusoid.user.id === this.currentUser.id) return false
const reasonsToMute = this.userIsMuted ||
@ -269,7 +303,11 @@ const Status = {
// Wordfiltered
this.muteWordHits.length > 0 ||
// bot status
(this.muteBotStatuses && this.botStatus && !this.compact)
(this.muteBotStatuses && this.botStatus && !this.compact) ||
// mentions blocked user
this.mentionsBlockedUser ||
// mentions muted user
this.mentionsMutedUser
return !this.unmuted && !this.shouldNotMute && reasonsToMute
},
userIsMuted () {
@ -312,6 +350,9 @@ const Status = {
hideFilteredStatuses () {
return this.mergedConfig.hideFilteredStatuses
},
hideThreadsWithBlockedUsers () {
return this.mergedConfig.hideThreadsWithBlockedUsers
},
hideWordFilteredPosts () {
return this.mergedConfig.hideWordFilteredPosts
},
@ -319,8 +360,9 @@ const Status = {
return (!this.shouldNotMute) && (
(this.muted && this.hideFilteredStatuses) ||
(this.userIsMuted && this.hideMutedUsers) ||
(this.status.thread_muted && this.hideMutedThreads) ||
(this.muteWordHits.length > 0 && this.hideWordFilteredPosts)
((this.status.thread_muted || this.mentionsMutedUser) && this.hideMutedThreads) ||
(this.muteWordHits.length > 0 && this.hideWordFilteredPosts) ||
(this.mentionsBlockedUser && this.hideThreadsWithBlockedUsers)
)
},
isFocused () {
@ -397,6 +439,12 @@ const Status = {
},
visibilityLocalized () {
return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility)
},
isEdited () {
return this.status.edited_at !== null
},
editingAvailable () {
return this.$store.state.instance.editingAvailable
}
},
methods: {
@ -409,7 +457,7 @@ const Status = {
case 'direct':
return 'envelope'
case 'local':
return 'users'
return 'biohazard'
default:
return 'globe'
}

View file

@ -162,9 +162,9 @@
& .heading-reply-row {
position: relative;
margin-top: 0.2em;
align-content: baseline;
font-size: 0.85em;
margin-top: 0.2em;
line-height: 130%;
max-width: 100%;
align-items: stretch;

View file

@ -187,8 +187,29 @@
>
<Timeago
:time="status.created_at"
:with-direction="true"
:auto-update="60"
/>
<i18n-t
v-if="isEdited && editingAvailable && !isPreview"
keypath="status.edited_at"
tag="span"
class="edited-row"
>
<template #time>
<i18n-t
keypath="time.in_past"
tag="span"
>
<template>
<Timeago
:time="status.edited_at"
:auto-update="60"
/>
</template>
</i18n-t>
</template>
</i18n-t>
</router-link>
<span
v-if="status.visibility"
@ -453,6 +474,7 @@
:status="status"
@onError="showError"
@onSuccess="clearError"
@quote-toggle="toggleQuoting"
/>
</div>
</div>

View file

@ -4,10 +4,20 @@
display: flex;
flex-direction: column;
.translation {
border: 1px solid var(--accent, $fallback--link);
border-radius: var(--panelRadius, $fallback--panelRadius);
margin-top: 1em;
padding: 0.5em;
}
.emoji {
--_still_image-label-scale: 0.5;
--emoji-size: 50px;
--emoji-size: 50px;
--emoji-size: 38px;
}
.emoji:hover {
transform: scale(1.4);
transition: 0.05s;
}
._mfm_x2_ {
@ -93,7 +103,7 @@
overflow-y: hidden;
z-index: 1;
.media-body {
.media-body-wrapper {
min-height: 0;
mask:
linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
@ -143,26 +153,25 @@
color: var(--postGreentext, $fallback--cGreen);
}
.cyantext {
color: var(--postCyantext, $fallback--cBlue);
}
&.-compact {
align-items: top;
flex-direction: row;
--emoji-size: 16px;
& .body,
& .body:not(:active),
& .attachments {
max-height: 3.25em;
}
.body {
overflow: hidden;
white-space: normal;
min-width: 5em;
flex: 5 1 auto;
}
.body:not(:active) {
overflow: hidden;
mask-size: auto 3.5em, auto auto;
mask-position: 0 0, 0 0;
mask-repeat: repeat-x, repeat;
@ -197,3 +206,4 @@
}
}
}

View file

@ -43,6 +43,7 @@
</button>
<div
v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)"
class="media-body-wrapper"
>
<RichContent
:class="{ '-single-line': singleLine }"
@ -55,6 +56,23 @@
:attentions="status.attentions"
@parseReady="onParseReady"
/>
<div
v-if="status.translation"
class="translation"
>
<h4>{{ $t('status.translated_from', { language: status.translation.detected_language }) }}</h4>
<RichContent
:class="{ '-single-line': singleLine }"
class="text media-body"
:html="status.translation.text"
:emoji="status.emojis"
:handle-links="true"
:mfm="renderMisskeyMarkdown && (status.media_type === 'text/x.misskeymarkdown')"
:greentext="mergedConfig.greentext"
:attentions="status.attentions"
@parseReady="onParseReady"
/>
</div>
</div>
<button
v-show="hideSubjectStatus"

View file

@ -100,6 +100,9 @@ const StatusContent = {
maxThumbnails () {
return this.mergedConfig.maxThumbnails
},
mfmOnHover () {
return this.mergedConfig.mfmOnHover
},
...mapGetters(['mergedConfig']),
...mapState({
currentUser: state => state.users.currentUser

View file

@ -1,7 +1,7 @@
<template>
<div
class="StatusContent"
:class="{ '-compact': compact }"
:class="{ '-compact': compact, 'mfm-hover': mfmOnHover }"
>
<slot name="header" />
<StatusBody
@ -75,5 +75,17 @@
height: 50px;
}
}
&.mfm-hover:not(:hover) {
.mfm {
animation: none;
}
}
}
.quote-inline,
.quote + .link-preview {
display: none;
}
</style>

View file

@ -0,0 +1,60 @@
import { get } from 'lodash'
import Modal from '../modal/modal.vue'
import Status from '../status/status.vue'
const StatusHistoryModal = {
components: {
Modal,
Status
},
data () {
return {
statuses: []
}
},
computed: {
modalActivated () {
return this.$store.state.statusHistory.modalActivated
},
params () {
return this.$store.state.statusHistory.params
},
statusId () {
return this.params.id
},
historyCount () {
return this.statuses.length
},
history () {
return this.statuses
}
},
watch: {
params (newVal, oldVal) {
const newStatusId = get(newVal, 'id') !== get(oldVal, 'id')
if (newStatusId) {
this.resetHistory()
}
if (newStatusId || get(newVal, 'edited_at') !== get(oldVal, 'edited_at')) {
this.fetchStatusHistory()
}
}
},
methods: {
resetHistory () {
this.statuses = []
},
fetchStatusHistory () {
this.$store.dispatch('fetchStatusHistory', this.params)
.then(data => {
this.statuses = data
})
},
closeModal () {
this.$store.dispatch('closeStatusHistoryModal')
}
}
}
export default StatusHistoryModal

View file

@ -0,0 +1,46 @@
<template>
<Modal
v-if="modalActivated"
class="status-history-modal-view"
@backdropClicked="closeModal"
>
<div class="status-history-modal-panel panel">
<div class="panel-heading">
{{ $tc('status.edit_history_modal_title', historyCount - 1, { historyCount: historyCount - 1 }) }}
</div>
<div class="panel-body">
<div
v-if="historyCount > 0"
class="history-body"
>
<status
v-for="status in history"
:key="status.id"
:statusoid="status"
:isPreview="true"
class="conversation-status status-fadein panel-body"
/>
</div>
</div>
</div>
</Modal>
</template>
<script src="./status_history_modal.js"></script>
<style lang="scss">
.modal-view.status-history-modal-view {
align-items: flex-start;
}
.status-history-modal-panel {
flex-shrink: 0;
margin-top: 25%;
margin-bottom: 2em;
width: 100%;
max-width: 700px;
@media (orientation: landscape) {
margin-top: 8%;
}
}
</style>

View file

@ -51,6 +51,10 @@
width: 100%;
height: 100%;
object-fit: contain;
&::before {
line-height: 20px;
}
}
&.animated {

View file

@ -3,12 +3,19 @@
:datetime="time"
:title="localeDateString"
:class="{ warning: relativeTime.direction === 'time.in_future' }"
>
<template
v-if="withDirection"
>
{{
relativeTime.direction === '' ?
$tc(relativeTime.key, relativeTime.num, [relativeTime.num]) :
$t(relativeTime.direction, [$tc(relativeTime.key, relativeTime.num, [relativeTime.num])])
}}
</template>
<template v-else>
{{ $tc(relativeTime.key, relativeTime.num, [relativeTime.num]) }}
</template>
</time>
</template>
@ -18,7 +25,7 @@ import localeService from 'src/services/locale/locale.service.js'
export default {
name: 'Timeago',
props: ['time', 'autoUpdate', 'longFormat', 'nowThreshold'],
props: ['time', 'autoUpdate', 'longFormat', 'nowThreshold', 'withDirection'],
data () {
return {
relativeTime: { key: 'time.now', num: 0 },
@ -58,7 +65,9 @@ export default {
<style lang="scss">
@import '../../_variables.scss';
.timeago {
time.warning {
color: var(--alertWarning, $fallback--alertWarning);
}
}
</style>

View file

@ -145,7 +145,9 @@ const Timeline = {
this.$store.commit('showNewStatuses', { timeline: this.timelineName })
this.paused = false
}
if (!this.inProfile) {
window.scrollTo({ top: 0 })
}
},
fetchOlderStatuses: throttle(function () {
const store = this.$store

View file

@ -6,7 +6,7 @@ import {
faBookmark,
faEnvelope,
faHome,
faCircle
faCommentMedical
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -15,7 +15,7 @@ library.add(
faBookmark,
faEnvelope,
faHome,
faCircle
faCommentMedical
)
const TimelineMenuContent = {

View file

@ -9,22 +9,8 @@
fixed-width
class="fa-scale-110 fa-old-padding "
icon="home"
/>{{ $t("nav.home_timeline") }}
/><span :title="$t('nav.home_timeline_description')">{{ $t("nav.home_timeline") }}</span>
</router-link>
<span class="timeline-desc">{{ $t("nav.home_timeline_description") }}</span>
</li>
<li v-if="currentUser">
<router-link
class="menu-item"
:to="{ name: 'bubble-timeline' }"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding "
icon="circle"
/>{{ $t("nav.bubble_timeline") }}
</router-link>
<span class="timeline-desc">{{ $t("nav.bubble_timeline_description") }}</span>
</li>
<li v-if="currentUser || !privateMode">
<router-link
@ -35,9 +21,8 @@
fixed-width
class="fa-scale-110 fa-old-padding "
icon="users"
/>{{ $t("nav.public_tl") }}
/><span :title="$t('nav.public_timeline_description')">{{ $t("nav.public_tl") }}</span>
</router-link>
<span class="timeline-desc">{{ $t("nav.public_timeline_description") }}</span>
</li>
<li v-if="federating && (currentUser || !privateMode)">
<router-link
@ -48,9 +33,20 @@
fixed-width
class="fa-scale-110 fa-old-padding "
icon="globe"
/>{{ $t("nav.twkn") }}
/><span :title="$t('nav.twkn_timeline_description')">{{ $t("nav.twkn") }}</span>
</router-link>
</li>
<li v-if="currentUser">
<router-link
class="menu-item"
:to="{ name: 'bubble-timeline' }"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding "
icon="comment-medical"
/><span :title="$t('nav.bubble_timeline_description')">{{ $t("nav.bubble_timeline") }}</span>
</router-link>
<span class="timeline-desc">{{ $t("nav.twkn_timeline_description") }}</span>
</li>
<li v-if="currentUser">
<router-link

View file

@ -6,6 +6,7 @@ import ModerationTools from '../moderation_tools/moderation_tools.vue'
import AccountActions from '../account_actions/account_actions.vue'
import Select from '../select/select.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'
@ -32,7 +33,8 @@ export default {
data () {
return {
followRequestInProgress: false,
betterShadow: this.$store.state.interface.browserSupport.cssFilter
betterShadow: this.$store.state.interface.browserSupport.cssFilter,
showingConfirmMute: false
}
},
created () {
@ -112,6 +114,9 @@ export default {
hideFollowersCount () {
return this.isOtherUser && this.user.hide_followers_count
},
shouldConfirmMute () {
return this.mergedConfig.modalOnMute
},
...mapGetters(['mergedConfig'])
},
components: {
@ -122,14 +127,29 @@ export default {
ProgressButton,
FollowButton,
Select,
RichContent
RichContent,
ConfirmModal
},
methods: {
refetchRelationship () {
return this.$store.dispatch('fetchUserRelationship', this.user.id)
},
showConfirmMute () {
this.showingConfirmMute = true
},
hideConfirmMute () {
this.showingConfirmMute = false
},
muteUser () {
if (!this.shouldConfirmMute) {
this.doMuteUser()
} else {
this.showConfirmMute()
}
},
doMuteUser () {
this.$store.dispatch('muteUser', this.user.id)
this.hideConfirmMute()
},
unmuteUser () {
this.$store.dispatch('unmuteUser', this.user.id)

View file

@ -295,6 +295,27 @@
: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="span"
>
<template #user>
<span
v-text="user.screen_name_ui"
/>
</template>
</i18n-t>
</confirm-modal>
</teleport>
</div>
</template>

View file

@ -48,8 +48,9 @@ const UserProfile = {
},
created () {
const routeParams = this.$route.params
const hash = get(this.$route, 'hash', defaultTabKey).replace(/^#/, '')
if (hash !== '') this.tab = hash
this.load(routeParams.name || routeParams.id)
this.tab = get(this.$route, 'query.tab', defaultTabKey)
},
unmounted () {
this.stopFetching()
@ -58,6 +59,9 @@ const UserProfile = {
timeline () {
return this.$store.state.statuses.timelines.user
},
replies () {
return this.$store.state.statuses.timelines.replies
},
favorites () {
return this.$store.state.statuses.timelines.favorites
},
@ -82,28 +86,37 @@ const UserProfile = {
},
currentUser () {
return this.$store.state.users.currentUser
}
},
privateMode () { return this.$store.state.instance.private }
},
methods: {
setFooterRef (el) {
this.footerRef = el
},
load (userNameOrId) {
const startFetchingTimeline = (timeline, userId) => {
// Clear timeline only if load another user's profile
if (userId !== this.$store.state.statuses.timelines[timeline].userId) {
this.$store.commit('clearTimeline', { timeline })
}
this.$store.dispatch('startFetchingTimeline', { timeline, userId })
onRouteChange (previousTab, nextTab) {
const timelineTabMap = {
statuses: 'user',
replies: 'replies',
media: 'media'
}
// only we can see our own favourites
if (this.isUs) timelineTabMap['favorites'] = 'favorites'
const timeline = timelineTabMap[nextTab]
if (timeline) {
this.stopFetching()
this.$store.dispatch('startFetchingTimeline', { timeline: timeline, userId: this.userId })
}
},
load (userNameOrId) {
const loadById = (userId) => {
this.userId = userId
startFetchingTimeline('user', userId)
startFetchingTimeline('media', userId)
if (this.isUs) {
startFetchingTimeline('favorites', userId)
}
const timelines = ['user', 'favorites', 'replies', 'media']
timelines.forEach((timeline) => {
this.$store.commit('clearTimeline', { timeline: timeline })
})
this.onRouteChange(null, this.tab)
// Fetch all pinned statuses immediately
this.$store.dispatch('fetchPinnedStatuses', userId)
}
@ -137,6 +150,7 @@ const UserProfile = {
},
stopFetching () {
this.$store.dispatch('stopFetchingTimeline', 'user')
this.$store.dispatch('stopFetchingTimeline', 'replies')
this.$store.dispatch('stopFetchingTimeline', 'favorites')
this.$store.dispatch('stopFetchingTimeline', 'media')
},
@ -146,7 +160,7 @@ const UserProfile = {
},
onTabSwitch (tab) {
this.tab = tab
this.$router.replace({ query: { tab } })
this.$router.replace({ hash: `#${tab}` })
},
linkClicked ({ target }) {
if (target.tagName === 'SPAN') {
@ -176,8 +190,10 @@ const UserProfile = {
this.switchUser(newVal)
}
},
'$route.query': function (newVal) {
this.tab = newVal.tab || defaultTabKey
'$route.hash': function (newVal) {
const oldTab = this.tab
this.tab = newVal.replace(/^#/, '') || defaultTabKey
this.onRouteChange(oldTab, this.tab)
}
},
components: {

View file

@ -79,6 +79,26 @@
:in-profile="true"
:footer-slipgate="footerRef"
/>
<div
v-if="!currentUser"
key="replies-disabled"
:label="$t('user_card.replies')"
:title="$t('user_card.replies')"
:disabled="!currentUser"
/>
<Timeline
v-else-if="currentUser"
key="replies"
:label="$t('user_card.replies')"
:count="user.statuses_count"
:embedded="true"
:title="$t('user_card.replies')"
:timeline="replies"
timeline-name="replies"
:user-id="userId"
:in-profile="true"
:footer-slipgate="footerRef"
/>
<div
v-if="followsTabVisible"
key="followees"
@ -107,9 +127,9 @@
</FollowerList>
</div>
<Timeline
v-if="currentUser"
key="media"
:label="$t('user_card.media')"
:disabled="!media.visibleStatuses.length"
:embedded="true"
:title="$t('user_card.media')"
timeline-name="media"
@ -122,7 +142,7 @@
v-if="isUs"
key="favorites"
:label="$t('user_card.favorites')"
:disabled="!favorites.visibleStatuses.length"
:disabled="!isUs"
:embedded="true"
:title="$t('user_card.favorites')"
timeline-name="favorites"
@ -136,6 +156,22 @@
class="panel-footer"
/>
</div>
<div
v-else-if="!currentUser && privateMode"
class="panel user-profile-placeholder"
>
<div class="panel-heading">
<div class="title">
???
</div>
</div>
<div class="panel-body error">
<img
class="error-img"
src="/static/error.gif"
>
</div>
</div>
<div
v-else
class="panel user-profile-placeholder"
@ -246,6 +282,15 @@
justify-content: center;
align-items: middle;
padding: 7em;
&.error {
padding: 0em;
}
.error-img {
width: 100%;
border-radius: 0px 0px 10px 10px;
}
}
}
</style>

View file

@ -164,8 +164,8 @@
"fullname_required": "no es pot deixar en blanc",
"username_required": "no es pot deixar en blanc"
},
"fullname_placeholder": "p. ex. Anna Bofarull",
"username_placeholder": "p. ex. anna",
"fullname_placeholder": "p. ex. Lain Iwakura",
"username_placeholder": "p. ex. lain",
"captcha": "CAPTCHA",
"register": "Registre",
"reason": "Raó per a registrar-se",
@ -181,99 +181,99 @@
"avatarAltRadius": "Avatars (notificacions)",
"avatarRadius": "Avatars",
"background": "Fons de pantalla",
"bio": "Presentació",
"bio": "Bio",
"btnRadius": "Botons",
"cBlue": "Blau (respon, segueix)",
"cGreen": "Verd (republica)",
"cOrange": "Taronja (marca com a preferit)",
"cOrange": "Taronja (afavoreix)",
"cRed": "Vermell (canceŀla)",
"change_password": "Canvia la contrasenya",
"change_password_error": "No s'ha pogut canviar la contrasenya.",
"change_password_error": "Hi ha hagut un problema al canviar la teva contrasenya.",
"changed_password": "S'ha canviat la contrasenya correctament!",
"collapse_subject": "Replega les entrades amb títol",
"collapse_subject": "Replega els apunts amb assumpte",
"confirm_new_password": "Confirma la nova contrasenya",
"current_avatar": "L'avatar actual",
"current_password": "La contrasenya actual",
"current_avatar": "El teu avatar actual",
"current_password": "Contrasenya actual",
"current_profile_banner": "El fons de perfil actual",
"data_import_export_tab": "Importa o exporta dades",
"default_vis": "Abast per defecte de les entrades",
"data_import_export_tab": "Importa dades / exporta",
"default_vis": "Visibilitat per defecte dels apunts",
"delete_account": "Esborra el compte",
"delete_account_description": "Esborra permanentment les teves dades i desactiva el teu compte.",
"delete_account_error": "No s'ha pogut esborrar el compte. Si continua el problema, contacta amb l'administració del node.",
"delete_account_instructions": "Confirma que vols esborrar el compte escrivint la teva contrasenya aquí sota.",
"delete_account_error": "Hi ha hagut un problema al esborrar el teu compte. Si continua així, contacta amb l'administrador de l'instància.",
"delete_account_instructions": "Escriu la teva contrasenya en el camp de sota per a confirmar esborrar el compte.",
"export_theme": "Desa el tema",
"filtering": "Filtres",
"filtering_explanation": "Es silenciaran totes les entrades que continguin aquestes paraules. Separa-les per línies",
"follow_export": "Exporta la llista de contactes",
"follow_export_button": "Exporta tots els comptes que segueixes a un fitxer CSV",
"filtering": "Filtrant",
"filtering_explanation": "Es silenciaran tots els apunts que continguin aquestes paraules, una per línia",
"follow_export": "Exporta els seguits",
"follow_export_button": "Exporta els teus seguits a un fitxer CSV",
"follow_export_processing": "S'està processant la petició. Aviat podràs descarregar el fitxer",
"follow_import": "Importa els contactes",
"follow_import_error": "No s'ha pogut importar els contactes",
"follows_imported": "S'han importat els contactes. Trigaran una estoneta en ser processats.",
"follow_import": "Importa els seguits",
"follow_import_error": "Error al importar els seguidors",
"follows_imported": "S'han importat els seguits! Processar-los portarà una estona.",
"foreground": "Primer pla",
"general": "General",
"hide_attachments_in_convo": "Amaga els adjunts en les converses",
"hide_attachments_in_tl": "Amaga els adjunts en el flux d'entrades",
"import_followers_from_a_csv_file": "Importa els contactes des d'un fitxer CSV",
"hide_attachments_in_tl": "Amaga els adjunts en la línia de temps",
"import_followers_from_a_csv_file": "Importa els seguits des d'un fitxer CSV",
"import_theme": "Carrega un tema",
"inputRadius": "Caixes d'entrada de text",
"inputRadius": "Camps d'entrada",
"instance_default": "(default: {value})",
"interfaceLanguage": "Llengua de la interfície",
"invalid_theme_imported": "No s'ha entès l'arxiu carregat perquè no és un tema vàlid de Pleroma. No s'ha fet cap canvi als temes actuals.",
"limited_availability": "No està disponible en aquest navegador",
"invalid_theme_imported": "L'arxiu seleccionat no és un tema vàlid de Akkoma. No s'ha fet cap canvi al teu tema actual.",
"limited_availability": "No està disponible en el teu navegador",
"links": "Enllaços",
"lock_account_description": "Restringeix el teu compte només a seguidores aprovades",
"loop_video": "Reprodueix els vídeos en bucle",
"loop_video_silent_only": "Reprodueix en bucles només els vídeos sense so (com els \"GIF\" de Mastodon)",
"lock_account_description": "Restringeix el teu compte només a seguidors aprovats",
"loop_video": "Vídeos en bucle",
"loop_video_silent_only": "Només bucle de vídeos sense so (com els \"GIF\" de Mastodon)",
"name": "Nom",
"name_bio": "Nom i presentació",
"name_bio": "Nom i bio",
"new_password": "Contrasenya nova",
"notification_visibility": "Notifica'm quan algú",
"notification_visibility_follows": "Comença a seguir-me",
"notification_visibility_likes": "Favorits",
"notification_visibility_mentions": "Em menciona",
"notification_visibility_repeats": "Republica una entrada meva",
"no_rich_text_description": "Neteja el formatat de text de totes les entrades",
"nsfw_clickthrough": "Amaga el contingut NSFW darrer d'una imatge clicable",
"oauth_tokens": "Llistats OAuth",
"notification_visibility": "Tipus de notificacions a mostrar",
"notification_visibility_follows": "Seguits",
"notification_visibility_likes": "m'afavoreix",
"notification_visibility_mentions": "em menciona",
"notification_visibility_repeats": "em repeteix",
"no_rich_text_description": "Neteja el format de text de tots els apunts",
"nsfw_clickthrough": "Amaga els Mèdia sensibles/NSFW",
"oauth_tokens": "Codis OAuth",
"token": "Token",
"refresh_token": "Actualitza el token",
"valid_until": "Vàlid fins",
"revoke_token": "Revocar",
"revoke_token": "Revoca",
"panelRadius": "Panells",
"pause_on_unfocused": "Pausa la reproducció en continu quan la pestanya perdi el focus",
"pause_on_unfocused": "Pausa quan la pestanya perdi el focus",
"presets": "Temes",
"profile_background": "Fons de pantalla",
"profile_banner": "Fons de perfil",
"profile_background": "Fons del perfil",
"profile_banner": "Banner del perfil",
"profile_tab": "Perfil",
"radii_help": "Configura l'arrodoniment de les vores (en píxels)",
"replies_in_timeline": "Respostes al flux",
"replies_in_timeline": "Respostes en línia de temps",
"reply_visibility_all": "Mostra totes les respostes",
"reply_visibility_following": "Mostra només les respostes a entrades meves o d'usuàries que jo segueixo",
"reply_visibility_self": "Mostra només les respostes a entrades meves",
"saving_err": "No s'ha pogut desar la configuració",
"saving_ok": "S'ha desat la configuració",
"reply_visibility_following": "Mostra només les respostes dirigides a mi o a usuaris que segueixo",
"reply_visibility_self": "Mostra només les respostes dirigides a mi",
"saving_err": "Error al desar la configuració",
"saving_ok": "Configuració desada",
"security_tab": "Seguretat",
"set_new_avatar": "Canvia l'avatar",
"set_new_profile_background": "Canvia el fons de pantalla",
"set_new_profile_banner": "Canvia el fons del perfil",
"set_new_avatar": "Establir un nou avatar",
"set_new_profile_background": "Canvia el fons del perfil",
"set_new_profile_banner": "Establir un nou banner del perfil",
"settings": "Configuració",
"stop_gifs": "Anima els GIF només en passar-hi el ratolí per sobre",
"streaming": "Carrega automàticament entrades noves quan estigui a dalt de tot",
"stop_gifs": "Anima les imatges animades fins que hi passis el cursor per sobre",
"streaming": "Mostra automàticament els nous apunts quan et desplacis a la part superior",
"text": "Text",
"theme": "Tema",
"theme_help": "Personalitza els colors del tema. Escriu-los en format RGB hexadecimal (#rrggbb).",
"tooltipRadius": "Missatges sobreposats",
"user_settings": "Configuració personal",
"theme_help": "Utilitza els codis de color hex (#rrggbb) per a personalitzar el color del teu tema.",
"tooltipRadius": "Globus/alertes",
"user_settings": "Configuració d'usuari",
"values": {
"false": "no",
"true": "sí"
},
"show_moderator_badge": "Mostra una insígnia de Moderació en el meu perfil",
"show_admin_badge": "Mostra una insígnia \"d'Administració\" en el meu perfil",
"show_moderator_badge": "Mostra l'insígnia \"Moderador\" en el meu perfil",
"show_admin_badge": "Mostra l'insígnia \"Administrador\" en el meu perfil",
"hide_followers_description": "No mostris qui m'està seguint",
"hide_follows_description": "No mostris a qui segueixo",
"notification_visibility_emoji_reactions": "Reaccions",
"notification_visibility_emoji_reactions": "reacciona",
"new_email": "Nou correu electrònic",
"profile_fields": {
"value": "Contingut",
@ -281,69 +281,69 @@
"add_field": "Afegeix un camp",
"label": "Metadades del perfil"
},
"mutes_tab": "Silenciaments",
"mutes_tab": "Silenciats",
"interface": "Interfície",
"instance_default_simple": "(per defecte)",
"checkboxRadius": "Caselles",
"import_blocks_from_a_csv_file": "Importa bloquejos des d'un arxiu csv",
"hide_post_stats": "Amaga les estadístiques de les entrades (p. ex. el nombre de favorits)",
"hide_post_stats": "Amaga les estadístiques dels apunts (p. ex. el número de favorits)",
"use_one_click_nsfw": "Obre els adjunts NSFW amb només un clic",
"hide_muted_posts": "Amaga les entrades de comptes silenciats",
"avatar_size_instruction": "La mida mínima recomanada per la imatge de l'avatar és de 150x150 píxels.",
"hide_muted_posts": "Amaga els apunts de comptes silenciats",
"avatar_size_instruction": "La mida mínima recomanada per les imatges dels avatars és de 150x150 píxels.",
"domain_mutes": "Dominis",
"discoverable": "Permet la descoberta d'aquest compte en resultats de cerques i altres serveis",
"discoverable": "Permet descobrir aquest compte en resultats de cerques i altres serveis",
"mutes_and_blocks": "Silenciaments i bloquejos",
"composing": "Composant",
"chatMessageRadius": "Missatge de xat",
"changed_email": "Correu electrònic canviat amb èxit!",
"change_email_error": "Hi ha hagut un problema al canviar el teu correu electrònic.",
"change_email": "Canvia el correu electrònic",
"bot": "Aquest és un compte automatitzat",
"bot": "Aquest és un compte bot",
"blocks_tab": "Bloquejos",
"blocks_imported": "Bloquejos importats! Processar-los pot trigar una mica.",
"block_import_error": "Error al importar bloquejos",
"block_import": "Importa bloquejos",
"block_export_button": "Exporta els teus bloquejos a un arxiu csv",
"block_export": "Exporta bloquejos",
"allow_following_move": "Permet el seguiment automàtic quan un compte a qui seguim es mou",
"allow_following_move": "Permet el seguiment automàtic quan un compte a qui seguim es mogui",
"mfa": {
"scan": {
"secret_code": "Clau",
"title": "Escanejar",
"desc": "S'està usant l'aplicació two-factor, escaneja aquest codi QR o introdueix la clau de text:"
"desc": "S'està usant la teva aplicació de dos factors, escaneja aquest codi QR o introdueix la clau de text:"
},
"authentication_methods": "Mètodes d'autenticació",
"waiting_a_recovery_codes": "Rebent còpies de seguretat dels codis…",
"recovery_codes": "Codis de recuperació.",
"warning_of_generate_new_codes": "Quan generes nous codis de recuperació, els antics ja no funcionaran més.",
"warning_of_generate_new_codes": "Quan generes nous codis de recuperació, els teus antics ja no funcionaran més.",
"generate_new_recovery_codes": "Genera nous codis de recuperació",
"otp": "OTP",
"confirm_and_enable": "Confirmar i habilitar OTP",
"recovery_codes_warning": "Anote els codis o guarda'ls en un lloc segur, o no els veuràs una altra volta. Si perds l'accés a la teua aplicació 2FA i els codis de recuperació, no podràs accedir al compte.",
"confirm_and_enable": "Confirma i habilita OTP",
"recovery_codes_warning": "Anota els codis o desa'ls en un lloc segur - si no ho fas, no els podràs veure mai més . Si perds l'accés a la teva aplicació 2FA i als codis de recuperació, no podràs accedir al compte.",
"title": "Autenticació de dos factors",
"setup_otp": "Configurar OTP",
"wait_pre_setup_otp": "preconfiguració OTP",
"verify": {
"desc": "Per habilitar l'autenticació two-factor, introdueix el codi des de la teva aplicació two-factor:"
"desc": "Per a habilitar l'autenticació de dos factors, introdueix el codi des de la teva aplicació de dos factors:"
}
},
"enter_current_password_to_confirm": "Posar la contrasenya actual per confirmar la teva identitat",
"enter_current_password_to_confirm": "Posa la teva contrasenya actual per a confirmar la teva identitat",
"security": "Seguretat",
"app_name": "Nom de l'aplicació",
"subject_line_mastodon": "Com a mastodon: copiar com és",
"mute_export_button": "Exportar silenciats a un fitxer csv",
"mute_export_button": "Exportar els teus silenciats a un fitxer csv",
"mute_import_error": "Error al importar silenciats",
"mutes_imported": "Silenciats importats! Processar-los portarà una estona.",
"import_mutes_from_a_csv_file": "Importar silenciats des d'un fitxer csv",
"word_filter": "Filtre de paraules",
"hide_media_previews": "Ocultar les vistes prèvies multimèdia",
"hide_filtered_statuses": "Amagar estats filtrats",
"hide_filtered_statuses": "Amaga apunts filtrats",
"play_videos_in_modal": "Reproduir vídeos en un marc emergent",
"file_export_import": {
"errors": {
"invalid_file": "El fitxer seleccionat no és vàlid com a còpia de seguretat de la configuració. No s'ha realitzat cap canvi.",
"invalid_file": "El fitxer seleccionat no és suportat per Akkoma com a còpia de seguretat de la configuració. No s'ha realitzat cap canvi.",
"file_too_new": "Versió important incompatible: {fileMajor}, aquest PleromaFE (configuració versió {feMajor}) és massa antiga per gestionar-lo",
"file_too_old": "Versió important incompatible: {fileMajor}, la versió del fitxer és massa antiga i no està implementada (s'ha establert un mínim ver. {feMajor})",
"file_too_old": "Versió important incompatible: {fileMajor}, la versió del fitxer és massa antiga i no està suportada (min. set. ver. {feMajor})",
"file_slightly_new": "La versió menor del fitxer és diferent, alguns paràmetres podrien no carregar-se"
},
"backup_settings": "Còpia de seguretat de la configuració a un fitxer",
@ -352,42 +352,42 @@
"backup_restore": "Còpia de seguretat de la configuració"
},
"user_mutes": "Usuaris",
"subject_line_email": "Com a l'email: \"re: tema\"",
"subject_line_email": "Com a l'email: \"re: assumpte\"",
"search_user_to_block": "Busca a qui vols bloquejar",
"save": "Guardar els canvis",
"save": "Desar els canvis",
"use_contain_fit": "No retallar els adjunts en miniatures",
"reset_profile_background": "Restablir fons del perfil",
"reset_profile_banner": "Restablir banner del perfil",
"emoji_reactions_on_timeline": "Mostrar reaccions emoji al flux",
"max_thumbnails": "Quantitat màxima de miniatures per publicació",
"hide_user_stats": "Amagar les estadístiques de l'usuari (p. ex. el nombre de seguidors)",
"emoji_reactions_on_timeline": "Mostra reaccions emoji en la línia de temps",
"max_thumbnails": "Quantitat màxima de miniatures per apunt (buit = sense limit)",
"hide_user_stats": "Amaga les estadístiques de l'usuari (p. ex. el número de seguidors)",
"reset_banner_confirm": "Realment vols restablir el banner?",
"reset_background_confirm": "Realment vols restablir el fons del perfil?",
"subject_input_always_show": "Sempre mostrar el camp del tema",
"reset_background_confirm": "Realment vols restablir el fons?",
"subject_input_always_show": "Sempre mostrar el camp del assumpte",
"subject_line_noop": "No copiar",
"subject_line_behavior": "Copiar el tema a les respostes",
"subject_line_behavior": "Copiar l'assumpte en les respostes",
"search_user_to_mute": "Busca a qui vols silenciar",
"mute_export": "Exportar silenciats",
"scope_copy": "Copiar visibilitat quan contestes (En els missatges directes sempre es copia)",
"reset_avatar": "Restablir avatar",
"right_sidebar": "Mostrar barra lateral a la dreta",
"scope_copy": "Copiar visibilitat quan responguis (en els missatges directes sempre es copia)",
"reset_avatar": "Restablir l'avatar",
"right_sidebar": "Ordre invers de les columnes",
"no_blocks": "No hi han bloquejats",
"no_mutes": "No hi han silenciats",
"hide_follows_count_description": "No mostrar el nombre de comptes que segueixo",
"hide_follows_count_description": "No mostrar el número dels meus seguits",
"mute_import": "Importar silenciats",
"hide_all_muted_posts": "Ocultar publicacions silenciades",
"hide_all_muted_posts": "Ocultar apunts silenciades",
"hide_wallpaper": "Amagar el fons de la instància",
"notification_visibility_moves": "Usuari Migrat",
"reply_visibility_following_short": "Mostrar respostes als meus seguidors",
"reply_visibility_self_short": "Mostrar respostes només a un mateix",
"autohide_floating_post_button": "Ocultar automàticament el botó 'Nova Publicació' (mòbil)",
"minimal_scopes_mode": "Minimitzar les opcions de visibilitat de la publicació",
"sensitive_by_default": "Marcar publicacions com a sensibles per defecte",
"useStreamingApi": "Rebre publicacions i notificacions en temps real",
"hide_isp": "Ocultar el panell especific de la instància",
"notification_visibility_moves": "es mou",
"reply_visibility_following_short": "Mostrar respostes als meus seguits",
"reply_visibility_self_short": "Mostrar només respostes a mi mateix",
"autohide_floating_post_button": "Ocultar automàticament el botó 'Nou Apunt' (mòbil)",
"minimal_scopes_mode": "Minimitzar les opcions de selecció del abast del apunt",
"sensitive_by_default": "Marcar apunts com a sensibles per defecte",
"useStreamingApi": "Rebre apunts i notificacions en temps real",
"hide_isp": "Amaga el panell especific de la instància",
"preload_images": "Precarregar les imatges",
"setting_changed": "La configuració és diferent a la predeterminada",
"hide_followers_count_description": "No mostrar el nombre de seguidors",
"hide_followers_count_description": "No mostrar el número de seguidors",
"reset_avatar_confirm": "Realment vols restablir l'avatar?",
"accent": "Accent",
"useStreamingApiWarning": "És genial emprar-lo. Si es trenca, refresca, suposo?",
@ -397,10 +397,10 @@
"size": "Mida (en píxels)",
"custom": "Personalitza",
"_tab_label": "Fonts",
"help": "Selecciona la font per als elements de la interfície. Per a \"personalitzat\" deus escriure el nom de la font exactament com apareix al sistema.",
"help": "Selecciona la font per als elements de la interfície. Per a \"personalitzat\" has d'escriure el nom de la font exactament com apareix al sistema.",
"components": {
"post": "Text de les publicacions",
"postCode": "Text monoespai en publicació (text enriquit)",
"post": "Text dels apunts",
"postCode": "Text mono-espai en un apunt (text enriquit)",
"input": "Camps d'entrada",
"interface": "Interfície"
},
@ -418,19 +418,19 @@
"checkbox": "He llegit els termes i condicions",
"link": "un bonic enllaç",
"fine_print": "Llegiu el nostre {0} per no aprendre res útil!",
"text": "Un grapat més de {0} i {1}"
"text": "Un grapat més {0} i {1}"
},
"shadows": {
"spread": "Difon",
"filter_hint": {
"drop_shadow_syntax": "{0} no suporta el paràmetre {1} i la paraula clau {2}.",
"avatar_inset": "Tingues en compte que combinar ombres interiors i no interiors als avatars podria donar resultats inesperats amb avatars transparents.",
"inset_classic": "Les ombres interiors estaran usant {0}",
"inset_classic": "Les ombres interiors usaran {0}",
"always_drop_shadow": "Advertència, aquesta ombra sempre utilitza {0} quan el navegador ho suporta.",
"spread_zero": "Ombres amb propagació > 0 apareixeran com si estigueren posades a zero"
},
"components": {
"popup": "Texts i finestres emergents (popups & tooltips)",
"popup": "Globus i finestres emergents",
"panel": "Panell",
"panelHeader": "Capçalera del panell",
"avatar": "Avatar de l'usuari (en vista de perfil)",
@ -438,8 +438,8 @@
"buttonHover": "Botó (surant)",
"buttonPressed": "Botó (pressionat)",
"topBar": "Barra superior",
"buttonPressedHover": "Botó (surant i pressionat)",
"avatarStatus": "Avatar de l'usuari (en vista de publicació)",
"buttonPressedHover": "Botó (pressionat i surant)",
"avatarStatus": "Avatar de l'usuari (en vista de apunt)",
"button": "Botó"
},
"hintV3": "per a les ombres també pots usar la notació {0} per a utilitzar un altre espai de color.",
@ -456,24 +456,24 @@
"future_version_imported": "El fitxer importat es va crear per a una versió del front-end més recent.",
"migration_snapshot_ok": "Per a estar segurs, s'ha carregat la instantània del tema. Pots intentar carregar les dades del tema.",
"migration_napshot_gone": "Per alguna raó, faltava la instantània, algunes coses podrien veure's diferents del que recordes.",
"snapshot_source_mismatch": "Conflicte de versions: probablement el front-end s'ha revertit i actualitzat una altra volta, si has canviat el tema en una versió anterior, segurament vols utilitzar la versió antiga; d'altra banda utilitza la nova versió.",
"snapshot_source_mismatch": "Conflicte de versions: probablement el front-end s'ha revertit i actualitzat de nou, si has canviat el tema en una versió anterior, segurament vols utilitzar la versió antiga; d'altra banda utilitza la nova versió.",
"v2_imported": "El fitxer que has importat va ser creat per a un front-end més antic. Intentem maximitzar la compatibilitat, però podrien haver inconsistències.",
"fe_upgraded": "El motor de temes de PleromaFE es va actualitzar després de l'actualització de la versió.",
"snapshot_missing": "No hi havia cap instantània del tema al fitxer, per tant podria veure's diferent del previst originalment.",
"upgraded_from_v2": "PleromaFE s'ha actualitzat, el tema pot veure's un poc diferent de com recordes.",
"upgraded_from_v2": "PleromaFE s'ha actualitzat, el tema es pot veure una mica diferent de com el recordes.",
"fe_downgraded": "Versió de PleromaFE revertida.",
"older_version_imported": "El fitxer que has importat va ser creat en una versió del front-end més antiga.",
"snapshot_present": "S'ha carregat la instantània del tema, de manera que tots els valors estan sobreescrits. En canvi, podeu carregar les dades reals del tema."
},
"keep_as_is": "Mantindre com està",
"save_load_hint": "Les opcions \"Mantindre\" conserven les opcions configurades actualment al seleccionar o carregar temes, també emmagatzema aquestes opcions quan s'exporta un tema. Quan es desactiven totes les caselles de verificació, el tema exportat ho guardarà tot.",
"save_load_hint": "Les opcions \"Mantindre\" conserven les opcions configurades actualment al seleccionar o carregar temes, també emmagatzema aquestes opcions quan s'exporta un tema. Quan es desactiven totes les caselles de verificació, exportar el tema ho desarà tot.",
"keep_color": "Mantindre colors",
"keep_opacity": "Mantindre opacitat",
"keep_shadows": "Mantindre ombres",
"keep_fonts": "Mantindre fonts",
"keep_roundness": "Mantindre rodoneses",
"clear_all": "Netejar tot",
"reset": "Reinciar",
"reset": "Reiniciar",
"load_theme": "Carregar tema",
"use_source": "Nova versió",
"clear_opacity": "Netejar opacitat"
@ -482,9 +482,9 @@
"contrast": {
"hint": "El ràtio de contrast és {ratio}. {level} {context}",
"level": {
"bad": "no compleix amb cap pauta d'accecibilitat",
"aaa": "Compleix amb el nivell AA (recomanat)",
"aa": "Compleix amb el nivell AA (mínim)"
"bad": "no compleix amb cap pauta d'accessibilitat",
"aaa": "Compleix amb la guia del nivell AA (recomanat)",
"aa": "Compleix amb la guia del nivell AA (mínim)"
},
"context": {
"18pt": "per a textos grans (+18pt)",
@ -501,8 +501,8 @@
"pressed": "Pressionat",
"chat": {
"outgoing": "Eixint",
"border": "Borde",
"incoming": "Entrants"
"border": "Vora",
"incoming": "Entrant"
},
"borders": "Bordes",
"panel_header": "Capçalera del panell",
@ -512,8 +512,8 @@
"toggled": "Commutat",
"alert": "Fons d'alertes",
"alert_error": "Error",
"alert_warning": "Precaució",
"post": "Publicacions/Biografies d'usuaris",
"alert_warning": "Avís",
"post": "Apunts/Bio d'usuari",
"badge_notification": "Notificacions",
"selectedMenu": "Element del menú seleccionat",
"tabs": "Pestanyes",
@ -524,7 +524,7 @@
"highlight": "Elements destacats",
"disabled": "Deshabilitat",
"icons": "Icones",
"selectedPost": "Publicació seleccionada",
"selectedPost": "Apunt seleccionat",
"underlay": "Subratllat"
},
"common_colors": {
@ -538,32 +538,32 @@
}
},
"version": {
"frontend_version": "Versió \"Frontend\"",
"backend_version": "Versió \"backend\"",
"frontend_version": "Versió del \"Frontend\"",
"backend_version": "Versió del \"backend\"",
"title": "Versió"
},
"theme_help_v2_1": "També pots anular alguns components de color i opacitat activant la casella. Usa el botó \"Esborrar tot\" per esborrar totes les anulacions.",
"theme_help_v2_1": "També pots anul·lar els colors d'alguns components i la seva opacitat activant la casella. Usa el botó \"Esborrar tot\" per esborrar totes les anul·lacions.",
"type_domains_to_mute": "Buscar dominis per a silenciar",
"greentext": "Text verd (meme arrows)",
"fun": "Divertit",
"notification_setting_filters": "Filtres",
"virtual_scrolling": "Optimitzar la representació del flux",
"virtual_scrolling": "Optimitza el renderitzat de la línia de temps",
"notification_setting_block_from_strangers": "Bloqueja les notificacions dels usuaris que no segueixes",
"enable_web_push_notifications": "Habilitar notificacions del navegador",
"notification_blocks": "Bloquejar a un usuari para totes les notificacions i també les cancel·la.",
"notification_blocks": "Bloquejar a un usuari para totes les notificacions i cancel·la la subscripció.",
"more_settings": "Més opcions",
"notification_setting_privacy": "Privacitat",
"upload_a_photo": "Pujar una foto",
"notification_setting_hide_notification_contents": "Amagar el remitent i els continguts de les notificacions push",
"notifications": "Notificacions",
"notification_mutes": "Per a deixar de rebre notificacions d'un usuari en concret, silencia'l-ho.",
"theme_help_v2_2": "Les icones per baix d'algunes entrades són indicadors del contrast del fons/text, desplaça el ratolí per a més informació. Tingues en compte que quan s'utilitzen indicadors de contrast de transparència es mostra el pitjor cas possible.",
"hide_shoutbox": "Oculta la casella de gàbia de grills",
"always_show_post_button": "Mostra sempre el botó flotant de publicació nova",
"pad_emoji": "Acompanya els emojis amb espais en afegir des del selector",
"notification_mutes": "Per a deixar de rebre notificacions d'un usuari en concret, silencia'l.",
"theme_help_v2_2": "Les icones per sota d'algunes entrades són indicadors del contrast del fons/text, posiciona el ratolí al damunt per a més informació. Tingues en compte que quan s'utilitzen indicadors de contrast de transparència es mostra el pitjor cas possible.",
"hide_shoutbox": "Amaga la casella de gàbia de grills",
"always_show_post_button": "Mostra sempre el botó flotant d'Apunt Nou",
"pad_emoji": "Acompanya els emojis amb espais al afegir-los des del selector",
"mentions_new_style": "Enllaços d'esment més elegants",
"mentions_new_place": "Posa les mencions en una línia separada",
"post_status_content_type": "Format de publicació",
"post_status_content_type": "Tipus de contingut del apunt",
"expert_mode": "Mostra avançat",
"setting_server_side": "Aquest ajust està lligat al teu perfil i afectarà a totes les sessions i clients",
"account_backup": "Copia de seguretat del compte",
@ -572,7 +572,7 @@
"account_alias": "Àlies del compte",
"list_aliases_error": "Error al obtenir els àlies: {error}",
"move_account_notes": "Si vols moure el compte a un altre lloc has d'anar a aquest altre compte i afegir un àlies que apunti a aquest.",
"hide_wordfiltered_statuses": "Amaga publicacions filtrades per paraules",
"hide_wordfiltered_statuses": "Amaga apunts filtrats per paraules",
"account_privacy": "Privacitat",
"hide_favorites_description": "No mostrar la llista dels meus favorits (la gent seguirà sent notificada)",
"mascot": "Mascota de Mastodon FE",
@ -585,7 +585,7 @@
"mention_link_show_tooltip": "Mostra noms d'usuari complet com a globus per a usuaris remots",
"notification_setting_hide_if_cw": "Amaga els continguts de les publicacions push si son sota un Avís de Contingut",
"mute_bot_posts": "Silencia publicacions de bot",
"post_look_feel": "Aspecte i Sensació de les Publicacions",
"post_look_feel": "Aspecte i Sensació dels apunts",
"mention_links": "Enllaços de mencions",
"email_language": "Llengua per a rebre correus des d'el servidor",
"wordfilter": "Filtre de paraules",
@ -594,7 +594,7 @@
"remove_backup": "Treure",
"list_backups_error": "Error al recuperar la llista de copies de seguretat: {error}",
"add_backup": "Crea una nova copia de seguretat",
"added_backup": "Afegida una nova còpia de seguretat nova.",
"added_backup": "Afegida una nova còpia de seguretat.",
"add_backup_error": "Error al afegir una nova còpia de seguretat: {error}",
"current_mascot": "La teva mascota actual",
"account_alias_table_head": "Àlies",
@ -607,11 +607,11 @@
"move_account_target": "Compte destí (p.ex. {example})",
"moved_account": "El compte s'ha mogut.",
"move_account_error": "Error al moure el compte: {error}",
"hide_bot_indication": "Amaga l'indicació de bot en las publicacions",
"hide_bot_indication": "Amaga l'indicació de bot en els apunts",
"hide_muted_threads": "Amaga fils silenciats",
"posts": "Publicacions",
"posts": "Apunts",
"user_profiles": "Perfils d'usuari",
"notification_visibility_polls": "Terminis de les enquestes on has votat",
"notification_visibility_polls": "finalitza una enquesta on hi has votat",
"conversation_display": "Estil de visualització de la conversa",
"conversation_display_tree": "Estil d'arbre",
"show_scrollbars": "Mostra les barres de desplaçament de la columna lateral",
@ -631,41 +631,41 @@
"mention_link_display_full": "sempre com a noms complets (p. ex. {'@'}maria{'@'}exemple.cat)",
"mention_link_show_avatar": "Mostra l'avatar del usuari sota l'enllaç",
"mention_link_fade_domain": "Dominis esvaïts (p. ex. {'@'}exemple.cat a {'@'}joan{'@'}exemple.cat)",
"mention_link_bolden_you": "Destaca mencions per tu quan siguis mencionat",
"mention_link_bolden_you": "Destaca mencions per tu quan et mencionin",
"show_yous": "Mostra (Tu)s"
},
"time": {
"now": "ara mateix",
"now_short": "ara mateix",
"in_future": "in {0}",
"in_future": "en {0}",
"in_past": "fa {0}",
"unit": {
"day": "{0} dia",
"days": "{0} dies",
"days": "{0} dia | {0} dies",
"day_short": "{0} dia",
"days_short": "{0} dies",
"days_short": "{0}d",
"hour": "{0} hora",
"hours": "{0} hores",
"hours": "{0} hora | {0} hores",
"hour_short": "{0}h",
"hours_short": "{0}h",
"minute": "{0} minute",
"minutes": "{0} minutes",
"minutes": "{0} minuts | {0} minuts",
"minute_short": "{0}min",
"minutes_short": "{0}min",
"month": "{0} mes",
"months": "{0} mesos",
"months": "{0} mes | {0} mesos",
"month_short": "{0} mes",
"months_short": "{0} mesos",
"months_short": "{0}mes",
"second": "{0} segon",
"seconds": "{0} segons",
"seconds": "{0} segon | {0} segons",
"second_short": "{0}s",
"seconds_short": "{0}s",
"week": "{0} setmana",
"weeks": "{0} setmanes",
"weeks": "{0} setmana | {0} setmanes",
"week_short": "{0} setm.",
"weeks_short": "{0}setm.",
"year": "{0} any",
"years": "{0} anys",
"years": "{0} any | {0} anys",
"year_short": "{0} any",
"years_short": "{0}anys"
}
@ -674,17 +674,17 @@
"collapse": "Replega",
"conversation": "Conversa",
"error_fetching": "S'ha produït un error en carregar les entrades",
"load_older": "Carrega entrades anteriors",
"no_retweet_hint": "L'entrada és només per a seguidores o és \"directa\", i per tant no es pot republicar",
"repeated": "republicat",
"load_older": "Carrega apunts anteriors",
"no_retweet_hint": "L'apunt és només per a seguidors o és \"directe\" i no es pot repetir o citar",
"repeated": "repetit",
"show_new": "Mostra els nous",
"up_to_date": "Actualitzat",
"socket_reconnected": "Connexió a temps real establerta",
"socket_broke": "Connexió a temps real perduda: codi CloseEvent {0}",
"error": "Error de càrrega de la línia de temps: {0}",
"no_statuses": "No hi ha entrades",
"error": "Error carregant la línia de temps: {0}",
"no_statuses": "No hi ha apunts",
"reload": "Recarrega",
"no_more_statuses": "No hi ha més entrades"
"no_more_statuses": "No hi ha més apunts"
},
"user_card": {
"approve": "Aprova",
@ -692,35 +692,35 @@
"blocked": "Bloquejat!",
"deny": "Denega",
"follow": "Segueix",
"followees": "Segueixo",
"followers": "Seguidors/es",
"followees": "Seguint",
"followers": "Seguidors",
"following": "Seguint!",
"follows_you": "Et segueix!",
"mute": "Silencia",
"muted": "Silenciat",
"per_day": "per dia",
"remote_follow": "Seguiment remot",
"statuses": "Estats",
"statuses": "Apunts",
"unblock_progress": "Desbloquejant…",
"unmute": "Deixa de silenciar",
"follow_progress": "Sol·licitant…",
"admin_menu": {
"force_nsfw": "Marca totes les entrades amb \"No segur per a entorns laborals\"",
"strip_media": "Esborra els audiovisuals de les entrades",
"force_nsfw": "Marca tots els apunts amb \"No segur per a entorns laborals\"",
"strip_media": "Esborra els Mèdia dels apunts",
"disable_any_subscription": "Deshabilita completament seguir algú",
"quarantine": "Deshabilita la federació a les entrades de les usuàries",
"quarantine": "Deshabilita la federació dels apunts dels usuaris",
"moderation": "Moderació",
"revoke_admin": "Revoca l'Admin",
"activate_account": "Activa el compte",
"deactivate_account": "Desactiva el compte",
"revoke_moderator": "Revoca Moderació",
"revoke_moderator": "Revoca Moderador",
"delete_account": "Esborra el compte",
"disable_remote_subscription": "Deshabilita seguir algú des d'una instància remota",
"delete_user": "Esborra la usuària",
"grant_admin": "Concedir permisos d'Administració",
"grant_moderator": "Concedir permisos de Moderació",
"force_unlisted": "Força que les publicacions no estiguin llistades",
"sandbox": "Força que els missatges siguin només seguidors",
"delete_user": "Esborra l'usuari",
"grant_admin": "Concedir permisos d'Administrador",
"grant_moderator": "Concedir permisos de Moderador",
"force_unlisted": "Força que els apunts no estiguin llistats",
"sandbox": "Força que els apunts siguin només per a seguidors",
"delete_user_data_and_deactivate_confirmation": "Això esborrarà permanentment les dades d'aquest compte i el desactivarà. Estàs absolutament segur?"
},
"edit_profile": "Edita el perfil",
@ -747,15 +747,15 @@
"striped": "Fons a ratlles",
"side": "Ratlla lateral"
},
"media": "Media",
"media": "Mèdia",
"domain_muted": "Desbloqueja el domini",
"deactivated": "Desactivat",
"follow_cancel": "Cancel·la la sol·licitut",
"follow_cancel": "Cancel·la la sol·licitud",
"mute_domain": "Bloqueja el domini",
"note": "Nota privada"
},
"user_profile": {
"timeline_title": "Flux personal",
"timeline_title": "Línia de temps del usuari",
"profile_loading_error": "Disculpes, hi ha hagut un error carregant aquest perfil.",
"profile_does_not_exist": "Disculpes, aquest perfil no existeix."
},
@ -865,29 +865,29 @@
}
},
"status": {
"delete": "Esborra l'entrada",
"delete_confirm": "Segur que vols esborrar aquesta entrada?",
"delete": "Esborra l'apunt",
"delete_confirm": "Segur que vols esborrar aquest apunt?",
"thread_muted_and_words": ", té les paraules:",
"show_full_subject": "Mostra tot el tema",
"show_full_subject": "Mostra tot l'assumpte",
"show_content": "Mostra el contingut",
"repeats": "Repeticions",
"bookmark": "Marcadors",
"status_unavailable": "Entrada no disponible",
"bookmark": "Marcador",
"status_unavailable": "Apunt no disponible",
"expand": "Expandeix",
"copy_link": "Copia l'enllaç a l'entrada",
"hide_full_subject": "Amaga tot el tema",
"copy_link": "Copia l'enllaç al apunt",
"hide_full_subject": "Amaga tot l'assumpte",
"favorites": "Favorits",
"replies_list": "Contestacions:",
"replies_list": "Respostes:",
"mute_conversation": "Silencia la conversa",
"thread_muted": "Fil silenciat",
"hide_content": "Amaga el contingut",
"status_deleted": "S'ha esborrat aquesta entrada",
"status_deleted": "Aquest apunt ha estat esborrat",
"nsfw": "No segur per a entorns laborals",
"unbookmark": "Desmarca",
"external_source": "Font externa",
"unpin": "Deixa de destacar al perfil",
"pinned": "Destacat",
"reply_to": "Contesta a",
"reply_to": "Respon a",
"pin": "Destaca al perfil",
"unmute_conversation": "Deixa de silenciar la conversa",
"mentions": "Mencions",
@ -919,21 +919,21 @@
},
"user_reporting": {
"additional_comments": "Comentaris addicionals",
"forward_description": "Aquest compte és d'un altre servidor. Vols enviar una còpia del report allà també?",
"forward_description": "Aquest compte és d'un altre servidor. Vols enviar-hi una còpia del informe?",
"forward_to": "Endavant a {0}",
"generic_error": "Hi ha hagut un error mentre s'estava processant la teva sol·licitud.",
"title": "Reportant {0}",
"add_comment_description": "Aquest report serà enviat a la moderació a la instància. Pots donar una explicació de per què estàs reportant aquest compte:",
"add_comment_description": "El informe serà enviat als moderadors de l'instància. Pots donar una explicació de per què estàs reportant aquest compte:",
"submit": "Envia"
},
"tool_tip": {
"add_reaction": "Afegeix una Reacció",
"accept_follow_request": "Accepta la sol·licitud de seguir",
"accept_follow_request": "Accepta la sol·licitud de seguiment",
"repeat": "Repeteix",
"reply": "Respon",
"favorite": "Favorit",
"user_settings": "Configuració d'usuària",
"reject_follow_request": "Rebutja la sol·licitud de seguir",
"user_settings": "Configuració d'usuari",
"reject_follow_request": "Rebutja la sol·licitud de seguiment",
"bookmark": "Marcador",
"media_upload": "Pujar multimèdia",
"quote": "Cita"
@ -967,13 +967,13 @@
"password_reset": "Reinicia la contrasenya",
"forgot_password": "Has oblidat la contrasenya?",
"too_many_requests": "Has arribat al límit d'intents. Prova de nou d'aquí una estona.",
"password_reset_required_but_mailer_is_disabled": "Has de reiniciar la teva contrasenya però el reinici de la contrasenya està deshabilitat. Si us plau, contacta l'administració de la teva instància.",
"placeholder": "El teu correu electrònic o nom d'usuària",
"instruction": "Introdueix la teva adreça de correu electrònic o nom d'usuària. T'enviarem un enllaç per reiniciar la teva contrasenya.",
"password_reset_required_but_mailer_is_disabled": "Has de reiniciar la teva contrasenya però el reinici de la contrasenya està desactivat. Si us plau, contacta l'administrador de la teva instància.",
"placeholder": "El teu correu electrònic o nom d'usuari",
"instruction": "Introdueix la teva adreça de correu electrònic o nom d'usuari. T'enviarem un enllaç per a reiniciar la teva contrasenya.",
"return_home": "Torna a la pàgina principal",
"password_reset_required": "Has de reiniciar la teva contrasenya per iniciar la sessió.",
"password_reset_disabled": "El reinici de la contrasenya està deshabilitat. Si us plau, contacta l'administració de la teva instància.",
"check_email": "Comprova que has rebut al correu electrònic un enllaç per reiniciar la teva contrasenya."
"password_reset_required": "Has de reiniciar la teva contrasenya per a iniciar la sessió.",
"password_reset_disabled": "El reinici de la contrasenya està desactivat. Si us plau, contacta l'administrador de la teva instància.",
"check_email": "Comprova que has rebut al correu electrònic un enllaç per a reiniciar la teva contrasenya."
},
"file_type": {
"image": "Imatge",

View file

@ -18,12 +18,12 @@
"not_applicable": "N/A",
"accept": "Accept",
"accept_desc": "This instance only accepts messages from the following instances:",
"reject": "Reject",
"reject_desc": "This instance will not accept messages from the following instances:",
"reject": "Instance Blocks",
"reject_desc": "This instance blocks posts to and from the following instances:",
"quarantine": "Quarantine",
"quarantine_desc": "This instance will send only public posts to the following instances:",
"ftl_removal": "Removal from \"Known Network\" Timeline",
"ftl_removal_desc": "This instance removes these instances from \"Known Network\" timeline:",
"quarantine_desc": "This instance will not send posts to the following instances:",
"ftl_removal": "Removal from Federated Timeline",
"ftl_removal_desc": "This instance removes these instances from the Federated Timeline:",
"media_removal": "Media Removal",
"media_removal_desc": "This instance removes media from posts on the following instances:",
"media_nsfw": "Media force-set as sensitive",
@ -106,7 +106,8 @@
"direct": "Direct",
"private": "Followers-only",
"public": "Public",
"unlisted": "Unlisted"
"unlisted": "Unlisted",
"local": "Local-only post -- only your instance can see this status"
}
},
"image_cropper": {
@ -125,7 +126,7 @@
"description": "Log in with OAuth",
"logout": "Log out",
"password": "Password",
"placeholder": "myusername",
"placeholder": "i.e. bobvibes420",
"register": "Register",
"username": "Username",
"hint": "Log in to join the discussion",
@ -148,18 +149,18 @@
"about": "About",
"administration": "Administration",
"back": "Back",
"friend_requests": "Follow requests",
"friend_requests": "Follow Requests",
"mentions": "Mentions",
"interactions": "Interactions",
"dms": "Direct messages",
"public_tl": "Public timeline",
"dms": "Direct Messages",
"public_tl": "Local Timeline",
"public_timeline_description": "Public posts from this instance",
"timeline": "Timeline",
"home_timeline": "Home timeline",
"home_timeline": "Home Timeline",
"home_timeline_description": "Posts from people you follow",
"bubble_timeline": "Bubble timeline",
"bubble_timeline": "Recommended Instances",
"bubble_timeline_description": "Posts from instances close to yours, as recommended by the admins",
"twkn": "Known Network",
"twkn": "Federated Timeline",
"twkn_timeline_description": "Posts from the entire network",
"bookmarks": "Bookmarks",
"user_search": "User Search",
@ -180,7 +181,7 @@
"load_older": "Load older notifications",
"notifications": "Notifications",
"read": "Read!",
"repeated_you": "repeated your status",
"repeated_you": "boosted your status",
"no_more_notifications": "No more notifications",
"migrated_to": "migrated to",
"reacted_with": "reacted with {0}",
@ -217,7 +218,7 @@
"storage_unavailable": "Pleroma could not access browser storage. Your login or your local settings won't be saved and you might encounter unexpected issues. Try enabling cookies."
},
"interactions": {
"favs_repeats": "Repeats and favorites",
"favs_repeats": "Boosts and favorites",
"follows": "New follows",
"moves": "User migrates",
"load_older": "Load older interactions"
@ -236,8 +237,8 @@
"text/bbcode": "BBCode",
"text/x.misskeymarkdown": "MFM"
},
"content_warning": "Subject (optional)",
"default": "Just arrived at Luna Nova Academy",
"content_warning": "Content Warning / Subject (optional)",
"default": "Just landed on Neptune",
"direct_warning_to_all": "This post will be visible to all the mentioned users.",
"direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
"posting": "Posting",
@ -245,6 +246,9 @@
"preview": "Preview",
"preview_empty": "Empty",
"empty_status_error": "Can't post an empty status with no files",
"edit_status": "Edit Status",
"edit_remote_warning": "Other instances may not support edits!",
"edit_unsupported_warning": "Polls and mentions will not be changed by editing.",
"media_description_error": "Failed to update media, try again",
"scope_notice": {
"public": "This post will be visible to everyone",
@ -257,7 +261,7 @@
"private": "Followers-only - post to followers only",
"public": "Public - post to public timelines",
"unlisted": "Unlisted - do not post to public timelines",
"local": "Local - do not federate this post"
"local": "Local - only your instance can see this status"
}
},
"registration": {
@ -369,7 +373,17 @@
"changed_password": "Password changed successfully!",
"chatMessageRadius": "Chat message",
"collapse_subject": "Collapse posts with subjects",
"columns": "Columns",
"composing": "Composing",
"confirmation_dialogs": "Confirmation options",
"confirm_dialogs": "Require confirmation for:",
"confirm_dialogs_repeat": "Repeating a post",
"confirm_dialogs_unfollow": "Unfollowing someone",
"confirm_dialogs_block": "Blocking someone",
"confirm_dialogs_mute": "Muting someone",
"confirm_dialogs_delete": "Deleting a post",
"confirm_dialogs_approve_follow": "Accepting a follow request",
"confirm_dialogs_deny_follow": "Rejecting a follow request",
"confirm_new_password": "Confirm new password",
"current_avatar": "Your current avatar",
"current_mascot": "Your current mascot",
@ -424,6 +438,10 @@
"hide_shoutbox": "Hide instance shoutbox",
"right_sidebar": "Reverse order of columns",
"always_show_post_button": "Always show floating New Post button",
"hide_site_favicon": "Hide instance favicon in top panel",
"hide_site_name": "Hide instance name in top panel",
"hide_threads_with_blocked_users": "Hide threads mentioning blocked users",
"hide_user_stats": "Hide user statistics (e.g. the number of followers)",
"hide_wallpaper": "Hide instance wallpaper",
"preload_images": "Preload images",
"use_one_click_nsfw": "Open NSFW attachments with just one click",
@ -479,7 +497,7 @@
"notification_visibility_follows": "Follows",
"notification_visibility_likes": "Favorites",
"notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats",
"notification_visibility_repeats": "Boosts",
"notification_visibility_moves": "User Migrates",
"notification_visibility_emoji_reactions": "Reactions",
"notification_visibility_polls": "Ends of polls you voted in",
@ -540,6 +558,7 @@
"conversation_display": "Conversation display style",
"conversation_display_tree": "Tree-style",
"disable_sticky_headers": "Don't stick column headers to top of the screen",
"show_nav_shortcuts": "Show extra navigation shortcuts in top panel",
"show_scrollbars": "Show side column's scrollbars",
"third_column_mode": "When there's enough space, show third column containing",
"third_column_mode_none": "Don't show third column at all",
@ -556,8 +575,10 @@
"sensitive_by_default": "Mark posts as sensitive by default",
"sensitive_if_subject": "Automatically mark images as sensitive if a subject line is specified",
"render_mfm": "Render Misskey Markdown",
"render_mfm_on_hover": "Pause MFM animations until status hover",
"useStreamingApiWarning": "It's cool use it. If it breaks refresh I guess?",
"stop_gifs": "Pause animated images until you hover on them",
"stop_gifs": "Pause animated images until you hover on them (breaks MFM emojis)",
"show_wider_shortcuts": "Show wider gap between top panel shortcuts",
"streaming": "Automatically show new posts when scrolled to the top",
"user_mutes": "Users",
"useStreamingApi": "Receive posts and notifications real-time",
@ -567,6 +588,7 @@
"theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
"tooltipRadius": "Tooltips/alerts",
"translation_language": "Automatic Translation Language",
"type_domains_to_mute": "Search domains to mute",
"upload_a_photo": "Upload a photo",
"user_settings": "User Settings",
@ -776,8 +798,8 @@
"conversation": "Conversation",
"error": "Error fetching timeline: {0}",
"load_older": "Load older statuses",
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated or quoted",
"repeated": "repeated",
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be boosted or quoted",
"repeated": "boosted",
"show_new": "Show new",
"reload": "Reload",
"up_to_date": "Up-to-date",
@ -788,15 +810,28 @@
},
"status": {
"favorites": "Favorites",
"repeats": "Repeats",
"repeats": "Boosts",
"delete": "Delete status",
"pin": "Pin on profile",
"unpin": "Unpin from profile",
"pinned": "Pinned",
"bookmark": "Bookmark",
"unbookmark": "Unbookmark",
"translate": "Translate",
"translated_from": "Translated from {language}",
"delete_confirm": "Do you really want to delete this status?",
"reply_to": "Reply to",
"delete_confirm_title": "Confirm deletion",
"edit": "Edit",
"edited_at": " (Edited {time})",
"edit_history": "Edit History",
"edit_history_modal_title": "Edited {historyCount} time | Edited {historyCount} times",
"delete_confirm_accept_button": "Yes, delete it",
"delete_confirm_cancel_button": "No, keep it",
"repeat_confirm": "Do you really want to repeat this status?",
"repeat_confirm_title": "Confirm repeat",
"repeat_confirm_accept_button": "Yes, repeat it",
"repeat_confirm_cancel_button": "No, don't repeat",
"mentions": "Mentions",
"replies_list": "Replies:",
"replies_list_with_others": "Replies (+{numReplies} other): | Replies (+{numReplies} others):",
@ -841,10 +876,23 @@
},
"user_card": {
"approve": "Approve",
"approve_confirm_title": "Approve follow request",
"approve_confirm": "Are you sure you want to let this user follow you?",
"approve_confirm_accept_button": "Yes, accept",
"approve_confirm_cancel_button": "No, cancel",
"block": "Block",
"block_confirm": "Are you sure you want to block {user}?",
"block_confirm_title": "Block user",
"block_confirm_cancel_button": "No, don't block",
"block_confirm_accept_button": "Yes, block",
"block_progress": "Blocking…",
"blocked": "Blocked!",
"deactivated": "Deactivated",
"deny": "Deny",
"deny_confirm_title": "Deny follow request",
"deny_confirm": "Are you sure you want to deny this user's follow request?",
"deny_confirm_accept_button": "Yes, deny",
"deny_confirm_cancel_button": "No, cancel",
"edit_profile": "Edit profile",
"favorites": "Favorites",
"follow": "Follow",
@ -862,9 +910,15 @@
"mention": "Mention",
"message": "Message",
"mute": "Mute",
"mute_confirm": "Are you sure you want to mute {user}?",
"mute_confirm_title": "Mute user",
"mute_confirm_cancel_button": "No, don't mute",
"mute_confirm_accept_button": "Yes, mute",
"muted": "Muted",
"per_day": "per day",
"remote_follow": "Remote follow",
"remove_follower": "Remove follower",
"replies": "With Replies",
"report": "Report",
"statuses": "Statuses",
"subscribe": "Subscribe",
@ -872,11 +926,15 @@
"unblock": "Unblock",
"unblock_progress": "Unblocking…",
"block_progress": "Blocking…",
"unfollow_confirm": "Are you sure you want to unfollow {user}?",
"unfollow_confirm_title": "Unfollow user",
"unfollow_confirm_cancel_button": "No, don't unfollow",
"unfollow_confirm_accept_button": "Yes, unfollow",
"unmute": "Unmute",
"unmute_progress": "Unmuting…",
"mute_progress": "Muting…",
"hide_repeats": "Hide repeats",
"show_repeats": "Show repeats",
"hide_repeats": "Hide boosts",
"show_repeats": "Show boosts",
"domain_muted": "Unblock domain",
"mute_domain": "Block domain",
"bot": "Bot",
@ -928,7 +986,7 @@
"tool_tip": {
"media_upload": "Upload media",
"quote": "Quote",
"repeat": "Repeat",
"repeat": "Boost",
"reply": "Reply",
"favorite": "Favorite",
"add_reaction": "Add Reaction",

View file

@ -561,69 +561,39 @@
"mention_link_display_full": "名前とドメイン、例: {'@'}foo{'@'}example.org",
"fun": "お楽しみ",
"virtual_scrolling": "タイムラインの描画を最適化する",
"type_domains_to_mute": "ミュートしたいドメインを検索",
"useStreamingApiWarning": "(実験中で、投稿を取りこぼすかもしれないので、おすすめしません)",
"useStreamingApi": "投稿と通知を、すぐに受け取る",
"user_mutes": "ユーザー",
"reset_background_confirm": "本当にバックグラウンドを初期化しますか?",
"reset_banner_confirm": "本当にバナーを初期化しますか?",
"reset_avatar_confirm": "本当にアバターを初期化しますか?",
"hide_wallpaper": "インスタンスのバックグラウンドを隠す",
"reset_profile_background": "プロフィールのバックグラウンドを初期化",
"reset_profile_banner": "プロフィールのバナーを初期化",
"reset_avatar": "アバターを初期化",
"notification_visibility_emoji_reactions": "リアクション",
"notification_visibility_moves": "ユーザーの引っ越し",
"new_email": "新しいメールアドレス",
"post_look_feel": "投稿の見た目",
"mention_links": "メンションリンク",
"profile_fields": {
"value": "内容",
"name": "ラベル",
"add_field": "枠を追加",
"label": "プロフィール補足情報"
"word_filter": "単語フィルタ"
},
"accent": "アクセント",
"mutes_imported": "ミュートをインポートしました!少し時間がかかるかもしれません。",
"emoji_reactions_on_timeline": "絵文字リアクションをタイムラインに表示",
"domain_mutes": "ドメイン",
"mutes_and_blocks": "ミュートとブロック",
"chatMessageRadius": "チャットメッセージ",
"change_email_error": "メールアドレスを変えることが、できなかったかもしれません。",
"changed_email": "メールアドレスが、変わりました!",
"change_email": "メールアドレスを変える",
"bot": "これは bot アカウントです",
"mute_export_button": "ミュートをCSVファイルにエクスポートする",
"import_mutes_from_a_csv_file": "CSVファイルからミュートをインポートする",
"mute_import_error": "ミュートのインポートに失敗しました",
"mute_import": "ミュートのインポート",
"mute_export": "ミュートのエクスポート",
"allow_following_move": "フォロー中のアカウントが引っ越したとき、自動フォローを許可する",
"setting_changed": "規定の設定と異なっています",
"greentext": "引用を緑色で表示",
"sensitive_by_default": "はじめから投稿をセンシティブとして設定",
"sensitive_if_subject": "ステータスにサブジェクトをついたらNSFWにする",
"render_mfm": "Misskey Markdownを表示",
"more_settings": "その他の設定",
"reply_visibility_self_short": "自分宛のリプライを見る",
"reply_visibility_following_short": "フォローしている人に宛てられたリプライを見る",
"hide_all_muted_posts": "ミュートした投稿を隠す",
"hide_media_previews": "メディアのプレビューを隠す",
"word_filter": "単語フィルタ",
"file_export_import": {
"errors": {
"invalid_file": "これはPleromaの設定をバックアップしたファイルではありません。",
"file_slightly_new": "ファイルのマイナーバージョンが異なり、一部の設定が読み込まれないことがあります"
},
"restore_settings": "設定をファイルから復元する",
"backup_settings_theme": "テーマを含む設定をファイルにバックアップする",
"backup_settings": "設定をファイルにバックアップする",
"backup_restore": "設定をバックアップ"
},
"save": "変更を保存",
"hide_shoutbox": "Shoutboxを表示しない",
"always_show_post_button": "投稿ボタンを常に表示",
"right_sidebar": "サイドバーを右に表示"
"status": {
"bookmark": "ブックマーク",
"copy_link": "リンクをコピー",
"delete": "ステータスを削除",
"delete_confirm": "本当にこのステータスを削除してもよろしいですか?",
"expand": "広げる",
"external_source": "外部ソース",
"favorites": "お気に入り",
"hide_content": "隠す",
"hide_full_subject": "隠す",
"mentions": "メンション",
"mute_conversation": "スレッドをミュート",
"nsfw": "閲覧注意",
"pin": "プロフィールにピン留め",
"pinned": "ピン留め",
"plus_more": "ほか{number}件",
"repeats": "リピート",
"replies_list": "返信:",
"reply_to": "返信",
"show_content": "見る",
"show_full_subject": "全部見る",
"status_deleted": "この投稿は削除されました",
"status_unavailable": "利用できません",
"thread_muted": "ミュートされたスレッド",
"thread_muted_and_words": "以下の単語を含むため:",
"translate": "翻訳",
"translated_from": "{language}から翻訳されました",
"unbookmark": "ブックマーク解除",
"unmute_conversation": "スレッドのミュートを解除",
"unpin": "プロフィールのピン留めを外す",
"you": "(あなた)"
},
"time": {
"now": "たった今",

View file

@ -19,6 +19,8 @@ import reportsModule from './modules/reports.js'
import pollsModule from './modules/polls.js'
import postStatusModule from './modules/postStatus.js'
import announcementsModule from './modules/announcements.js'
import editStatusModule from './modules/editStatus.js'
import statusHistoryModule from './modules/statusHistory.js'
import { createI18n } from 'vue-i18n'
@ -81,7 +83,9 @@ const persistedStateOptions = {
reports: reportsModule,
polls: pollsModule,
postStatus: postStatusModule,
announcements: announcementsModule
announcements: announcementsModule,
editStatus: editStatusModule,
statusHistory: statusHistoryModule
},
plugins,
strict: false // Socket modifies itself, let's ignore this for now.

View file

@ -98,6 +98,13 @@ const api = {
showImmediately: timelineData.visibleStatuses.length === 0,
timeline: 'friends'
})
} else if (message.event === 'status.update') {
dispatch('addNewStatuses', {
statuses: [message.status],
userId: false,
showImmediately: message.status.id in timelineData.visibleStatusesObject,
timeline: 'friends'
})
} else if (message.event === 'delete') {
dispatch('deleteStatusById', message.id)
}

View file

@ -31,10 +31,15 @@ export const defaultState = {
// bad name: actually hides posts of muted USERS
hideMutedPosts: undefined, // instance default
hideMutedThreads: undefined, // instance default
hideThreadsWithBlockedUsers: undefined, // instance default
hideWordFilteredPosts: undefined, // instance default
muteBotStatuses: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default
collapseMessageWithSubject: true, // instance default
padEmoji: true,
showNavShortcuts: undefined, // instance default
showWiderShortcuts: undefined, // instance default
hideSiteFavicon: undefined, // instance default
hideSiteName: undefined, // instance default
hideAttachments: false,
hideAttachmentsInConv: false,
maxThumbnails: 16,
@ -44,11 +49,11 @@ export const defaultState = {
loopVideoSilentOnly: true,
streaming: false,
emojiReactionsOnTimeline: true,
alwaysShowNewPostButton: false,
alwaysShowNewPostButton: true,
autohideFloatingPostButton: false,
pauseOnUnfocused: true,
stopGifs: true,
replyVisibility: 'all',
stopGifs: false,
replyVisibility: 'following',
thirdColumnMode: 'notifications',
notificationVisibility: {
follows: true,
@ -66,7 +71,7 @@ export const defaultState = {
highlight: {},
interfaceLanguage: browserLocale,
hideScopeNotice: false,
useStreamingApi: true,
useStreamingApi: false,
sidebarRight: undefined, // instance default
scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default
@ -75,6 +80,14 @@ 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
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: true,
@ -84,7 +97,7 @@ export const defaultState = {
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default
mentionLinkShowTooltip: undefined, // instance default
mentionLinkShowAvatar: undefined, // instance default
mentionLinkShowAvatar: true, // instance default
mentionLinkFadeDomain: undefined, // instance default
mentionLinkShowYous: undefined, // instance default
mentionLinkBoldenYou: undefined, // instance default
@ -94,12 +107,14 @@ export const defaultState = {
virtualScrolling: undefined, // instance default
sensitiveByDefault: undefined, // instance default
sensitiveIfSubject: undefined,
renderMisskeyMarkdown: undefined,
renderMisskeyMarkdown: true,
mfmOnHover: undefined, // instance default
conversationDisplay: undefined, // instance default
conversationTreeAdvanced: undefined, // instance default
conversationOtherRepliesButton: undefined, // instance default
conversationTreeFadeAncestors: undefined, // instance default
maxDepthInThread: undefined // instance default
maxDepthInThread: undefined, // instance default
translationLanguage: undefined // instance default
}
// caching the instance default properties
@ -172,6 +187,7 @@ const config = {
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
dispatch('setInstanceOption', { name: 'interfaceLanguage', value })
break
case 'thirdColumnMode':
dispatch('setLayoutWidth', undefined)

25
src/modules/editStatus.js Normal file
View file

@ -0,0 +1,25 @@
const editStatus = {
state: {
params: null,
modalActivated: false
},
mutations: {
openEditStatusModal (state, params) {
state.params = params
state.modalActivated = true
},
closeEditStatusModal (state) {
state.modalActivated = false
}
},
actions: {
openEditStatusModal ({ commit }, params) {
commit('openEditStatusModal', params)
},
closeEditStatusModal ({ commit }) {
commit('closeEditStatusModal')
}
}
}
export default editStatus

View file

@ -17,8 +17,8 @@ const defaultState = {
defaultAvatar: '/images/avi.png',
defaultBanner: '/images/banner.png',
background: '/static/aurora_borealis.jpg',
collapseMessageWithSubject: false,
greentext: false,
collapseMessageWithSubject: true,
greentext: true,
useAtIcon: false,
mentionLinkDisplay: 'short',
mentionLinkShowTooltip: true,
@ -30,12 +30,22 @@ const defaultState = {
// bad name: actually hides posts of muted USERS
hideMutedPosts: false,
hideMutedThreads: true,
hideThreadsWithBlockedUsers: false,
hideWordFilteredPosts: false,
hidePostStats: false,
hideBotIndication: false,
hideSitename: false,
hideSiteFavicon: false,
hideSiteName: false,
hideUserStats: false,
muteBotStatuses: false,
modalOnRepeat: false,
modalOnUnfollow: false,
modalOnBlock: true,
modalOnMute: false,
modalOnDelete: true,
modalOnLogout: true,
modalOnApproveFollow: false,
modalOnDenyFollow: false,
loginMethod: 'password',
logo: '/static/logo.svg',
logoMargin: '.2em',
@ -49,13 +59,16 @@ const defaultState = {
scopeCopy: true,
showFeaturesPanel: true,
showInstanceSpecificPanel: false,
showNavShortcuts: true,
showWiderShortcuts: false,
sidebarRight: false,
subjectLineBehavior: 'email',
theme: 'pleroma-dark',
virtualScrolling: true,
sensitiveByDefault: false,
sensitiveIfSubject: true,
renderMisskeyMarkdown: false,
renderMisskeyMarkdown: true,
mfmOnHover: false,
conversationDisplay: 'linear',
conversationTreeAdvanced: false,
conversationOtherRepliesButton: 'below',
@ -153,7 +166,7 @@ const instance = {
async getCustomEmoji ({ commit, state }) {
try {
const res = await window.fetch('/api/pleroma/emoji.json')
const res = await window.fetch('/api/v1/pleroma/emoji')
if (res.ok) {
const result = await res.json()
const values = Array.isArray(result) ? Object.assign({}, ...result) : result

View file

@ -0,0 +1,25 @@
const statusHistory = {
state: {
params: {},
modalActivated: false
},
mutations: {
openStatusHistoryModal (state, params) {
state.params = params
state.modalActivated = true
},
closeStatusHistoryModal (state) {
state.modalActivated = false
}
},
actions: {
openStatusHistoryModal ({ commit }, params) {
commit('openStatusHistoryModal', params)
},
closeStatusHistoryModal ({ commit }) {
commit('closeStatusHistoryModal')
}
}
}
export default statusHistory

Some files were not shown because too many files have changed in this diff Show more