mirror of
https://github.com/docusealco/docuseal
synced 2026-11-05 06:42:32 -05:00
review fixes
This commit is contained in:
1 parent
d071e8c429
commit
e11f4bddbe
40 files changed
+299
-192
No files matched your search
@@ -281,7 +281,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
}
|
||||
|
||||
onSync = (e) => {
|
||||
const { schema, documents, fields, submitters } = e.detail
|
||||
const { schema, documents, fields, submitters, pendingFieldAttachmentUuids } = e.detail
|
||||
const selectedAreas = this.component.selectedAreasRef.value.map((area) => {
|
||||
const field = this.component.template.fields.find((f) => f.areas?.includes(area))
|
||||
|
||||
@@ -293,6 +293,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
|
||||
this.component.selectedAreasRef.value = selectedAreas.map((item) => item && fields.find((f) => f.uuid === item.uuid)?.areas?.[item.index]).filter(Boolean)
|
||||
this.component.selectedSubmitter = submitters.find((s) => s.uuid === selectedSubmitterUuid) || submitters[0]
|
||||
this.component.pendingFieldAttachmentUuids = pendingFieldAttachmentUuids
|
||||
}
|
||||
|
||||
onScrollTo = (e) => {
|
||||
|
||||
@@ -26,17 +26,41 @@ export default targetable(class extends HTMLElement {
|
||||
|
||||
window.addEventListener('dragleave', this.onWindowDragleave)
|
||||
|
||||
this.fileDropzone?.addEventListener('drop', this.onDropFile)
|
||||
this.folderDropzone?.addEventListener('drop', this.onDropNewFolder)
|
||||
this.addEventListener('drop', this.onDrop)
|
||||
this.addEventListener('dragstart', this.onDragstart)
|
||||
this.addEventListener('dragover', this.onDragover)
|
||||
this.addEventListener('dragleave', this.onDragleave)
|
||||
}
|
||||
|
||||
this.folderCards.forEach((el) => el.addEventListener('drop', (e) => this.onDropFolder(e, el)))
|
||||
this.templateCards.forEach((el) => el.addEventListener('drop', this.onDropTemplate))
|
||||
this.templateCards.forEach((el) => el.addEventListener('dragstart', this.onTemplateDragStart))
|
||||
dropTarget (e) {
|
||||
return e.target.closest([
|
||||
'[data-target="dashboard-dropzone.fileDropzone"]',
|
||||
'[data-target="dashboard-dropzone.folderDropzone"]',
|
||||
'[data-targets="dashboard-dropzone.folderCards"]',
|
||||
'[data-targets="dashboard-dropzone.templateCards"]'
|
||||
].join(', '))
|
||||
}
|
||||
|
||||
return [this.fileDropzone, this.folderDropzone, ...this.folderCards, ...this.templateCards].forEach((el) => {
|
||||
el?.addEventListener('dragover', this.onDragover)
|
||||
el?.addEventListener('dragleave', this.onDragleave)
|
||||
})
|
||||
onDrop = (e) => {
|
||||
const el = this.dropTarget(e)
|
||||
|
||||
if (!el) return
|
||||
|
||||
if (el === this.fileDropzone) {
|
||||
this.onDropFile(e)
|
||||
} else if (el === this.folderDropzone) {
|
||||
this.onDropNewFolder(e)
|
||||
} else if (el.dataset.targets === 'dashboard-dropzone.folderCards') {
|
||||
this.onDropFolder(e, el)
|
||||
} else {
|
||||
this.onDropTemplate(e, el)
|
||||
}
|
||||
}
|
||||
|
||||
onDragstart = (e) => {
|
||||
if (e.target.closest('[data-targets="dashboard-dropzone.templateCards"]')) {
|
||||
this.onTemplateDragStart(e)
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
@@ -128,7 +152,7 @@ export default targetable(class extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
onDropTemplate = (e) => {
|
||||
onDropTemplate = (e, el) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (e.dataTransfer.files.length) {
|
||||
@@ -136,10 +160,10 @@ export default targetable(class extends HTMLElement {
|
||||
loading.classList.add('bottom-5', 'left-0', 'flex', 'justify-center', 'w-full', 'absolute')
|
||||
loading.innerHTML = loadingIconHtml
|
||||
|
||||
e.target.appendChild(loading)
|
||||
e.target.classList.add('opacity-50')
|
||||
el.appendChild(loading)
|
||||
el.classList.add('opacity-50')
|
||||
|
||||
const id = e.target.href.split('/').pop()
|
||||
const id = el.href.split('/').pop()
|
||||
|
||||
this.uploadFiles(e.dataTransfer.files, `/templates/${id}/clone_and_replace`)
|
||||
}
|
||||
@@ -173,16 +197,20 @@ export default targetable(class extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
onDragover (e) {
|
||||
if (e.dataTransfer?.types?.includes('Files') || this.dataset.targets !== 'dashboard-dropzone.templateCards') {
|
||||
this.style.backgroundColor = '#F7F3F0'
|
||||
onDragover = (e) => {
|
||||
const el = this.dropTarget(e)
|
||||
|
||||
if (this.classList.contains('before:border-base-300')) {
|
||||
this.classList.remove('before:border-base-300')
|
||||
this.classList.add('before:border-base-content/30')
|
||||
} else if (this.classList.contains('border-base-300')) {
|
||||
this.classList.remove('border-base-300')
|
||||
this.classList.add('border-base-content/30')
|
||||
if (!el) return
|
||||
|
||||
if (e.dataTransfer?.types?.includes('Files') || el.dataset.targets !== 'dashboard-dropzone.templateCards') {
|
||||
el.style.backgroundColor = '#F7F3F0'
|
||||
|
||||
if (el.classList.contains('before:border-base-300')) {
|
||||
el.classList.remove('before:border-base-300')
|
||||
el.classList.add('before:border-base-content/30')
|
||||
} else if (el.classList.contains('border-base-300')) {
|
||||
el.classList.remove('border-base-300')
|
||||
el.classList.add('border-base-content/30')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,15 +233,19 @@ export default targetable(class extends HTMLElement {
|
||||
a.remove()
|
||||
}
|
||||
|
||||
onDragleave () {
|
||||
this.style.backgroundColor = null
|
||||
onDragleave = (e) => {
|
||||
const el = this.dropTarget(e)
|
||||
|
||||
if (this.classList.contains('before:border-base-content/30')) {
|
||||
this.classList.remove('before:border-base-content/30')
|
||||
this.classList.add('before:border-base-300')
|
||||
} else if (this.classList.contains('border-base-content/30')) {
|
||||
this.classList.remove('border-base-content/30')
|
||||
this.classList.add('border-base-300')
|
||||
if (!el) return
|
||||
|
||||
el.style.backgroundColor = null
|
||||
|
||||
if (el.classList.contains('before:border-base-content/30')) {
|
||||
el.classList.remove('before:border-base-content/30')
|
||||
el.classList.add('before:border-base-300')
|
||||
} else if (el.classList.contains('border-base-content/30')) {
|
||||
el.classList.remove('border-base-content/30')
|
||||
el.classList.add('border-base-300')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export default class extends HTMLElement {
|
||||
document.removeEventListener('turbo:submit-end', this.onSubmit)
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
onSubmit = async (e) => {
|
||||
if (!e.detail.success) return
|
||||
if (e.detail.fetchResponse?.redirected && e.detail.fetchResponse.location.href !== window.location.href) return
|
||||
if (e.detail.formSubmission?.formElement?.method === 'get') return
|
||||
@@ -16,7 +16,19 @@ export default class extends HTMLElement {
|
||||
if (e.detail.formSubmission?.formElement?.dataset?.closeOnSubmit === 'false') return
|
||||
if (e.detail.formSubmission?.formElement?.closest('native-event[data-on="submit"]')) return
|
||||
|
||||
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'submitted' })
|
||||
const flash = await this.flashFrom(e.detail.fetchResponse)
|
||||
|
||||
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'submitted', ...flash })
|
||||
}
|
||||
|
||||
async flashFrom (fetchResponse) {
|
||||
const html = await fetchResponse?.responseHTML
|
||||
const flash = html && new DOMParser().parseFromString(html, 'text/html').getElementById('native_flash')
|
||||
|
||||
if (flash?.dataset.alert) return { alert: flash.dataset.alert }
|
||||
if (flash?.dataset.notice) return { notice: flash.dataset.notice }
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
this.addEventListener('pointerdown', this.onPointerdown)
|
||||
}
|
||||
|
||||
onPointerdown = (event) => {
|
||||
if (this.dataset.disabled === 'true') return
|
||||
if (/Hotwire Native/.test(navigator.userAgent)) return
|
||||
if (!this.isMobile()) return
|
||||
if (window.innerWidth >= 768) return
|
||||
|
||||
this.querySelectorAll('a[data-turbo-frame]').forEach((link) => {
|
||||
link.dataset.turboFrame = '_top'
|
||||
})
|
||||
const link = event.target.closest('a[data-turbo-frame]')
|
||||
|
||||
if (link) link.dataset.turboFrame = '_top'
|
||||
}
|
||||
|
||||
isMobile () {
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
if (this.lastElementChild) {
|
||||
this.init()
|
||||
} else {
|
||||
queueMicrotask(() => this.init())
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
const form = this.querySelector('form') || (this.querySelector('input, button, select') || this.lastElementChild).form
|
||||
|
||||
if (this.dataset.interval) {
|
||||
@@ -24,10 +38,4 @@ export default class extends HTMLElement {
|
||||
form.requestSubmit()
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,11 @@ export function nativeIcon (name) {
|
||||
}
|
||||
|
||||
const handlers = window.webkit?.messageHandlers
|
||||
const isNativeLoaded = window.isNativeLoaded
|
||||
|
||||
if (handlers?.native && window === window.top && !isNativeLoaded) {
|
||||
window.isNativeLoaded = true
|
||||
|
||||
if (handlers?.native && window === window.top) {
|
||||
handlers.native.postMessage({ type: 'reset' })
|
||||
|
||||
const reportPathConfiguration = () => {
|
||||
@@ -150,7 +153,7 @@ if (handlers?.native && window === window.top) {
|
||||
})
|
||||
}
|
||||
|
||||
if (handlers?.native && window.Turbo) {
|
||||
if (handlers?.native && window.Turbo && !isNativeLoaded) {
|
||||
const staleSnapshots = new Set()
|
||||
let isRestoring = false
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@drop="isDragFile = false"
|
||||
>
|
||||
<HoverDropzone
|
||||
v-if="sortedDocuments.length && withUploadButton && editable"
|
||||
v-if="sortedDocuments.length && withUploadButton && editable && !isSheetView"
|
||||
:is-dragging="isDragFile"
|
||||
:template-id="template.id"
|
||||
:accept-file-types="acceptFileTypes"
|
||||
@@ -26,7 +26,7 @@
|
||||
:is-required="defaultRequiredFields.includes(toRaw(dragField))"
|
||||
/>
|
||||
<MobileDrawer
|
||||
v-if="isDocumentsView || (withMobileDrawers && withDocumentsList && (isBreakpointLg || isMobile))"
|
||||
v-if="isDocumentsView || withDocumentsDrawer"
|
||||
side="left"
|
||||
:inline="isDocumentsView"
|
||||
:open="isDocumentsDrawerOpen"
|
||||
@@ -91,16 +91,16 @@
|
||||
</div>
|
||||
</MobileDrawer>
|
||||
<MobileDrawer
|
||||
v-if="isFieldsView || (withMobileDrawers && withFieldsList && editable)"
|
||||
v-if="isFieldsView || withFieldsDrawer"
|
||||
side="right"
|
||||
:inline="isFieldsView"
|
||||
:open="isFieldsDrawerOpen"
|
||||
:background-color="backgroundColor"
|
||||
class="md:hidden"
|
||||
@close="isFieldsDrawerOpen = false"
|
||||
>
|
||||
<div class="pb-6">
|
||||
<Fields
|
||||
ref="fields"
|
||||
:with-top-padding="true"
|
||||
:fields="template.fields"
|
||||
:submitters="template.submitters"
|
||||
@@ -153,7 +153,7 @@
|
||||
@documents-modified="onDocumentsModified"
|
||||
/>
|
||||
<div
|
||||
v-if="pendingFieldAttachmentUuids.length && editable"
|
||||
v-if="pendingFieldAttachmentUuids.length && editable && !nativeView"
|
||||
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
|
||||
>
|
||||
<div class="alert border-base-content/30 py-2 px-2.5 max-sm:flex max-sm:items-center max-sm:justify-between max-sm:gap-2 max-sm:text-left max-sm:text-sm">
|
||||
@@ -519,11 +519,11 @@
|
||||
:class="($slots.buttons || withTitle) && !nativePlatform ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')"
|
||||
>
|
||||
<div
|
||||
v-if="withDocumentsList"
|
||||
v-if="withDocumentsList && !withDocumentsDrawer"
|
||||
id="documents_container"
|
||||
ref="previews"
|
||||
:style="{ 'display': isBreakpointLg || isMobile ? 'none' : 'initial' }"
|
||||
class="overflow-y-auto overflow-x-hidden w-52 flex-none pr-3 mt-0.5 pt-0.5 hidden lg:block"
|
||||
class="overflow-y-auto overflow-x-hidden w-52 flex-none pr-3 mt-0.5 pt-0.5"
|
||||
>
|
||||
<DocumentPreview
|
||||
v-for="(item, index) in template.schema"
|
||||
@@ -685,9 +685,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="withFieldsList"
|
||||
v-if="withFieldsList && !(nativePlatform ? isBreakpointLg : isBreakpointMd)"
|
||||
id="fields_list_container"
|
||||
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block fields-list-container"
|
||||
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 fields-list-container"
|
||||
:class="drawField || drawCustomField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
||||
>
|
||||
<div
|
||||
@@ -739,7 +739,7 @@
|
||||
:detect-custom-fields-index="detectCustomFieldsIndex"
|
||||
:field-types="fieldTypes"
|
||||
:with-sticky-submitters="withStickySubmitters"
|
||||
:with-add-fields-pill="!showDrawField && !drawField && !drawCustomField"
|
||||
:with-add-fields-pill="!nativePlatform && !showDrawField && !drawField && !drawCustomField"
|
||||
:with-fields-detection="withFieldsDetection"
|
||||
:with-detect-existing-fields="withDetectExistingFields"
|
||||
:with-signature-id="withSignatureId"
|
||||
@@ -769,7 +769,7 @@
|
||||
class="sticky bottom-0 z-20"
|
||||
>
|
||||
<native-action
|
||||
v-if="nativePlatform && withDocumentsList && isBreakpointLg && sortedDocuments.length && editable"
|
||||
v-if="nativePlatform && withDocumentsList && (isBreakpointLg || isMobile) && sortedDocuments.length && editable"
|
||||
data-placement="drawer"
|
||||
data-side="leading"
|
||||
data-icon="files"
|
||||
@@ -785,7 +785,7 @@
|
||||
data-icon="plus"
|
||||
data-native="drawer"
|
||||
data-haptic="true"
|
||||
:data-label="'Fields'"
|
||||
:data-label="t('fields')"
|
||||
data-view="fields"
|
||||
/>
|
||||
<native-action
|
||||
@@ -800,8 +800,8 @@
|
||||
/>
|
||||
</native-action>
|
||||
<button
|
||||
v-if="drawField && (isBreakpointLg || isMobile) && !nativePlatform"
|
||||
class="btn btn-neutral text-white rounded-full no-animation transition-none absolute bottom-6 -left-3 md:-left-4 right-0 mx-auto w-fit z-10 flex-nowrap gap-2 px-5 max-[400px]:px-4 md:hidden"
|
||||
v-if="drawField && isBreakpointMd && !nativePlatform"
|
||||
class="btn btn-neutral text-white rounded-full no-animation transition-none absolute bottom-6 -left-3 md:-left-4 right-0 mx-auto w-fit z-10 flex-nowrap gap-2 px-5 max-[400px]:px-4"
|
||||
style="max-width: calc(100% - 11rem); --btn-focus-scale: 1"
|
||||
@click.prevent="[drawField = null, drawOption = null]"
|
||||
>
|
||||
@@ -824,7 +824,7 @@
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="withMobileDrawers && withDocumentsList && (isBreakpointLg || isMobile)"
|
||||
v-if="withDocumentsDrawer"
|
||||
class="btn btn-neutral text-white btn-circle btn-lg absolute bottom-4 left-1 md:left-0 z-10"
|
||||
:title="t('documents')"
|
||||
@click.prevent="isDocumentsDrawerOpen = true"
|
||||
@@ -836,9 +836,9 @@
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="withMobileDrawers && withFieldsList && editable"
|
||||
class="btn btn-neutral text-white btn-circle btn-lg absolute bottom-4 right-4 z-10 md:hidden"
|
||||
title="Fields"
|
||||
v-if="withFieldsDrawer"
|
||||
class="btn btn-neutral text-white btn-circle btn-lg absolute bottom-4 right-4 z-10"
|
||||
:title="t('fields')"
|
||||
@click.prevent="isFieldsDrawerOpen = true"
|
||||
>
|
||||
<IconPlus
|
||||
@@ -1369,6 +1369,7 @@ export default {
|
||||
return {
|
||||
documentRefs: [],
|
||||
isBreakpointLg: false,
|
||||
isBreakpointMd: false,
|
||||
isDownloading: false,
|
||||
isPreviewLoading: false,
|
||||
isLoadingBlankPage: false,
|
||||
@@ -1448,6 +1449,12 @@ export default {
|
||||
withMobileDrawers () {
|
||||
return !this.nativePlatform && !this.nativeView && this.sortedDocuments.length > 0
|
||||
},
|
||||
withDocumentsDrawer () {
|
||||
return this.withMobileDrawers && this.withDocumentsList && (this.isBreakpointLg || this.isMobile)
|
||||
},
|
||||
withFieldsDrawer () {
|
||||
return this.withMobileDrawers && this.withFieldsList && this.editable && this.isBreakpointMd
|
||||
},
|
||||
isMobile () {
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
|
||||
@@ -2402,8 +2409,11 @@ export default {
|
||||
},
|
||||
onWindowResize (e) {
|
||||
const breakpointLg = 1024
|
||||
const breakpointMd = 768
|
||||
const width = this.$el.getRootNode().querySelector('div[data-v-app]').offsetWidth
|
||||
|
||||
this.isBreakpointLg = this.$el.getRootNode().querySelector('div[data-v-app]').offsetWidth < breakpointLg
|
||||
this.isBreakpointLg = width < breakpointLg
|
||||
this.isBreakpointMd = width < breakpointMd
|
||||
},
|
||||
onPagesWheel (event) {
|
||||
if (!event.ctrlKey && !event.metaKey) return
|
||||
@@ -2497,7 +2507,7 @@ export default {
|
||||
},
|
||||
syncNative () {
|
||||
const { schema, documents, fields, submitters } = this.template
|
||||
const detail = JSON.stringify({ schema, documents, fields, submitters })
|
||||
const detail = JSON.stringify({ schema, documents, fields, submitters, pendingFieldAttachmentUuids: this.pendingFieldAttachmentUuids })
|
||||
|
||||
if (this.nativeView) {
|
||||
window.webkit?.messageHandlers?.modal?.postMessage({ action: 'dispatch', name: 'template-builder:sync', detail, dismiss: 'false' })
|
||||
@@ -3439,6 +3449,10 @@ export default {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (this.nativeView && this.pendingFieldAttachmentUuids.length) {
|
||||
this.syncNative()
|
||||
}
|
||||
},
|
||||
updateName (value) {
|
||||
this.template.name = value
|
||||
|
||||
@@ -201,6 +201,7 @@ const en = {
|
||||
uploading_: 'Uploading...',
|
||||
add_document: 'Add Document',
|
||||
add_fields: 'Add Fields',
|
||||
fields: 'Fields',
|
||||
none: 'None',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
@@ -445,6 +446,7 @@ const es = {
|
||||
uploading_: 'Subiendo...',
|
||||
add_document: 'Subir',
|
||||
add_fields: 'Agregar campos',
|
||||
fields: 'Campos',
|
||||
any: 'Cualquier',
|
||||
drawn: 'Dibujado',
|
||||
drawn_or_typed: 'Dibujado o Escrito',
|
||||
@@ -701,6 +703,7 @@ const it = {
|
||||
uploading_: 'Caricamento in corso...',
|
||||
add_document: 'Aggiungi',
|
||||
add_fields: 'Aggiungi campi',
|
||||
fields: 'Campi',
|
||||
none: 'Nessuno',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
@@ -945,6 +948,7 @@ const pt = {
|
||||
uploading_: 'Carregando...',
|
||||
add_document: 'Enviar',
|
||||
add_fields: 'Adicionar campos',
|
||||
fields: 'Campos',
|
||||
any: 'Qualquer',
|
||||
drawn: 'Desenhado',
|
||||
drawn_or_typed: 'Desenhado ou Digitado',
|
||||
@@ -1201,6 +1205,7 @@ const fr = {
|
||||
uploading_: 'Téléversement...',
|
||||
add_document: 'Téléverser',
|
||||
add_fields: 'Ajouter des champs',
|
||||
fields: 'Champs',
|
||||
none: 'Aucun',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
@@ -1451,6 +1456,7 @@ const de = {
|
||||
uploading_: 'Hochladen...',
|
||||
add_document: 'Hochladen',
|
||||
add_fields: 'Felder hinzufügen',
|
||||
fields: 'Felder',
|
||||
none: 'Keine',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
@@ -1701,6 +1707,7 @@ const nl = {
|
||||
uploading_: 'Bezig met uploaden...',
|
||||
add_document: 'Doc. toevoegen',
|
||||
add_fields: 'Velden toevoegen',
|
||||
fields: 'Velden',
|
||||
none: 'Geen',
|
||||
ssn: 'SSN',
|
||||
ein: 'EIN',
|
||||
|
||||
Reference in new issue
Block a user