Compare commits

...

28 commits

Author SHA1 Message Date
0810c57c8b Merge pull request 'Hide bubble timeline icon from desktop nav if empty' (#197) from sfr/pleroma-fe:fix/navbar-hide-bubble into develop
Reviewed-on: AkkomaGang/pleroma-fe#197
2022-11-10 12:10:05 +00:00
e77931d68c Use correct sensitiveIfSubject key 2022-11-10 12:06:59 +00:00
f3962e3be7 Merge pull request 'Add "requested to follow you" text on card' (#200) from inbound-requesting into develop
Reviewed-on: AkkomaGang/pleroma-fe#200
2022-11-10 03:16:17 +00:00
1e8fc5bcc4 Add "requested to follow you" text on card 2022-11-10 03:01:20 +00:00
Sol Fisher Romanoff
642fe3dc10
Hide bubble timeline icon from desktop nav if empty 2022-11-08 18:50:42 +02:00
8713f1870f use correct key for languages
fixes #196
2022-11-08 12:25:47 +00:00
837c61569a Merge pull request 'Fix profile field deletion' (#195) from sn0w/pleroma-fe:fix-profile-field-deletion into develop
Reviewed-on: AkkomaGang/pleroma-fe#195
2022-11-07 14:00:43 +00:00
a83c3a1fa1
Fix profile field deletion 2022-11-07 14:53:13 +01:00
Weblate
4d8f288bd9 Merge branch 'origin/develop' into Weblate. 2022-11-06 22:52:26 +00:00
3286641f3c Add software info on hover (#194)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#194
2022-11-06 22:52:25 +00:00
Weblate
677f5ae071 Merge branch 'origin/develop' into Weblate. 2022-11-06 21:26:06 +00:00
sfr
15bac1e401 Add reports management (#186)
implements part of #178, other parts will come later

Co-authored-by: Sol Fisher Romanoff <sol@solfisher.com>
Reviewed-on: AkkomaGang/pleroma-fe#186
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-11-06 21:26:05 +00:00
Weblate
22b4aed8f6 Merge branch 'origin/develop' into Weblate. 2022-11-06 20:59:46 +00:00
23b0b01829 Merge pull request 'Highlight emoji reactions picked by yourself' (#193) from sfr/pleroma-fe:fix/emoji-reaction-picked into develop
Reviewed-on: AkkomaGang/pleroma-fe#193
2022-11-06 20:59:44 +00:00
Weblate
53c487535e Merge branch 'origin/develop' into Weblate. 2022-11-06 20:57:51 +00:00
278b2c25ad Merge pull request 'Clear search bar on reopen' (#192) from sfr/pleroma-fe:clear-searchbar into develop
Reviewed-on: AkkomaGang/pleroma-fe#192
2022-11-06 20:57:49 +00:00
Sol Fisher Romanoff
6a045dbc58
Highlight emoji reactions picked by yourself 2022-11-06 16:12:08 +02:00
Sol Fisher Romanoff
cd9dc9d2b2
Clear search bar on reopen 2022-11-06 15:46:35 +02:00
Weblate
3f2d54f057 Merge branch 'origin/develop' into Weblate. 2022-11-02 22:34:38 +00:00
251e440dad Merge pull request 'Ensure MFM scaling is ignored when rendering is disabled' (#189) from ignore-scale-mfm-disabled into develop
Reviewed-on: AkkomaGang/pleroma-fe#189
2022-11-02 22:34:36 +00:00
ffac376b5a Ensure MFM scaling is ignored when rendering is disabled
Fixes #173
2022-11-02 22:33:54 +00:00
Weblate
8bd18643e4 Translated using Weblate (French)
Currently translated at 100.0% (997 of 997 strings)

Co-authored-by: Thomate <thomas@burdick.fr>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/fr/
Translation: Pleroma fe/pleroma-fe
2022-11-02 22:09:52 +00:00
Weblate
5c28865018 Translated using Weblate (Catalan)
Currently translated at 100.0% (997 of 997 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: sola <spla@mastodont.cat>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translation: Pleroma fe/pleroma-fe
2022-11-02 22:09:52 +00:00
721e3b016d Merge pull request 'Debounce word filters' (#188) from debounce-wordfilter into develop
Reviewed-on: AkkomaGang/pleroma-fe#188
2022-11-02 22:09:46 +00:00
469063ff52 Debounce word filters
Fixes #182
2022-11-02 22:08:58 +00:00
d8643b5b4a Take instance stopGifs value 2022-10-29 22:08:25 +01:00
04c744e764 Stop stopGifs option from instance 2022-10-29 21:56:58 +01:00
bda433b006 Add mfm autocomplete (#183)
I thought it could be neat to have an autocomplete like Misskey has for MFM.

A condition was removed that prevented autocomplete to actually autocomplete stuff when only the first character was entered. It doesn't affect the other autocompletes since none of them display their elements if nothing was actually searched. (in that case MFM returns the full list of elements)

Co-authored-by: solidsanek <solidsanek@outerheaven.club>
Reviewed-on: AkkomaGang/pleroma-fe#183
Reviewed-by: floatingghost <hannah@coffee-and-dreams.uk>
Co-authored-by: solidsanek <solidsanek@noreply.akkoma>
Co-committed-by: solidsanek <solidsanek@noreply.akkoma>
2022-10-29 20:50:31 +00:00
49 changed files with 1365 additions and 80 deletions

View file

@ -5,6 +5,7 @@ import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import SettingsModal from './components/settings_modal/settings_modal.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import ModModal from './components/mod_modal/mod_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue'
@ -33,6 +34,7 @@ export default {
MobileNav,
DesktopNav,
SettingsModal,
ModModal,
UserReportingModal,
PostStatusModal,
EditStatusModal,

View file

@ -61,6 +61,7 @@
<EditStatusModal v-if="editingAvailable" />
<StatusHistoryModal v-if="editingAvailable" />
<SettingsModal />
<ModModal />
<GlobalNoticeList />
</div>
</template>

View file

@ -148,6 +148,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('showWiderShortcuts')
copyInstanceOption('showNavShortcuts')
copyInstanceOption('showPanelNavShortcuts')
copyInstanceOption('stopGifs')
copyInstanceOption('logo')
store.dispatch('setInstanceOption', {
@ -396,6 +397,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
store.dispatch('startFetchingAnnouncements')
store.dispatch('startFetchingReports')
getTOS({ store })
getStickers({ store })

View file

@ -16,7 +16,8 @@ import {
faUsers,
faCommentMedical,
faBookmark,
faInfoCircle
faInfoCircle,
faUserTie
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -34,7 +35,8 @@ library.add(
faUsers,
faCommentMedical,
faBookmark,
faInfoCircle
faInfoCircle,
faUserTie
)
export default {
@ -98,6 +100,9 @@ export default {
privateMode () { return this.$store.state.instance.private },
shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout
},
showBubbleTimeline () {
return this.$store.state.instance.localBubbleInstances.length > 0
}
},
methods: {
@ -109,6 +114,9 @@ export default {
},
openSettingsModal () {
this.$store.dispatch('openSettingsModal')
},
openModModal () {
this.$store.dispatch('openModModal')
}
}
}

View file

@ -55,7 +55,7 @@
/>
</router-link>
<router-link
v-if="currentUser"
v-if="currentUser && showBubbleTimeline"
:to="{ name: 'bubble-timeline' }"
class="nav-icon"
>
@ -151,6 +151,18 @@
:title="$t('nav.preferences')"
/>
</button>
<button
v-if="currentUser && currentUser.role === 'admin' || currentUser.role === 'moderator'"
class="button-unstyled nav-icon"
@click.stop="openModModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="user-tie"
:title="$t('nav.moderation')"
/>
</button>
<a
v-if="currentUser && currentUser.role === 'admin'"
href="/pleroma/admin/#/login-pleroma"

View file

@ -178,7 +178,7 @@ const EmojiInput = {
textAtCaret: async function (newWord) {
const firstchar = newWord.charAt(0)
this.suggestions = []
if (newWord === firstchar) return
if (newWord === firstchar && firstchar !== '$') return
const matchedSuggestions = await this.suggest(newWord)
// Async: cancel if textAtCaret has changed during wait
if (this.textAtCaret !== newWord) return
@ -277,7 +277,6 @@ const EmojiInput = {
},
replaceText (e, suggestion) {
const len = this.suggestions.length || 0
if (this.textAtCaret.length === 1) { return }
if (len > 0 || suggestion) {
const chosenSuggestion = suggestion || this.suggestions[this.highlighted]
const replacement = chosenSuggestion.replacement

View file

@ -42,7 +42,7 @@
:class="{ highlighted: index === highlighted }"
@click.stop.prevent="onClick($event, suggestion)"
>
<span class="image">
<span v-if="!suggestion.mfm" class="image">
<img
v-if="suggestion.img"
:src="suggestion.img"

View file

@ -1,3 +1,6 @@
const MFM_TAGS = ['blur', 'bounce', 'flip', 'font', 'jelly', 'jump', 'rainbow', 'rotate', 'shake', 'sparkle', 'spin', 'tada', 'twitch', 'x2', 'x3', 'x4']
.map(tag => ({ displayText: tag, detailText: '$[' + tag + ' ]', replacement: '$[' + tag + ' ]', mfm: true }))
/**
* suggest - generates a suggestor function to be used by emoji-input
* data: object providing source information for specific types of suggestions:
@ -21,6 +24,10 @@ export default data => {
if (firstChar === '@' && usersCurry) {
return usersCurry(input)
}
if (firstChar === '$') {
return MFM_TAGS
.filter(({ replacement }) => replacement.toLowerCase().indexOf(input) !== -1)
}
return []
}
}

View file

@ -92,7 +92,7 @@
}
}
.picked-reaction {
.button-default.picked-reaction {
border: 1px solid var(--accent, $fallback--link);
margin-left: -1px; // offset the border, can't use inset shadows either
margin-right: calc(0.5em - 1px);

View file

@ -0,0 +1,58 @@
import Modal from 'src/components/modal/modal.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue'
import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
import {
faWindowMinimize
} from '@fortawesome/free-regular-svg-icons'
library.add(
faTimes,
faWindowMinimize,
faChevronDown
)
const ModModal = {
components: {
Modal,
ModModalContent: getResettableAsyncComponent(
() => import('./mod_modal_content.vue'),
{
loadingComponent: PanelLoading,
errorComponent: AsyncComponentError,
delay: 0
}
)
},
methods: {
closeModal () {
this.$store.dispatch('closeModModal')
},
peekModal () {
this.$store.dispatch('togglePeekModModal')
}
},
computed: {
moderator () {
return this.$store.state.users.currentUser &&
(this.$store.state.users.currentUser.role === 'admin' ||
this.$store.state.users.currentUser.role === 'moderator')
},
modalActivated () {
return this.$store.state.interface.modModalState !== 'hidden'
},
modalOpenedOnce () {
return this.$store.state.interface.modModalLoaded
},
modalPeeked () {
return this.$store.state.interface.modModalState === 'minimized'
}
}
}
export default ModModal

View file

@ -0,0 +1,44 @@
@import 'src/_variables.scss';
.mod-modal {
overflow: hidden;
&.peek {
.mod-modal-panel {
/* Explanation:
* Modal is positioned vertically centered.
* 100vh - 100% = Distance between modal's top+bottom boundaries and screen
* (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen
* + 100% - we move modal completely off-screen, it's top boundary touches
* bottom of the screen
* - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible
*/
transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
@media all and (max-width: 800px) {
/* For mobile, the modal takes 100% of the available screen.
This ensures the minimized modal is always 50px above the browser bottom bar regardless of whether or not it is visible.
*/
transform: translateY(calc(100% - 50px));
}
}
}
.mod-modal-panel {
overflow: hidden;
transition: transform;
transition-timing-function: ease-in-out;
transition-duration: 300ms;
width: 1000px;
max-width: 90vw;
height: 90vh;
@media all and (max-width: 800px) {
max-width: 100vw;
height: 100%;
}
.panel-body {
height: inherit;
}
}
}

View file

@ -0,0 +1,43 @@
<template>
<Modal
v-if="moderator"
:is-open="modalActivated"
class="mod-modal"
:class="{ peek: modalPeeked }"
:no-background="modalPeeked"
>
<div class="mod-modal-panel panel">
<div class="panel-heading">
<span class="title">
{{ $t('moderation.moderation') }}
</span>
<button
class="btn button-default"
:title="$t('general.peek')"
@click="peekModal"
>
<FAIcon
:icon="['far', 'window-minimize']"
fixed-width
/>
</button>
<button
class="btn button-default"
:title="$t('general.close')"
@click="closeModal"
>
<FAIcon
icon="times"
fixed-width
/>
</button>
</div>
<div class="panel-body">
<ModModalContent v-if="modalOpenedOnce" />
</div>
</div>
</Modal>
</template>
<script src="./mod_modal.js"></script>
<style src="./mod_modal.scss" lang="scss"></style>

View file

@ -0,0 +1,63 @@
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import ReportsTab from './tabs/reports_tab/reports_tab.vue'
// import StatusesTab from './tabs/statuses_tab.vue'
// import UsersTab from './tabs/users_tab.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faFlag,
faMessage,
faUsers
} from '@fortawesome/free-solid-svg-icons'
library.add(
faFlag,
faMessage,
faUsers
)
const ModModalContent = {
components: {
TabSwitcher,
ReportsTab
// StatusesTab,
// UsersTab
},
computed: {
open () {
return this.$store.state.interface.modModalState !== 'hidden'
},
bodyLock () {
return this.$store.state.interface.modModalState === 'visible'
}
},
methods: {
onOpen () {
const targetTab = this.$store.state.interface.modModalTargetTab
// We're being told to open in specific tab
if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
return elm.props && elm.props['data-tab-name'] === targetTab
})
if (tabIndex >= 0) {
this.$refs.tabSwitcher.setTab(tabIndex)
}
}
// Clear the state of target tab, so that next time moderation is opened
// it doesn't force it.
this.$store.dispatch('clearModModalTargetTab')
}
},
mounted () {
this.onOpen()
},
watch: {
open: function (value) {
if (value) this.onOpen()
}
}
}
export default ModModalContent

View file

@ -0,0 +1,21 @@
@import 'src/_variables.scss';
.mod_tab-switcher {
height: 100%;
.content {
margin: 1em 1em 1.4em;
> div {
margin-bottom: .5em;
&:last-child {
margin-bottom: 0;
}
}
textarea {
width: 100%;
max-width: 100%;
height: 100px;
}
}
}

View file

@ -0,0 +1,20 @@
<template>
<tab-switcher
ref="tabSwitcher"
class="mod_tab-switcher"
:side-tab-bar="true"
:scrollable-tabs="true"
:body-scroll-lock="bodyLock"
>
<div
:label="$t('moderation.reports.reports')"
icon="flag"
data-tab-name="reports"
>
<ReportsTab />
</div>
</tab-switcher>
</template>
<script src="./mod_modal_content.js"></script>
<style src="./mod_modal_content.scss" lang="scss"></style>

View file

@ -0,0 +1,124 @@
import Popover from 'src/components/popover/popover.vue'
import Status from 'src/components/status/status.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import ReportNote from './report_note.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown,
faChevronUp
} from '@fortawesome/free-solid-svg-icons'
library.add(
faChevronDown,
faChevronUp
)
const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
const STRIP_MEDIA = 'mrf_tag:media-strip'
const FORCE_UNLISTED = 'mrf_tag:force-unlisted'
const SANDBOX = 'mrf_tag:sandbox'
const ReportCard = {
data () {
return {
hidden: true,
statusesHidden: true,
notesHidden: true,
note: null,
tags: {
FORCE_NSFW,
STRIP_MEDIA,
FORCE_UNLISTED,
SANDBOX
}
}
},
props: [
'account',
'actor',
'content',
'id',
'notes',
'state',
'statuses'
],
components: {
ReportNote,
Popover,
Status,
UserAvatar
},
created () {
this.$store.dispatch('fetchUser', this.account.id)
},
computed: {
isOpen () {
return this.state === 'open'
},
tagPolicyEnabled () {
return this.$store.state.instance.federationPolicy.mrf_policies.includes('TagPolicy')
},
user () {
return this.$store.getters.findUser(this.account.id)
}
},
methods: {
toggleHidden () {
this.hidden = !this.hidden
},
decode (content) {
content = content.replaceAll('<br/>', '\n')
const textarea = document.createElement('textarea')
textarea.innerHTML = content
return textarea.value
},
updateReportState (state) {
this.$store.dispatch('updateReportStates', { reports: [{ id: this.id, state }] })
},
toggleNotes () {
this.notesHidden = !this.notesHidden
},
addNoteToReport () {
if (this.note.length > 0) {
this.$store.dispatch('addNoteToReport', { id: this.id, note: this.note })
this.note = null
}
},
toggleStatuses () {
this.statusesHidden = !this.statusesHidden
},
hasTag (tag) {
return this.user.tags.includes(tag)
},
toggleTag (tag) {
if (this.hasTag(tag)) {
this.$store.state.api.backendInteractor.untagUser({ user: this.user, tag }).then(response => {
if (!response.ok) { return }
this.$store.commit('untagUser', { user: this.user, tag })
})
} else {
this.$store.state.api.backendInteractor.tagUser({ user: this.user, tag }).then(response => {
if (!response.ok) { return }
this.$store.commit('tagUser', { user: this.user, tag })
})
}
},
toggleActivationStatus () {
this.$store.dispatch('toggleActivationStatus', { user: this.user })
},
deleteUser () {
this.$store.state.backendInteractor.deleteUser({ user: this.user })
.then(e => {
this.$store.dispatch('markStatusesAsDeleted', status => this.user.id === status.user.id)
const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile'
const isTargetUser = this.$route.params.name === this.user.name || this.$route.params.id === this.user.id
if (isProfile && isTargetUser) {
window.history.back()
}
})
}
}
}
export default ReportCard

View file

@ -0,0 +1,202 @@
<template>
<div class="report-card panel">
<div
class="panel-heading"
@click="toggleHidden"
>
<h4>{{ $t('moderation.reports.report') + ' ' + this.account.screen_name }}</h4>
<button
v-if="isOpen"
class="button-default"
@click.stop="updateReportState('closed')"
>
{{ $t('moderation.reports.close') }}
</button>
<button
v-if="isOpen"
class="button-default"
@click.stop="updateReportState('resolved')"
>
{{ $t('moderation.reports.resolve') }}
</button>
<button
v-else
class="button-default"
@click.stop="updateReportState('open')"
>
{{ $t('moderation.reports.reopen') }}
</button>
</div>
<div
v-if="!hidden"
class="panel-body report-body"
>
<div class="report-content">
<div v-if="content">
{{ decode(content) }}
</div>
<i v-else class="faint">
{{ $t('moderation.reports.no_content') }}
</i>
<div class="report-author">
<UserAvatar
class="small-avatar"
:user="actor"
/>
{{ this.actor.screen_name }}
</div>
</div>
<div
class="dropdown"
v-if="!hidden && this.statuses.length > 0"
>
<button
class="button button-unstyled dropdown-header"
@click="toggleStatuses"
>
{{ this.statuses.length + ' ' + $t('moderation.reports.statuses') }}
<FAIcon
class="timelines-chevron"
fixed-width
:icon="statusesHidden ? 'chevron-down' : 'chevron-up'"
/>
</button>
<div v-if="!statusesHidden">
<Status
v-for="status in statuses"
:key="status.id"
:collapsable="false"
:expandable="false"
:compact="false"
:statusoid="status"
:no-heading="false"
/>
</div>
</div>
<div
class="dropdown"
v-if="!hidden && this.notes.length > 0"
>
<button
class="button button-unstyled dropdown-header"
@click="toggleNotes"
>
{{ this.notes.length + ' ' + $t('moderation.reports.notes') }}
<FAIcon
class="timelines-chevron"
fixed-width
:icon="notesHidden ? 'chevron-down' : 'chevron-up'"
/>
</button>
<div v-if="!notesHidden">
<ReportNote
v-for="note in notes"
:key="note.id"
:report_id="id"
v-bind="note"
/>
</div>
</div>
<div class="report-add-note">
<textarea
rows="1"
cols="1"
v-model.trim="note"
:placeholder="$t('moderation.reports.note_placeholder')"
/>
<button
class="btn button-default"
@click.stop="addNoteToReport"
>
{{ $t('moderation.reports.add_note') }}
</button>
</div>
</div>
<div
v-if="!hidden"
class="panel-footer"
>
<button
class="btn button-default"
@click.stop="toggleActivationStatus"
>
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
</button>
<button
class="btn button-default"
@click.stop="deleteUser"
>
{{ $t('user_card.admin_menu.delete_account') }}
</button>
<Popover
trigger="click"
placement="top"
:offset="{ y: 5 }"
remove-padding
>
<template v-slot:trigger>
<button
class="btn button-default"
:disabled="!tagPolicyEnabled"
:title="tagPolicyEnabled ? '' : $t('moderation.reports.account.tag_policy_notice')"
>
<span>{{ $t("moderation.reports.tags") }}</span>
{{ ' ' }}
<FAIcon
icon="chevron-down"
/>
</button>
</template>
<template v-slot:content="{close}">
<div
class="dropdown-menu"
:disabled="!tagPolicyEnabled"
>
<button
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="toggleTag(tags.FORCE_NSFW)"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
/>
{{ $t('user_card.admin_menu.force_nsfw') }}
</button>
<button
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="toggleTag(tags.STRIP_MEDIA)"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
/>
{{ $t('user_card.admin_menu.strip_media') }}
</button>
<button
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="toggleTag(tags.FORCE_UNLISTED)"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
/>
{{ $t('user_card.admin_menu.force_unlisted') }}
</button>
<button
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="toggleTag(tags.SANDBOX)"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
/>
{{ $t('user_card.admin_menu.sandbox') }}
</button>
</div>
</template>
</popover>
</div>
</div>
</template>
<script src="./report_card.js"></script>

View file

@ -0,0 +1,37 @@
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import Timeago from 'src/components/timeago/timeago.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
const ReportNote = {
data () {
return {
showingDeleteDialog: false
}
},
props: [
'content',
'created_at',
'user',
'report_id',
'id'
],
components: {
ConfirmModal,
Timeago,
UserAvatar
},
methods: {
deleteNoteFromReport () {
this.$store.dispatch('deleteNoteFromReport', { id: this.report_id, note: this.id })
this.showingDeleteDialog = false
},
showDeleteDialog () {
this.showingDeleteDialog = true
},
hideDeleteDialog () {
this.showingDeleteDialog = false
}
}
}
export default ReportNote

View file

@ -0,0 +1,43 @@
<template>
<div class="report-note">
<div class="note-header">
<div class="note-author">
<UserAvatar
class="small-avatar"
:user="user"
/>
{{ this.user.screen_name }}
</div>
<div class="header-right">
<Timeago
class="faint"
:time="created_at"
:auto-update="60"
:long-format="true"
:with-direction="true"
/>
<button
class="btn button-default"
@click.stop="showDeleteDialog"
>
{{ $t('moderation.reports.delete_note') }}
</button>
</div>
</div>
<div class="note-content">
{{ content }}
</div>
<confirm-modal
v-if="showingDeleteDialog"
:title="$t('moderation.reports.delete_note_title')"
:confirm-text="$t('moderation.reports.delete_note_accept')"
:cancel-text="$t('moderation.reports.delete_note_cancel')"
@accepted="deleteNoteFromReport"
@cancelled="hideDeleteDialog"
>
{{ $t('moderation.reports.delete_note_confirm') }}
</confirm-modal>
</div>
</template>
<script src="./report_note.js"></script>

View file

@ -0,0 +1,26 @@
import { filter } from 'lodash'
import ReportCard from './report_card.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
const ReportsTab = {
data () {
return {
showClosed: false
}
},
components: {
Checkbox,
ReportCard
},
computed: {
reports () {
return this.$store.state.reports.reports
},
openReports () {
return filter(this.reports, { state: 'open' })
}
}
}
export default ReportsTab

View file

@ -0,0 +1,83 @@
@import '../../../../_variables.scss';
.report-card {
.report-body {
& > * {
padding: 1em;
}
& > :not(:last-child) {
border-bottom: 1px solid;
border-bottom-color: var(--border, #222);
}
.report-content {
white-space: pre-wrap;
}
.report-author {
padding-top: 0.5em;
}
.small-avatar {
height: 25px;
width: 25px;
padding-right: 0.4em;
vertical-align: middle;
}
.dropdown {
display: flex;
flex-direction: column;
padding: 0;
.dropdown-header {
padding: 1em;
color: var(--link, $fallback--link);
&:hover {
background-color: var(--selectedMenu, $fallback--lightBg);
color: var(--selectedMenuText, $fallback--link);
}
}
}
.report-note {
padding: 1em;
.note-header {
display: flex;
justify-content: space-between;
padding-bottom: 0.5em;
}
button {
margin-left: 0.5em;
}
}
.report-add-note {
textarea {
resize: none;
}
button {
min-height: 2em;
min-width: 10em;
padding: 0 2em;
margin-top: 0.5em;
}
}
}
.panel-footer {
display: flex;
& > * {
margin-right: 0.5em;
}
}
}
.reports-header {
display: flex;
align-items: center;
justify-content: space-between;
}

View file

@ -0,0 +1,20 @@
<template>
<div :label="$t('moderation.reports.reports')">
<div class="content">
<div class="reports-header">
<h2>{{ $t('moderation.reports.reports') }}</h2>
<Checkbox v-model="showClosed">
{{ $t('moderation.reports.show_closed') }}
</Checkbox>
</div>
<ReportCard
v-for="report in (showClosed ? reports : openReports)"
:key="report.id"
v-bind="report"
/>
</div>
</div>
</template>
<script src="./reports_tab.js"></script>
<style src="./reports_tab.scss" lang="scss"></style>

View file

@ -148,7 +148,7 @@ const PostStatusForm = {
spoilerText: this.subject || '',
status: this.statusText || '',
sensitiveIfSubject,
nsfw: this.statusIsSensitive || !!sensitiveByDefault,
nsfw: this.statusIsSensitive || (sensitiveIfSubject && this.subject) || !!sensitiveByDefault,
files: this.statusFiles || [],
poll: this.statusPoll || {},
mediaDescriptions: this.statusMediaDescriptions || {},
@ -418,7 +418,7 @@ const PostStatusForm = {
addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo)
if (this.newStatus.sensitiveIfSubject && this.newStatus.spoilerText !== '') {
if (this.$store.getters.mergedConfig.sensitiveIfSubject && this.newStatus.spoilerText !== '') {
this.newStatus.nsfw = true
}
this.$emit('resize', { delayed: true })
@ -498,7 +498,7 @@ const PostStatusForm = {
})
},
onSubjectInput (e) {
if (this.newStatus.sensitiveIfSubject) {
if (this.$store.getters.mergedConfig.sensitiveIfSubject) {
this.newStatus.nsfw = true
}
},

View file

@ -130,11 +130,11 @@ export default {
codeblocks.forEach((pre) => {
content = content.replace(pre,
pre.replaceAll('<br/>', '\n')
.replaceAll('&amp;', '&')
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&quot', '"')
.replaceAll('&#39;', "'")
.replaceAll('&amp;', '&')
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&quot', '"')
.replaceAll('&#39;', "'")
)
})
}

View file

@ -32,6 +32,7 @@ const SearchBar = {
this.$emit('toggled', this.hidden)
this.$nextTick(() => {
if (!this.hidden) {
this.searchTerm = undefined
this.$refs.searchInput.focus()
}
})

View file

@ -1,4 +1,4 @@
import { filter, trim } from 'lodash'
import { filter, trim, debounce } from 'lodash'
import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue'
import IntegerSetting from '../helpers/integer_setting.vue'
@ -27,13 +27,13 @@ const FilteringTab = {
get () {
return this.muteWordsStringLocal
},
set (value) {
set: debounce(function (value) {
this.muteWordsStringLocal = value
this.$store.dispatch('setOption', {
name: 'muteWords',
value: filter(value.split('\n'), (word) => trim(word).length > 0)
})
}
}, 500)
}
},
// Updating nested properties

View file

@ -105,8 +105,8 @@ const GeneralTab = {
return this.$store.getters.mergedConfig.profileVersion
},
translationLanguages () {
const langs = this.$store.state.instance.translationLanguages || []
return (langs || []).map(lang => ({ key: lang.code, value: lang.code, label: lang.name }))
const langs = this.$store.state.instance.supportedTranslationLanguages || { source: [] }
return langs.source.map(lang => ({ key: lang.code, value: lang.code, label: lang.name }))
},
translationLanguage: {
get: function () { return this.$store.getters.mergedConfig.translationLanguage },

View file

@ -151,7 +151,7 @@ const ProfileTab = {
return false
},
deleteField (index, event) {
this.$delete(this.newFields, index)
this.newFields.splice(index, 1)
},
uploadFile (slot, e) {
const file = e.target.files[0]

View file

@ -15,7 +15,8 @@ import {
faTachometerAlt,
faCog,
faInfoCircle,
faList
faList,
faUserTie
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -30,7 +31,8 @@ library.add(
faTachometerAlt,
faCog,
faInfoCircle,
faList
faList,
faUserTie
)
const SideDrawer = {
@ -102,6 +104,9 @@ const SideDrawer = {
},
openSettingsModal () {
this.$store.dispatch('openSettingsModal')
},
openModModal () {
this.$store.dispatch('openModModal')
}
}
}

View file

@ -143,6 +143,21 @@
/> {{ $t("nav.about") }}
</router-link>
</li>
<li
v-if="currentUser && currentUser.role === 'admin' || currentUser.role === 'moderator'"
@click="toggleDrawer"
>
<button
class="button-unstyled -link -fullwidth"
@click="openModModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="user-tie"
/> {{ $t("nav.moderation") }}
</button>
</li>
<li
v-if="currentUser && currentUser.role === 'admin'"
@click="toggleDrawer"

View file

@ -460,6 +460,16 @@ const Status = {
return 'globe'
}
},
faviconAlt (status) {
if (!status.user.instance) {
return ''
}
const software = ((status.user.instance) && (status.user.instance.nodeinfo) && (status.user.instance.nodeinfo.software)) || {}
if (software.name) {
return `${status.user.instance.name} (${software.name || ''} ${software.version || ''})`
}
return ''
},
showError (error) {
this.error = error
},

View file

@ -177,6 +177,7 @@
v-if="!!(status.user && status.user.favicon)"
class="status-favicon"
:src="status.user.favicon"
:title="faviconAlt(status)"
>
</div>

View file

@ -1,7 +1,7 @@
<template>
<div
class="StatusBody"
:class="{ '-compact': compact }"
:class="{ '-compact': compact, 'mfm-disabled': !renderMisskeyMarkdown }"
>
<div class="body">
<div

View file

@ -82,9 +82,16 @@
}
}
&.mfm-disabled {
span {
font-size: 100% !important;
}
.mfm {
animation: none !important;
}
.emoji {
width: 32px !important;
height: 32px !important;
}
}
}

View file

@ -64,8 +64,12 @@ export default {
settingsModalVisible () {
return this.settingsModalState === 'visible'
},
modModalVisible () {
return this.modModalState === 'visible'
},
...mapState({
settingsModalState: state => state.interface.settingsModalState
settingsModalState: state => state.interface.settingsModalState,
modModalState: state => state.interface.modModalState
})
},
beforeUpdate () {

View file

@ -235,7 +235,7 @@
line-height: 22px;
flex-wrap: wrap;
.following {
.following, .requested_by {
flex: 1 0 auto;
margin: 0;
margin-bottom: .25em;

View file

@ -122,6 +122,12 @@
>
{{ $t('user_card.follows_you') }}
</div>
<div
v-if="relationship.requested_by && loggedIn && isOtherUser"
class="requested_by"
>
{{ $t('user_card.requested_by') }}
</div>
<div
v-if="isOtherUser && (loggedIn || !switcher)"
class="highlighter"

View file

@ -164,6 +164,74 @@
"load_older": "Carrega interaccions més antigues",
"moves": "Migracions d'usuari"
},
"languages": {
"ar": "Àrab",
"az": "Azerbaidjanès",
"bg": "Búlgar",
"cs": "Txec",
"da": "Danès",
"de": "Alemany",
"el": "Grec",
"en": "Anglès",
"eo": "Esperanto",
"es": "Espanyol",
"fa": "Persa",
"fi": "Finès",
"fr": "Francès",
"ga": "Irlandès",
"he": "Hebreu",
"hi": "Indi",
"hu": "Hungarès",
"id": "Indonesi",
"it": "Italià",
"ja": "Japonès",
"ko": "Coreà",
"lt": "Lituà",
"lv": "Latvià",
"nl": "Holandès",
"pl": "Polonès",
"pt": "Portuguès",
"ru": "Rus",
"sk": "Eslovè",
"sv": "Suec",
"tr": "Turc",
"translated_from": {
"ar": "Traduït de @:languages.ar",
"az": "Traduït de @:languages.az",
"bg": "Traduït de @:languages.bg",
"cs": "Traduït de @:languages.cs",
"da": "Traduït de @:languages.da",
"de": "Traduït de @:languages.de",
"el": "Traduït de @:languages.el",
"en": "Traduït de @:languages.en",
"eo": "Traduït de @:languages.eo",
"es": "Traduït de @:languages.es",
"fa": "Traduït de @:languages.fa",
"fi": "Traduït de @:languages.en",
"fr": "Traduït de @:languages.fr",
"ga": "Traduït de @:languages.ga",
"he": "Traduït de @:languages.he",
"hi": "Traduït de @:languages.hi",
"hu": "Traduït de @:languages.hu",
"id": "Traduït de @:languages.id",
"it": "Traduït de @:languages.it",
"ja": "Traduït de @:languages.ja",
"ko": "Traduït de @:languages.ko",
"lt": "Traduït de @:languages.lt",
"lv": "Traduït de @:languages.lv",
"nl": "Traduït de @:languages.nl",
"pl": "Traduït de @:languages.pl",
"pt": "Traduït de @:languages.pt",
"ru": "Traduït de @:languages.ru",
"sk": "Traduït de @:languages.sk",
"sv": "Traduït de @:languages.sv",
"tr": "Traduït de @:languages.fr",
"uk": "Traduït de @:languages.uk",
"zh": "Traduït de @:languages.zh"
},
"uk": "Ucraïnès",
"zh": "Xinès"
},
"lists": {
"create": "Crea",
"delete": "Esborra la llista",
@ -279,10 +347,13 @@
"text/plain": "Text pla",
"text/x.misskeymarkdown": "MFM"
},
"content_warning": "Assumpte (opcional)",
"content_warning": "Avís de contingut (opcional)",
"default": "Just ara he arribat a Catalunya",
"direct_warning_to_all": "Aquest apunt serà visible per a tots els usuaris mencionats.",
"direct_warning_to_first_only": "Aquest apunt només serà visible per als usuaris mencionats al principi del missatge.",
"edit_remote_warning": "Els canvis fets en aquest apunt poden no ser visibles en algunes instàncies!",
"edit_status": "Edita l'apunt",
"edit_unsupported_warning": "Les enquestes i les mencions no es canviaran al editar.",
"empty_status_error": "No es pot enviar un apunt buit i sense fitxers adjunts",
"media_description": "Descripció multimèdia",
"media_description_error": "Ha fallat la pujada del Mèdia, prova de nou",
@ -313,7 +384,7 @@
"email": "Adreça de Correu",
"email_language": "En quina llengua vols rebre els correus del servidor?",
"fullname": "Nom a mostrar",
"fullname_placeholder": "p. ex. Lain Iwakura",
"fullname_placeholder": "p. ex. Atsuko Kagari",
"new_captcha": "Clica a la imatge per a obtenir un nou captcha",
"password_confirm": "Confirma la contrasenya",
"reason": "Raó per a registrar-se",
@ -321,7 +392,7 @@
"register": "Registre",
"registration": "Registre",
"token": "Codi d'invitació",
"username_placeholder": "p. ex. lain",
"username_placeholder": "p. ex. akko",
"validations": {
"email_required": "no es pot deixar en blanc",
"fullname_required": "no es pot deixar en blanc",
@ -392,9 +463,19 @@
"changed_password": "S'ha canviat la contrasenya correctament!",
"chatMessageRadius": "Missatge de xat",
"checkboxRadius": "Caselles",
"collapse_subject": "Replega els apunts amb assumpte",
"collapse_subject": "Replega els apunts amb avís de contingut",
"columns": "Columnes",
"composing": "Composant",
"confirm_dialogs": "Requereix confirmació per:",
"confirm_dialogs_approve_follow": "Acceptant peticions de seguiment",
"confirm_dialogs_block": "Bloquejant algú",
"confirm_dialogs_delete": "Esborrant un apunt",
"confirm_dialogs_deny_follow": "Rebutjant una sol·licitud de seguiment",
"confirm_dialogs_mute": "Silenciant algú",
"confirm_dialogs_repeat": "Repetint un apunt",
"confirm_dialogs_unfollow": "Deixant de seguir a algú",
"confirm_new_password": "Confirma la nova contrasenya",
"confirmation_dialogs": "Opcions de confirmació",
"conversation_display": "Estil de visualització de la conversa",
"conversation_display_linear": "Estil linear",
"conversation_display_tree": "Estil d'arbre",
@ -426,8 +507,8 @@
"backup_settings_theme": "Còpia de seguretat de la configuració i tema a un fitxer",
"errors": {
"file_slightly_new": "La versió menor del fitxer és diferent, alguns paràmetres podrien no carregar-se",
"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à suportada (min. set. ver. {feMajor})",
"file_too_new": "Versió major incompatible: {fileMajor}, aquest PleromaFE (configuració versió {feMajor}) és massa antiga per gestionar-lo",
"file_too_old": "Versió major incompatible: {fileMajor}, la versió del fitxer és massa antiga i no està suportada (min. set. ver. {feMajor})",
"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."
},
"restore_settings": "Restaurar configuració des d'un fitxer"
@ -520,7 +601,7 @@
"more_settings": "Més opcions",
"move_account": "Mou el compte",
"move_account_error": "Error al moure el compte: {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 aquest compte a un altre lloc, has d'anar a aquest altre compte i afegir un àlies que apunti a aquest.",
"move_account_target": "Compte destí (p.ex. {example})",
"moved_account": "El compte s'ha mogut.",
"mute_bot_posts": "Silencia apunts de bots",
@ -604,7 +685,7 @@
"security": "Seguretat",
"security_tab": "Seguretat",
"sensitive_by_default": "Marca apunts com a sensibles per defecte",
"sensitive_if_subject": "Marca automàticament les imatges com a sensibles si s'ha especificat la línia assumpte",
"sensitive_if_subject": "Marca automàticament les imatges com a sensibles si s'ha especificat un avís de contingut",
"set_new_avatar": "Establir un nou avatar",
"set_new_mascot": "Establir una nova mascota",
"set_new_profile_background": "Canvia el fons del perfil",
@ -612,6 +693,19 @@
"setting_changed": "La configuració és diferent a la predeterminada",
"setting_server_side": "Aquest ajust està lligat al teu perfil i afectarà a totes les sessions i clients",
"settings": "Configuració",
"settings_profile": "Perfils de configuracions",
"settings_profile_creation": "Crea nou perfil",
"settings_profile_creation_new_name_label": "Nom",
"settings_profile_creation_submit": "Crea",
"settings_profile_currently": "Actualment usant {name} (versió: {version})",
"settings_profile_delete": "Esborra",
"settings_profile_delete_confirm": "Vols de debò esborrar aquest perfil?",
"settings_profile_force_sync": "Sincronitza",
"settings_profile_in_use": "En ús",
"settings_profile_use": "Usa",
"settings_profiles_refresh": "Recarrega els perfils de configuracions",
"settings_profiles_show": "Mostra tots els perfils de configuracions",
"settings_profiles_unshow": "Amaga tots els perfils de configuracions",
"show_admin_badge": "Mostra l'insígnia \"Administrador\" en el meu perfil",
"show_moderator_badge": "Mostra l'insígnia \"Moderador\" en el meu perfil",
"show_nav_shortcuts": "Mostra els accessos directes addicionals en el panell superior",
@ -767,9 +861,9 @@
"use_source": "Nova versió"
}
},
"subject_input_always_show": "Sempre mostrar el camp del assumpte",
"subject_line_behavior": "Copiar l'assumpte en les respostes",
"subject_line_email": "Com a l'email: \"re: assumpte\"",
"subject_input_always_show": "Sempre mostra el camp d'avís de contingut",
"subject_line_behavior": "Copiar l'avís de contingut en les respostes",
"subject_line_email": "Com en el correu: \"re: avís\"",
"subject_line_mastodon": "Com a mastodon: copiar com és",
"subject_line_noop": "No copiar",
"text": "Text",
@ -783,7 +877,8 @@
"third_column_mode_postform": "Formulari de publicació principal i navegació",
"token": "Token",
"tooltipRadius": "Globus/alertes",
"tree_advanced": "Permet una navegació més flexible en la vista d'arbre",
"translation_language": "Traducció Automàtica de la Llengua",
"tree_advanced": "Mostra els botons extra per a obrir i tancar les cadenes en els fils",
"tree_fade_ancestors": "Mostra els avantpassats del apunt actual en text dèbil",
"type_domains_to_mute": "Buscar dominis per a silenciar",
"upload_a_photo": "Pujar una foto",
@ -793,6 +888,7 @@
"use_contain_fit": "No retallar els adjunts en miniatures",
"use_one_click_nsfw": "Obre els adjunts NSFW amb només un clic",
"user_mutes": "Usuaris",
"user_profile_default_tab": "Pestanya per defecte en el Perfil d'Usuari",
"user_profiles": "Perfils d'usuari",
"user_settings": "Configuració d'usuari",
"valid_until": "Vàlid fins",
@ -809,6 +905,12 @@
"word_filter": "Filtre de paraules",
"wordfilter": "Filtre de paraules"
},
"settings_profile": {
"creating": "Creant nou perfil de configuracions \"{profile}\"...",
"synchronization_error": "No s'han pogut sincronitzar les configuracions: {err}",
"synchronized": "Configuracions sincronitzades!",
"synchronizing": "Sincronitzant el perfil de configuració \"{profile}\"..."
},
"status": {
"ancestor_follow": "Veure {numReplies} altres respostes sota aquest apunt | Veure {numReplies} altres respostes sota aquest apunt",
"ancestor_follow_with_icon": "{icon} {text}",
@ -818,12 +920,19 @@
"copy_link": "Copia l'enllaç a l'apunt",
"delete": "Esborra l'apunt",
"delete_confirm": "Segur que vols esborrar aquest apunt?",
"delete_confirm_accept_button": "Sí, esborra'l",
"delete_confirm_cancel_button": "No, desa'l",
"delete_confirm_title": "Confirma esborrat",
"edit": "Edita",
"edit_history": "Historial d'edició",
"edit_history_modal_title": "Editat {historyCount} vegada | Editat {historyCount} vegades",
"edited_at": "Editat {time}",
"expand": "Expandeix",
"external_source": "Font externa",
"favorites": "Favorits",
"hide_attachment": "Amaga l'adjunt",
"hide_content": "Amaga el contingut",
"hide_full_subject": "Amaga tot l'assumpte",
"hide_full_subject": "Amaga tot l'avís de contingut",
"many_attachments": "L'apunt té {number} adjunt | L'apunt té {number} adjunts",
"mentions": "Menciona",
"move_down": "Mou l'adjunt a la dreta",
@ -831,32 +940,44 @@
"mute_conversation": "Silencia la conversa",
"nsfw": "No segur per a entorns laborals",
"open_gallery": "Obre la galeria",
"override_translation_source_language": "Anul·la la llengua d'origen",
"pin": "Destaca al perfil",
"pinned": "Destacat",
"plus_more": "+{number} més",
"redraft": "Esborra i torna a escriure",
"redraft_confirm": "De debò vols esborrar i tornar a escriure aquest apunt? Les interaccions del apunt original no es mantindran.",
"redraft_confirm_accept_button": "Sí, esborra i redacta de nou",
"redraft_confirm_cancel_button": "No, manté l'original",
"redraft_confirm_title": "Confirma esborra i re escriu",
"remove_attachment": "Elimina l'adjunt",
"repeat_confirm": "Vols de debò repetir aquest apunt?",
"repeat_confirm_accept_button": "Sí, repeteix-lo",
"repeat_confirm_cancel_button": "No, no el repeteixis",
"repeat_confirm_title": "Confirma repetició",
"repeats": "Repeticions",
"replies_list": "Respostes:",
"replies_list_with_others": "Respostes (+{numReplies} altre): | Respostes (+{numReplies} altres):",
"replies_list_with_others": "Veure (+{numReplies} resposta més): | Veure (+{numReplies} respostes més):",
"reply_to": "Respon a",
"show_all_attachments": "Mostra tots els adjunts",
"show_all_conversation": "Mostra la conversa sencera ({numStatus} altre apunt) | Mostra la conversa sencera ({numStatus} altres apunts)",
"show_all_conversation_with_icon": "{icon} {text}",
"show_attachment_description": "Descripció prèvia (obre l'adjunt per a descripció sencera)",
"show_attachment_in_modal": "Mostra en el modal de Mèdia",
"show_attachment_in_modal": "Mostra l'adjunt en un finestra",
"show_content": "Mostra el contingut",
"show_full_subject": "Mostra tot l'assumpte",
"show_full_subject": "Mostra tot l'avís de contingut",
"show_only_conversation_under_this": "Només mostra respostes a aquest apunt",
"status_deleted": "Aquest apunt ha estat esborrat",
"status_unavailable": "Apunt no disponible",
"thread_follow": "Mira la part restant d'aquest fil ({numStatus} apunt en total) | Mira la part restant d'aquest fil ({numStatus} apunts en total)",
"thread_follow": "Veure {numStatus} resposta més | Veure {numStatus} respostes més",
"thread_follow_with_icon": "{icon} {text}",
"thread_hide": "Amaga aquest fil",
"thread_muted": "Fil silenciat",
"thread_muted_and_words": ", té les paraules:",
"thread_show": "Mostra aquest fil",
"thread_show_full": "Mostra-ho tot sota aquest fil ({numStatus} apunt en total, màx. profunditat {depth}) | Mostra-ho tot sota aquest fil ({numStatus} apunts en total, màx. profunditat {depth})",
"thread_show_full": "Mostra {numStatus} resposta | Mostra totes {numStatus} respostes",
"thread_show_full_with_icon": "{icon} {text}",
"translate": "Tradueix",
"translated_from": "Tradueix des de {language}",
"unbookmark": "Desmarca",
"unmute_conversation": "Deixa de silenciar la conversa",
"unpin": "Deixa de destacar al perfil",
@ -899,6 +1020,9 @@
"socket_reconnected": "Connexió a temps real establerta",
"up_to_date": "Actualitzat"
},
"toast": {
"no_translation_target_set": "No s'ha configurat una llengua objectiu -això podria fallar. Si us plau configura una llengua objectiu en la teva configuració."
},
"tool_tip": {
"accept_follow_request": "Accepta la sol·licitud de seguiment",
"add_reaction": "Reacciona",
@ -947,12 +1071,24 @@
"strip_media": "Esborra els Mèdia dels apunts"
},
"approve": "Aprova",
"approve_confirm": "Estàs segur que vols deixar que et segueixi aquest usuari?",
"approve_confirm_accept_button": "Sí, accepta",
"approve_confirm_cancel_button": "No, cancel·la",
"approve_confirm_title": "Aprova la sol·licitud de seguiment",
"block": "Bloqueja",
"block_confirm": "Estàs segur que vols bloquejar a {user}?",
"block_confirm_accept_button": "Sí, bloqueja'l",
"block_confirm_cancel_button": "No, no el bloquegis",
"block_confirm_title": "Bloqueja l'usuari",
"block_progress": "Bloquejant…",
"blocked": "Bloquejat!",
"bot": "Bot",
"deactivated": "Desactivat",
"deny": "Denega",
"deny_confirm": "Estàs segur que vols denegar la sol·licitud de seguiment d'aquest usuari?",
"deny_confirm_accept_button": "Sí, denega",
"deny_confirm_cancel_button": "No, cancel·la",
"deny_confirm_title": "Denega sol·licitud de seguiment",
"domain_muted": "Desbloqueja el domini",
"edit_profile": "Edita el perfil",
"favorites": "Favorits",
@ -978,18 +1114,28 @@
"mention": "Menció",
"message": "Missatge",
"mute": "Silencia",
"mute_confirm": "Estàs segur que vols silenciar a {user}?",
"mute_confirm_accept_button": "Sí, silencia'l",
"mute_confirm_cancel_button": "No, no el silenciïs",
"mute_confirm_title": "Silencia usuari",
"mute_domain": "Bloqueja el domini",
"mute_progress": "Silenciant…",
"muted": "Silenciat",
"note": "Nota privada",
"per_day": "per dia",
"remote_follow": "Seguiment remot",
"remove_follower": "Esborra seguidor",
"replies": "Amb respostes",
"report": "Informa",
"show_repeats": "Mostra les repeticions",
"statuses": "Apunts",
"subscribe": "Subscriu-te",
"unblock": "Desbloqueja",
"unblock_progress": "Desbloquejant…",
"unfollow_confirm": "Estàs segur que vols deixar de seguir a {user}?",
"unfollow_confirm_accept_button": "Sí, deixa'l de seguir",
"unfollow_confirm_cancel_button": "No, no el deixis de seguir",
"unfollow_confirm_title": "Deixa de seguir l'usuari",
"unmute": "Deixa de silenciar",
"unmute_progress": "Deixant de silenciar…",
"unsubscribe": "Anul·la la subscripció"

View file

@ -258,7 +258,11 @@
"placeholder": "myusername",
"recovery_code": "Recovery code",
"register": "Register",
"username": "Username"
"username": "Username",
"logout_confirm_cancel_button": "Cancel",
"logout_confirm_accept_button": "Log out",
"logout_confirm": "Are you sure you want to log out?",
"logout_confirm_title": "Log out"
},
"media_modal": {
"counter": "{current} / {total}",
@ -266,6 +270,31 @@
"next": "Next",
"previous": "Previous"
},
"moderation": {
"moderation": "Moderation",
"reports": {
"add_note": "Add note",
"close": "Close",
"delete_note": "Delete",
"delete_note_accept": "Yes, delete it",
"delete_note_cancel": "No, keep it",
"delete_note_confirm": "Are you sure you want to delete this note?",
"delete_note_title": "Confirm deletion",
"no_content": "No description given",
"note_placeholder": "Leave a note...",
"notes": "notes",
"reopen": "Reopen",
"report": "Report on",
"reports": "Reports",
"resolve": "Resolve",
"show_closed": "Show closed",
"statuses": "statuses",
"tag_policy_notice": "Enable the TagPolicy MRF to set post restrictions",
"tags": "Set post restrictions"
},
"statuses": "Statuses",
"users": "Users"
},
"nav": {
"about": "About",
"administration": "Administration",
@ -282,6 +311,7 @@
"interactions": "Interactions",
"lists": "Lists",
"mentions": "Mentions",
"moderation": "Moderation",
"preferences": "Preferences",
"public_timeline_description": "Public posts from this instance",
"public_tl": "Public timeline",
@ -1101,6 +1131,7 @@
"followers": "Followers",
"following": "Following!",
"follows_you": "Follows you!",
"requested_by": "Has requested to follow you",
"hidden": "Hidden",
"hide_repeats": "Hide repeats",
"highlight": {

View file

@ -273,23 +273,23 @@
"back": "Retour",
"bookmarks": "Marques-Pages",
"bubble_timeline": "Flux de cette bulle",
"bubble_timeline_description": "Les status des instances proches de celle-ci, choisies par l'administration",
"bubble_timeline_description": "Les statuts des instances proches de celle-ci, choisies par l'administration",
"chats": "Chats",
"dms": "Messages directs",
"friend_requests": "Demandes de suivi",
"home_timeline": "Flux personnel",
"home_timeline_description": "Les status de vos abonnements",
"home_timeline_description": "Les statuts de vos abonnements",
"interactions": "Interactions",
"lists": "Listes",
"mentions": "Mentions",
"preferences": "Préférences",
"public_timeline_description": "Tous les status publics de cette instance",
"public_timeline_description": "Tous les statuts publics de cette instance",
"public_tl": "Flux publique",
"search": "Recherche",
"timeline": "Flux personnel",
"timelines": "Flux",
"twkn": "Réseau connu",
"twkn_timeline_description": "Les status du réseau entier",
"twkn_timeline_description": "Les statuts du réseau entier",
"user_search": "Recherche de comptes",
"who_to_follow": "Suggestion de suivi"
},
@ -337,7 +337,7 @@
"votes_count": "{count} vote | {count} votes"
},
"post_status": {
"account_not_locked_warning": "Votre compte n'est pas {0}. N'importe qui peut vous suivre pour voir vos billets en Abonné·e·s uniquement.",
"account_not_locked_warning": "Votre compte n'est pas {0}. N'importe qui peut vous suivre et voir vos statuts réservés aux abonné·es.",
"account_not_locked_warning_link": "verrouillé",
"attachments_sensitive": "Marquer les pièce-jointes comme sensible",
"content_type": {
@ -693,6 +693,19 @@
"setting_changed": "Préférence modifiée",
"setting_server_side": "Modifier cette préférence répercute sur tous vos clients",
"settings": "Paramètres",
"settings_profile": "Profils de paramètres",
"settings_profile_creation": "Créer un profil",
"settings_profile_creation_new_name_label": "Nom",
"settings_profile_creation_submit": "Créer",
"settings_profile_currently": "Profil actuel : {name} (version {version})",
"settings_profile_delete": "Supprimer",
"settings_profile_delete_confirm": "Voulez-vous vraiment supprimer ce profil ?",
"settings_profile_force_sync": "Synchroniser",
"settings_profile_in_use": "Actuel",
"settings_profile_use": "Utiliser",
"settings_profiles_refresh": "Recharger",
"settings_profiles_show": "Afficher touts les profils",
"settings_profiles_unshow": "Cacher les profils",
"show_admin_badge": "Afficher le badge d'Admin sur mon profil",
"show_moderator_badge": "Afficher le badge de Modo' sur mon profil",
"show_nav_shortcuts": "Afficher plus de raccourcis de navigations dans le panneau supérieur",
@ -892,6 +905,12 @@
"word_filter": "Filtrage par mots",
"wordfilter": "Filtrage par mot-clé"
},
"settings_profile": {
"creating": "Création du profil « {profil} » …",
"synchronization_error": "Impossible de synchroniser les paramètres : {err}",
"synchronized": "Paramètres synchronisés !",
"synchronizing": "Synchronisation du profil « {profile} » …"
},
"status": {
"ancestor_follow": "Voir {numReplies} autre réponse en dessous de ce status | Voir {numReplies} autres réponses en dessous de ce status",
"ancestor_follow_with_icon": "{icon} {text}",
@ -925,6 +944,11 @@
"pin": "Agrafer sur le profil",
"pinned": "Agraffé",
"plus_more": "+{number} autre | +{number} autres",
"redraft": "Supprimer et réécrire",
"redraft_confirm": "Supprimer et réécrire ce status ? Les interactions avec l'original seront perdues.",
"redraft_confirm_accept_button": "Oui : supprimer et réécrire",
"redraft_confirm_cancel_button": "Non : garder l'original",
"redraft_confirm_title": "Confirmer la suppression et réécriture",
"remove_attachment": "Supprimer la pièce jointe",
"repeat_confirm": "Partager ce statut ?",
"repeat_confirm_accept_button": "Partager",
@ -1047,12 +1071,24 @@
"strip_media": "Supprimer les medias des statuts"
},
"approve": "Accepter",
"approve_confirm": "Voulez-vous vraiment approuver cet abonnement ?",
"approve_confirm_accept_button": "Oui : accepter",
"approve_confirm_cancel_button": "Non : rejeter",
"approve_confirm_title": "Approuver une demande d'abonnement",
"block": "Bloquer",
"block_confirm": "Êtes-vous sûr de vouloir bloquer {user} ?",
"block_confirm_accept_button": "Oui",
"block_confirm_cancel_button": "Non",
"block_confirm_title": "Bloquer l'utilisateur",
"block_progress": "Blocage…",
"blocked": "Bloqué !",
"bot": "Robot",
"deactivated": "Désactivé",
"deny": "Rejeter",
"deny_confirm": "Êtes-vous sûr de vouloir rejeter cette demande d'abonnement ?",
"deny_confirm_accept_button": "Oui : rejeter",
"deny_confirm_cancel_button": "Non : annuler",
"deny_confirm_title": "Rejeter une demande d'abonnement",
"domain_muted": "Débloquer la domaine",
"edit_profile": "Éditer le profil",
"favorites": "Favoris",
@ -1061,8 +1097,8 @@
"follow_progress": "Demande en cours…",
"follow_sent": "Demande envoyée !",
"follow_unfollow": "Désabonner",
"followees": "Suivis",
"followers": "Vous suivent",
"followees": "Abonnements",
"followers": "Abonné·es",
"following": "Suivi !",
"follows_you": "Vous suit !",
"hidden": "Caché",
@ -1078,18 +1114,28 @@
"mention": "Mention",
"message": "Message",
"mute": "Masquer",
"mute_confirm": "Êtes-vous sûr de vouloir masquer {user} ?",
"mute_confirm_accept_button": "Oui",
"mute_confirm_cancel_button": "Non",
"mute_confirm_title": "Masquer",
"mute_domain": "Bloquer la domaine",
"mute_progress": "Masquage…",
"muted": "Masqué",
"note": "Note privée",
"per_day": "par jour",
"remote_follow": "Suivre d'une autre instance",
"remove_follower": "Désabonner",
"replies": "Statuts et réponses",
"report": "Signalement",
"show_repeats": "Montrer les partages",
"statuses": "Statuts",
"subscribe": "Abonner",
"unblock": "Débloquer",
"unblock_progress": "Déblocage…",
"unfollow_confirm": "Êtes-vous sûr de vouloir vous désabonner de {user} ?",
"unfollow_confirm_accept_button": "Oui : me désabonner",
"unfollow_confirm_cancel_button": "Non : garder l'abonnement",
"unfollow_confirm_title": "Désabonner",
"unmute": "Démasquer",
"unmute_progress": "Démasquage…",
"unsubscribe": "Désabonner"

View file

@ -163,6 +163,7 @@ const api = {
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
dispatch('startFetchingAnnouncements')
dispatch('startFetchingReports')
dispatch('pushGlobalNotice', {
level: 'error',
messageKey: 'timeline.socket_broke',
@ -280,6 +281,19 @@ const api = {
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'announcements', fetcher })
},
// Reports
startFetchingReports (store) {
if (store.state.fetchers['reports']) return
const fetcher = store.state.backendInteractor.startFetchingReports({ store })
store.commit('addFetcher', { fetcherName: 'reports', fetcher })
},
stopFetchingReports (store) {
const fetcher = store.state.fetchers.reports
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'reports', fetcher })
},
getSupportedTranslationlanguages (store) {
store.state.backendInteractor.getSupportedTranslationlanguages({ store })
.then((data) => {

View file

@ -55,7 +55,7 @@ export const defaultState = {
alwaysShowNewPostButton: false,
autohideFloatingPostButton: false,
pauseOnUnfocused: true,
stopGifs: true,
stopGifs: undefined,
replyVisibility: 'all',
thirdColumnMode: 'notifications',
notificationVisibility: {

View file

@ -2,6 +2,9 @@ const defaultState = {
settingsModalState: 'hidden',
settingsModalLoaded: false,
settingsModalTargetTab: null,
modModalState: 'hidden',
modModalLoaded: false,
modModalTargetTab: null,
settings: {
currentSaveStateNotice: null,
noticeClearTimeout: null,
@ -63,6 +66,30 @@ const interfaceMod = {
setSettingsModalTargetTab (state, value) {
state.settingsModalTargetTab = value
},
closeModModal (state) {
state.modModalState = 'hidden'
},
togglePeekModModal (state) {
switch (state.modModalState) {
case 'minimized':
state.modModalState = 'visible'
return
case 'visible':
state.modModalState = 'minimized'
return
default:
throw new Error('Illegal minimization state of mod modal')
}
},
openModModal (state) {
state.modModalState = 'visible'
if (!state.modModalLoaded) {
state.modModalLoaded = true
}
},
setModModalTargetTab (state, value) {
state.modModalTargetTab = value
},
pushGlobalNotice (state, notice) {
state.globalNotices.push(notice)
},
@ -105,6 +132,18 @@ const interfaceMod = {
commit('setSettingsModalTargetTab', value)
commit('openSettingsModal')
},
closeModModal ({ commit }) {
commit('closeModModal')
},
openModModal ({ commit }) {
commit('openModModal')
},
togglePeekModModal ({ commit }) {
commit('togglePeekModModal')
},
clearModModalTargetTab ({ commit }) {
commit('setModModalTargetTab', null)
},
pushGlobalNotice (
{ commit, dispatch, state },
{

View file

@ -1,11 +1,17 @@
import filter from 'lodash/filter'
import { filter, find, forEach, remove } from 'lodash'
const getReport = (state, id) => find(state.reports, { id })
const updateReport = (state, { report, param, value }) => {
getReport(state, report.id)[param] = value
}
const reports = {
state: {
userId: null,
statuses: [],
preTickedIds: [],
modalActivated: false
modalActivated: false,
reports: []
},
mutations: {
openUserReportingModal (state, { userId, statuses, preTickedIds }) {
@ -16,6 +22,38 @@ const reports = {
},
closeUserReportingModal (state) {
state.modalActivated = false
},
setReport (state, { report }) {
let existing = getReport(state, report.id)
if (existing) {
existing = report
} else {
state.reports.push(report)
}
},
updateReportStates (state, { reports }) {
forEach(reports, (report) => {
updateReport(state, { report, param: 'state', value: report.state })
})
},
addNoteToReport (state, { id, note, user }) {
// akkoma doesn't return the note from this API endpoint, and there's no
// good way to get it. the note data is spoofed in the frontend until
// reload.
// definitely worth adding this to the backend at some point
const report = getReport(state, id)
const date = new Date()
report.notes.push({
content: note,
user,
created_at: date.toISOString(),
id: date.getTime()
})
},
deleteNoteFromReport (state, { id, note }) {
const report = getReport(state, id)
remove(report.notes, { id: note })
}
},
actions: {
@ -31,6 +69,22 @@ const reports = {
},
closeUserReportingModal ({ commit }) {
commit('closeUserReportingModal')
},
updateReportStates ({ rootState, commit }, { reports }) {
commit('updateReportStates', { reports })
return rootState.api.backendInteractor.updateReportStates({ reports })
},
getReport ({ rootState, commit }, { id }) {
return rootState.api.backendInteractor.getReport({ id })
.then(report => commit('setReport', { report }))
},
addNoteToReport ({ rootState, commit }, { id, note }) {
commit('addNoteToReport', { id, note, user: rootState.users.currentUser })
return rootState.api.backendInteractor.addNoteToReport({ id, note })
},
deleteNoteFromReport ({ rootState, commit }, { id, note }) {
commit('deleteNoteFromReport', { id, note })
return rootState.api.backendInteractor.deleteNoteFromReport({ id, note })
}
}
}

View file

@ -1,5 +1,5 @@
import { each, map, concat, last, get } from 'lodash'
import { parseStatus, parseSource, parseUser, parseNotification, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
import { parseStatus, parseSource, parseUser, parseNotification, parseReport, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
import { RegistrationError, StatusCodeError } from '../errors/errors'
/* eslint-env browser */
@ -19,6 +19,9 @@ const ADMIN_USERS_URL = '/api/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
const ADMIN_REPORTS_URL = '/api/v1/pleroma/admin/reports'
const ADMIN_REPORT_NOTES_URL = id => `/api/v1/pleroma/admin/reports/${id}/notes`
const ADMIN_REPORT_NOTE_URL = (report, note) => `/api/v1/pleroma/admin/reports/${report}/notes/${note}`
const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
@ -342,7 +345,7 @@ const fetchUserRelationship = ({ id, credentials }) => {
return new Promise((resolve, reject) => response.json()
.then((json) => {
if (!response.ok) {
return reject(new StatusCodeError(response.status, json, { url }, response))
return reject(new StatusCodeError(400, json, { url }, response))
}
return resolve(json)
}))
@ -635,6 +638,57 @@ const deleteUser = ({ credentials, user }) => {
})
}
const getReports = ({ state, limit, page, pageSize, credentials }) => {
let url = ADMIN_REPORTS_URL
const args = [
state && `state=${state}`,
limit && `limit=${limit}`,
page && `page=${page}`,
pageSize && `page_size=${pageSize}`
].filter(_ => _).join('&')
url = url + (args ? '?' + args : '')
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
.then((data) => data.reports.map(parseReport))
}
const updateReportStates = ({ credentials, reports }) => {
// reports syntax: [{ id: int, state: string }...]
const updates = {
reports: reports.map(report => {
return {
id: report.id.toString(),
state: report.state
}
})
}
return promisedRequest({
url: ADMIN_REPORTS_URL,
method: 'PATCH',
payload: updates,
credentials
})
}
const addNoteToReport = ({ id, note, credentials }) => {
return promisedRequest({
url: ADMIN_REPORT_NOTES_URL(id),
method: 'POST',
payload: { content: note },
credentials
})
}
const deleteNoteFromReport = ({ report, note, credentials }) => {
return promisedRequest({
url: ADMIN_REPORT_NOTE_URL(report, note),
method: 'DELETE',
credentials
})
}
const fetchTimeline = ({
timeline,
credentials,
@ -1726,7 +1780,11 @@ const apiService = {
getSettingsProfile,
saveSettingsProfile,
listSettingsProfiles,
deleteSettingsProfile
deleteSettingsProfile,
getReports,
updateReportStates,
addNoteToReport,
deleteNoteFromReport
}
export default apiService

View file

@ -5,6 +5,7 @@ import followRequestFetcher from '../../services/follow_request_fetcher/follow_r
import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js'
import announcementsFetcher from '../../services/announcements_fetcher/announcements_fetcher.service.js'
import configFetcher from '../config_fetcher/config_fetcher.service.js'
import reportsFetcher from '../reports_fetcher/reports_fetcher.service.js'
const backendInteractorService = credentials => ({
startFetchingTimeline ({ timeline, store, userId = false, listId = false, tag }) {
@ -39,6 +40,10 @@ const backendInteractorService = credentials => ({
return announcementsFetcher.startFetching({ store, credentials })
},
startFetchingReports ({ store, state, limit, page, pageSize }) {
return reportsFetcher.startFetching({ store, credentials, state, limit, page, pageSize })
},
startUserSocket ({ store }) {
const serv = store.rootState.instance.server.replace('http', 'ws')
const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })

View file

@ -88,6 +88,9 @@ export const parseUser = (data) => {
output.friends_count = data.following_count
output.bot = data.bot
if (data.akkoma) {
output.instance = data.akkoma.instance
}
if (data.pleroma) {
const relationship = data.pleroma.relationship
@ -429,6 +432,24 @@ export const parseNotification = (data) => {
return output
}
export const parseReport = (data) => {
const report = {}
report.account = parseUser(data.account)
report.actor = parseUser(data.actor)
report.statuses = data.statuses.map(parseStatus)
report.notes = data.notes.map(note => {
note.user = parseUser(note.user)
return note
})
report.state = data.state
report.content = data.content
report.created_at = data.created_at
report.id = data.id
return report
}
const isNsfw = (status) => {
const nsfwRegex = /#nsfw/i
return (status.tags || []).includes('nsfw') || !!(status.text || '').match(nsfwRegex)

View file

@ -0,0 +1,20 @@
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
import { forEach } from 'lodash'
const fetchAndUpdate = ({ store, credentials, state, limit, page, pageSize }) => {
return apiService.getReports({ credentials, state, limit, page, pageSize })
.then(reports => forEach(reports, report => store.commit('setReport', { report })))
}
const startFetching = ({ store, credentials, state, limit, page, pageSize }) => {
const boundFetchAndUpdate = () => fetchAndUpdate({ store, credentials, state, limit, page, pageSize })
boundFetchAndUpdate()
return promiseInterval(boundFetchAndUpdate, 60000)
}
const reportsFetcher = {
startFetching
}
export default reportsFetcher

View file

@ -1092,12 +1092,12 @@
"@fortawesome/fontawesome-common-types@^0.3.0":
version "0.3.0"
resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.3.0.tgz"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.3.0.tgz#949995a05c0d8801be7e0a594f775f1dbaa0d893"
integrity sha512-CA3MAZBTxVsF6SkfkHXDerkhcQs0QPofy43eFdbWJJkZiq3SfiaH1msOkac59rQaqto5EqWnASboY1dBuKen5w==
"@fortawesome/fontawesome-svg-core@1.3.0":
version "1.3.0"
resolved "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.3.0.tgz"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.3.0.tgz#343fac91fa87daa630d26420bfedfba560f85885"
integrity sha512-UIL6crBWhjTNQcONt96ExjUnKt1D68foe3xjEensLDclqQ6YagwCRYVQdrp/hW0ALRp/5Fv/VKw+MqTUWYYvPg==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.3.0"
@ -1141,9 +1141,9 @@
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@intlify/bundle-utils@next":
version "3.1.2"
resolved "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-3.1.2.tgz"
integrity sha512-amgSo0NN5OKWYdcgFmfJqo2tcUcZ6C66Bxm5ALQnB0m3MUQtS9aJzKoIo+EU9XQiOVmlBFxRtNoZm+psHa5FNA==
version "3.2.1"
resolved "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-3.2.1.tgz"
integrity sha512-rf4cLBOnbqmpXVcCdcYHilZpMt1m82syh3WLBJlZvGxN2KkH9HeHVH4+bnibF/SDXCHNh6lM6wTpS/qw+PkcMg==
dependencies:
"@intlify/message-compiler" next
"@intlify/shared" next
@ -1168,7 +1168,7 @@
dependencies:
"@intlify/shared" "9.2.2"
"@intlify/message-compiler@9.2.2":
"@intlify/message-compiler@9.2.2", "@intlify/message-compiler@next":
version "9.2.2"
resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz"
integrity sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==
@ -1176,24 +1176,11 @@
"@intlify/shared" "9.2.2"
source-map "0.6.1"
"@intlify/message-compiler@next":
version "9.3.0-beta.3"
resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.3.0-beta.3.tgz"
integrity sha512-j8OwToBQgs01RBMX4GCDNQfcnmw3AiDG3moKIONTrfXcf+1yt/rWznLTYH/DXbKcFMAFijFpCzMYjUmH1jVFYA==
dependencies:
"@intlify/shared" "9.3.0-beta.3"
source-map "0.6.1"
"@intlify/shared@9.2.2", "@intlify/shared@next":
version "9.2.2"
resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz"
integrity sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==
"@intlify/shared@9.3.0-beta.3":
version "9.3.0-beta.3"
resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.3.0-beta.3.tgz"
integrity sha512-Z/0TU4GhFKRxKh+0RbwJExik9zz57gXYgxSYaPn7YQdkQ/pabSioCY/SXnYxQHL6HzULF5tmqarFm6glbGqKhw==
"@intlify/vue-devtools@9.2.2":
version "9.2.2"
resolved "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz"
@ -8095,9 +8082,9 @@ mute-stream@0.0.7:
integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==
nan@^2.12.1:
version "2.16.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916"
integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
nanoid@^3.3.4:
version "3.3.4"