parent
1dd02cc147
commit
4f4f7128fa
|
|
@ -101,7 +101,7 @@ class FormSubmissionFormatter
|
|||
$fields = $fields->merge($removeFields);
|
||||
}
|
||||
$fields = $fields->filter(function ($field) {
|
||||
return ! in_array($field['type'], ['nf-text', 'nf-code', 'nf-page-break', 'nf-divider', 'nf-image']);
|
||||
return !in_array($field['type'], ['nf-text', 'nf-code', 'nf-page-break', 'nf-divider', 'nf-image']);
|
||||
})->values();
|
||||
|
||||
$returnArray = [];
|
||||
|
|
@ -112,14 +112,14 @@ class FormSubmissionFormatter
|
|||
}
|
||||
|
||||
if ($field['removed'] ?? false) {
|
||||
$field['name'] = $field['name'].' (deleted)';
|
||||
$field['name'] = $field['name'] . ' (deleted)';
|
||||
}
|
||||
|
||||
// Add ID to avoid name clashes
|
||||
$field['name'] = $field['name'].' ('.\Str::of($field['id']).')';
|
||||
$field['name'] = $field['name'] . ' (' . \Str::of($field['id']) . ')';
|
||||
|
||||
// If not present skip
|
||||
if (! isset($data[$field['id']])) {
|
||||
if (!isset($data[$field['id']])) {
|
||||
if ($this->setEmptyForNoValue) {
|
||||
$returnArray[$field['name']] = '';
|
||||
}
|
||||
|
|
@ -128,16 +128,16 @@ class FormSubmissionFormatter
|
|||
}
|
||||
|
||||
// If hide hidden fields
|
||||
if (! $this->showHiddenFields) {
|
||||
if (!$this->showHiddenFields) {
|
||||
if (FormLogicPropertyResolver::isHidden($field, $this->idFormData ?? [])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->createLinks && $field['type'] == 'url') {
|
||||
$returnArray[$field['name']] = '<a href="'.$data[$field['id']].'">'.$data[$field['id']].'</a>';
|
||||
$returnArray[$field['name']] = '<a href="' . $data[$field['id']] . '">' . $data[$field['id']] . '</a>';
|
||||
} elseif ($this->createLinks && $field['type'] == 'email') {
|
||||
$returnArray[$field['name']] = '<a href="mailto:'.$data[$field['id']].'">'.$data[$field['id']].'</a>';
|
||||
$returnArray[$field['name']] = '<a href="mailto:' . $data[$field['id']] . '">' . $data[$field['id']] . '</a>';
|
||||
} elseif ($field['type'] == 'multi_select') {
|
||||
$val = $data[$field['id']];
|
||||
if ($this->outputStringsOnly && is_array($val)) {
|
||||
|
|
@ -184,29 +184,31 @@ class FormSubmissionFormatter
|
|||
$fields = $this->form->properties;
|
||||
$transformedFields = [];
|
||||
foreach ($fields as $field) {
|
||||
if (! isset($field['id']) || ! isset($data[$field['id']])) {
|
||||
if (!isset($field['id']) || !isset($data[$field['id']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If hide hidden fields
|
||||
if (! $this->showHiddenFields) {
|
||||
if (!$this->showHiddenFields) {
|
||||
if (FormLogicPropertyResolver::isHidden($field, $this->idFormData)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->createLinks && $field['type'] == 'url') {
|
||||
$field['value'] = '<a href="'.$data[$field['id']].'">'.$data[$field['id']].'</a>';
|
||||
$field['value'] = '<a href="' . $data[$field['id']] . '">' . $data[$field['id']] . '</a>';
|
||||
} elseif ($this->createLinks && $field['type'] == 'email') {
|
||||
$field['value'] = '<a href="mailto:'.$data[$field['id']].'">'.$data[$field['id']].'</a>';
|
||||
$field['value'] = '<a href="mailto:' . $data[$field['id']] . '">' . $data[$field['id']] . '</a>';
|
||||
} elseif ($field['type'] == 'checkbox') {
|
||||
$field['value'] = $data[$field['id']] ? 'Yes' : 'No';
|
||||
} elseif ($field['type'] == 'date') {
|
||||
$dateFormat = ($field['date_format'] ?? 'dd/MM/yyyy') == 'dd/MM/yyyy' ? 'd/m/Y' : 'm/d/Y';
|
||||
$dateFormat .= (isset($field['with_time']) && $field['with_time']) ? ' H:i' : '';
|
||||
if (is_array($data[$field['id']])) {
|
||||
$field['value'] = isset($data[$field['id']][1]) ? (new Carbon($data[$field['id']][0]))->format('d/m/Y')
|
||||
.' - '.(new Carbon($data[$field['id']][1]))->format('d/m/Y') : (new Carbon($data[$field['id']][0]))->format('d/m/Y');
|
||||
$field['value'] = isset($data[$field['id']][1]) ? (new Carbon($data[$field['id']][0]))->format($dateFormat)
|
||||
. ' - ' . (new Carbon($data[$field['id']][1]))->format($dateFormat) : (new Carbon($data[$field['id']][0]))->format($dateFormat);
|
||||
} else {
|
||||
$field['value'] = (new Carbon($data[$field['id']]))->format((isset($field['with_time']) && $field['with_time']) ? 'd/m/Y H:i' : 'd/m/Y');
|
||||
$field['value'] = (new Carbon($data[$field['id']]))->format($dateFormat);
|
||||
}
|
||||
} elseif ($field['type'] == 'multi_select') {
|
||||
$val = $data[$field['id']];
|
||||
|
|
@ -230,7 +232,7 @@ class FormSubmissionFormatter
|
|||
return [
|
||||
'unsigned_url' => route('open.forms.submissions.file', [$formId, $file]),
|
||||
'signed_url' => $this->getFileUrl($formId, $file),
|
||||
'label' => \Str::limit($file, 20, '[...].'.end($splitText)),
|
||||
'label' => \Str::limit($file, 20, '[...].' . end($splitText)),
|
||||
];
|
||||
})->toArray();
|
||||
} else {
|
||||
|
|
@ -241,7 +243,6 @@ class FormSubmissionFormatter
|
|||
'file_name' => $file,
|
||||
];
|
||||
});
|
||||
|
||||
}
|
||||
} else {
|
||||
if (is_array($data[$field['id']]) && $this->outputStringsOnly) {
|
||||
|
|
|
|||
|
|
@ -1,38 +1,82 @@
|
|||
<template>
|
||||
<input-wrapper
|
||||
v-bind="inputWrapperProps"
|
||||
>
|
||||
<InputWrapper v-bind="props">
|
||||
<template #label>
|
||||
<slot name="label" />
|
||||
</template>
|
||||
|
||||
<div v-if="!dateRange" class="flex">
|
||||
<input :id="id?id:name" v-model="fromDate" :type="useTime ? 'datetime-local' : 'date'" :class="inputClasses"
|
||||
:disabled="disabled?true:null"
|
||||
:style="inputStyle" :name="name" data-date-format="YYYY-MM-DD"
|
||||
:min="setMinDate" :max="setMaxDate"
|
||||
<UPopover
|
||||
v-model:open="pickerOpen"
|
||||
:disabled="props.disabled"
|
||||
:popper="{ placement: 'bottom-start' }"
|
||||
>
|
||||
<button
|
||||
class="cursor-pointer overflow-hidden"
|
||||
:class="inputClasses"
|
||||
:disabled="props.disabled"
|
||||
>
|
||||
</div>
|
||||
<div v-else :class="inputClasses">
|
||||
<div class="flex -mx-2">
|
||||
<p class="text-gray-900 px-4">
|
||||
From
|
||||
</p>
|
||||
<input :id="id?id:name" v-model="fromDate" :type="useTime ? 'datetime-local' : 'date'" :disabled="disabled?true:null"
|
||||
:style="inputStyle" :name="name" data-date-format="YYYY-MM-DD"
|
||||
class="flex-grow border-transparent focus:outline-none "
|
||||
:min="setMinDate" :max="setMaxDate"
|
||||
>
|
||||
<p class="text-gray-900 px-4">
|
||||
To
|
||||
</p>
|
||||
<input v-if="dateRange" :id="id?id:name" v-model="toDate" :type="useTime ? 'datetime-local' : 'date'"
|
||||
:disabled="disabled?true:null"
|
||||
:style="inputStyle" :name="name" class="flex-grow border-transparent focus:outline-none"
|
||||
:min="setMinDate" :max="setMaxDate"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center min-w-0">
|
||||
<div
|
||||
class="flex-grow min-w-0 flex items-center gap-x-2"
|
||||
:class="[
|
||||
props.theme.default.inputSpacing.vertical,
|
||||
props.theme.default.inputSpacing.horizontal,
|
||||
{'hover:bg-gray-50 dark:hover:bg-gray-900': !props.disabled}
|
||||
]"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:calendar-20-solid"
|
||||
class="w-4 h-4 flex-shrink-0"
|
||||
dynamic
|
||||
/>
|
||||
<div class="flex-grow truncate overflow-hidden">
|
||||
<p class="flex-grow truncate h-[24px]">
|
||||
{{ formattedDatePreview }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-if="fromDate && !props.disabled"
|
||||
class="hover:bg-gray-50 dark:hover:bg-gray-900 border-l px-2"
|
||||
:class="[props.theme.default.inputSpacing.vertical]"
|
||||
@click.prevent="clear()"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:x-mark-20-solid"
|
||||
class="w-5 h-5 text-gray-500"
|
||||
width="2em"
|
||||
dynamic
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<DatePicker
|
||||
v-if="props.dateRange"
|
||||
v-model.range="modeledValue"
|
||||
:mode="props.withTime ? 'dateTime' : 'date'"
|
||||
is-required
|
||||
borderless
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:is-dark="props.isDark"
|
||||
color="form-color"
|
||||
@close="close"
|
||||
/>
|
||||
<DatePicker
|
||||
v-else
|
||||
v-model="modeledValue"
|
||||
:mode="props.withTime ? 'dateTime' : 'date'"
|
||||
is-required
|
||||
borderless
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:is-dark="props.isDark"
|
||||
color="form-color"
|
||||
@close="close"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<template #help>
|
||||
<slot name="help" />
|
||||
|
|
@ -40,148 +84,173 @@
|
|||
<template #error>
|
||||
<slot name="error" />
|
||||
</template>
|
||||
</input-wrapper>
|
||||
</InputWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { inputProps, useFormInput } from './useFormInput.js'
|
||||
import InputWrapper from './components/InputWrapper.vue'
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import { DatePicker } from 'v-calendar'
|
||||
import 'v-calendar/dist/style.css'
|
||||
import { format } from 'date-fns'
|
||||
import { tailwindcssPaletteGenerator } from '~/lib/colors.js'
|
||||
|
||||
export default {
|
||||
name: 'DateInput',
|
||||
components: { InputWrapper },
|
||||
mixins: [],
|
||||
const props = defineProps({
|
||||
...inputProps,
|
||||
withTime: { type: Boolean, default: false },
|
||||
dateRange: { type: Boolean, default: false },
|
||||
disablePastDates: { type: Boolean, default: false },
|
||||
disableFutureDates: { type: Boolean, default: false },
|
||||
dateFormat: { type: String, default: 'dd/MM/yyyy' },
|
||||
outputDateFormat: { type: String, default: 'yyyy-MM-dd\'T\'HH:mm:ssXXX' },
|
||||
isDark: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
props: {
|
||||
...inputProps,
|
||||
withTime: { type: Boolean, default: false },
|
||||
dateRange: { type: Boolean, default: false },
|
||||
disablePastDates: { type: Boolean, default: false },
|
||||
disableFutureDates: { type: Boolean, default: false }
|
||||
const input = useFormInput(props, getCurrentInstance())
|
||||
const fromDate = ref(null)
|
||||
const toDate = ref(null)
|
||||
const datepicker = ref(null)
|
||||
const pickerOpen = ref(false)
|
||||
|
||||
const twColors = computed(() => {
|
||||
return tailwindcssPaletteGenerator(props.color).primary
|
||||
})
|
||||
|
||||
const modeledValue = computed({
|
||||
get () {
|
||||
return props.dateRange ? { start: fromDate.value, end: toDate.value } : fromDate.value
|
||||
},
|
||||
|
||||
setup (props, context) {
|
||||
return {
|
||||
...useFormInput(props, context)
|
||||
set (value) {
|
||||
if (props.dateRange) {
|
||||
fromDate.value = format(value.start, props.outputDateFormat)
|
||||
toDate.value = format(value.end, props.outputDateFormat)
|
||||
} else {
|
||||
fromDate.value = format(value, props.outputDateFormat)
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
data: () => ({
|
||||
fromDate: null,
|
||||
toDate: null
|
||||
}),
|
||||
const inputClasses = computed(() => {
|
||||
const classes = [props.theme.DateInput.input, 'w-full']
|
||||
if (props.disabled) {
|
||||
classes.push('!cursor-not-allowed dark:!bg-gray-600 !bg-gray-200')
|
||||
}
|
||||
if (input.hasError.value) {
|
||||
|
||||
computed: {
|
||||
inputClasses () {
|
||||
let str = 'border border-gray-300 dark:bg-notion-dark-light dark:border-gray-600 dark:placeholder-gray-500 dark:text-gray-300 flex-1 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-opacity-100 placeholder-gray-400 px-4 py-2 rounded-lg shadow-sm text-base text-black text-gray-700'
|
||||
str += this.dateRange ? ' w-50' : ' w-full'
|
||||
str += this.disabled ? ' !cursor-not-allowed !bg-gray-200' : ''
|
||||
return str
|
||||
},
|
||||
useTime () {
|
||||
return this.withTime && !this.dateRange
|
||||
},
|
||||
setMinDate () {
|
||||
if (this.disablePastDates) {
|
||||
return new Date().toISOString().split('T')[0]
|
||||
}
|
||||
return false
|
||||
},
|
||||
setMaxDate () {
|
||||
if (this.disableFutureDates) {
|
||||
return new Date().toISOString().split('T')[0]
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
classes.push('!ring-red-500 !ring-2 !border-transparent')
|
||||
}
|
||||
return classes.join(' ')
|
||||
})
|
||||
|
||||
watch: {
|
||||
color: {
|
||||
handler () {
|
||||
this.setInputColor()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
fromDate: {
|
||||
handler (val) {
|
||||
if (this.dateRange) {
|
||||
if (!Array.isArray(this.compVal)) {
|
||||
this.compVal = []
|
||||
}
|
||||
this.compVal[0] = this.dateToUTC(val)
|
||||
} else {
|
||||
this.compVal = this.dateToUTC(val)
|
||||
}
|
||||
},
|
||||
immediate: false
|
||||
},
|
||||
toDate: {
|
||||
handler (val) {
|
||||
if (this.dateRange) {
|
||||
if (!Array.isArray(this.compVal)) {
|
||||
this.compVal = [null]
|
||||
}
|
||||
this.compVal[1] = this.dateToUTC(val)
|
||||
} else {
|
||||
this.compVal = null
|
||||
}
|
||||
},
|
||||
immediate: false
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
if (this.compVal) {
|
||||
if (Array.isArray(this.compVal)) {
|
||||
this.fromDate = this.compVal[0] ?? null
|
||||
this.toDate = this.compVal[1] ?? null
|
||||
} else {
|
||||
this.fromDate = this.dateToLocal(this.compVal)
|
||||
}
|
||||
}
|
||||
|
||||
this.setInputColor()
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Pressing enter won't submit form
|
||||
* @param event
|
||||
* @returns {boolean}
|
||||
*/
|
||||
onEnterPress (event) {
|
||||
event.preventDefault()
|
||||
return false
|
||||
},
|
||||
setInputColor () {
|
||||
if (this.$refs.datepicker) {
|
||||
const dateInput = this.$refs.datepicker.$el.getElementsByTagName('input')[0]
|
||||
dateInput.style.setProperty('--tw-ring-color', this.color)
|
||||
}
|
||||
},
|
||||
dateToUTC (val) {
|
||||
if (!val) {
|
||||
return null
|
||||
}
|
||||
if (!this.useTime) {
|
||||
return val
|
||||
}
|
||||
return new Date(val).toISOString()
|
||||
},
|
||||
dateToLocal (val) {
|
||||
if (!val) {
|
||||
return null
|
||||
}
|
||||
const dateObj = new Date(val)
|
||||
let dateStr = dateObj.getFullYear() + '-' +
|
||||
String(dateObj.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(dateObj.getDate()).padStart(2, '0')
|
||||
if (this.useTime) {
|
||||
dateStr += 'T' + String(dateObj.getHours()).padStart(2, '0') + ':' +
|
||||
String(dateObj.getMinutes()).padStart(2, '0')
|
||||
}
|
||||
return dateStr
|
||||
const minDate = computed(() => {
|
||||
if (props.disablePastDates) {
|
||||
return new Date()
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
const maxDate = computed(() => {
|
||||
if (props.disableFutureDates) {
|
||||
return new Date()
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
const handleCompValChange = () => {
|
||||
if (input.compVal.value) {
|
||||
if (Array.isArray(input.compVal.value)) {
|
||||
fromDate.value = input.compVal.value[0] ?? null
|
||||
toDate.value = input.compVal.value[1] ?? null
|
||||
} else {
|
||||
fromDate.value = input.compVal.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setInputColor = () => {
|
||||
if (datepicker.value) {
|
||||
const dateInput = datepicker.value.$el.getElementsByTagName('input')[0]
|
||||
dateInput.style.setProperty('--tw-ring-color', props.color)
|
||||
}
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
fromDate.value = null
|
||||
toDate.value = null
|
||||
pickerOpen.value = false
|
||||
}
|
||||
|
||||
const formattedDate = (value) => {
|
||||
if (props.withTime) {
|
||||
try {
|
||||
return format(new Date(value), props.dateFormat + ' HH:mm')
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
try {
|
||||
return format(new Date(value), props.dateFormat)
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const formattedDatePreview = computed(() => {
|
||||
if (!fromDate.value) return ''
|
||||
if (props.dateRange) {
|
||||
if (!toDate.value) return formattedDate(fromDate.value)
|
||||
return `${formattedDate(fromDate.value)} - ${formattedDate(toDate.value)}`
|
||||
}
|
||||
return formattedDate(fromDate.value)
|
||||
})
|
||||
|
||||
watch(() => props.color, () => {
|
||||
setInputColor()
|
||||
}, { immediate: true })
|
||||
|
||||
watch(() => props.dateRange, () => {
|
||||
fromDate.value = null
|
||||
toDate.value = null
|
||||
}, { immediate: true })
|
||||
|
||||
watch(() => fromDate.value, (val) => {
|
||||
if (props.dateRange) {
|
||||
if (!Array.isArray(input.compVal.value)) input.compVal.value = []
|
||||
input.compVal.value[0] = val
|
||||
} else {
|
||||
input.compVal.value = val
|
||||
}
|
||||
}, { immediate: false })
|
||||
|
||||
watch(() => toDate.value, (val) => {
|
||||
if (props.dateRange) {
|
||||
if (!Array.isArray(input.compVal.value)) input.compVal.value = [null]
|
||||
input.compVal.value[1] = val
|
||||
} else {
|
||||
input.compVal.value = null
|
||||
}
|
||||
}, { immediate: false })
|
||||
|
||||
watch(() => input.compVal.value, (val, oldVal) => {
|
||||
if (!oldVal) handleCompValChange()
|
||||
}, { immediate: false })
|
||||
|
||||
onMounted(() => {
|
||||
handleCompValChange()
|
||||
setInputColor()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vc-form-color {
|
||||
--vc-accent-50: v-bind('twColors[50]');
|
||||
--vc-accent-100: v-bind('twColors[100]');
|
||||
--vc-accent-200: v-bind('twColors[200]');
|
||||
--vc-accent-300: v-bind('twColors[300]');
|
||||
--vc-accent-400: v-bind('twColors[400]');
|
||||
--vc-accent-500: v-bind('twColors[500]');
|
||||
--vc-accent-600: v-bind('twColors[600]');
|
||||
--vc-accent-700: v-bind('twColors[700]');
|
||||
--vc-accent-800: v-bind('twColors[800]');
|
||||
--vc-accent-900: v-bind('twColors[900]');
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -338,9 +338,8 @@ export default {
|
|||
}
|
||||
|
||||
if (this.isPublicFormPage && this.form.editable_submissions) {
|
||||
const urlParam = new URLSearchParams(window.location.search)
|
||||
if (urlParam && urlParam.get('submission_id')) {
|
||||
this.form.submission_id = urlParam.get('submission_id')
|
||||
if (useRoute().query?.submission_id) {
|
||||
this.form.submission_id = useRoute().query?.submission_id
|
||||
const data = await this.getSubmissionData()
|
||||
if (data !== null && data) {
|
||||
this.dataForm = useForm(data)
|
||||
|
|
@ -353,15 +352,7 @@ export default {
|
|||
if (pendingData !== null && pendingData && Object.keys(this.pendingSubmission.get()).length !== 0) {
|
||||
this.fields.forEach((field) => {
|
||||
if (field.type === 'date' && field.prefill_today === true) { // For Prefill with 'today'
|
||||
const dateObj = new Date()
|
||||
let currentDate = dateObj.getFullYear() + '-' +
|
||||
String(dateObj.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(dateObj.getDate()).padStart(2, '0')
|
||||
if (field.with_time === true) {
|
||||
currentDate += 'T' + String(dateObj.getHours()).padStart(2, '0') + ':' +
|
||||
String(dateObj.getMinutes()).padStart(2, '0')
|
||||
}
|
||||
pendingData[field.id] = currentDate
|
||||
pendingData[field.id] = new Date().toISOString()
|
||||
}
|
||||
})
|
||||
this.dataForm = useForm(pendingData)
|
||||
|
|
@ -395,15 +386,7 @@ export default {
|
|||
// Array url prefills
|
||||
formData[field.id] = urlPrefill.getAll(field.id + '[]')
|
||||
} else if (field.type === 'date' && field.prefill_today === true) { // For Prefill with 'today'
|
||||
const dateObj = new Date()
|
||||
let currentDate = dateObj.getFullYear() + '-' +
|
||||
String(dateObj.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(dateObj.getDate()).padStart(2, '0')
|
||||
if (field.with_time === true) {
|
||||
currentDate += 'T' + String(dateObj.getHours()).padStart(2, '0') + ':' +
|
||||
String(dateObj.getMinutes()).padStart(2, '0')
|
||||
}
|
||||
formData[field.id] = currentDate
|
||||
formData[field.id] = new Date().toISOString()
|
||||
} else { // Default prefill if any
|
||||
formData[field.id] = field.prefill
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
<script>
|
||||
import { computed } from 'vue'
|
||||
import FormLogicPropertyResolver from "~/lib/forms/FormLogicPropertyResolver.js"
|
||||
import { darkModeEnabled } from '~/lib/forms/public-page.js'
|
||||
import { default as _has } from 'lodash/has'
|
||||
|
||||
export default {
|
||||
|
|
@ -176,6 +177,9 @@ export default {
|
|||
},
|
||||
fieldSideBarOpened() {
|
||||
return this.adminPreview && (this.form && this.selectedFieldIndex !== null) ? (this.form.properties[this.selectedFieldIndex] && this.showEditFieldSidebar) : false
|
||||
},
|
||||
isDark () {
|
||||
return this.form.dark_mode === 'dark' || this.form.dark_mode === 'auto' && darkModeEnabled()
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -245,7 +249,8 @@ export default {
|
|||
uppercaseLabels: this.form.uppercase_labels == 1 || this.form.uppercase_labels == true,
|
||||
theme: this.theme,
|
||||
maxCharLimit: (field.max_char_limit) ? parseInt(field.max_char_limit) : 2000,
|
||||
showCharLimit: field.show_char_limit || false
|
||||
showCharLimit: field.show_char_limit || false,
|
||||
isDark: this.isDark
|
||||
}
|
||||
|
||||
if (['select', 'multi_select'].includes(field.type)) {
|
||||
|
|
@ -261,9 +266,11 @@ export default {
|
|||
inputProperties.allowCreation = (field.allow_creation === true)
|
||||
inputProperties.searchable = (inputProperties.options.length > 4)
|
||||
} else if (field.type === 'date') {
|
||||
inputProperties.dateFormat = field.date_format
|
||||
if (field.with_time) {
|
||||
inputProperties.withTime = true
|
||||
} else if (field.date_range) {
|
||||
}
|
||||
if (field.date_range) {
|
||||
inputProperties.dateRange = true
|
||||
}
|
||||
if (field.disable_past_dates) {
|
||||
|
|
|
|||
|
|
@ -8,18 +8,12 @@
|
|||
<p class="text-gray-400 mb-2 text-xs">
|
||||
Exclude this field or make it required.
|
||||
</p>
|
||||
<v-checkbox v-model="field.hidden" class="mb-3" :name="field.id + '_hidden'"
|
||||
@update:model-value="onFieldHiddenChange">
|
||||
Hidden
|
||||
</v-checkbox>
|
||||
<v-checkbox v-model="field.required" class="mb-3" :name="field.id + '_required'"
|
||||
@update:model-value="onFieldRequiredChange">
|
||||
Required
|
||||
</v-checkbox>
|
||||
<v-checkbox v-model="field.disabled" class="mb-3" :name="field.id + '_disabled'"
|
||||
@update:model-value="onFieldDisabledChange">
|
||||
Disabled
|
||||
</v-checkbox>
|
||||
<toggle-switch-input :form="field" name="required" label="Required"
|
||||
@update:model-value="onFieldRequiredChange"/>
|
||||
<toggle-switch-input :form="field" name="hidden" label="Hidden"
|
||||
@update:model-value="onFieldHiddenChange"/>
|
||||
<toggle-switch-input :form="field" name="disabled" label="Disabled"
|
||||
@update:model-value="onFieldDisabledChange"/>
|
||||
</div>
|
||||
|
||||
<!-- Checkbox -->
|
||||
|
|
@ -119,38 +113,53 @@
|
|||
<h3 class="font-semibold block text-lg">
|
||||
Date Options
|
||||
</h3>
|
||||
<v-checkbox v-model="field.date_range" class="mt-3" :name="field.id + '_date_range'"
|
||||
@update:model-value="onFieldDateRangeChange">
|
||||
Date Range
|
||||
</v-checkbox>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Adds an end date. This cannot be used with the time option yet.
|
||||
</p>
|
||||
<v-checkbox v-model="field.with_time" :name="field.id + '_with_time'" @update:model-value="onFieldWithTimeChange">
|
||||
Date with time
|
||||
</v-checkbox>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Include time. Or not. This cannot be used with the date range option yet.
|
||||
</p>
|
||||
|
||||
<select-input v-if="field.with_time" name="timezone" class="mt-3" :form="field" :options="timezonesOptions"
|
||||
label="Timezone" :searchable="true" help="Make sure to select correct timezone. Leave blank otherwise." />
|
||||
<v-checkbox v-model="field.prefill_today" name="prefill_today" @update:model-value="onFieldPrefillTodayChange">
|
||||
Prefill with 'today'
|
||||
</v-checkbox>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
if enabled we will pre-fill this field with the current date
|
||||
</p>
|
||||
|
||||
<v-checkbox v-model="field.disable_past_dates" name="disable_past_dates" class="mb-3"
|
||||
@update:model-value="onFieldDisablePastDatesChange">
|
||||
Disable past dates
|
||||
</v-checkbox>
|
||||
|
||||
<v-checkbox v-model="field.disable_future_dates" name="disable_future_dates" class="mb-3"
|
||||
@update:model-value="onFieldDisableFutureDatesChange">
|
||||
Disable future dates
|
||||
</v-checkbox>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
class="mt-3"
|
||||
name="date_range"
|
||||
label="End date"
|
||||
@update:model-value="onFieldDateRangeChange"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="prefill_today"
|
||||
label="Prefill with 'today'"
|
||||
@update:model-value="onFieldPrefillTodayChange"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="disable_past_dates"
|
||||
label="Disable past dates"
|
||||
@update:model-value="onFieldDisablePastDatesChange"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="disable_future_dates"
|
||||
label="Disable future dates"
|
||||
@update:model-value="onFieldDisableFutureDatesChange"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="with_time"
|
||||
label="Include time"
|
||||
/>
|
||||
<select-input
|
||||
v-if="field.with_time"
|
||||
name="timezone"
|
||||
class="mt-4"
|
||||
:form="field"
|
||||
:options="timezonesOptions"
|
||||
label="Timezone"
|
||||
:searchable="true"
|
||||
help="Make sure to select the same timezone you're using in Notion. Leave blank otherwise."
|
||||
/>
|
||||
<flat-select-input
|
||||
name="date_format"
|
||||
class="mt-4"
|
||||
:form="field"
|
||||
:options="dateFormatOptions"
|
||||
label="Date format"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- select/multiselect Options -->
|
||||
|
|
@ -268,7 +277,7 @@
|
|||
|
||||
<!-- Help -->
|
||||
<rich-text-area-input name="help" class="mt-3" :form="field" :editor-toolbar="editorToolbarCustom"
|
||||
label="Field Help" help="Your field help will be shown below/above the field, just like this message."
|
||||
label="Field Help" help="Your field help will be shown below/above the field, just like this text."
|
||||
:help-position="field.help_position" />
|
||||
<select-input name="help_position" class="mt-3" :options="[
|
||||
{ name: 'Below input', value: 'below_input' },
|
||||
|
|
@ -316,6 +325,7 @@ import timezones from '~/data/timezones.json'
|
|||
import countryCodes from '~/data/country_codes.json'
|
||||
import CountryFlag from 'vue-country-flag-next'
|
||||
import FormBlockLogicEditor from '../../components/form-logic-components/FormBlockLogicEditor.vue'
|
||||
import { format } from 'date-fns'
|
||||
import { default as _has } from 'lodash/has'
|
||||
|
||||
export default {
|
||||
|
|
@ -370,6 +380,15 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
dateFormatOptions () {
|
||||
const date = new Date()
|
||||
return ['dd/MM/yyyy', 'MM-dd-yyyy'].map(dateFormat => {
|
||||
return {
|
||||
name: format(date, dateFormat),
|
||||
value: dateFormat
|
||||
}
|
||||
})
|
||||
},
|
||||
displayBasedOnAdvanced() {
|
||||
if (this.field.generates_uuid || this.field.generates_auto_increment_id) {
|
||||
return false
|
||||
|
|
@ -445,16 +464,9 @@ export default {
|
|||
onFieldDateRangeChange(val) {
|
||||
this.field.date_range = val
|
||||
if (this.field.date_range) {
|
||||
this.field.with_time = false
|
||||
this.field.prefill_today = false
|
||||
}
|
||||
},
|
||||
onFieldWithTimeChange(val) {
|
||||
this.field.with_time = val
|
||||
if (this.field.with_time) {
|
||||
this.field.date_range = false
|
||||
}
|
||||
},
|
||||
onFieldGenUIdChange(val) {
|
||||
this.field.generates_uuid = val
|
||||
if (this.field.generates_uuid) {
|
||||
|
|
@ -554,6 +566,9 @@ export default {
|
|||
url: {
|
||||
max_char_limit: 2000
|
||||
},
|
||||
date: {
|
||||
date_format: this.dateFormatOptions[0].value
|
||||
}
|
||||
}
|
||||
if (this.field.type in defaultFieldValues) {
|
||||
Object.keys(defaultFieldValues[this.field.type]).forEach(key => {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
<template>
|
||||
<span v-if="valueIsObject">
|
||||
<template v-if="value[0]">{{ value[0] }}</template>
|
||||
<template v-if="value[1]"><b>to</b> {{ value[1] }}</template>
|
||||
<template v-if="value[0]">{{ formattedDate(value[0]) }}</template>
|
||||
<template v-if="value[1]"><b class="mx-2">to</b>{{ formattedDate(value[1]) }}</template>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ value }}
|
||||
{{ formattedDate(value) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { format } from 'date-fns'
|
||||
import { default as _has } from 'lodash/has'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
property: {
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
required: true
|
||||
}
|
||||
|
|
@ -30,6 +36,22 @@ export default {
|
|||
mounted () {
|
||||
},
|
||||
methods: {
|
||||
formattedDate(val) {
|
||||
if (!val) return ''
|
||||
const dateFormat = _has(this.property, 'date_format') ? this.property.date_format : 'dd/MM/yyyy'
|
||||
if (this.property?.with_time) {
|
||||
try {
|
||||
return format(new Date(val), dateFormat + ' HH:mm')
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
try {
|
||||
return format(new Date(val), dateFormat)
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
// hexToHSL.js functionality
|
||||
const hexToHSL = (hex) => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) || []
|
||||
try {
|
||||
let r = parseInt(result[1], 16)
|
||||
let g = parseInt(result[2], 16)
|
||||
let b = parseInt(result[3], 16)
|
||||
r /= 255
|
||||
g /= 255
|
||||
b /= 255
|
||||
const max = Math.max(r, g, b),
|
||||
min = Math.min(r, g, b)
|
||||
let h = 0,
|
||||
s,
|
||||
l = (max + min) / 2
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0 // achromatic
|
||||
} else {
|
||||
const d = max - min
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0)
|
||||
break
|
||||
case g:
|
||||
h = (b - r) / d + 2
|
||||
break
|
||||
case b:
|
||||
h = (r - g) / d + 4
|
||||
break
|
||||
}
|
||||
h /= 6
|
||||
}
|
||||
|
||||
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }
|
||||
} catch (error) {
|
||||
console.error('Invalid HEX color', hex)
|
||||
return { h: 0, s: 0, l: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// hslToHex.js functionality
|
||||
const hslToHex = ({ h, s, l }) => {
|
||||
l /= 100
|
||||
const a = (s * Math.min(l, 1 - l)) / 100
|
||||
const f = n => {
|
||||
const k = (n + h / 30) % 12
|
||||
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
|
||||
return Math.round(255 * color).toString(16).padStart(2, '0')
|
||||
}
|
||||
return `#${f(0)}${f(8)}${f(4)}`
|
||||
}
|
||||
|
||||
// generateColor.js functionality
|
||||
const generateColor = ({ hex, preserve, shades }) => {
|
||||
const colorHSL = hexToHSL(hex)
|
||||
const obj = {}
|
||||
const lightnessDelta = {}
|
||||
|
||||
shades.forEach(({ name, lightness }) => {
|
||||
const { h, s, l } = colorHSL
|
||||
const hsl = { h, s, l: lightness }
|
||||
const hex = hslToHex(hsl)
|
||||
obj[name] = hex
|
||||
if (preserve) lightnessDelta[name] = Math.abs(l - lightness)
|
||||
})
|
||||
|
||||
if (preserve) {
|
||||
const [closestShade] = Object.keys(lightnessDelta).sort(
|
||||
(a, b) => lightnessDelta[a] - lightnessDelta[b]
|
||||
)
|
||||
obj[closestShade] = hex
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
// tailwindcssPaletteGenerator functionality
|
||||
const tailwindcssPaletteGenerator = (options) => {
|
||||
let colors = []
|
||||
let names = ['primary', 'secondary', 'tertiary', 'quaternary', 'quinary', 'senary', 'septenary', 'octonary', 'nonary', 'denary']
|
||||
let preserve = true
|
||||
let shades = [
|
||||
{ name: '50', lightness: 98 },
|
||||
{ name: '100', lightness: 95 },
|
||||
{ name: '200', lightness: 90 },
|
||||
{ name: '300', lightness: 82 },
|
||||
{ name: '400', lightness: 64 },
|
||||
{ name: '500', lightness: 46 },
|
||||
{ name: '600', lightness: 33 },
|
||||
{ name: '700', lightness: 24 },
|
||||
{ name: '800', lightness: 14 },
|
||||
{ name: '900', lightness: 7 },
|
||||
{ name: '950', lightness: 4 }
|
||||
]
|
||||
|
||||
if (typeof options === 'string') options = { colors: [options], names, preserve, shades }
|
||||
if (Array.isArray(options)) options = { colors: options, names, preserve, shades }
|
||||
if (typeof options === 'object' && !Array.isArray(options)) {
|
||||
options = Object.assign({ colors, names, preserve, shades }, options)
|
||||
}
|
||||
|
||||
const palette = {}
|
||||
options.colors.forEach((hex, i) => {
|
||||
const name = options.names[i]
|
||||
palette[name] = generateColor({ hex, preserve, shades: options.shades })
|
||||
})
|
||||
|
||||
return palette
|
||||
}
|
||||
|
||||
// Exporting the functions
|
||||
export { generateColor, tailwindcssPaletteGenerator }
|
||||
|
|
@ -6,11 +6,20 @@ export const themes = {
|
|||
default: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full py-2 px-4 bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
inputSpacing: {
|
||||
vertical: 'py-2',
|
||||
horizontal: 'px-4'
|
||||
},
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
Button: {
|
||||
body: 'transition ease-in duration-200 text-center font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg filter hover:brightness-110'
|
||||
},
|
||||
DateInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
CodeInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden',
|
||||
|
|
@ -51,11 +60,20 @@ export const themes = {
|
|||
default: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full py-2 px-2 bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
inputSpacing: {
|
||||
vertical: 'py-2',
|
||||
horizontal: 'px-4'
|
||||
},
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
Button: {
|
||||
body: 'transition ease-in duration-200 text-center font-semibold focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-110'
|
||||
},
|
||||
DateInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
SelectInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'relative w-full flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full px-2 bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||
|
|
@ -96,11 +114,20 @@ export const themes = {
|
|||
default: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full py-2 px-2 bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
||||
inputSpacing: {
|
||||
vertical: 'py-2',
|
||||
horizontal: 'px-4'
|
||||
},
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
Button: {
|
||||
body: 'rounded-md transition ease-in duration-200 text-center font-semibold shadow shadow-inner-notion focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-110'
|
||||
},
|
||||
DateInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded shadow-inner-notion border-transparent focus:border-transparent flex-1 appearance-none w-full bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion p-[1px]',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
SelectInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded relative w-full border-transparent flex-1 appearance-none bg-notion-input-background shadow-inner-notion w-full px-2 text-gray-900 placeholder-gray-400 dark:bg-notion-dark-light dark:placeholder-gray-500 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt dev --host=127.0.0.1",
|
||||
"dev": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt dev",
|
||||
"generate": "export NODE_TLS_REJECT_UNAUTHORIZED=0; nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
|
|
@ -24,8 +24,9 @@
|
|||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.4.7",
|
||||
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
|
||||
"@nuxt/ui": "^2.14.2",
|
||||
"@nuxt/ui": "^2.15.0",
|
||||
"@pinia/nuxt": "^0.5.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/vite-plugin": "^2.10.2",
|
||||
"@sentry/vue": "^7.93.0",
|
||||
"@vueuse/components": "^10.5.0",
|
||||
|
|
@ -37,7 +38,7 @@
|
|||
"clone-deep": "^4.0.1",
|
||||
"codemirror": "^6.0.1",
|
||||
"crisp-sdk-web": "^1.0.21",
|
||||
"date-fns": "^2.28.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"fuse.js": "^6.4.6",
|
||||
"js-sha256": "^0.10.0",
|
||||
"libphonenumber-js": "^1.10.44",
|
||||
|
|
@ -48,6 +49,7 @@
|
|||
"qrcode": "^1.5.1",
|
||||
"query-builder-vue-3": "^1.0.1",
|
||||
"tinymotion": "^0.2.0",
|
||||
"v-calendar": "^3.1.2",
|
||||
"vue": "^3.2.13",
|
||||
"vue-chartjs": "^5.2.0",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
|
|
|
|||
|
|
@ -113,6 +113,11 @@ const loadForm = async (setup=false) => {
|
|||
|
||||
await loadForm(true)
|
||||
|
||||
// Start loader if record needs to be loaded
|
||||
if (useRoute().query?.submission_id) {
|
||||
recordsStore.startLoading()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
crisp.hideChat()
|
||||
document.body.classList.add('public-page')
|
||||
|
|
|
|||
Loading…
Reference in New Issue