Merge branch 'develop' into fedi-absturztau-be

This commit is contained in:
Puniko 2022-11-27 09:57:22 +01:00
commit 59a3e3fcbb
30 changed files with 537 additions and 2178 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ selenium-debug.log
config/local.json
config/local.*.json
docs/site/
.vscode/

View File

@ -1,19 +1,13 @@
{
"extends": [
"stylelint-rscss/config",
"stylelint-config-recommended-vue/scss",
"stylelint-config-recommended",
"stylelint-config-standard"
],
"customSyntax": "postcss-scss",
"rules": {
"declaration-no-important": true,
"rscss/no-descendant-combinator": false,
"rscss/class-format": [
true,
{
"component": "pascal-case",
"variant": "^-[a-z]\\w+",
"element": "^[a-z]\\w+"
}
]
"selector-class-pattern": null,
"custom-property-pattern": null
}
}

View File

@ -7,7 +7,7 @@ pipeline:
commands:
- yarn
- yarn lint
- yarn stylelint
#- yarn stylelint
test:
when:

View File

@ -2,7 +2,6 @@ var path = require('path')
var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
const WorkboxPlugin = require('workbox-webpack-plugin');
var { VueLoaderPlugin } = require('vue-loader')
var env = process.env.NODE_ENV
@ -119,11 +118,6 @@ module.exports = {
]
},
plugins: [
new WorkboxPlugin.InjectManifest({
swSrc: path.join(__dirname, '..', 'src/sw.js'),
swDest: 'sw-pleroma.js',
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024,
}),
new VueLoaderPlugin()
]
}

View File

