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 - shpuld (shpuld@shitposter.club): CSS and styling
- Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images. - Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images.
- hj (hj@shigusegubu.club): Code - 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 # 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 # FOR ADMINS

View file

@ -10,11 +10,21 @@
<link rel="stylesheet" href="/static/font/css/lato.css"> <link rel="stylesheet" href="/static/font/css/lato.css">
<link rel="stylesheet" href="/static/mfm.css"> <link rel="stylesheet" href="/static/mfm.css">
<!--server-generated-meta--> <!--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"> <link rel="icon" type="image/png" href="/favicon.png">
</head> </head>
<body class="hidden"> <body class="hidden">
<noscript>To use Pleroma, please enable JavaScript.</noscript> <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="app"></div>
<div id="modal"></div>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
</body> </body>
</html> </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 MobileNav from './components/mobile_nav/mobile_nav.vue'
import DesktopNav from './components/desktop_nav/desktop_nav.vue' import DesktopNav from './components/desktop_nav/desktop_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.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 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 GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { windowWidth, windowHeight } from './services/window_utils/window_utils' import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
@ -33,6 +35,8 @@ export default {
SettingsModal, SettingsModal,
UserReportingModal, UserReportingModal,
PostStatusModal, PostStatusModal,
EditStatusModal,
StatusHistoryModal,
GlobalNoticeList GlobalNoticeList
}, },
data: () => ({ data: () => ({
@ -83,6 +87,7 @@ export default {
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile' return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
}, },
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
editingAvailable () { return this.$store.state.instance.editingAvailable },
layoutType () { return this.$store.state.interface.layoutType }, layoutType () { return this.$store.state.interface.layoutType },
privateMode () { return this.$store.state.instance.private }, privateMode () { return this.$store.state.instance.private },
reverseLayout () { reverseLayout () {

View file

@ -58,8 +58,10 @@
<MobilePostStatusButton /> <MobilePostStatusButton />
<UserReportingModal /> <UserReportingModal />
<PostStatusModal /> <PostStatusModal />
<EditStatusModal v-if="editingAvailable" />
<StatusHistoryModal v-if="editingAvailable" />
<SettingsModal /> <SettingsModal />
<div id="modal" /> <UpdateNotification />
<GlobalNoticeList /> <GlobalNoticeList />
</div> </div>
</template> </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: 'mediaProxyAvailable', value: features.includes('media_proxy') })
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') }) store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) 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: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'translationEnabled', value: features.includes('akkoma:machine_translation') })
const uploadLimits = metadata.uploadLimits const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
@ -376,8 +378,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
routes: routes(store), routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => { scrollBehavior: (to, _from, savedPosition) => {
if (to.matched.some(m => m.meta.dontScroll)) { if (to.matched.some(m => m.meta.dontScroll)) {
return false return {}
} }
return savedPosition || { left: 0, top: 0 } return savedPosition || { left: 0, top: 0 }
} }
}) })

View file