@ -2,6 +2,7 @@ var path = require('path')
var config = require('../config')
var utils = require('./utils')
var webpack = require('webpack')
const WorkboxPlugin = require('workbox-webpack-plugin');
var { merge } = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var MiniCssExtractPlugin = require('mini-css-extract-plugin')
@ -32,6 +33,11 @@ var webpackConfig = merge(baseWebpackConfig, {
chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')
},
plugins: [
new WorkboxPlugin.InjectManifest({
swSrc: path.join(__dirname, '..', 'src/sw.js'),
swDest: 'sw-pleroma.js',
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024,
}),
// http://vuejs.github.io/vue-loader/workflow/production.html
new webpack.DefinePlugin({
'process.env': env,

View File

@ -11,7 +11,7 @@
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"stylelint": "npx stylelint src/components/status/status.scss",
"stylelint": "stylelint src/**/*.scss",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
},
@ -23,8 +23,8 @@
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@fortawesome/vue-fontawesome": "3.0.1",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@vuelidate/core": "2.0.0-alpha.42",
"@vuelidate/validators": "2.0.0-alpha.30",
"@vuelidate/core": "^2.0.0",
"@vuelidate/validators": "^2.0.0",
"body-scroll-lock": "2.7.1",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
@ -33,8 +33,6 @@
"escape-html": "1.0.3",
"js-cookie": "^3.0.1",
"localforage": "1.10.0",
"marked": "^4.2.2",
"marked-mfm": "^0.5.0",
"parse-link-header": "^2.0.0",
"phoenix": "1.6.2",
"punycode.js": "2.1.0",
@ -103,7 +101,9 @@
"nightwatch": "0.9.21",
"opn": "4.0.2",
"ora": "0.4.1",
"postcss-html": "^1.5.0",
"postcss-loader": "3.0.0",
"postcss-sass": "^0.5.0",
"raw-loader": "0.5.1",
"sass": "^1.56.0",
"sass-loader": "^13.2.0",
@ -112,9 +112,11 @@
"shelljs": "0.8.5",
"sinon": "2.4.1",
"sinon-chai": "2.14.0",
"stylelint": "13.6.1",
"stylelint-config-standard": "20.0.0",
"stylelint-rscss": "0.4.0",
"stylelint": "^14.15.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^29.0.0",
"stylelint-config-standard-scss": "^6.1.0",
"stylelint-rscss": "^0.4.0",
"url-loader": "^4.1.1",
"vue-loader": "^17.0.0",
"vue-style-loader": "^4.1.2",

View File

@ -152,6 +152,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('showPanelNavShortcuts')
copyInstanceOption('stopGifs')
copyInstanceOption('logo')
copyInstanceOption('conversationDisplay')
store.dispatch('setInstanceOption', {
name: 'logoMask',

View File

@ -22,6 +22,8 @@ import Lists from 'components/lists/lists.vue'
import ListTimeline from 'components/list_timeline/list_timeline.vue'
import ListEdit from 'components/list_edit/list_edit.vue'
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
import RegistrationRequestSent from 'components/registration_request_sent/registration_request_sent.vue'
import AwaitingEmailConfirmation from 'components/awaiting_email_confirmation/awaiting_email_confirmation.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@ -62,6 +64,8 @@ export default (store) => {
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration },
{ name: 'registration-request-sent', path: '/registration-request-sent', component: RegistrationRequestSent },
{ name: 'awaiting-email-confirmation', path: '/awaiting-email-confirmation', component: AwaitingEmailConfirmation },
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
{ name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },

View File

@ -26,6 +26,9 @@ const AccountActions = {
ConfirmModal
},
methods: {
refetchRelationship () {
return this.$store.dispatch('fetchUserRelationship', this.user.id)
},
showConfirmBlock () {
this.showingConfirmBlock = true
},
@ -57,6 +60,14 @@ const AccountActions = {
},
reportUser () {
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
},
muteDomain () {
this.$store.dispatch('muteDomain', this.user.screen_name.split('@')[1])
.then(() => this.refetchRelationship())
},
unmuteDomain () {
this.$store.dispatch('unmuteDomain', this.user.screen_name.split('@')[1])
.then(() => this.refetchRelationship())
}
},
computed: {

View File

@ -55,6 +55,20 @@
>
{{ $t('user_card.report') }}
</button>
<button
v-if="relationship.domain_blocking"
class="btn button-default btn-block dropdown-item"
@click="unmuteDomain"
>
{{ $t('user_card.domain_muted') }}
</button>
<button
v-else-if="!user.is_local"
class="btn button-default btn-block dropdown-item"
@click="muteDomain"
>
{{ $t('user_card.mute_domain') }}
</button>
</div>
</template>
<template v-slot:trigger>

View File

@ -0,0 +1,4 @@
export default {
computed: {
}
}

View File

@ -0,0 +1,12 @@
<template>
<div class="panel panel-default">
<div class="panel-heading">
<h4>{{ $t('registration.awaiting_email_confirmation_title') }}</h4>
</div>
<div class="panel-body">
<p>{{ $t('registration.awaiting_email_confirmation') }}</p>
</div>
</div>
</template>
<script src="./awaiting_email_confirmation.js"></script>

View File

@ -62,6 +62,10 @@ const EmojiPicker = {
this.scrolledGroup(target)
this.triggerLoadMore(target)
},
onWheel (e) {
e.preventDefault()
this.$refs['emoji-tabs'].scrollBy(e.deltaY, 0)
},
highlight (key) {
this.setShowStickers(false)
this.activeGroup = key
@ -138,7 +142,7 @@ const EmojiPicker = {
if (this.keyword === '') return list
const regex = new RegExp(escapeRegExp(trim(this.keyword)), 'i')
return list.filter(emoji => {
return regex.test(emoji.displayText)
return (regex.test(emoji.displayText) || (!emoji.imageUrl && emoji.replacement === this.keyword))
})
}
},

View File

@ -1,7 +1,11 @@
<template>
<div class="emoji-picker panel panel-default panel-body">
<div class="heading">
<span class="emoji-tabs">
<span
class="emoji-tabs"
@wheel="onWheel"
ref="emoji-tabs"
>
<span
v-for="group in emojis"
:key="group.id"

View File

@ -79,8 +79,16 @@ const registration = {
if (!this.v$.$invalid) {
try {
await this.signUp(this.user)
this.$router.push({ name: 'friends' })
const data = await this.signUp(this.user)
if (data.me) {
this.$router.push({ name: 'friends' })
} else if (data.identifier === 'awaiting_approval') {
this.$router.push({ name: 'registration-request-sent' })
} else if (data.identifier === 'missing_confirmed_email') {
this.$router.push({ name: 'awaiting-email-confirmation' })
} else {
console.warn('Unknown response from sign up', data)
}
} catch (error) {
console.warn('Registration failed: ', error)
this.setCaptcha()

View File

@ -177,6 +177,7 @@
<div
v-if="accountApprovalRequired"
class="form-group"
:class="{ 'form-group--error': v$.user.reason.$error }"
>
<label
class="form--label"

View File

@ -0,0 +1,4 @@
export default {
computed: {
}
}

View File

@ -0,0 +1,12 @@
<template>
<div class="panel panel-default">
<div class="panel-heading">
<h4>{{ $t('registration.request_sent_title') }}</h4>
</div>
<div class="panel-body">
<p>{{ $t('registration.request_sent') }}</p>
</div>
</div>
</template>
<script src="./registration_request_sent.js"></script>

View File

@ -2,8 +2,6 @@ import { unescape, flattenDeep } from 'lodash'
import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js'
import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js'
import { marked } from 'marked'
import markedMfm from 'marked-mfm'
import StillImage from 'src/components/still-image/still-image.vue'
import MentionsLine, { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.vue'
import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'

View File

@ -1,4 +1,4 @@
@import '../../_variables.scss';
@import "../../_variables.scss";
.Status {
min-width: 0;
@ -130,6 +130,15 @@
.heading-left {
display: flex;
min-width: 0;
flex-wrap: wrap;
img {
aspect-ratio: 1 / 1;
}
.nowrap {
white-space: nowrap;
}
}
.heading-right {
@ -139,6 +148,7 @@
.button-unstyled {
padding: 5px;
margin: -5px;
height: min-content;
&:hover svg {
color: $fallback--lightText;
@ -185,7 +195,7 @@
.reply-to-popover {
.reply-to:hover::before {
content: '';
content: "";
display: block;
position: absolute;
bottom: 0;
@ -195,13 +205,12 @@
}
.faint-link:hover {
// override default
text-decoration: none;
}
&.-strikethrough {
.reply-to::after {
content: '';
content: "";
display: block;
position: absolute;
top: 50%;
@ -293,10 +302,12 @@
position: relative;
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: left;
margin-top: var(--status-margin, $status-margin);
> * {
max-width: 4em;
min-width: fit-content;
flex: 1;
}
}
@ -340,7 +351,7 @@
margin-left: 0.2em;
&::before {
content: ' ';
content: " ";
}
}
@ -387,7 +398,7 @@
align-items: center;
&::before {
content: '';
content: "";
position: absolute;
height: 100%;
width: 1px;

View File

@ -166,19 +166,21 @@
>
{{ status.user.name }}
</h4>
<router-link
class="account-name"
:title="status.user.screen_name_ui"
:to="userProfileLink"
>
{{ status.user.screen_name_ui }}
</router-link>
<img
v-if="!!(status.user && status.user.favicon)"
class="status-favicon"
:src="status.user.favicon"
:title="faviconAlt(status)"
>
<span class="nowrap">
<router-link
class="account-name"
:title="status.user.screen_name_ui"
:to="userProfileLink"
>
@{{ status.user.screen_name_ui }}
</router-link>
<img
v-if="!!(status.user && status.user.favicon)"
class="status-favicon"
:src="status.user.favicon"
:title="faviconAlt(status)"
>
</span>
</div>
<span class="heading-right">

View File

@ -68,6 +68,7 @@
.StatusContent {
flex: 1;
min-width: 0;
overflow: hidden;
img, video {
&.emoji {

View File

@ -154,14 +154,6 @@ export default {
unmuteUser () {
this.$store.dispatch('unmuteUser', this.user.id)
},
muteDomain () {
this.$store.dispatch('muteDomain', this.user.screen_name.split('@')[1])
.then(() => this.refetchRelationship())
},
unmuteDomain () {
this.$store.dispatch('unmuteDomain', this.user.screen_name.split('@')[1])
.then(() => this.refetchRelationship())
},
subscribeUser () {
return this.$store.dispatch('subscribeUser', this.user.id)
},

View File

@ -67,6 +67,17 @@
icon="external-link-alt"
/>
</a>
<a
v-if="isOtherUser"
:href="user.statusnet_profile_url + '.rss'"
target="_blank"
class="button-unstyled external-link-button"
>
<FAIcon
class="icon"
icon="rss"
/>
</a>
<AccountActions
v-if="isOtherUser && loggedIn"
:user="user"
@ -225,22 +236,6 @@
{{ $t('user_card.mute') }}
</button>
</div>
<div>
<button
v-if="relationship.domain_blocking"
class="btn button-default btn-block toggled"
@click="unmuteDomain"
>
{{ $t('user_card.domain_muted') }}
</button>
<button
v-else
class="btn button-default btn-block"
@click="muteDomain"
>
{{ $t('user_card.mute_domain') }}
</button>
</div>
<div>
<button
class="btn button-default btn-block"

View File

@ -33,6 +33,8 @@ const FriendList = withLoadMore({
additionalPropNames: ['userId']
})(List)
const isUserPage = ({ name }) => name === 'user-profile' || name === 'external-user-profile'
const UserProfile = {
data () {
return {
@ -182,12 +184,12 @@ const UserProfile = {
},
watch: {
'$route.params.id': function (newVal) {
if (newVal) {
if (isUserPage(this.$route) && newVal) {
this.switchUser(newVal)
}
},
'$route.params.name': function (newVal) {
if (newVal) {
if (isUserPage(this.$route) && newVal) {
this.switchUser(newVal)
}
},

View File

@ -409,6 +409,8 @@
}
},
"registration": {
"awaiting_email_confirmation_title": "Awaiting email confirmation",
"awaiting_email_confirmation": "Your account has been registered and an email has been sent to your address. Please check the email to complete registration.",
"bio": "Bio",
"bio_placeholder": "e.g.\nHi! Welcome to my bio.\nI love watching anime and playing games. I hope we can be friends!",
"captcha": "CAPTCHA",
@ -422,6 +424,8 @@
"reason_placeholder": "This instance approves registrations manually.\nLet the administration know why you want to register.",
"register": "Register",
"registration": "Registration",
"request_sent": "Your registration request has been sent for approval. You will receive an email when your account is approved.",
"request_sent_title": "Registration request sent",
"token": "Invite token",
"username_placeholder": "e.g. akko",
"validations": {

View File

@ -254,6 +254,10 @@
"hint": "Inicia sesión para unirte a la discusión",
"login": "Identificarse",
"logout": "Cerrar sesión",
"logout_confirm": "¿Estás seguro que quieres cerrar la sesión?",
"logout_confirm_accept_button": "Cerrar sesión",
"logout_confirm_cancel_button": "Cancelar",
"logout_confirm_title": "Cerrar sesión",
"password": "Contraseña",
"placeholder": "miusuario",
"recovery_code": "Código de recuperación",
@ -277,14 +281,15 @@
"delete_note_confirm": "¿Estás seguro que quieres eliminar esta nota?",
"delete_note_title": "Confirma la eliminación",
"no_content": "Sin descripción dada",
"no_reports": "No hay informes que mostrar",
"note_placeholder": "Dejar una nota...",
"notes": "notas",
"notes": "{ count } nota | { count } notas",
"reopen": "Reabrir",
"report": "Reportar",
"reports": "Reportes",
"resolve": "Resolver",
"show_closed": "Mostrar cerrados",
"statuses": "estados",
"statuses": "{ count } estado | { count } estados",
"tag_policy_notice": "Habilitar TagPolicy MRF para establecer restricciones de publicación",
"tags": "Establecer restricciones de publicación"
},
@ -445,6 +450,7 @@
},
"settings": {
"accent": "Acento",
"account_alias": "Alias de cuenta",
"account_alias_table_head": "Alias",
"account_backup": "Copia de seguridad de la cuenta",
"account_backup_description": "Esto le permite descargar un archivo con la información de su cuenta y sus publicaciones, pero aún no se pueden importar a una cuenta de Pleroma.",
@ -492,7 +498,16 @@
"columns": "Columnas",
"composing": "Redactando",
"confirm_dialogs": "Requiere confirmación de:",
"confirm_dialogs_approve_follow": "Aceptar una solicitud de seguimiento",
"confirm_dialogs_block": "Bloqueando a alguien",
"confirm_dialogs_delete": "Eliminar una publicación",
"confirm_dialogs_deny_follow": "Rechazar una solicitud de seguimiento",
"confirm_dialogs_mute": "Silenciar a alguien",
"confirm_dialogs_repeat": "Repitiendo una publicación",
"confirm_dialogs_unfollow": "Dejar de seguir a alguien",
"confirm_new_password": "Confirmar la nueva contraseña",
"confirmation_dialogs": "Opciones de confirmación",
"conversation_display": "Estilo de visualización de la conversación",
"conversation_display_linear": "Lineal",
"conversation_display_tree": "Ramificado",
"conversation_other_replies_button": "Mostrar el botón \"otras respuestas\"",
@ -543,6 +558,7 @@
"hide_all_muted_posts": "Ocultar las publicaciones silenciadas",
"hide_attachments_in_convo": "Ocultar adjuntos en las conversaciones",
"hide_attachments_in_tl": "Ocultar adjuntos en la línea temporal",
"hide_bot_indication": "Ocultar indicación de bot en publicaciones",
"hide_favorites_description": "No mostrar la lista de mis favoritos (las personas aún serán notificadas)",
"hide_filtered_statuses": "Ocultar estados filtrados",
"hide_followers_count_description": "No mostrar el número de cuentas que me siguen",
@ -587,8 +603,10 @@
"mention_link_display_full": "siempre como nombre completo (p. ej. {'@'}foo{'@'}example.org)",
"mention_link_display_full_for_remote": "como nombre completo solo para usuarios remotos (p. ej. {'@'}foo{'@'}example.org)",
"mention_link_display_short": "siempre como nombres cortos (p. ej. {'@'}foo)",
"mention_link_fade_domain": "Renombrar dominios (p. ej. {'@'}ejemplo.org en {'@'}algo{'@'}ejemplo.org)",
"mention_link_show_avatar": "Mostrar el avatar del usuario detrás del enlace",
"mention_link_show_tooltip": "Mostrar el nombre completo de los usuarios remotos como información emergente",
"mention_links": "Mencionar enlaces",
"mfa": {
"authentication_methods": "Métodos de autentificación",
"confirm_and_enable": "Confirmar y habilitar OTP",
@ -637,6 +655,7 @@
"notification_mutes": "Para dejar de recibir notificaciones de un usuario específico, siléncialo.",
"notification_setting_block_from_strangers": "Bloquea las notificaciones de los usuarios que no sigues",
"notification_setting_filters": "Filtros",
"notification_setting_hide_if_cw": "Ocultar las notificaciones \"push\", si la publicación está marcada como \"contenido sensible\"",
"notification_setting_hide_notification_contents": "Ocultar el remitente y el contenido de las notificaciones push",
"notification_setting_privacy": "Privacidad",
"notification_visibility": "Tipos de notificaciones a mostrar",

View File

@ -495,9 +495,17 @@ const users = {
let data = await rootState.api.backendInteractor.register(
{ params: { ...userInfo } }
)
store.commit('signUpSuccess')
store.commit('setToken', data.access_token)
store.dispatch('loginUser', data.access_token)
if (data.identifier === 'awaiting_approval' || data.identifier === 'missing_confirmed_email') {
store.commit('signUpSuccess')
return data
} else if (data.me !== undefined) {
store.commit('signUpSuccess')
store.commit('setToken', data.access_token)
store.dispatch('loginUser', data.access_token)
return data
} else {
store.commit('signUpFailure', data)
}
} catch (e) {
let errors = e.message
store.commit('signUpFailure', errors)

View File

@ -650,7 +650,7 @@ const getReports = ({ state, limit, page, pageSize, credentials }) => {
url = url + (args ? '?' + args : '')
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
.then((data) => data.reports.map(parseReport))
.then((data) => data?.reports?.map(parseReport) ?? [])
}
const updateReportStates = ({ credentials, reports }) => {

2441
yarn.lock

File diff suppressed because it is too large Load Diff