@ -58,7 +58,7 @@ export default (store) => {
component: RemoteUserResolver, component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute 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: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration }, { name: 'registration', path: '/registration', component: Registration },
@ -75,7 +75,7 @@ export default (store) => {
{ name: 'list-timeline', path: '/lists/:id', component: ListTimeline }, { name: 'list-timeline', path: '/lists/:id', component: ListTimeline },
{ name: 'list-edit', path: '/lists/:id/edit', component: ListEdit }, { name: 'list-edit', path: '/lists/:id/edit', component: ListEdit },
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage }, { 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 return routes

View file

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

View file

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

View file

@ -1,6 +1,8 @@
import ProgressButton from '../progress_button/progress_button.vue' import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue' import Popover from '../popover/popover.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { mapState } from 'vuex'
import { import {
faEllipsisV faEllipsisV
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
@ -14,13 +16,22 @@ const AccountActions = {
'user', 'relationship' 'user', 'relationship'
], ],
data () { data () {
return { } return {
showingConfirmBlock: false
}
}, },
components: { components: {
ProgressButton, ProgressButton,
Popover Popover,
ConfirmModal
}, },
methods: { methods: {
showConfirmBlock () {
this.showingConfirmBlock = true
},
hideConfirmBlock () {
this.showingConfirmBlock = false
},
showRepeats () { showRepeats () {
this.$store.dispatch('showReblogs', this.user.id) this.$store.dispatch('showReblogs', this.user.id)
}, },
@ -28,14 +39,33 @@ const AccountActions = {
this.$store.dispatch('hideReblogs', this.user.id) this.$store.dispatch('hideReblogs', this.user.id)
}, },
blockUser () { blockUser () {
if (!this.shouldConfirmBlock) {
this.doBlockUser()
} else {
this.showConfirmBlock()
}
},
doBlockUser () {
this.$store.dispatch('blockUser', this.user.id) this.$store.dispatch('blockUser', this.user.id)
this.hideConfirmBlock()
}, },
unblockUser () { unblockUser () {
this.$store.dispatch('unblockUser', this.user.id) this.$store.dispatch('unblockUser', this.user.id)
}, },
removeUserFromFollowers () {
this.$store.dispatch('removeUserFromFollowers', this.user.id)
},
reportUser () { reportUser () {
this.$store.dispatch('openUserReportingModal', { userId: this.user.id }) 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" class="dropdown-divider"
/> />
</template> </template>
<button
v-if="relationship.followed_by"
class="btn button-default btn-block dropdown-item"
@click="removeUserFromFollowers"
>
{{ $t('user_card.remove_follower') }}
</button>
<button <button
v-if="relationship.blocking" v-if="relationship.blocking"
class="btn button-default btn-block dropdown-item" class="btn button-default btn-block dropdown-item"
@ -59,6 +66,27 @@
</button> </button>
</template> </template>
</Popover> </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> </div>
</template> </template>

View file

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

View file

@ -132,6 +132,9 @@ const Attachment = {
...mapGetters(['mergedConfig']) ...mapGetters(['mergedConfig'])
}, },
watch: { watch: {
'attachment.description' (newVal) {
this.localDescription = newVal
},
localDescription (newVal) { localDescription (newVal) {
this.onEdit(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: { computed: {
statusId () { statusId () {
return this.$route.params.id 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> <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 <conversation
v-else
:collapsable="false" :collapsable="false"
is-page="true" is-page="true"
:status-id="statusId" :status-id="statusId"
@ -7,3 +24,23 @@
</template> </template>
<script src="./conversation-page.js"></script> <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 { reduce, filter, findIndex, clone, get } from 'lodash'
import Status from '../status/status.vue' import Status from '../status/status.vue'
import ThreadTree from '../thread_tree/thread_tree.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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -77,6 +79,9 @@ const conversation = {
const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2 const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2
return maxDepth >= 1 ? maxDepth : 1 return maxDepth >= 1 ? maxDepth : 1
}, },
streamingEnabled () {
return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
},
displayStyle () { displayStyle () {
return this.$store.getters.mergedConfig.conversationDisplay return this.$store.getters.mergedConfig.conversationDisplay
}, },
@ -339,7 +344,11 @@ const conversation = {
}, },
maybeHighlight () { maybeHighlight () {
return this.isExpanded ? this.highlight : null return this.isExpanded ? this.highlight : null
} },
...mapGetters(['mergedConfig']),
...mapState({
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus
})
}, },
components: { components: {
Status, Status,
@ -395,6 +404,11 @@ const conversation = {
setHighlight (id) { setHighlight (id) {
if (!id) return if (!id) return
this.highlight = id this.highlight = id
if (!this.streamingEnabled) {
this.$store.dispatch('fetchStatus', id)
}
this.$store.dispatch('fetchFavsAndRepeats', id) this.$store.dispatch('fetchFavsAndRepeats', id)
this.$store.dispatch('fetchEmojiReactionsBy', id) this.$store.dispatch('fetchEmojiReactionsBy', id)
}, },

View file

@ -1,4 +1,5 @@
import SearchBar from 'components/search_bar/search_bar.vue' 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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faSignInAlt, faSignInAlt,
@ -11,6 +12,11 @@ import {
faSearch, faSearch,
faTachometerAlt, faTachometerAlt,
faCog, faCog,
faGlobe,
faBolt,
faUsers,
faCommentMedical,
faBookmark,
faInfoCircle faInfoCircle
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
@ -25,12 +31,18 @@ library.add(
faSearch, faSearch,
faTachometerAlt, faTachometerAlt,
faCog, faCog,
faGlobe,
faBolt,
faUsers,
faCommentMedical,
faBookmark,
faInfoCircle faInfoCircle
) )
export default { export default {
components: { components: {
SearchBar SearchBar,
ConfirmModal
}, },
data: () => ({ data: () => ({
searchBarHidden: true, searchBarHidden: true,
@ -40,7 +52,8 @@ export default {
window.CSS.supports('-moz-mask-size', 'contain') || window.CSS.supports('-moz-mask-size', 'contain') ||
window.CSS.supports('-ms-mask-size', 'contain') || window.CSS.supports('-ms-mask-size', 'contain') ||
window.CSS.supports('-o-mask-size', 'contain') window.CSS.supports('-o-mask-size', 'contain')
) ),
showingConfirmLogout: false
}), }),
computed: { computed: {
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask }, enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
@ -65,20 +78,34 @@ export default {
}) })
}, },
logo () { return this.$store.state.instance.logo }, logo () { return this.$store.state.instance.logo },
mergedConfig () {
return this.$store.getters.mergedConfig
},
sitename () { return this.$store.state.instance.name }, 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 }, hideSitename () { return this.$store.state.instance.hideSitename },
logoLeft () { return this.$store.state.instance.logoLeft }, logoLeft () { return this.$store.state.instance.logoLeft },
currentUser () { return this.$store.state.users.currentUser }, 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: { methods: {
scrollToTop () { scrollToTop () {
window.scrollTo(0, 0) window.scrollTo(0, 0)
}, },
logout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
},
onSearchBarToggled (hidden) { onSearchBarToggled (hidden) {
this.searchBarHidden = hidden this.searchBarHidden = hidden
}, },

View file

@ -15,16 +15,16 @@
display: grid; display: grid;
grid-template-rows: var(--navbar-height); grid-template-rows: var(--navbar-height);
grid-template-columns: 2fr auto 2fr; grid-template-columns: 2fr auto 2fr;
grid-template-areas: "sitename logo actions"; grid-template-areas: "nav-left logo actions";
box-sizing: border-box; box-sizing: border-box;
padding: 0 1.2em; padding: 0 1.2em;
margin: auto; margin: auto;
max-width: 980px; max-width: 1110px;
} }
&.-logoLeft .inner-nav { &.-logoLeft .inner-nav {
grid-template-columns: auto 2fr 2fr; grid-template-columns: auto 2fr 2fr;
grid-template-areas: "logo sitename actions"; grid-template-areas: "logo nav-left actions";
} }
.button-default { .button-default {
@ -84,24 +84,52 @@
} }
.nav-icon { .nav-icon {
margin-left: 1em; margin-left: 0.2em;
width: 2em; width: 2em;
height: 100%; height: 100%;
font-size: 130%;
text-align: center; text-align: center;
&-logout { &-logout {
margin-left: 2em; 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 { .svg-inline--fa {
color: $fallback--link; color: $fallback--link;
color: var(--topBarLink, $fallback--link); color: var(--topBarLink, $fallback--link);
} }
} }
.sitename { .-wide {
grid-area: sitename; .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 { .actions {
@ -120,5 +148,10 @@
justify-content: flex-end; justify-content: flex-end;
text-align: right; text-align: right;
} }
&.left {
justify-content: flex-start;
text-alignt: left;
}
} }
} }

View file

@ -5,16 +5,79 @@
:class="{ '-logoLeft': logoLeft }" :class="{ '-logoLeft': logoLeft }"
@click="scrollToTop()" @click="scrollToTop()"
> >
<div class="inner-nav"> <div
<div class="item sitename"> class="inner-nav"
:class="{ '-wide': showWiderShortcuts }"
>
<div class="item nav-left-wrapper">
<router-link <router-link
v-if="!hideSitename" class="site-brand"
class="site-name"
:to="{ name: 'root' }" :to="{ name: 'root' }"
active-class="home" active-class="home"
>
<img
v-if="!hideSiteFavicon"
class="favicon"
src="/favicon.png"
>
<span
v-if="!hideSiteName"
class="site-name"
> >
{{ sitename }} {{ sitename }}
</span>
</router-link> </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> </div>
<router-link <router-link
class="logo" class="logo"
@ -36,6 +99,46 @@
@toggled="onSearchBarToggled" @toggled="onSearchBarToggled"
@click.stop @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 <button
class="button-unstyled nav-icon" class="button-unstyled nav-icon"
@click.stop="openSettingsModal" @click.stop="openSettingsModal"
@ -61,20 +164,20 @@
:title="$t('nav.administration')" :title="$t('nav.administration')"
/> />
</a> </a>
<button </div>
v-if="currentUser" </div>
class="button-unstyled nav-icon nav-icon-logout" <teleport to="#modal">
@click.prevent="logout" <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 {{ $t('login.logout_confirm') }}
fixed-width </confirm-modal>
class="fa-scale-110 fa-old-padding" </teleport>
icon="sign-out-alt"
:title="$t('login.logout')"
/>
</button>
</div>
</div>
</nav> </nav>
</template> </template>
<script src="./desktop_nav.js"></script> <script src="./desktop_nav.js"></script>

View file

@ -39,7 +39,7 @@
right: 0; right: 0;
top: 0; top: 0;
background: rgba(27,31,35,.5); background: rgba(27,31,35,.5);
z-index: 99; z-index: 2000;
} }
} }
@ -51,7 +51,7 @@
margin: 15vh auto; margin: 15vh auto;
position: fixed; position: fixed;
transform: translateX(-50%); transform: translateX(-50%);
z-index: 999; z-index: 2001;
cursor: default; cursor: default;
display: block; display: block;
background-color: $fallback--bg; 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 { .emoji-picker-panel {
position: absolute; position: relative;
z-index: 20; z-index: 20;
margin-top: 2px; margin-top: 2px;
top: 0px !important;
&.hide { &.hide {
display: none display: none

View file

@ -6,7 +6,7 @@ import {
faStickyNote, faStickyNote,
faSmileBeam faSmileBeam
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { trim } from 'lodash' import { trim, escapeRegExp, startCase } from 'lodash'
library.add( library.add(
faBoxOpen, faBoxOpen,
@ -21,23 +21,6 @@ const LOAD_EMOJI_BY = 60
// When to start loading new batch emoji, in pixels // When to start loading new batch emoji, in pixels
const LOAD_EMOJI_MARGIN = 64 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 = { const EmojiPicker = {
props: { props: {
enableStickerPicker: { enableStickerPicker: {
@ -49,7 +32,7 @@ const EmojiPicker = {
data () { data () {
return { return {
keyword: '', keyword: '',
activeGroup: 'custom', activeGroup: 'standard',
showingStickers: false, showingStickers: false,
groupsScrolledClass: 'scrolled-top', groupsScrolledClass: 'scrolled-top',
keepOpen: false, keepOpen: false,
@ -80,13 +63,8 @@ const EmojiPicker = {
this.triggerLoadMore(target) this.triggerLoadMore(target)
}, },
highlight (key) { highlight (key) {
const ref = this.$refs['group-' + key]
const top = ref.offsetTop
this.setShowStickers(false) this.setShowStickers(false)
this.activeGroup = key this.activeGroup = key
this.$nextTick(() => {
this.$refs['emoji-groups'].scrollTop = top + 1
})
}, },
updateScrolledClass (target) { updateScrolledClass (target) {
if (target.scrollTop <= 5) { if (target.scrollTop <= 5) {
@ -155,6 +133,13 @@ const EmojiPicker = {
}, },
setShowStickers (value) { setShowStickers (value) {
this.showingStickers = 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: { watch: {
@ -175,9 +160,8 @@ const EmojiPicker = {
return 0 return 0
}, },
filteredEmoji () { filteredEmoji () {
return filterByKeyword( return this.filterByKeyword(
this.$store.state.instance.customEmoji || [], this.$store.state.instance.customEmoji || []
trim(this.keyword)
) )
}, },
customEmojiBuffer () { customEmojiBuffer () {
@ -185,25 +169,50 @@ const EmojiPicker = {
}, },
emojis () { emojis () {
const standardEmojis = this.$store.state.instance.emoji || [] 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 [ return [
{
id: 'custom',
text: this.$t('emoji.custom'),
icon: 'smile-beam',
emojis: customEmojis
},
{ {
id: 'standard', id: 'standard',
text: this.$t('emoji.unicode'), text: this.$t('emoji.unicode'),
icon: 'box-open', first: {
emojis: filterByKeyword(standardEmojis, trim(this.keyword)) 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 () { 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 () { stickerPickerEnabled () {
return (this.$store.state.instance.stickers || []).length !== 0 return (this.$store.state.instance.stickers || []).length !== 0

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
import Popover from '../popover/popover.vue' import Popover from '../popover/popover.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faEllipsisH, faEllipsisH,
@ -6,7 +7,9 @@ import {
faEyeSlash, faEyeSlash,
faThumbtack, faThumbtack,
faShareAlt, faShareAlt,
faExternalLinkAlt faQuoteLeft,
faExternalLinkAlt,
faHistory
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { import {
faBookmark as faBookmarkReg, faBookmark as faBookmarkReg,
@ -20,20 +23,49 @@ library.add(
faEyeSlash, faEyeSlash,
faThumbtack, faThumbtack,
faShareAlt, faShareAlt,
faQuoteLeft,
faExternalLinkAlt, faExternalLinkAlt,
faFlag faFlag,
faHistory
) )
const ExtraButtons = { const ExtraButtons = {
props: ['status'], props: ['status'],
components: { Popover }, components: {
Popover,
ConfirmModal
},
data () {
return {
expanded: false,
showingDeleteDialog: false
}
},
methods: { methods: {
deleteStatus () { deleteStatus () {
const confirmed = window.confirm(this.$t('status.delete_confirm')) if (this.shouldConfirmDelete) {
if (confirmed) { this.showDeleteStatusConfirmDialog()
this.$store.dispatch('deleteStatus', { id: this.status.id }) } 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 () { pinStatus () {
this.$store.dispatch('pinStatus', this.status.id) this.$store.dispatch('pinStatus', this.status.id)
.then(() => this.$emit('onSuccess')) .then(() => this.$emit('onSuccess'))
@ -71,6 +103,25 @@ const ExtraButtons = {
}, },
reportStatus () { reportStatus () {
this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] }) 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: { computed: {
@ -89,9 +140,23 @@ const ExtraButtons = {
canMute () { canMute () {
return !!this.currentUser return !!this.currentUser
}, },
canTranslate () {
return this.$store.state.instance.translationEnabled === true
},
statusLink () { statusLink () {
if (this.status.is_local) {
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}` 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" icon="bookmark"
/><span>{{ $t("status.unbookmark") }}</span> /><span>{{ $t("status.unbookmark") }}</span>
</button> </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 <button
v-if="canDelete" v-if="canDelete"
class="button-default dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@ -116,6 +138,28 @@
:icon="['far', 'flag']" :icon="['far', 'flag']"
/><span>{{ $t("user_card.report") }}</span> /><span>{{ $t("user_card.report") }}</span>
</button> </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> </div>
</template> </template>
<template v-slot:trigger> <template v-slot:trigger>
@ -125,6 +169,18 @@
icon="ellipsis-h" icon="ellipsis-h"
/> />
</button> </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> </template>
</Popover> </Popover>
</template> </template>
@ -151,4 +207,11 @@
} }
} }
} }
@media all and (min-width: 801px) {
.extra-quote {
display: none !important;
}
}
</style> </style>

View file

@ -36,6 +36,7 @@
.FavoriteButton { .FavoriteButton {
display: flex; display: flex;
margin-right: 5px;
> :first-child { > :first-child {
padding: 10px; 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' import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
export default { export default {
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'], props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
components: {
ConfirmModal
},
data () { data () {
return { return {
inProgress: false inProgress: false,
showingConfirmUnfollow: false
} }
}, },
computed: { computed: {
shouldConfirmUnfollow () {
return this.$store.getters.mergedConfig.modalOnUnfollow
},
isPressed () { isPressed () {
return this.inProgress || this.relationship.following return this.inProgress || this.relationship.following
}, },
@ -35,6 +43,12 @@ export default {
} }
}, },
methods: { methods: {
showConfirmUnfollow () {
this.showingConfirmUnfollow = true
},
hideConfirmUnfollow () {
this.showingConfirmUnfollow = false
},
onClick () { onClick () {
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow() this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
}, },
@ -45,12 +59,21 @@ export default {
}) })
}, },
unfollow () { unfollow () {
if (this.shouldConfirmUnfollow) {
this.showConfirmUnfollow()
} else {
this.doUnfollow()
}
},
doUnfollow () {
const store = this.$store const store = this.$store
this.inProgress = true this.inProgress = true
requestUnfollow(this.relationship.id, store).then(() => { requestUnfollow(this.relationship.id, store).then(() => {
this.inProgress = false this.inProgress = false
store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id }) store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
}) })
this.hideConfirmUnfollow()
} }
} }
} }

View file

@ -7,6 +7,27 @@
@click="onClick" @click="onClick"
> >
{{ label }} {{ 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> </button>
</template> </template>

View file

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

View file

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

View file

@ -1,10 +1,18 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue' 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' import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
const FollowRequestCard = { const FollowRequestCard = {
props: ['user'], props: ['user'],
components: { components: {
BasicUserCard BasicUserCard,
ConfirmModal
},
data () {
return {
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false
}
}, },
methods: { methods: {
findFollowRequestNotificationId () { findFollowRequestNotificationId () {
@ -13,7 +21,26 @@ const FollowRequestCard = {
) )
return notif && notif.id return notif && notif.id
}, },
showApproveConfirmDialog () {
this.showingApproveConfirmDialog = true
},
hideApproveConfirmDialog () {
this.showingApproveConfirmDialog = false
},
showDenyConfirmDialog () {
this.showingDenyConfirmDialog = true
},
hideDenyConfirmDialog () {
this.showingDenyConfirmDialog = false
},
approveUser () { approveUser () {
if (this.shouldConfirmApprove) {
this.showApproveConfirmDialog()
} else {
this.doApprove()
}
},
doApprove () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
@ -25,14 +52,34 @@ const FollowRequestCard = {
notification.type = 'follow' notification.type = 'follow'
} }
}) })
this.hideApproveConfirmDialog()
}, },
denyUser () { denyUser () {
if (this.shouldConfirmDeny) {
this.showDenyConfirmDialog()
} else {
this.doDeny()
}
},
doDeny () {
const notifId = this.findFollowRequestNotificationId() const notifId = this.findFollowRequestNotificationId()
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => { .then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: notifId }) this.$store.dispatch('dismissNotificationLocal', { id: notifId })
this.$store.dispatch('removeFollowRequest', this.user) 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') }} {{ $t('user_card.deny') }}
</button> </button>
</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>
</basic-user-card> </basic-user-card>
</template> </template>

View file

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

View file

@ -188,9 +188,21 @@ $modal-view-button-icon-margin: 0.5em;
overflow-y: auto; overflow-y: auto;
min-height: 1em; min-height: 1em;
max-width: 500px; max-width: 500px;
max-height: 9.5em;
word-break: break-word; word-break: break-word;
white-space: pre-line; 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 { .modal-image {

View file

@ -15,11 +15,15 @@
.mention-avatar { .mention-avatar {
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
width: 1.5em; width: 2em;
height: 1.5em; height: 2em;
vertical-align: middle; vertical-align: middle;
user-select: none; user-select: none;
margin-right: 0.2em; margin-right: 0.2em;
.still-image.avatar {
border-radius: 14px;
}
} }
.full { .full {
@ -113,3 +117,11 @@
color: var(--faint, $fallback--faint); 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" @click.prevent="onClick"
> >
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<UserAvatar
v-if="shouldShowAvatar"
class="mention-avatar"
:user="user"
/>
<span class="shortName">@<span <span class="shortName">@<span
class="userName" class="userName"
v-html="userName" v-html="userName"

View file

@ -1,7 +1,9 @@
import SideDrawer from '../side_drawer/side_drawer.vue' import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue' import Notifications from '../notifications/notifications.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils' import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service' import GestureService from '../../services/gesture_service/gesture_service'
import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes, faTimes,
@ -18,11 +20,13 @@ library.add(
const MobileNav = { const MobileNav = {
components: { components: {
SideDrawer, SideDrawer,
Notifications Notifications,
ConfirmModal
}, },
data: () => ({ data: () => ({
notificationsCloseGesture: undefined, notificationsCloseGesture: undefined,
notificationsOpen: false notificationsOpen: false,
showingConfirmLogout: false
}), }),
created () { created () {
this.notificationsCloseGesture = GestureService.swipeGesture( this.notificationsCloseGesture = GestureService.swipeGesture(
@ -41,8 +45,17 @@ const MobileNav = {
unseenNotificationsCount () { unseenNotificationsCount () {
return this.unseenNotifications.length return this.unseenNotifications.length
}, },
hideSitename () { return this.$store.state.instance.hideSitename }, mergedConfig () {
sitename () { return this.$store.state.instance.name } 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: { methods: {
toggleMobileSidebar () { toggleMobileSidebar () {
@ -68,9 +81,23 @@ const MobileNav = {
scrollToTop () { scrollToTop () {
window.scrollTo(0, 0) window.scrollTo(0, 0)
}, },
showConfirmLogout () {
this.showingConfirmLogout = true
},
hideConfirmLogout () {
this.showingConfirmLogout = false
},
logout () { logout () {
if (!this.shouldConfirmLogout) {
this.doLogout()
} else {
this.showConfirmLogout()
}
},
doLogout () {
this.$router.replace('/main/public') this.$router.replace('/main/public')
this.$store.dispatch('logout') this.$store.dispatch('logout')
this.hideConfirmLogout()
}, },
markNotificationsAsSeen () { markNotificationsAsSeen () {
// this.$refs.notifications.markAsSeen() // this.$refs.notifications.markAsSeen()

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue' import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue' import Timeago from '../timeago/timeago.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx' 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 { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -36,7 +37,9 @@ const Notification = {
return { return {
userExpanded: false, userExpanded: false,
betterShadow: this.$store.state.interface.browserSupport.cssFilter, betterShadow: this.$store.state.interface.browserSupport.cssFilter,
unmuted: false unmuted: false,
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false
} }
}, },
props: [ 'notification' ], props: [ 'notification' ],
@ -46,7 +49,8 @@ const Notification = {
UserCard, UserCard,
Timeago, Timeago,
Status, Status,
RichContent RichContent,
ConfirmModal
}, },
methods: { methods: {
toggleUserExpanded () { toggleUserExpanded () {
@ -61,7 +65,26 @@ const Notification = {
toggleMute () { toggleMute () {
this.unmuted = !this.unmuted this.unmuted = !this.unmuted
}, },
showApproveConfirmDialog () {
this.showingApproveConfirmDialog = true
},
hideApproveConfirmDialog () {
this.showingApproveConfirmDialog = false
},
showDenyConfirmDialog () {
this.showingDenyConfirmDialog = true
},
hideDenyConfirmDialog () {
this.showingDenyConfirmDialog = false
},
approveUser () { approveUser () {
if (this.shouldConfirmApprove) {
this.showApproveConfirmDialog()
} else {
this.doApprove()
}
},
doApprove () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id }) this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
@ -71,13 +94,22 @@ const Notification = {
notification.type = 'follow' notification.type = 'follow'
} }
}) })
this.hideApproveConfirmDialog()
}, },
denyUser () { denyUser () {
if (this.shouldConfirmDeny) {
this.showDenyConfirmDialog()
} else {
this.doDeny()
}
},
doDeny () {
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => { .then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id }) this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
}) })
this.hideDenyConfirmDialog()
} }
}, },
computed: { computed: {
@ -107,6 +139,15 @@ const Notification = {
isStatusNotification () { isStatusNotification () {
return isStatusNotification(this.notification.type) return isStatusNotification(this.notification.type)
}, },
mergedConfig () {
return this.$store.getters.mergedConfig
},
shouldConfirmApprove () {
return this.mergedConfig.modalOnApproveFollow
},
shouldConfirmDeny () {
return this.mergedConfig.modalOnDenyFollow
},
...mapState({ ...mapState({
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser
}) })

View file

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

View file

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

View file

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

View file

@ -55,6 +55,14 @@ const pxStringToNumber = (str) => {
const PostStatusForm = { const PostStatusForm = {
props: [ props: [
'statusId',
'statusText',
'statusIsSensitive',
'statusPoll',
'statusFiles',
'statusMediaDescriptions',
'statusScope',
'statusContentType',
'replyTo', 'replyTo',
'quoteId', 'quoteId',
'repliedUser', 'repliedUser',
@ -63,6 +71,7 @@ const PostStatusForm = {
'subject', 'subject',
'disableSubject', 'disableSubject',
'disableScopeSelector', 'disableScopeSelector',
'disableVisibilitySelector',
'disableNotice', 'disableNotice',
'disableLockWarning', 'disableLockWarning',
'disablePolls', 'disablePolls',
@ -120,23 +129,40 @@ const PostStatusForm = {
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject } = this.$store.getters.mergedConfig const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject } = this.$store.getters.mergedConfig
return { let statusParams = {
dropFiles: [],
uploadingFiles: false,
error: null,
posting: false,
highlighted: 0,
newStatus: {
spoilerText: this.subject || '', spoilerText: this.subject || '',
status: statusText, status: statusText,
sensitiveIfSubject, sensitiveByDefault,
nsfw: !!sensitiveByDefault, nsfw: !!sensitiveByDefault,
files: [], files: [],
poll: {}, poll: {},
mediaDescriptions: {}, mediaDescriptions: {},
visibility: this.suggestedVisibility(), visibility: this.suggestedVisibility(),
contentType 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, caret: 0,
pollFormVisible: false, pollFormVisible: false,
showDropIcon: 'hide', showDropIcon: 'hide',
@ -232,6 +258,9 @@ const PostStatusForm = {
uploadFileLimitReached () { uploadFileLimitReached () {
return this.newStatus.files.length >= this.fileLimit return this.newStatus.files.length >= this.fileLimit
}, },
isEdit () {
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
},
...mapGetters(['mergedConfig']), ...mapGetters(['mergedConfig']),
...mapState({ ...mapState({
mobileLayout: state => state.interface.mobileLayout 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-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span> <span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
</p> </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 <div
v-if="!disablePreview" v-if="!disablePreview"
class="preview-heading faint" class="preview-heading faint"
@ -180,6 +187,7 @@
class="visibility-tray" class="visibility-tray"
> >
<scope-selector <scope-selector
v-if="!disableVisibilitySelector"
:show-all="showAllScopes" :show-all="showAllScopes"
:user-default="userDefaultScope" :user-default="userDefaultScope"
:original-scope="copyMessageScope" :original-scope="copyMessageScope"
@ -420,6 +428,16 @@
align-items: baseline; align-items: baseline;
} }
.visibility-notice.edit-warning {
> :first-child {
margin-top: 0;
}
> :last-child {
margin-bottom: 0;
}
}
.media-upload-icon, .poll-icon, .emoji-icon { .media-upload-icon, .poll-icon, .emoji-icon {
font-size: 1.85em; font-size: 1.85em;
line-height: 1.1; line-height: 1.1;

View file

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

View file

@ -64,11 +64,11 @@
color: $fallback--text; color: $fallback--text;
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
border-style: solid; border-style: dashed;
border-width: 1px; border-width: 1px;
border-radius: $fallback--attachmentRadius; border-radius: $fallback--attachmentRadius;
border-radius: var(--attachmentRadius, $fallback--attachmentRadius); border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
border-color: $fallback--border; border-color: $fallback--cBlue;
border-color: var(--border, $fallback--border); border-color: var(--cBlue, $fallback--cBlue);
} }
</style> </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 { library } from '@fortawesome/fontawesome-svg-core'
import { faRetweet } from '@fortawesome/free-solid-svg-icons' import { faRetweet } from '@fortawesome/free-solid-svg-icons'
@ -5,13 +6,24 @@ library.add(faRetweet)
const RetweetButton = { const RetweetButton = {
props: ['status', 'loggedIn', 'visibility'], props: ['status', 'loggedIn', 'visibility'],
components: {
ConfirmModal
},
data () { data () {
return { return {
animated: false animated: false,
showingConfirmDialog: false
} }
}, },
methods: { methods: {
retweet () { retweet () {
if (!this.status.repeated && this.shouldConfirmRepeat) {
this.showConfirmDialog()
} else {
this.doRetweet()
}
},
doRetweet () {
if (!this.status.repeated) { if (!this.status.repeated) {
this.$store.dispatch('retweet', { id: this.status.id }) this.$store.dispatch('retweet', { id: this.status.id })
} else { } else {
@ -21,6 +33,13 @@ const RetweetButton = {
setTimeout(() => { setTimeout(() => {
this.animated = false this.animated = false
}, 500) }, 500)
this.hideConfirmDialog()
},
showConfirmDialog () {
this.showingConfirmDialog = true
},
hideConfirmDialog () {
this.showingConfirmDialog = false
} }
}, },
computed: { computed: {
@ -29,6 +48,9 @@ const RetweetButton = {
}, },
mergedConfig () { mergedConfig () {
return this.$store.getters.mergedConfig return this.$store.getters.mergedConfig
},
shouldConfirmRepeat () {
return this.mergedConfig.modalOnRepeat
} }
} }
} }

View file

@ -33,6 +33,18 @@
> >
{{ status.repeat_num }} {{ status.repeat_num }}
</span> </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> </div>
</template> </template>

View file

@ -124,6 +124,14 @@ export default {
} }
const renderMisskeyMarkdown = (content) => { 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, { marked.use(markedMfm, {
mangle: false, mangle: false,
gfm: false, gfm: false,
@ -133,6 +141,7 @@ export default {
mfmHtml.innerHTML = marked.parse(content) mfmHtml.innerHTML = marked.parse(content)
// Add options with set values to CSS // Add options with set values to CSS
if (mfmHtml.content.firstChild) {
Array.from(mfmHtml.content.firstChild.getElementsByClassName('mfm')).map((el) => { Array.from(mfmHtml.content.firstChild.getElementsByClassName('mfm')).map((el) => {
if (el.dataset.speed) { if (el.dataset.speed) {
el.style.animationDuration = el.dataset.speed el.style.animationDuration = el.dataset.speed
@ -147,6 +156,7 @@ export default {
} }
} }
}) })
}
return mfmHtml.innerHTML return mfmHtml.innerHTML
} }
@ -359,8 +369,6 @@ export const preProcessPerLine = (html, greentext) => {
.trim() .trim()
if (cleanedString.startsWith('&gt;')) { if (cleanedString.startsWith('&gt;')) {
return `<span class='greentext'>${string}</span>` 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 { import {
faEnvelope, faEnvelope,
faLock, faLock,
faBiohazard,
faLockOpen, faLockOpen,
faGlobe faGlobe
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
@ -9,6 +10,7 @@ import {
library.add( library.add(
faEnvelope, faEnvelope,
faGlobe, faGlobe,
faBiohazard,
faLock, faLock,
faLockOpen faLockOpen
) )

View file

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

View file

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

View file

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

View file

@ -111,6 +111,20 @@
id="unscrolled-content" id="unscrolled-content"
class="extra-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>
</div> </div>
</Modal> </Modal>

View file

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

View file

@ -50,6 +50,7 @@ const GeneralTab = {
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') || Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
// Future spec, still not supported in Nightly 63 as of 08/2018 // Future spec, still not supported in Nightly 63 as of 08/2018
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks') Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks')
} }
}, },
components: { components: {
@ -82,11 +83,20 @@ const GeneralTab = {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) 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() ...SharedComputedObject()
}, },
methods: { methods: {
changeDefaultScope (value) { changeDefaultScope (value) {
this.$store.dispatch('setServerSideOption', { name: 'defaultScope', 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') }} {{ $t('settings.hide_wallpaper') }}
</BooleanSetting> </BooleanSetting>
</li> </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> <li>
<BooleanSetting path="stopGifs"> <BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }} {{ $t('settings.stop_gifs') }}
@ -103,6 +135,28 @@
<BooleanSetting path="renderMisskeyMarkdown"> <BooleanSetting path="renderMisskeyMarkdown">
{{ $t('settings.render_mfm') }} {{ $t('settings.render_mfm') }}
</BooleanSetting> </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>
<li> <li>
<BooleanSetting <BooleanSetting
@ -120,6 +174,77 @@
{{ $t('settings.autohide_floating_post_button') }} {{ $t('settings.autohide_floating_post_button') }}
</BooleanSetting> </BooleanSetting>
</li> </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> </ul>
</div> </div>
<div class="setting-item"> <div class="setting-item">

View file

@ -1,6 +1,6 @@
import { extractCommit } from 'src/services/version/version.service' 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 pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commit/'
const VersionTab = { const VersionTab = {

View file

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

View file

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

View file

@ -12,7 +12,7 @@
:key="group.role" :key="group.role"
class="staff-group" class="staff-group"
> >
<h4>{{ $t('general.role.' + group.role) }}</h4> <h4>The Admin Team</h4>
<basic-user-card <basic-user-card
v-for="user in group.users" v-for="user in group.users"
:key="user.screen_name" :key="user.screen_name"
@ -30,10 +30,21 @@
.staff-group { .staff-group {
padding-left: 1em; padding-left: 1em;
padding-top: 1em; padding-top: 0.2em;
padding-bottom: 2px;
.basic-user-card { .basic-user-card {
padding: 0.6em 0.4em;
padding-left: 0; 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, faStar,
faEyeSlash, faEyeSlash,
faEye, faEye,
faBiohazard,
faThumbtack, faThumbtack,
faChevronUp, faChevronUp,
faChevronDown, faChevronDown,
@ -56,6 +57,7 @@ library.add(
faEllipsisH, faEllipsisH,
faEyeSlash, faEyeSlash,
faEye, faEye,
faBiohazard,
faThumbtack, faThumbtack,
faChevronUp, faChevronUp,
faChevronDown, faChevronDown,
@ -261,6 +263,38 @@ const Status = {
hasMentionsLine () { hasMentionsLine () {
return this.mentionsLine.length > 0 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 () { muted () {
if (this.statusoid.user.id === this.currentUser.id) return false if (this.statusoid.user.id === this.currentUser.id) return false
const reasonsToMute = this.userIsMuted || const reasonsToMute = this.userIsMuted ||
@ -269,7 +303,11 @@ const Status = {
// Wordfiltered // Wordfiltered
this.muteWordHits.length > 0 || this.muteWordHits.length > 0 ||
// bot status // 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 return !this.unmuted && !this.shouldNotMute && reasonsToMute
}, },
userIsMuted () { userIsMuted () {
@ -312,6 +350,9 @@ const Status = {
hideFilteredStatuses () { hideFilteredStatuses () {
return this.mergedConfig.hideFilteredStatuses return this.mergedConfig.hideFilteredStatuses
}, },
hideThreadsWithBlockedUsers () {
return this.mergedConfig.hideThreadsWithBlockedUsers
},
hideWordFilteredPosts () { hideWordFilteredPosts () {
return this.mergedConfig.hideWordFilteredPosts return this.mergedConfig.hideWordFilteredPosts
}, },
@ -319,8 +360,9 @@ const Status = {
return (!this.shouldNotMute) && ( return (!this.shouldNotMute) && (
(this.muted && this.hideFilteredStatuses) || (this.muted && this.hideFilteredStatuses) ||
(this.userIsMuted && this.hideMutedUsers) || (this.userIsMuted && this.hideMutedUsers) ||
(this.status.thread_muted && this.hideMutedThreads) || ((this.status.thread_muted || this.mentionsMutedUser) && this.hideMutedThreads) ||
(this.muteWordHits.length > 0 && this.hideWordFilteredPosts) (this.muteWordHits.length > 0 && this.hideWordFilteredPosts) ||
(this.mentionsBlockedUser && this.hideThreadsWithBlockedUsers)
) )
}, },
isFocused () { isFocused () {
@ -397,6 +439,12 @@ const Status = {
}, },
visibilityLocalized () { visibilityLocalized () {
return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility) 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: { methods: {
@ -409,7 +457,7 @@ const Status = {
case 'direct': case 'direct':
return 'envelope' return 'envelope'
case 'local': case 'local':
return 'users' return 'biohazard'
default: default:
return 'globe' return 'globe'
} }

View file

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

View file

@ -187,8 +187,29 @@
> >
<Timeago <Timeago
:time="status.created_at" :time="status.created_at"
:with-direction="true"
:auto-update="60" :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> </router-link>
<span <span
v-if="status.visibility" v-if="status.visibility"
@ -453,6 +474,7 @@
:status="status" :status="status"
@onError="showError" @onError="showError"
@onSuccess="clearError" @onSuccess="clearError"
@quote-toggle="toggleQuoting"
/> />
</div> </div>
</div> </div>

View file

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

View file

@ -43,6 +43,7 @@
</button> </button>
<div <div
v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)" v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)"
class="media-body-wrapper"
> >
<RichContent <RichContent
:class="{ '-single-line': singleLine }" :class="{ '-single-line': singleLine }"
@ -55,6 +56,23 @@
:attentions="status.attentions" :attentions="status.attentions"
@parseReady="onParseReady" @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> </div>
<button <button
v-show="hideSubjectStatus" v-show="hideSubjectStatus"

View file

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

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="StatusContent" class="StatusContent"
:class="{ '-compact': compact }" :class="{ '-compact': compact, 'mfm-hover': mfmOnHover }"
> >
<slot name="header" /> <slot name="header" />
<StatusBody <StatusBody
@ -75,5 +75,17 @@
height: 50px; height: 50px;
} }
} }
&.mfm-hover:not(:hover) {
.mfm {
animation: none;
} }
}
}
.quote-inline,
.quote + .link-preview {
display: none;
}
</style> </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%; width: 100%;
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
&::before {
line-height: 20px;
}
} }
&.animated { &.animated {

View file

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

View file

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

View file

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

View file

@ -9,22 +9,8 @@
fixed-width fixed-width
class="fa-scale-110 fa-old-padding " class="fa-scale-110 fa-old-padding "
icon="home" icon="home"
/>{{ $t("nav.home_timeline") }} /><span :title="$t('nav.home_timeline_description')">{{ $t("nav.home_timeline") }}</span>
</router-link> </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>
<li v-if="currentUser || !privateMode"> <li v-if="currentUser || !privateMode">
<router-link <router-link
@ -35,9 +21,8 @@
fixed-width fixed-width
class="fa-scale-110 fa-old-padding " class="fa-scale-110 fa-old-padding "
icon="users" icon="users"
/>{{ $t("nav.public_tl") }} /><span :title="$t('nav.public_timeline_description')">{{ $t("nav.public_tl") }}</span>
</router-link> </router-link>
<span class="timeline-desc">{{ $t("nav.public_timeline_description") }}</span>
</li> </li>
<li v-if="federating && (currentUser || !privateMode)"> <li v-if="federating && (currentUser || !privateMode)">
<router-link <router-link
@ -48,9 +33,20 @@
fixed-width fixed-width
class="fa-scale-110 fa-old-padding " class="fa-scale-110 fa-old-padding "
icon="globe" 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> </router-link>
<span class="timeline-desc">{{ $t("nav.twkn_timeline_description") }}</span>
</li> </li>
<li v-if="currentUser"> <li v-if="currentUser">
<router-link <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 AccountActions from '../account_actions/account_actions.vue'
import Select from '../select/select.vue' import Select from '../select/select.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx' 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 generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -32,7 +33,8 @@ export default {
data () { data () {
return { return {
followRequestInProgress: false, followRequestInProgress: false,
betterShadow: this.$store.state.interface.browserSupport.cssFilter betterShadow: this.$store.state.interface.browserSupport.cssFilter,
showingConfirmMute: false
} }
}, },
created () { created () {
@ -112,6 +114,9 @@ export default {
hideFollowersCount () { hideFollowersCount () {
return this.isOtherUser && this.user.hide_followers_count return this.isOtherUser && this.user.hide_followers_count
}, },
shouldConfirmMute () {
return this.mergedConfig.modalOnMute
},
...mapGetters(['mergedConfig']) ...mapGetters(['mergedConfig'])
}, },
components: { components: {
@ -122,14 +127,29 @@ export default {
ProgressButton, ProgressButton,
FollowButton, FollowButton,
Select, Select,
RichContent RichContent,
ConfirmModal
}, },
methods: { methods: {
refetchRelationship () { refetchRelationship () {
return this.$store.dispatch('fetchUserRelationship', this.user.id) return this.$store.dispatch('fetchUserRelationship', this.user.id)
}, },
showConfirmMute () {
this.showingConfirmMute = true
},
hideConfirmMute () {
this.showingConfirmMute = false
},
muteUser () { muteUser () {
if (!this.shouldConfirmMute) {
this.doMuteUser()
} else {
this.showConfirmMute()
}
},
doMuteUser () {
this.$store.dispatch('muteUser', this.user.id) this.$store.dispatch('muteUser', this.user.id)
this.hideConfirmMute()
}, },
unmuteUser () { unmuteUser () {
this.$store.dispatch('unmuteUser', this.user.id) this.$store.dispatch('unmuteUser', this.user.id)

View file

@ -295,6 +295,27 @@
:handle-links="true" :handle-links="true"
/> />
</div> </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> </div>
</template> </template>

View file

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

View file

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

View file

@ -164,8 +164,8 @@
"fullname_required": "no es pot deixar en blanc", "fullname_required": "no es pot deixar en blanc",
"username_required": "no es pot deixar en blanc" "username_required": "no es pot deixar en blanc"
}, },
"fullname_placeholder": "p. ex. Anna Bofarull", "fullname_placeholder": "p. ex. Lain Iwakura",
"username_placeholder": "p. ex. anna", "username_placeholder": "p. ex. lain",
"captcha": "CAPTCHA", "captcha": "CAPTCHA",
"register": "Registre", "register": "Registre",
"reason": "Raó per a registrar-se", "reason": "Raó per a registrar-se",
@ -181,99 +181,99 @@
"avatarAltRadius": "Avatars (notificacions)", "avatarAltRadius": "Avatars (notificacions)",
"avatarRadius": "Avatars", "avatarRadius": "Avatars",
"background": "Fons de pantalla", "background": "Fons de pantalla",
"bio": "Presentació", "bio": "Bio",
"btnRadius": "Botons", "btnRadius": "Botons",
"cBlue": "Blau (respon, segueix)", "cBlue": "Blau (respon, segueix)",
"cGreen": "Verd (republica)", "cGreen": "Verd (republica)",
"cOrange": "Taronja (marca com a preferit)", "cOrange": "Taronja (afavoreix)",
"cRed": "Vermell (canceŀla)", "cRed": "Vermell (canceŀla)",
"change_password": "Canvia la contrasenya", "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!", "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", "confirm_new_password": "Confirma la nova contrasenya",
"current_avatar": "L'avatar actual", "current_avatar": "El teu avatar actual",
"current_password": "La contrasenya actual", "current_password": "Contrasenya actual",
"current_profile_banner": "El fons de perfil actual", "current_profile_banner": "El fons de perfil actual",
"data_import_export_tab": "Importa o exporta dades", "data_import_export_tab": "Importa dades / exporta",
"default_vis": "Abast per defecte de les entrades", "default_vis": "Visibilitat per defecte dels apunts",
"delete_account": "Esborra el compte", "delete_account": "Esborra el compte",
"delete_account_description": "Esborra permanentment les teves dades i desactiva el teu 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_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": "Confirma que vols esborrar el compte escrivint la teva contrasenya aquí sota.", "delete_account_instructions": "Escriu la teva contrasenya en el camp de sota per a confirmar esborrar el compte.",
"export_theme": "Desa el tema", "export_theme": "Desa el tema",
"filtering": "Filtres", "filtering": "Filtrant",
"filtering_explanation": "Es silenciaran totes les entrades que continguin aquestes paraules. Separa-les per línies", "filtering_explanation": "Es silenciaran tots els apunts que continguin aquestes paraules, una per línia",
"follow_export": "Exporta la llista de contactes", "follow_export": "Exporta els seguits",
"follow_export_button": "Exporta tots els comptes que segueixes a un fitxer CSV", "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_export_processing": "S'està processant la petició. Aviat podràs descarregar el fitxer",
"follow_import": "Importa els contactes", "follow_import": "Importa els seguits",
"follow_import_error": "No s'ha pogut importar els contactes", "follow_import_error": "Error al importar els seguidors",
"follows_imported": "S'han importat els contactes. Trigaran una estoneta en ser processats.", "follows_imported": "S'han importat els seguits! Processar-los portarà una estona.",
"foreground": "Primer pla", "foreground": "Primer pla",
"general": "General", "general": "General",
"hide_attachments_in_convo": "Amaga els adjunts en les converses", "hide_attachments_in_convo": "Amaga els adjunts en les converses",
"hide_attachments_in_tl": "Amaga els adjunts en el flux d'entrades", "hide_attachments_in_tl": "Amaga els adjunts en la línia de temps",
"import_followers_from_a_csv_file": "Importa els contactes des d'un fitxer CSV", "import_followers_from_a_csv_file": "Importa els seguits des d'un fitxer CSV",
"import_theme": "Carrega un tema", "import_theme": "Carrega un tema",
"inputRadius": "Caixes d'entrada de text", "inputRadius": "Camps d'entrada",
"instance_default": "(default: {value})", "instance_default": "(default: {value})",
"interfaceLanguage": "Llengua de la interfície", "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.", "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 aquest navegador", "limited_availability": "No està disponible en el teu navegador",
"links": "Enllaços", "links": "Enllaços",
"lock_account_description": "Restringeix el teu compte només a seguidores aprovades", "lock_account_description": "Restringeix el teu compte només a seguidors aprovats",
"loop_video": "Reprodueix els vídeos en bucle", "loop_video": "Vídeos en bucle",
"loop_video_silent_only": "Reprodueix en bucles només els vídeos sense so (com els \"GIF\" de Mastodon)", "loop_video_silent_only": "Només bucle de vídeos sense so (com els \"GIF\" de Mastodon)",
"name": "Nom", "name": "Nom",
"name_bio": "Nom i presentació", "name_bio": "Nom i bio",
"new_password": "Contrasenya nova", "new_password": "Contrasenya nova",
"notification_visibility": "Notifica'm quan algú", "notification_visibility": "Tipus de notificacions a mostrar",
"notification_visibility_follows": "Comença a seguir-me", "notification_visibility_follows": "Seguits",
"notification_visibility_likes": "Favorits", "notification_visibility_likes": "m'afavoreix",
"notification_visibility_mentions": "Em menciona", "notification_visibility_mentions": "em menciona",
"notification_visibility_repeats": "Republica una entrada meva", "notification_visibility_repeats": "em repeteix",
"no_rich_text_description": "Neteja el formatat de text de totes les entrades", "no_rich_text_description": "Neteja el format de text de tots els apunts",
"nsfw_clickthrough": "Amaga el contingut NSFW darrer d'una imatge clicable", "nsfw_clickthrough": "Amaga els Mèdia sensibles/NSFW",
"oauth_tokens": "Llistats OAuth", "oauth_tokens": "Codis OAuth",
"token": "Token", "token": "Token",
"refresh_token": "Actualitza el token", "refresh_token": "Actualitza el token",
"valid_until": "Vàlid fins", "valid_until": "Vàlid fins",
"revoke_token": "Revocar", "revoke_token": "Revoca",
"panelRadius": "Panells", "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", "presets": "Temes",
"profile_background": "Fons de pantalla", "profile_background": "Fons del perfil",
"profile_banner": "Fons de perfil", "profile_banner": "Banner del perfil",
"profile_tab": "Perfil", "profile_tab": "Perfil",
"radii_help": "Configura l'arrodoniment de les vores (en píxels)", "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_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_following": "Mostra només les respostes dirigides a mi o a usuaris que segueixo",
"reply_visibility_self": "Mostra només les respostes a entrades meves", "reply_visibility_self": "Mostra només les respostes dirigides a mi",
"saving_err": "No s'ha pogut desar la configuració", "saving_err": "Error al desar la configuració",
"saving_ok": "S'ha desat la configuració", "saving_ok": "Configuració desada",
"security_tab": "Seguretat", "security_tab": "Seguretat",
"set_new_avatar": "Canvia l'avatar", "set_new_avatar": "Establir un nou avatar",
"set_new_profile_background": "Canvia el fons de pantalla", "set_new_profile_background": "Canvia el fons del perfil",
"set_new_profile_banner": "Canvia el fons del perfil", "set_new_profile_banner": "Establir un nou banner del perfil",
"settings": "Configuració", "settings": "Configuració",
"stop_gifs": "Anima els GIF només en passar-hi el ratolí per sobre", "stop_gifs": "Anima les imatges animades fins que hi passis el cursor per sobre",
"streaming": "Carrega automàticament entrades noves quan estigui a dalt de tot", "streaming": "Mostra automàticament els nous apunts quan et desplacis a la part superior",
"text": "Text", "text": "Text",
"theme": "Tema", "theme": "Tema",
"theme_help": "Personalitza els colors del tema. Escriu-los en format RGB hexadecimal (#rrggbb).", "theme_help": "Utilitza els codis de color hex (#rrggbb) per a personalitzar el color del teu tema.",
"tooltipRadius": "Missatges sobreposats", "tooltipRadius": "Globus/alertes",
"user_settings": "Configuració personal", "user_settings": "Configuració d'usuari",
"values": { "values": {
"false": "no", "false": "no",
"true": "sí" "true": "sí"
}, },
"show_moderator_badge": "Mostra una insígnia de Moderació en el meu perfil", "show_moderator_badge": "Mostra l'insígnia \"Moderador\" en el meu perfil",
"show_admin_badge": "Mostra una insígnia \"d'Administració\" 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_followers_description": "No mostris qui m'està seguint",
"hide_follows_description": "No mostris a qui segueixo", "hide_follows_description": "No mostris a qui segueixo",
"notification_visibility_emoji_reactions": "Reaccions", "notification_visibility_emoji_reactions": "reacciona",
"new_email": "Nou correu electrònic", "new_email": "Nou correu electrònic",
"profile_fields": { "profile_fields": {
"value": "Contingut", "value": "Contingut",
@ -281,69 +281,69 @@
"add_field": "Afegeix un camp", "add_field": "Afegeix un camp",
"label": "Metadades del perfil" "label": "Metadades del perfil"
}, },
"mutes_tab": "Silenciaments", "mutes_tab": "Silenciats",
"interface": "Interfície", "interface": "Interfície",
"instance_default_simple": "(per defecte)", "instance_default_simple": "(per defecte)",
"checkboxRadius": "Caselles", "checkboxRadius": "Caselles",
"import_blocks_from_a_csv_file": "Importa bloquejos des d'un arxiu csv", "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", "use_one_click_nsfw": "Obre els adjunts NSFW amb només un clic",
"hide_muted_posts": "Amaga les entrades de comptes silenciats", "hide_muted_posts": "Amaga els apunts de comptes silenciats",
"avatar_size_instruction": "La mida mínima recomanada per la imatge de l'avatar és de 150x150 píxels.", "avatar_size_instruction": "La mida mínima recomanada per les imatges dels avatars és de 150x150 píxels.",
"domain_mutes": "Dominis", "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", "mutes_and_blocks": "Silenciaments i bloquejos",
"composing": "Composant", "composing": "Composant",
"chatMessageRadius": "Missatge de xat", "chatMessageRadius": "Missatge de xat",
"changed_email": "Correu electrònic canviat amb èxit!", "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_error": "Hi ha hagut un problema al canviar el teu correu electrònic.",
"change_email": "Canvia el 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_tab": "Bloquejos",
"blocks_imported": "Bloquejos importats! Processar-los pot trigar una mica.", "blocks_imported": "Bloquejos importats! Processar-los pot trigar una mica.",
"block_import_error": "Error al importar bloquejos", "block_import_error": "Error al importar bloquejos",
"block_import": "Importa bloquejos", "block_import": "Importa bloquejos",
"block_export_button": "Exporta els teus bloquejos a un arxiu csv", "block_export_button": "Exporta els teus bloquejos a un arxiu csv",
"block_export": "Exporta bloquejos", "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": { "mfa": {
"scan": { "scan": {
"secret_code": "Clau", "secret_code": "Clau",
"title": "Escanejar", "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ó", "authentication_methods": "Mètodes d'autenticació",
"waiting_a_recovery_codes": "Rebent còpies de seguretat dels codis…", "waiting_a_recovery_codes": "Rebent còpies de seguretat dels codis…",
"recovery_codes": "Codis de recuperació.", "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ó", "generate_new_recovery_codes": "Genera nous codis de recuperació",
"otp": "OTP", "otp": "OTP",
"confirm_and_enable": "Confirmar i habilitar OTP", "confirm_and_enable": "Confirma i habilita 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.", "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", "title": "Autenticació de dos factors",
"setup_otp": "Configurar OTP", "setup_otp": "Configurar OTP",
"wait_pre_setup_otp": "preconfiguració OTP", "wait_pre_setup_otp": "preconfiguració OTP",
"verify": { "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", "security": "Seguretat",
"app_name": "Nom de l'aplicació", "app_name": "Nom de l'aplicació",
"subject_line_mastodon": "Com a mastodon: copiar com és", "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", "mute_import_error": "Error al importar silenciats",
"mutes_imported": "Silenciats importats! Processar-los portarà una estona.", "mutes_imported": "Silenciats importats! Processar-los portarà una estona.",
"import_mutes_from_a_csv_file": "Importar silenciats des d'un fitxer csv", "import_mutes_from_a_csv_file": "Importar silenciats des d'un fitxer csv",
"word_filter": "Filtre de paraules", "word_filter": "Filtre de paraules",
"hide_media_previews": "Ocultar les vistes prèvies multimèdia", "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", "play_videos_in_modal": "Reproduir vídeos en un marc emergent",
"file_export_import": { "file_export_import": {
"errors": { "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_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" "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", "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ó" "backup_restore": "Còpia de seguretat de la configuració"
}, },
"user_mutes": "Usuaris", "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", "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", "use_contain_fit": "No retallar els adjunts en miniatures",
"reset_profile_background": "Restablir fons del perfil", "reset_profile_background": "Restablir fons del perfil",
"reset_profile_banner": "Restablir banner del perfil", "reset_profile_banner": "Restablir banner del perfil",
"emoji_reactions_on_timeline": "Mostrar reaccions emoji al flux", "emoji_reactions_on_timeline": "Mostra reaccions emoji en la línia de temps",
"max_thumbnails": "Quantitat màxima de miniatures per publicació", "max_thumbnails": "Quantitat màxima de miniatures per apunt (buit = sense limit)",
"hide_user_stats": "Amagar les estadístiques de l'usuari (p. ex. el nombre de seguidors)", "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_banner_confirm": "Realment vols restablir el banner?",
"reset_background_confirm": "Realment vols restablir el fons del perfil?", "reset_background_confirm": "Realment vols restablir el fons?",
"subject_input_always_show": "Sempre mostrar el camp del tema", "subject_input_always_show": "Sempre mostrar el camp del assumpte",
"subject_line_noop": "No copiar", "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", "search_user_to_mute": "Busca a qui vols silenciar",
"mute_export": "Exportar silenciats", "mute_export": "Exportar silenciats",
"scope_copy": "Copiar visibilitat quan contestes (En els missatges directes sempre es copia)", "scope_copy": "Copiar visibilitat quan responguis (en els missatges directes sempre es copia)",
"reset_avatar": "Restablir avatar", "reset_avatar": "Restablir l'avatar",
"right_sidebar": "Mostrar barra lateral a la dreta", "right_sidebar": "Ordre invers de les columnes",
"no_blocks": "No hi han bloquejats", "no_blocks": "No hi han bloquejats",
"no_mutes": "No hi han silenciats", "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", "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", "hide_wallpaper": "Amagar el fons de la instància",
"notification_visibility_moves": "Usuari Migrat", "notification_visibility_moves": "es mou",
"reply_visibility_following_short": "Mostrar respostes als meus seguidors", "reply_visibility_following_short": "Mostrar respostes als meus seguits",
"reply_visibility_self_short": "Mostrar respostes només a un mateix", "reply_visibility_self_short": "Mostrar només respostes a mi mateix",
"autohide_floating_post_button": "Ocultar automàticament el botó 'Nova Publicació' (mòbil)", "autohide_floating_post_button": "Ocultar automàticament el botó 'Nou Apunt' (mòbil)",
"minimal_scopes_mode": "Minimitzar les opcions de visibilitat de la publicació", "minimal_scopes_mode": "Minimitzar les opcions de selecció del abast del apunt",
"sensitive_by_default": "Marcar publicacions com a sensibles per defecte", "sensitive_by_default": "Marcar apunts com a sensibles per defecte",
"useStreamingApi": "Rebre publicacions i notificacions en temps real", "useStreamingApi": "Rebre apunts i notificacions en temps real",
"hide_isp": "Ocultar el panell especific de la instància", "hide_isp": "Amaga el panell especific de la instància",
"preload_images": "Precarregar les imatges", "preload_images": "Precarregar les imatges",
"setting_changed": "La configuració és diferent a la predeterminada", "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?", "reset_avatar_confirm": "Realment vols restablir l'avatar?",
"accent": "Accent", "accent": "Accent",
"useStreamingApiWarning": "És genial emprar-lo. Si es trenca, refresca, suposo?", "useStreamingApiWarning": "És genial emprar-lo. Si es trenca, refresca, suposo?",
@ -397,10 +397,10 @@
"size": "Mida (en píxels)", "size": "Mida (en píxels)",
"custom": "Personalitza", "custom": "Personalitza",
"_tab_label": "Fonts", "_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": { "components": {
"post": "Text de les publicacions", "post": "Text dels apunts",
"postCode": "Text monoespai en publicació (text enriquit)", "postCode": "Text mono-espai en un apunt (text enriquit)",
"input": "Camps d'entrada", "input": "Camps d'entrada",
"interface": "Interfície" "interface": "Interfície"
}, },
@ -418,19 +418,19 @@
"checkbox": "He llegit els termes i condicions", "checkbox": "He llegit els termes i condicions",
"link": "un bonic enllaç", "link": "un bonic enllaç",
"fine_print": "Llegiu el nostre {0} per no aprendre res útil!", "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": { "shadows": {
"spread": "Difon", "spread": "Difon",
"filter_hint": { "filter_hint": {
"drop_shadow_syntax": "{0} no suporta el paràmetre {1} i la paraula clau {2}.", "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.", "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.", "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" "spread_zero": "Ombres amb propagació > 0 apareixeran com si estigueren posades a zero"
}, },
"components": { "components": {
"popup": "Texts i finestres emergents (popups & tooltips)", "popup": "Globus i finestres emergents",
"panel": "Panell", "panel": "Panell",
"panelHeader": "Capçalera del panell", "panelHeader": "Capçalera del panell",
"avatar": "Avatar de l'usuari (en vista de perfil)", "avatar": "Avatar de l'usuari (en vista de perfil)",
@ -438,8 +438,8 @@
"buttonHover": "Botó (surant)", "buttonHover": "Botó (surant)",
"buttonPressed": "Botó (pressionat)", "buttonPressed": "Botó (pressionat)",
"topBar": "Barra superior", "topBar": "Barra superior",
"buttonPressedHover": "Botó (surant i pressionat)", "buttonPressedHover": "Botó (pressionat i surant)",
"avatarStatus": "Avatar de l'usuari (en vista de publicació)", "avatarStatus": "Avatar de l'usuari (en vista de apunt)",
"button": "Botó" "button": "Botó"
}, },
"hintV3": "per a les ombres també pots usar la notació {0} per a utilitzar un altre espai de color.", "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.", "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_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.", "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.", "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ó.", "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.", "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.", "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.", "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." "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à", "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_color": "Mantindre colors",
"keep_opacity": "Mantindre opacitat", "keep_opacity": "Mantindre opacitat",
"keep_shadows": "Mantindre ombres", "keep_shadows": "Mantindre ombres",
"keep_fonts": "Mantindre fonts", "keep_fonts": "Mantindre fonts",
"keep_roundness": "Mantindre rodoneses", "keep_roundness": "Mantindre rodoneses",
"clear_all": "Netejar tot", "clear_all": "Netejar tot",
"reset": "Reinciar", "reset": "Reiniciar",
"load_theme": "Carregar tema", "load_theme": "Carregar tema",
"use_source": "Nova versió", "use_source": "Nova versió",
"clear_opacity": "Netejar opacitat" "clear_opacity": "Netejar opacitat"
@ -482,9 +482,9 @@
"contrast": { "contrast": {
"hint": "El ràtio de contrast és {ratio}. {level} {context}", "hint": "El ràtio de contrast és {ratio}. {level} {context}",
"level": { "level": {
"bad": "no compleix amb cap pauta d'accecibilitat", "bad": "no compleix amb cap pauta d'accessibilitat",
"aaa": "Compleix amb el nivell AA (recomanat)", "aaa": "Compleix amb la guia del nivell AA (recomanat)",
"aa": "Compleix amb el nivell AA (mínim)" "aa": "Compleix amb la guia del nivell AA (mínim)"
}, },
"context": { "context": {
"18pt": "per a textos grans (+18pt)", "18pt": "per a textos grans (+18pt)",
@ -501,8 +501,8 @@
"pressed": "Pressionat", "pressed": "Pressionat",
"chat": { "chat": {
"outgoing": "Eixint", "outgoing": "Eixint",
"border": "Borde", "border": "Vora",
"incoming": "Entrants" "incoming": "Entrant"
}, },
"borders": "Bordes", "borders": "Bordes",
"panel_header": "Capçalera del panell", "panel_header": "Capçalera del panell",
@ -512,8 +512,8 @@
"toggled": "Commutat", "toggled": "Commutat",
"alert": "Fons d'alertes", "alert": "Fons d'alertes",
"alert_error": "Error", "alert_error": "Error",
"alert_warning": "Precaució", "alert_warning": "Avís",
"post": "Publicacions/Biografies d'usuaris", "post": "Apunts/Bio d'usuari",
"badge_notification": "Notificacions", "badge_notification": "Notificacions",
"selectedMenu": "Element del menú seleccionat", "selectedMenu": "Element del menú seleccionat",
"tabs": "Pestanyes", "tabs": "Pestanyes",
@ -524,7 +524,7 @@
"highlight": "Elements destacats", "highlight": "Elements destacats",
"disabled": "Deshabilitat", "disabled": "Deshabilitat",
"icons": "Icones", "icons": "Icones",
"selectedPost": "Publicació seleccionada", "selectedPost": "Apunt seleccionat",
"underlay": "Subratllat" "underlay": "Subratllat"
}, },
"common_colors": { "common_colors": {
@ -538,32 +538,32 @@
} }
}, },
"version": { "version": {
"frontend_version": "Versió \"Frontend\"", "frontend_version": "Versió del \"Frontend\"",
"backend_version": "Versió \"backend\"", "backend_version": "Versió del \"backend\"",
"title": "Versió" "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", "type_domains_to_mute": "Buscar dominis per a silenciar",
"greentext": "Text verd (meme arrows)", "greentext": "Text verd (meme arrows)",
"fun": "Divertit", "fun": "Divertit",
"notification_setting_filters": "Filtres", "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", "notification_setting_block_from_strangers": "Bloqueja les notificacions dels usuaris que no segueixes",
"enable_web_push_notifications": "Habilitar notificacions del navegador", "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", "more_settings": "Més opcions",
"notification_setting_privacy": "Privacitat", "notification_setting_privacy": "Privacitat",
"upload_a_photo": "Pujar una foto", "upload_a_photo": "Pujar una foto",
"notification_setting_hide_notification_contents": "Amagar el remitent i els continguts de les notificacions push", "notification_setting_hide_notification_contents": "Amagar el remitent i els continguts de les notificacions push",
"notifications": "Notificacions", "notifications": "Notificacions",
"notification_mutes": "Per a deixar de rebre notificacions d'un usuari en concret, silencia'l-ho.", "notification_mutes": "Per a deixar de rebre notificacions d'un usuari en concret, silencia'l.",
"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.", "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": "Oculta la casella de gàbia de grills", "hide_shoutbox": "Amaga la casella de gàbia de grills",
"always_show_post_button": "Mostra sempre el botó flotant de publicació nova", "always_show_post_button": "Mostra sempre el botó flotant d'Apunt Nou",
"pad_emoji": "Acompanya els emojis amb espais en afegir des del selector", "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_style": "Enllaços d'esment més elegants",
"mentions_new_place": "Posa les mencions en una línia separada", "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", "expert_mode": "Mostra avançat",
"setting_server_side": "Aquest ajust està lligat al teu perfil i afectarà a totes les sessions i clients", "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", "account_backup": "Copia de seguretat del compte",
@ -572,7 +572,7 @@
"account_alias": "Àlies del compte", "account_alias": "Àlies del compte",
"list_aliases_error": "Error al obtenir els àlies: {error}", "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.", "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", "account_privacy": "Privacitat",
"hide_favorites_description": "No mostrar la llista dels meus favorits (la gent seguirà sent notificada)", "hide_favorites_description": "No mostrar la llista dels meus favorits (la gent seguirà sent notificada)",
"mascot": "Mascota de Mastodon FE", "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", "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", "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", "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", "mention_links": "Enllaços de mencions",
"email_language": "Llengua per a rebre correus des d'el servidor", "email_language": "Llengua per a rebre correus des d'el servidor",
"wordfilter": "Filtre de paraules", "wordfilter": "Filtre de paraules",
@ -594,7 +594,7 @@
"remove_backup": "Treure", "remove_backup": "Treure",
"list_backups_error": "Error al recuperar la llista de copies de seguretat: {error}", "list_backups_error": "Error al recuperar la llista de copies de seguretat: {error}",
"add_backup": "Crea una nova copia de seguretat", "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}", "add_backup_error": "Error al afegir una nova còpia de seguretat: {error}",
"current_mascot": "La teva mascota actual", "current_mascot": "La teva mascota actual",
"account_alias_table_head": "Àlies", "account_alias_table_head": "Àlies",
@ -607,11 +607,11 @@
"move_account_target": "Compte destí (p.ex. {example})", "move_account_target": "Compte destí (p.ex. {example})",
"moved_account": "El compte s'ha mogut.", "moved_account": "El compte s'ha mogut.",
"move_account_error": "Error al moure el compte: {error}", "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", "hide_muted_threads": "Amaga fils silenciats",
"posts": "Publicacions", "posts": "Apunts",
"user_profiles": "Perfils d'usuari", "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": "Estil de visualització de la conversa",
"conversation_display_tree": "Estil d'arbre", "conversation_display_tree": "Estil d'arbre",
"show_scrollbars": "Mostra les barres de desplaçament de la columna lateral", "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_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_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_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" "show_yous": "Mostra (Tu)s"
}, },
"time": { "time": {
"now": "ara mateix", "now": "ara mateix",
"now_short": "ara mateix", "now_short": "ara mateix",
"in_future": "in {0}", "in_future": "en {0}",
"in_past": "fa {0}", "in_past": "fa {0}",
"unit": { "unit": {
"day": "{0} dia", "day": "{0} dia",
"days": "{0} dies", "days": "{0} dia | {0} dies",
"day_short": "{0} dia", "day_short": "{0} dia",
"days_short": "{0} dies", "days_short": "{0}d",
"hour": "{0} hora", "hour": "{0} hora",
"hours": "{0} hores", "hours": "{0} hora | {0} hores",
"hour_short": "{0}h", "hour_short": "{0}h",
"hours_short": "{0}h", "hours_short": "{0}h",
"minute": "{0} minute", "minute": "{0} minute",
"minutes": "{0} minutes", "minutes": "{0} minuts | {0} minuts",
"minute_short": "{0}min", "minute_short": "{0}min",
"minutes_short": "{0}min", "minutes_short": "{0}min",
"month": "{0} mes", "month": "{0} mes",
"months": "{0} mesos", "months": "{0} mes | {0} mesos",
"month_short": "{0} mes", "month_short": "{0} mes",
"months_short": "{0} mesos", "months_short": "{0}mes",
"second": "{0} segon", "second": "{0} segon",
"seconds": "{0} segons", "seconds": "{0} segon | {0} segons",
"second_short": "{0}s", "second_short": "{0}s",
"seconds_short": "{0}s", "seconds_short": "{0}s",
"week": "{0} setmana", "week": "{0} setmana",
"weeks": "{0} setmanes", "weeks": "{0} setmana | {0} setmanes",
"week_short": "{0} setm.", "week_short": "{0} setm.",
"weeks_short": "{0}setm.", "weeks_short": "{0}setm.",
"year": "{0} any", "year": "{0} any",
"years": "{0} anys", "years": "{0} any | {0} anys",
"year_short": "{0} any", "year_short": "{0} any",
"years_short": "{0}anys" "years_short": "{0}anys"
} }
@ -674,17 +674,17 @@
"collapse": "Replega", "collapse": "Replega",
"conversation": "Conversa", "conversation": "Conversa",
"error_fetching": "S'ha produït un error en carregar les entrades", "error_fetching": "S'ha produït un error en carregar les entrades",
"load_older": "Carrega entrades anteriors", "load_older": "Carrega apunts anteriors",
"no_retweet_hint": "L'entrada és només per a seguidores o és \"directa\", i per tant no es pot republicar", "no_retweet_hint": "L'apunt és només per a seguidors o és \"directe\" i no es pot repetir o citar",
"repeated": "republicat", "repeated": "repetit",
"show_new": "Mostra els nous", "show_new": "Mostra els nous",
"up_to_date": "Actualitzat", "up_to_date": "Actualitzat",
"socket_reconnected": "Connexió a temps real establerta", "socket_reconnected": "Connexió a temps real establerta",
"socket_broke": "Connexió a temps real perduda: codi CloseEvent {0}", "socket_broke": "Connexió a temps real perduda: codi CloseEvent {0}",
"error": "Error de càrrega de la línia de temps: {0}", "error": "Error carregant la línia de temps: {0}",
"no_statuses": "No hi ha entrades", "no_statuses": "No hi ha apunts",
"reload": "Recarrega", "reload": "Recarrega",
"no_more_statuses": "No hi ha més entrades" "no_more_statuses": "No hi ha més apunts"
}, },
"user_card": { "user_card": {
"approve": "Aprova", "approve": "Aprova",
@ -692,35 +692,35 @@
"blocked": "Bloquejat!", "blocked": "Bloquejat!",
"deny": "Denega", "deny": "Denega",
"follow": "Segueix", "follow": "Segueix",
"followees": "Segueixo", "followees": "Seguint",
"followers": "Seguidors/es", "followers": "Seguidors",
"following": "Seguint!", "following": "Seguint!",
"follows_you": "Et segueix!", "follows_you": "Et segueix!",
"mute": "Silencia", "mute": "Silencia",
"muted": "Silenciat", "muted": "Silenciat",
"per_day": "per dia", "per_day": "per dia",
"remote_follow": "Seguiment remot", "remote_follow": "Seguiment remot",
"statuses": "Estats", "statuses": "Apunts",
"unblock_progress": "Desbloquejant…", "unblock_progress": "Desbloquejant…",
"unmute": "Deixa de silenciar", "unmute": "Deixa de silenciar",
"follow_progress": "Sol·licitant…", "follow_progress": "Sol·licitant…",
"admin_menu": { "admin_menu": {
"force_nsfw": "Marca totes les entrades amb \"No segur per a entorns laborals\"", "force_nsfw": "Marca tots els apunts amb \"No segur per a entorns laborals\"",
"strip_media": "Esborra els audiovisuals de les entrades", "strip_media": "Esborra els Mèdia dels apunts",
"disable_any_subscription": "Deshabilita completament seguir algú", "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ó", "moderation": "Moderació",
"revoke_admin": "Revoca l'Admin", "revoke_admin": "Revoca l'Admin",
"activate_account": "Activa el compte", "activate_account": "Activa el compte",
"deactivate_account": "Desactiva el compte", "deactivate_account": "Desactiva el compte",
"revoke_moderator": "Revoca Moderació", "revoke_moderator": "Revoca Moderador",
"delete_account": "Esborra el compte", "delete_account": "Esborra el compte",
"disable_remote_subscription": "Deshabilita seguir algú des d'una instància remota", "disable_remote_subscription": "Deshabilita seguir algú des d'una instància remota",
"delete_user": "Esborra la usuària", "delete_user": "Esborra l'usuari",
"grant_admin": "Concedir permisos d'Administració", "grant_admin": "Concedir permisos d'Administrador",
"grant_moderator": "Concedir permisos de Moderació", "grant_moderator": "Concedir permisos de Moderador",
"force_unlisted": "Força que les publicacions no estiguin llistades", "force_unlisted": "Força que els apunts no estiguin llistats",
"sandbox": "Força que els missatges siguin només seguidors", "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?" "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", "edit_profile": "Edita el perfil",
@ -747,15 +747,15 @@
"striped": "Fons a ratlles", "striped": "Fons a ratlles",
"side": "Ratlla lateral" "side": "Ratlla lateral"
}, },
"media": "Media", "media": "Mèdia",
"domain_muted": "Desbloqueja el domini", "domain_muted": "Desbloqueja el domini",
"deactivated": "Desactivat", "deactivated": "Desactivat",
"follow_cancel": "Cancel·la la sol·licitut", "follow_cancel": "Cancel·la la sol·licitud",
"mute_domain": "Bloqueja el domini", "mute_domain": "Bloqueja el domini",
"note": "Nota privada" "note": "Nota privada"
}, },
"user_profile": { "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_loading_error": "Disculpes, hi ha hagut un error carregant aquest perfil.",
"profile_does_not_exist": "Disculpes, aquest perfil no existeix." "profile_does_not_exist": "Disculpes, aquest perfil no existeix."
}, },
@ -865,29 +865,29 @@
} }
}, },
"status": { "status": {
"delete": "Esborra l'entrada", "delete": "Esborra l'apunt",
"delete_confirm": "Segur que vols esborrar aquesta entrada?", "delete_confirm": "Segur que vols esborrar aquest apunt?",
"thread_muted_and_words": ", té les paraules:", "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", "show_content": "Mostra el contingut",
"repeats": "Repeticions", "repeats": "Repeticions",
"bookmark": "Marcadors", "bookmark": "Marcador",
"status_unavailable": "Entrada no disponible", "status_unavailable": "Apunt no disponible",
"expand": "Expandeix", "expand": "Expandeix",
"copy_link": "Copia l'enllaç a l'entrada", "copy_link": "Copia l'enllaç al apunt",
"hide_full_subject": "Amaga tot el tema", "hide_full_subject": "Amaga tot l'assumpte",
"favorites": "Favorits", "favorites": "Favorits",
"replies_list": "Contestacions:", "replies_list": "Respostes:",
"mute_conversation": "Silencia la conversa", "mute_conversation": "Silencia la conversa",
"thread_muted": "Fil silenciat", "thread_muted": "Fil silenciat",
"hide_content": "Amaga el contingut", "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", "nsfw": "No segur per a entorns laborals",
"unbookmark": "Desmarca", "unbookmark": "Desmarca",
"external_source": "Font externa", "external_source": "Font externa",
"unpin": "Deixa de destacar al perfil", "unpin": "Deixa de destacar al perfil",
"pinned": "Destacat", "pinned": "Destacat",
"reply_to": "Contesta a", "reply_to": "Respon a",
"pin": "Destaca al perfil", "pin": "Destaca al perfil",
"unmute_conversation": "Deixa de silenciar la conversa", "unmute_conversation": "Deixa de silenciar la conversa",
"mentions": "Mencions", "mentions": "Mencions",
@ -919,21 +919,21 @@
}, },
"user_reporting": { "user_reporting": {
"additional_comments": "Comentaris addicionals", "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}", "forward_to": "Endavant a {0}",
"generic_error": "Hi ha hagut un error mentre s'estava processant la teva sol·licitud.", "generic_error": "Hi ha hagut un error mentre s'estava processant la teva sol·licitud.",
"title": "Reportant {0}", "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" "submit": "Envia"
}, },
"tool_tip": { "tool_tip": {
"add_reaction": "Afegeix una Reacció", "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", "repeat": "Repeteix",
"reply": "Respon", "reply": "Respon",
"favorite": "Favorit", "favorite": "Favorit",
"user_settings": "Configuració d'usuària", "user_settings": "Configuració d'usuari",
"reject_follow_request": "Rebutja la sol·licitud de seguir", "reject_follow_request": "Rebutja la sol·licitud de seguiment",
"bookmark": "Marcador", "bookmark": "Marcador",
"media_upload": "Pujar multimèdia", "media_upload": "Pujar multimèdia",
"quote": "Cita" "quote": "Cita"
@ -967,13 +967,13 @@
"password_reset": "Reinicia la contrasenya", "password_reset": "Reinicia la contrasenya",
"forgot_password": "Has oblidat 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.", "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.", "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'usuària", "placeholder": "El teu correu electrònic o nom d'usuari",
"instruction": "Introdueix la teva adreça de correu electrònic o nom d'usuària. T'enviarem un enllaç per reiniciar la teva contrasenya.", "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", "return_home": "Torna a la pàgina principal",
"password_reset_required": "Has de reiniciar la teva contrasenya per iniciar la sessió.", "password_reset_required": "Has de reiniciar la teva contrasenya per a iniciar la sessió.",
"password_reset_disabled": "El reinici de la contrasenya està deshabilitat. Si us plau, contacta l'administració de la teva instància.", "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 reiniciar la teva contrasenya." "check_email": "Comprova que has rebut al correu electrònic un enllaç per a reiniciar la teva contrasenya."
}, },
"file_type": { "file_type": {
"image": "Imatge", "image": "Imatge",

View file

@ -18,12 +18,12 @@
"not_applicable": "N/A", "not_applicable": "N/A",
"accept": "Accept", "accept": "Accept",
"accept_desc": "This instance only accepts messages from the following instances:", "accept_desc": "This instance only accepts messages from the following instances:",
"reject": "Reject", "reject": "Instance Blocks",
"reject_desc": "This instance will not accept messages from the following instances:", "reject_desc": "This instance blocks posts to and from the following instances:",
"quarantine": "Quarantine", "quarantine": "Quarantine",
"quarantine_desc": "This instance will send only public posts to the following instances:", "quarantine_desc": "This instance will not send posts to the following instances:",
"ftl_removal": "Removal from \"Known Network\" Timeline", "ftl_removal": "Removal from Federated Timeline",
"ftl_removal_desc": "This instance removes these instances from \"Known Network\" timeline:", "ftl_removal_desc": "This instance removes these instances from the Federated Timeline:",
"media_removal": "Media Removal", "media_removal": "Media Removal",
"media_removal_desc": "This instance removes media from posts on the following instances:", "media_removal_desc": "This instance removes media from posts on the following instances:",
"media_nsfw": "Media force-set as sensitive", "media_nsfw": "Media force-set as sensitive",
@ -106,7 +106,8 @@
"direct": "Direct", "direct": "Direct",
"private": "Followers-only", "private": "Followers-only",
"public": "Public", "public": "Public",
"unlisted": "Unlisted" "unlisted": "Unlisted",
"local": "Local-only post -- only your instance can see this status"
} }
}, },
"image_cropper": { "image_cropper": {
@ -125,7 +126,7 @@
"description": "Log in with OAuth", "description": "Log in with OAuth",
"logout": "Log out", "logout": "Log out",
"password": "Password", "password": "Password",
"placeholder": "myusername", "placeholder": "i.e. bobvibes420",
"register": "Register", "register": "Register",
"username": "Username", "username": "Username",
"hint": "Log in to join the discussion", "hint": "Log in to join the discussion",
@ -148,18 +149,18 @@
"about": "About", "about": "About",
"administration": "Administration", "administration": "Administration",
"back": "Back", "back": "Back",
"friend_requests": "Follow requests", "friend_requests": "Follow Requests",
"mentions": "Mentions", "mentions": "Mentions",
"interactions": "Interactions", "interactions": "Interactions",
"dms": "Direct messages", "dms": "Direct Messages",
"public_tl": "Public timeline", "public_tl": "Local Timeline",
"public_timeline_description": "Public posts from this instance", "public_timeline_description": "Public posts from this instance",
"timeline": "Timeline", "timeline": "Timeline",
"home_timeline": "Home timeline", "home_timeline": "Home Timeline",
"home_timeline_description": "Posts from people you follow", "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", "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", "twkn_timeline_description": "Posts from the entire network",
"bookmarks": "Bookmarks", "bookmarks": "Bookmarks",
"user_search": "User Search", "user_search": "User Search",
@ -180,7 +181,7 @@
"load_older": "Load older notifications", "load_older": "Load older notifications",
"notifications": "Notifications", "notifications": "Notifications",
"read": "Read!", "read": "Read!",
"repeated_you": "repeated your status", "repeated_you": "boosted your status",
"no_more_notifications": "No more notifications", "no_more_notifications": "No more notifications",
"migrated_to": "migrated to", "migrated_to": "migrated to",
"reacted_with": "reacted with {0}", "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." "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": { "interactions": {
"favs_repeats": "Repeats and favorites", "favs_repeats": "Boosts and favorites",
"follows": "New follows", "follows": "New follows",
"moves": "User migrates", "moves": "User migrates",
"load_older": "Load older interactions" "load_older": "Load older interactions"
@ -236,8 +237,8 @@
"text/bbcode": "BBCode", "text/bbcode": "BBCode",
"text/x.misskeymarkdown": "MFM" "text/x.misskeymarkdown": "MFM"
}, },
"content_warning": "Subject (optional)", "content_warning": "Content Warning / Subject (optional)",
"default": "Just arrived at Luna Nova Academy", "default": "Just landed on Neptune",
"direct_warning_to_all": "This post will be visible to all the mentioned users.", "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.", "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
"posting": "Posting", "posting": "Posting",
@ -245,6 +246,9 @@
"preview": "Preview", "preview": "Preview",
"preview_empty": "Empty", "preview_empty": "Empty",
"empty_status_error": "Can't post an empty status with no files", "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", "media_description_error": "Failed to update media, try again",
"scope_notice": { "scope_notice": {
"public": "This post will be visible to everyone", "public": "This post will be visible to everyone",
@ -257,7 +261,7 @@
"private": "Followers-only - post to followers only", "private": "Followers-only - post to followers only",
"public": "Public - post to public timelines", "public": "Public - post to public timelines",
"unlisted": "Unlisted - do not 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": { "registration": {
@ -369,7 +373,17 @@
"changed_password": "Password changed successfully!", "changed_password": "Password changed successfully!",
"chatMessageRadius": "Chat message", "chatMessageRadius": "Chat message",
"collapse_subject": "Collapse posts with subjects", "collapse_subject": "Collapse posts with subjects",
"columns": "Columns",
"composing": "Composing", "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", "confirm_new_password": "Confirm new password",
"current_avatar": "Your current avatar", "current_avatar": "Your current avatar",
"current_mascot": "Your current mascot", "current_mascot": "Your current mascot",
@ -424,6 +438,10 @@
"hide_shoutbox": "Hide instance shoutbox", "hide_shoutbox": "Hide instance shoutbox",
"right_sidebar": "Reverse order of columns", "right_sidebar": "Reverse order of columns",
"always_show_post_button": "Always show floating New Post button", "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", "hide_wallpaper": "Hide instance wallpaper",
"preload_images": "Preload images", "preload_images": "Preload images",
"use_one_click_nsfw": "Open NSFW attachments with just one click", "use_one_click_nsfw": "Open NSFW attachments with just one click",
@ -479,7 +497,7 @@
"notification_visibility_follows": "Follows", "notification_visibility_follows": "Follows",
"notification_visibility_likes": "Favorites", "notification_visibility_likes": "Favorites",
"notification_visibility_mentions": "Mentions", "notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats", "notification_visibility_repeats": "Boosts",
"notification_visibility_moves": "User Migrates", "notification_visibility_moves": "User Migrates",
"notification_visibility_emoji_reactions": "Reactions", "notification_visibility_emoji_reactions": "Reactions",
"notification_visibility_polls": "Ends of polls you voted in", "notification_visibility_polls": "Ends of polls you voted in",
@ -540,6 +558,7 @@
"conversation_display": "Conversation display style", "conversation_display": "Conversation display style",
"conversation_display_tree": "Tree-style", "conversation_display_tree": "Tree-style",
"disable_sticky_headers": "Don't stick column headers to top of the screen", "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", "show_scrollbars": "Show side column's scrollbars",
"third_column_mode": "When there's enough space, show third column containing", "third_column_mode": "When there's enough space, show third column containing",
"third_column_mode_none": "Don't show third column at all", "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_by_default": "Mark posts as sensitive by default",
"sensitive_if_subject": "Automatically mark images as sensitive if a subject line is specified", "sensitive_if_subject": "Automatically mark images as sensitive if a subject line is specified",
"render_mfm": "Render Misskey Markdown", "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?", "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", "streaming": "Automatically show new posts when scrolled to the top",
"user_mutes": "Users", "user_mutes": "Users",
"useStreamingApi": "Receive posts and notifications real-time", "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_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.", "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", "tooltipRadius": "Tooltips/alerts",
"translation_language": "Automatic Translation Language",
"type_domains_to_mute": "Search domains to mute", "type_domains_to_mute": "Search domains to mute",
"upload_a_photo": "Upload a photo", "upload_a_photo": "Upload a photo",
"user_settings": "User Settings", "user_settings": "User Settings",
@ -776,8 +798,8 @@
"conversation": "Conversation", "conversation": "Conversation",
"error": "Error fetching timeline: {0}", "error": "Error fetching timeline: {0}",
"load_older": "Load older statuses", "load_older": "Load older statuses",
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated or quoted", "no_retweet_hint": "Post is marked as followers-only or direct and cannot be boosted or quoted",
"repeated": "repeated", "repeated": "boosted",
"show_new": "Show new", "show_new": "Show new",
"reload": "Reload", "reload": "Reload",
"up_to_date": "Up-to-date", "up_to_date": "Up-to-date",
@ -788,15 +810,28 @@
}, },
"status": { "status": {
"favorites": "Favorites", "favorites": "Favorites",
"repeats": "Repeats", "repeats": "Boosts",
"delete": "Delete status", "delete": "Delete status",
"pin": "Pin on profile", "pin": "Pin on profile",
"unpin": "Unpin from profile", "unpin": "Unpin from profile",
"pinned": "Pinned", "pinned": "Pinned",
"bookmark": "Bookmark", "bookmark": "Bookmark",
"unbookmark": "Unbookmark", "unbookmark": "Unbookmark",
"translate": "Translate",
"translated_from": "Translated from {language}",
"delete_confirm": "Do you really want to delete this status?", "delete_confirm": "Do you really want to delete this status?",
"reply_to": "Reply to", "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", "mentions": "Mentions",
"replies_list": "Replies:", "replies_list": "Replies:",
"replies_list_with_others": "Replies (+{numReplies} other): | Replies (+{numReplies} others):", "replies_list_with_others": "Replies (+{numReplies} other): | Replies (+{numReplies} others):",
@ -841,10 +876,23 @@
}, },
"user_card": { "user_card": {
"approve": "Approve", "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": "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!", "blocked": "Blocked!",
"deactivated": "Deactivated", "deactivated": "Deactivated",
"deny": "Deny", "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", "edit_profile": "Edit profile",
"favorites": "Favorites", "favorites": "Favorites",
"follow": "Follow", "follow": "Follow",
@ -862,9 +910,15 @@
"mention": "Mention", "mention": "Mention",
"message": "Message", "message": "Message",
"mute": "Mute", "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", "muted": "Muted",
"per_day": "per day", "per_day": "per day",
"remote_follow": "Remote follow", "remote_follow": "Remote follow",
"remove_follower": "Remove follower",
"replies": "With Replies",
"report": "Report", "report": "Report",
"statuses": "Statuses", "statuses": "Statuses",
"subscribe": "Subscribe", "subscribe": "Subscribe",
@ -872,11 +926,15 @@
"unblock": "Unblock", "unblock": "Unblock",
"unblock_progress": "Unblocking…", "unblock_progress": "Unblocking…",
"block_progress": "Blocking…", "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": "Unmute",
"unmute_progress": "Unmuting…", "unmute_progress": "Unmuting…",
"mute_progress": "Muting…", "mute_progress": "Muting…",
"hide_repeats": "Hide repeats", "hide_repeats": "Hide boosts",
"show_repeats": "Show repeats", "show_repeats": "Show boosts",
"domain_muted": "Unblock domain", "domain_muted": "Unblock domain",
"mute_domain": "Block domain", "mute_domain": "Block domain",
"bot": "Bot", "bot": "Bot",
@ -928,7 +986,7 @@
"tool_tip": { "tool_tip": {
"media_upload": "Upload media", "media_upload": "Upload media",
"quote": "Quote", "quote": "Quote",
"repeat": "Repeat", "repeat": "Boost",
"reply": "Reply", "reply": "Reply",
"favorite": "Favorite", "favorite": "Favorite",
"add_reaction": "Add Reaction", "add_reaction": "Add Reaction",

View file

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

View file

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

View file

@ -98,6 +98,13 @@ const api = {
showImmediately: timelineData.visibleStatuses.length === 0, showImmediately: timelineData.visibleStatuses.length === 0,
timeline: 'friends' 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') { } else if (message.event === 'delete') {
dispatch('deleteStatusById', message.id) dispatch('deleteStatusById', message.id)
} }

View file

@ -31,10 +31,15 @@ export const defaultState = {
// bad name: actually hides posts of muted USERS // bad name: actually hides posts of muted USERS
hideMutedPosts: undefined, // instance default hideMutedPosts: undefined, // instance default
hideMutedThreads: undefined, // instance default hideMutedThreads: undefined, // instance default
hideThreadsWithBlockedUsers: undefined, // instance default
hideWordFilteredPosts: undefined, // instance default hideWordFilteredPosts: undefined, // instance default
muteBotStatuses: undefined, // instance default muteBotStatuses: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default collapseMessageWithSubject: true, // instance default
padEmoji: true, padEmoji: true,
showNavShortcuts: undefined, // instance default
showWiderShortcuts: undefined, // instance default
hideSiteFavicon: undefined, // instance default
hideSiteName: undefined, // instance default
hideAttachments: false, hideAttachments: false,
hideAttachmentsInConv: false, hideAttachmentsInConv: false,
maxThumbnails: 16, maxThumbnails: 16,
@ -44,11 +49,11 @@ export const defaultState = {
loopVideoSilentOnly: true, loopVideoSilentOnly: true,
streaming: false, streaming: false,
emojiReactionsOnTimeline: true, emojiReactionsOnTimeline: true,
alwaysShowNewPostButton: false, alwaysShowNewPostButton: true,
autohideFloatingPostButton: false, autohideFloatingPostButton: false,
pauseOnUnfocused: true, pauseOnUnfocused: true,
stopGifs: true, stopGifs: false,
replyVisibility: 'all', replyVisibility: 'following',
thirdColumnMode: 'notifications', thirdColumnMode: 'notifications',
notificationVisibility: { notificationVisibility: {
follows: true, follows: true,
@ -66,7 +71,7 @@ export const defaultState = {
highlight: {}, highlight: {},
interfaceLanguage: browserLocale, interfaceLanguage: browserLocale,
hideScopeNotice: false, hideScopeNotice: false,
useStreamingApi: true, useStreamingApi: false,
sidebarRight: undefined, // instance default sidebarRight: undefined, // instance default
scopeCopy: undefined, // instance default scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default subjectLineBehavior: undefined, // instance default
@ -75,6 +80,14 @@ export const defaultState = {
minimalScopesMode: undefined, // instance default minimalScopesMode: undefined, // instance default
// This hides statuses filtered via a word filter // This hides statuses filtered via a word filter
hideFilteredStatuses: undefined, // instance default 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, playVideosInModal: false,
useOneClickNsfw: false, useOneClickNsfw: false,
useContainFit: true, useContainFit: true,
@ -84,7 +97,7 @@ export const defaultState = {
useAtIcon: undefined, // instance default useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default mentionLinkDisplay: undefined, // instance default
mentionLinkShowTooltip: undefined, // instance default mentionLinkShowTooltip: undefined, // instance default
mentionLinkShowAvatar: undefined, // instance default mentionLinkShowAvatar: true, // instance default
mentionLinkFadeDomain: undefined, // instance default mentionLinkFadeDomain: undefined, // instance default
mentionLinkShowYous: undefined, // instance default mentionLinkShowYous: undefined, // instance default
mentionLinkBoldenYou: undefined, // instance default mentionLinkBoldenYou: undefined, // instance default
@ -94,12 +107,14 @@ export const defaultState = {
virtualScrolling: undefined, // instance default virtualScrolling: undefined, // instance default
sensitiveByDefault: undefined, // instance default sensitiveByDefault: undefined, // instance default
sensitiveIfSubject: undefined, sensitiveIfSubject: undefined,
renderMisskeyMarkdown: undefined, renderMisskeyMarkdown: true,
mfmOnHover: undefined, // instance default
conversationDisplay: undefined, // instance default conversationDisplay: undefined, // instance default
conversationTreeAdvanced: undefined, // instance default conversationTreeAdvanced: undefined, // instance default
conversationOtherRepliesButton: undefined, // instance default conversationOtherRepliesButton: undefined, // instance default
conversationTreeFadeAncestors: undefined, // instance default conversationTreeFadeAncestors: undefined, // instance default
maxDepthInThread: undefined // instance default maxDepthInThread: undefined, // instance default
translationLanguage: undefined // instance default
} }
// caching the instance default properties // caching the instance default properties
@ -172,6 +187,7 @@ const config = {
case 'interfaceLanguage': case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value) messages.setLanguage(this.getters.i18n, value)
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value)) Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
dispatch('setInstanceOption', { name: 'interfaceLanguage', value })
break break
case 'thirdColumnMode': case 'thirdColumnMode':
dispatch('setLayoutWidth', undefined) 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', defaultAvatar: '/images/avi.png',
defaultBanner: '/images/banner.png', defaultBanner: '/images/banner.png',
background: '/static/aurora_borealis.jpg', background: '/static/aurora_borealis.jpg',
collapseMessageWithSubject: false, collapseMessageWithSubject: true,
greentext: false, greentext: true,
useAtIcon: false, useAtIcon: false,
mentionLinkDisplay: 'short', mentionLinkDisplay: 'short',
mentionLinkShowTooltip: true, mentionLinkShowTooltip: true,
@ -30,12 +30,22 @@ const defaultState = {
// bad name: actually hides posts of muted USERS // bad name: actually hides posts of muted USERS
hideMutedPosts: false, hideMutedPosts: false,
hideMutedThreads: true, hideMutedThreads: true,
hideThreadsWithBlockedUsers: false,
hideWordFilteredPosts: false, hideWordFilteredPosts: false,
hidePostStats: false, hidePostStats: false,
hideBotIndication: false, hideBotIndication: false,
hideSitename: false, hideSiteFavicon: false,
hideSiteName: false,
hideUserStats: false, hideUserStats: false,
muteBotStatuses: false, muteBotStatuses: false,
modalOnRepeat: false,
modalOnUnfollow: false,
modalOnBlock: true,
modalOnMute: false,
modalOnDelete: true,
modalOnLogout: true,
modalOnApproveFollow: false,
modalOnDenyFollow: false,
loginMethod: 'password', loginMethod: 'password',
logo: '/static/logo.svg', logo: '/static/logo.svg',
logoMargin: '.2em', logoMargin: '.2em',
@ -49,13 +59,16 @@ const defaultState = {
scopeCopy: true, scopeCopy: true,
showFeaturesPanel: true, showFeaturesPanel: true,
showInstanceSpecificPanel: false, showInstanceSpecificPanel: false,
showNavShortcuts: true,
showWiderShortcuts: false,
sidebarRight: false, sidebarRight: false,
subjectLineBehavior: 'email', subjectLineBehavior: 'email',
theme: 'pleroma-dark', theme: 'pleroma-dark',
virtualScrolling: true, virtualScrolling: true,
sensitiveByDefault: false, sensitiveByDefault: false,
sensitiveIfSubject: true, sensitiveIfSubject: true,
renderMisskeyMarkdown: false, renderMisskeyMarkdown: true,
mfmOnHover: false,
conversationDisplay: 'linear', conversationDisplay: 'linear',
conversationTreeAdvanced: false, conversationTreeAdvanced: false,
conversationOtherRepliesButton: 'below', conversationOtherRepliesButton: 'below',
@ -153,7 +166,7 @@ const instance = {
async getCustomEmoji ({ commit, state }) { async getCustomEmoji ({ commit, state }) {
try { try {
const res = await window.fetch('/api/pleroma/emoji.json') const res = await window.fetch('/api/v1/pleroma/emoji')
if (res.ok) { if (res.ok) {
const result = await res.json() const result = await res.json()
const values = Array.isArray(result) ? Object.assign({}, ...result) : result 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