Merge from docusealco/wip
This commit is contained in:
+1
-1
@@ -559,7 +559,7 @@ GEM
|
|||||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
unicode-emoji (4.0.4)
|
unicode-emoji (4.0.4)
|
||||||
uniform_notifier (1.16.0)
|
uniform_notifier (1.16.0)
|
||||||
uri (1.0.2)
|
uri (1.0.3)
|
||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class="field-area flex absolute lg:text-base -outline-offset-1"
|
class="field-area flex absolute lg:text-base -outline-offset-1"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:style="computedStyle"
|
:style="computedStyle"
|
||||||
:class="{ 'font-mono': field.preferences?.font === 'Courier', 'font-serif': field.preferences?.font === 'Times', 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
:class="{ 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'cells'"
|
v-else-if="field.type === 'cells'"
|
||||||
class="w-full flex items-center"
|
class="w-full flex items-center"
|
||||||
:class="{ 'justify-end': field.preferences?.align === 'right' }"
|
:class="{ 'justify-end': field.preferences?.align === 'right', ...fontClasses }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(char, index) in modelValue"
|
v-for="(char, index) in modelValue"
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
ref="textContainer"
|
ref="textContainer"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="flex items-center px-0.5 w-full"
|
class="flex items-center px-0.5 w-full"
|
||||||
:class="alignClasses[field.preferences?.align]"
|
:class="{ ...alignClasses, ...fontClasses }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="field && field.name && withFieldPlaceholder && !modelValue && modelValue !== 0"
|
v-if="field && field.name && withFieldPlaceholder && !modelValue && modelValue !== 0"
|
||||||
@@ -326,10 +326,26 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
alignClasses () {
|
alignClasses () {
|
||||||
|
if (!this.field.preferences) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
center: 'text-center',
|
'text-center': this.field.preferences.align === 'center',
|
||||||
left: 'text-left',
|
'text-left': this.field.preferences.align === 'left',
|
||||||
right: 'text-right'
|
'text-right': this.field.preferences.align === 'right'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fontClasses () {
|
||||||
|
if (!this.field.preferences) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'font-mono': this.field.preferences.font === 'Courier',
|
||||||
|
'font-serif': this.field.preferences.font === 'Times',
|
||||||
|
'font-bold': ['bold_italic', 'bold'].includes(this.field.preferences.font_type),
|
||||||
|
italic: ['bold_italic', 'italic'].includes(this.field.preferences.font_type)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
option () {
|
option () {
|
||||||
|
|||||||
@@ -141,6 +141,7 @@
|
|||||||
:with-required="false"
|
:with-required="false"
|
||||||
:with-areas="false"
|
:with-areas="false"
|
||||||
@click-formula="isShowFormulaModal = true"
|
@click-formula="isShowFormulaModal = true"
|
||||||
|
@click-font="isShowFontModal = true"
|
||||||
@click-description="isShowDescriptionModal = true"
|
@click-description="isShowDescriptionModal = true"
|
||||||
@click-condition="isShowConditionsModal = true"
|
@click-condition="isShowConditionsModal = true"
|
||||||
@scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]"
|
@scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]"
|
||||||
@@ -161,7 +162,7 @@
|
|||||||
ref="touchValueTarget"
|
ref="touchValueTarget"
|
||||||
class="flex items-center h-full w-full"
|
class="flex items-center h-full w-full"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
|
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? fontClasses : 'justify-center']"
|
||||||
@click="focusValueInput"
|
@click="focusValueInput"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -253,6 +254,17 @@
|
|||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowFontModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<FontModal
|
||||||
|
:field="field"
|
||||||
|
:editable="editable && !defaultField"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
@close="isShowFontModal = false"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
<Teleport
|
<Teleport
|
||||||
v-if="isShowConditionsModal"
|
v-if="isShowConditionsModal"
|
||||||
:to="modalContainerEl"
|
:to="modalContainerEl"
|
||||||
@@ -283,6 +295,7 @@ import FieldType from './field_type'
|
|||||||
import Field from './field'
|
import Field from './field'
|
||||||
import FieldSettings from './field_settings'
|
import FieldSettings from './field_settings'
|
||||||
import FormulaModal from './formula_modal'
|
import FormulaModal from './formula_modal'
|
||||||
|
import FontModal from './font_modal'
|
||||||
import ConditionsModal from './conditions_modal'
|
import ConditionsModal from './conditions_modal'
|
||||||
import DescriptionModal from './description_modal'
|
import DescriptionModal from './description_modal'
|
||||||
import { IconX, IconCheck, IconDotsVertical } from '@tabler/icons-vue'
|
import { IconX, IconCheck, IconDotsVertical } from '@tabler/icons-vue'
|
||||||
@@ -295,6 +308,7 @@ export default {
|
|||||||
IconCheck,
|
IconCheck,
|
||||||
FieldSettings,
|
FieldSettings,
|
||||||
FormulaModal,
|
FormulaModal,
|
||||||
|
FontModal,
|
||||||
IconDotsVertical,
|
IconDotsVertical,
|
||||||
DescriptionModal,
|
DescriptionModal,
|
||||||
ConditionsModal,
|
ConditionsModal,
|
||||||
@@ -352,6 +366,7 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isShowFormulaModal: false,
|
isShowFormulaModal: false,
|
||||||
|
isShowFontModal: false,
|
||||||
isShowConditionsModal: false,
|
isShowConditionsModal: false,
|
||||||
isContenteditable: false,
|
isContenteditable: false,
|
||||||
isSettingsFocus: false,
|
isSettingsFocus: false,
|
||||||
@@ -386,11 +401,19 @@ export default {
|
|||||||
defaultName () {
|
defaultName () {
|
||||||
return this.buildDefaultName(this.field, this.template.fields)
|
return this.buildDefaultName(this.field, this.template.fields)
|
||||||
},
|
},
|
||||||
alignClasses () {
|
fontClasses () {
|
||||||
|
if (!this.field.preferences) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
center: 'justify-center',
|
'justify-center': this.field.preferences.align === 'center',
|
||||||
left: 'justify-start',
|
'justify-start': this.field.preferences.align === 'left',
|
||||||
right: 'justify-end'
|
'justify-end': this.field.preferences.align === 'right',
|
||||||
|
'font-mono': this.field.preferences.font === 'Courier',
|
||||||
|
'font-serif': this.field.preferences.font === 'Times',
|
||||||
|
'font-bold': ['bold_italic', 'bold'].includes(this.field.preferences.font_type),
|
||||||
|
italic: ['bold_italic', 'italic'].includes(this.field.preferences.font_type)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
optionIndexText () {
|
optionIndexText () {
|
||||||
@@ -483,6 +506,10 @@ export default {
|
|||||||
this.$el.getRootNode().activeElement.blur()
|
this.$el.getRootNode().activeElement.blur()
|
||||||
},
|
},
|
||||||
maybeToggleDefaultValue () {
|
maybeToggleDefaultValue () {
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (['text', 'number'].includes(this.field.type)) {
|
if (['text', 'number'].includes(this.field.type)) {
|
||||||
this.isContenteditable = true
|
this.isContenteditable = true
|
||||||
|
|
||||||
|
|||||||
@@ -1316,6 +1316,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDropfield (area) {
|
onDropfield (area) {
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const field = this.fieldsDragFieldRef.value || {
|
const field = this.fieldsDragFieldRef.value || {
|
||||||
name: '',
|
name: '',
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
@@ -1338,9 +1342,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'date') {
|
if (field.type === 'date') {
|
||||||
field.preferences = {
|
field.preferences ||= {}
|
||||||
format: this.defaultDateFormat
|
field.preferences.format ||= this.defaultDateFormat
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,7 @@
|
|||||||
:editable="editable"
|
:editable="editable"
|
||||||
:background-color="dropdownBgColor"
|
:background-color="dropdownBgColor"
|
||||||
@click-formula="isShowFormulaModal = true"
|
@click-formula="isShowFormulaModal = true"
|
||||||
|
@click-font="isShowFontModal = true"
|
||||||
@click-description="isShowDescriptionModal = true"
|
@click-description="isShowDescriptionModal = true"
|
||||||
@click-condition="isShowConditionsModal = true"
|
@click-condition="isShowConditionsModal = true"
|
||||||
@set-draw="$emit('set-draw', $event)"
|
@set-draw="$emit('set-draw', $event)"
|
||||||
@@ -231,6 +232,17 @@
|
|||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowFontModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<FontModal
|
||||||
|
:field="field"
|
||||||
|
:editable="editable && !defaultField"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
@close="isShowFontModal = false"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
<Teleport
|
<Teleport
|
||||||
v-if="isShowConditionsModal"
|
v-if="isShowConditionsModal"
|
||||||
:to="modalContainerEl"
|
:to="modalContainerEl"
|
||||||
@@ -261,6 +273,7 @@ import FieldType from './field_type'
|
|||||||
import PaymentSettings from './payment_settings'
|
import PaymentSettings from './payment_settings'
|
||||||
import FieldSettings from './field_settings'
|
import FieldSettings from './field_settings'
|
||||||
import FormulaModal from './formula_modal'
|
import FormulaModal from './formula_modal'
|
||||||
|
import FontModal from './font_modal'
|
||||||
import ConditionsModal from './conditions_modal'
|
import ConditionsModal from './conditions_modal'
|
||||||
import DescriptionModal from './description_modal'
|
import DescriptionModal from './description_modal'
|
||||||
import { IconRouteAltLeft, IconMathFunction, IconNewSection, IconTrashX, IconSettings } from '@tabler/icons-vue'
|
import { IconRouteAltLeft, IconMathFunction, IconNewSection, IconTrashX, IconSettings } from '@tabler/icons-vue'
|
||||||
@@ -275,6 +288,7 @@ export default {
|
|||||||
PaymentSettings,
|
PaymentSettings,
|
||||||
IconNewSection,
|
IconNewSection,
|
||||||
FormulaModal,
|
FormulaModal,
|
||||||
|
FontModal,
|
||||||
DescriptionModal,
|
DescriptionModal,
|
||||||
ConditionsModal,
|
ConditionsModal,
|
||||||
IconRouteAltLeft,
|
IconRouteAltLeft,
|
||||||
@@ -305,6 +319,7 @@ export default {
|
|||||||
isNameFocus: false,
|
isNameFocus: false,
|
||||||
showPaymentModal: false,
|
showPaymentModal: false,
|
||||||
isShowFormulaModal: false,
|
isShowFormulaModal: false,
|
||||||
|
isShowFontModal: false,
|
||||||
isShowConditionsModal: false,
|
isShowConditionsModal: false,
|
||||||
isShowDescriptionModal: false,
|
isShowDescriptionModal: false,
|
||||||
renderDropdown: false
|
renderDropdown: false
|
||||||
|
|||||||
@@ -330,6 +330,19 @@
|
|||||||
v-if="field.type != 'stamp'"
|
v-if="field.type != 'stamp'"
|
||||||
class="pb-0.5 mt-0.5"
|
class="pb-0.5 mt-0.5"
|
||||||
>
|
>
|
||||||
|
<li v-if="field.type == 'text'">
|
||||||
|
<label
|
||||||
|
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||||
|
@click="$emit('click-font')"
|
||||||
|
>
|
||||||
|
<IconTypography
|
||||||
|
width="18"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">
|
||||||
|
{{ t('font') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="field.type != 'stamp'"
|
v-if="field.type != 'stamp'"
|
||||||
>
|
>
|
||||||
@@ -427,7 +440,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconRouteAltLeft, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy } from '@tabler/icons-vue'
|
import { IconRouteAltLeft, IconTypography, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldSettings',
|
name: 'FieldSettings',
|
||||||
@@ -438,6 +451,7 @@ export default {
|
|||||||
IconRouteAltLeft,
|
IconRouteAltLeft,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconNewSection,
|
IconNewSection,
|
||||||
|
IconTypography,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['template', 'save', 't'],
|
inject: ['template', 'save', 't'],
|
||||||
@@ -472,7 +486,7 @@ export default {
|
|||||||
default: true
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'remove-area'],
|
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'click-font', 'remove-area'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="modal modal-open items-start !animate-none overflow-y-auto"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute top-0 bottom-0 right-0 left-0"
|
||||||
|
@click.prevent="$emit('close')"
|
||||||
|
/>
|
||||||
|
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
||||||
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
|
<span>
|
||||||
|
{{ t('font') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-xl"
|
||||||
|
@click.prevent="$emit('close')"
|
||||||
|
>×</a>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center space-x-1.5">
|
||||||
|
<span>
|
||||||
|
<div class="dropdown">
|
||||||
|
<label
|
||||||
|
tabindex="0"
|
||||||
|
class="base-input flex items-center justify-between items-center"
|
||||||
|
style="height: 32px; padding-right: 0; width: 120px"
|
||||||
|
:class="fonts.find((f) => f.value === preferences.font)?.class"
|
||||||
|
>
|
||||||
|
<span style="margin-top: 1px">
|
||||||
|
{{ preferences.font || 'Default' }}
|
||||||
|
</span>
|
||||||
|
<IconChevronDown
|
||||||
|
class="ml-2 mr-2 mt-0.5"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
class="dropdown-content p-0 mt-1 block z-10 menu shadow bg-white border border-base-300 rounded-md w-52"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(font, index) in fonts"
|
||||||
|
:key="index"
|
||||||
|
:value="font.value"
|
||||||
|
:class="{ 'bg-base-300': preferences.font == font.value, [font.class]: true }"
|
||||||
|
class="hover:bg-base-300 px-2 py-1.5 cursor-pointer"
|
||||||
|
@click="[font.value ? preferences.font = font.value : delete preferences.font, closeDropdown()]"
|
||||||
|
>
|
||||||
|
{{ font.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span class="relative">
|
||||||
|
<select
|
||||||
|
class="select input-bordered bg-white select-sm text-center pl-2"
|
||||||
|
style="font-size: 16px; width: 86px; text-align-last: center;"
|
||||||
|
@change="$event.target.value ? preferences.font_size = parseInt($event.target.value) : delete preferences.font_size"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
:selected="!preferences.font_size"
|
||||||
|
value=""
|
||||||
|
>
|
||||||
|
Auto
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="size in sizes"
|
||||||
|
:key="size"
|
||||||
|
:value="size"
|
||||||
|
:selected="size === preferences.font_size"
|
||||||
|
>
|
||||||
|
{{ size }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<span
|
||||||
|
class="border-l pl-1.5 absolute bg-white absolute bottom-0 pointer-events-none text-sm h-5"
|
||||||
|
style="right: 13px; top: 7px"
|
||||||
|
>
|
||||||
|
pt
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<div
|
||||||
|
class="join"
|
||||||
|
style="height: 32px"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-for="(type, index) in types"
|
||||||
|
:key="index"
|
||||||
|
class="btn btn-sm join-item bg-white input-bordered hover:border-base-content/20 hover:bg-base-200/50 px-2"
|
||||||
|
:class="{ '!bg-base-300': preferences.font_type?.includes(type.value) }"
|
||||||
|
@click="setFontType(type.value)"
|
||||||
|
>
|
||||||
|
<component :is="type.icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<div
|
||||||
|
class="join"
|
||||||
|
style="height: 32px"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-for="(align, index) in aligns"
|
||||||
|
:key="index"
|
||||||
|
class="btn btn-sm join-item bg-white input-bordered hover:border-base-content/20 hover:bg-base-200/50 px-2"
|
||||||
|
:class="{ '!bg-base-300': preferences.align === align.value }"
|
||||||
|
@click="align.value && preferences.align != align.value ? preferences.align = align.value : delete preferences.align"
|
||||||
|
>
|
||||||
|
<component :is="align.icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<select
|
||||||
|
class="input input-bordered bg-white input-sm text-lg rounded-md"
|
||||||
|
style="-webkit-appearance: none; -moz-appearance: none; text-indent: 0px; text-overflow: ''; padding: 0px 6px; height: 32px"
|
||||||
|
@change="$event.target.value ? preferences.color = $event.target.value : delete preferences.color"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="(color, index) in colors"
|
||||||
|
:key="index"
|
||||||
|
:value="color.value"
|
||||||
|
:selected="color.value == preferences.color"
|
||||||
|
>
|
||||||
|
{{ color.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div
|
||||||
|
class="flex items-center border border-base-content/20 rounded-xl bg-white px-4 h-16"
|
||||||
|
:style="{
|
||||||
|
color: preferences.color || 'black',
|
||||||
|
fontSize: (preferences.font_size || 12) + 'pt',
|
||||||
|
}"
|
||||||
|
:class="textClasses"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
contenteditable="true"
|
||||||
|
class="outline-none"
|
||||||
|
>
|
||||||
|
{{ field.default_value || field.name || buildDefaultName(field, template.fields) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<button
|
||||||
|
class="base-button w-full"
|
||||||
|
@click.prevent="saveAndClose"
|
||||||
|
>
|
||||||
|
{{ t('save') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IconChevronDown, IconBold, IconItalic, IconAlignLeft, IconAlignRight, IconAlignCenter } from '@tabler/icons-vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FontModal',
|
||||||
|
components: {
|
||||||
|
IconChevronDown
|
||||||
|
},
|
||||||
|
inject: ['t', 'save', 'template'],
|
||||||
|
props: {
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
buildDefaultName: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['close'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
preferences: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
fonts () {
|
||||||
|
return [
|
||||||
|
{ value: null, label: 'Default' },
|
||||||
|
{ value: 'Times', label: 'Times', class: 'font-serif' },
|
||||||
|
{ value: 'Courier', label: 'Courier', class: 'font-mono' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
types () {
|
||||||
|
return [
|
||||||
|
{ icon: IconBold, value: 'bold' },
|
||||||
|
{ icon: IconItalic, value: 'italic' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
aligns () {
|
||||||
|
return [
|
||||||
|
{ icon: IconAlignLeft, value: 'left' },
|
||||||
|
{ icon: IconAlignCenter, value: 'center' },
|
||||||
|
{ icon: IconAlignRight, value: 'right' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sizes () {
|
||||||
|
return [...Array(23).keys()].map(i => i + 6)
|
||||||
|
},
|
||||||
|
colors () {
|
||||||
|
return [
|
||||||
|
{ label: '⬛', value: 'black' },
|
||||||
|
{ label: '🟦', value: 'blue' },
|
||||||
|
{ label: '🟥', value: 'red' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
textClasses () {
|
||||||
|
return {
|
||||||
|
'font-mono': this.preferences.font === 'Courier',
|
||||||
|
'font-serif': this.preferences.font === 'Times',
|
||||||
|
'justify-center': this.preferences.align === 'center',
|
||||||
|
'justify-start': this.preferences.align === 'left',
|
||||||
|
'justify-end': this.preferences.align === 'right',
|
||||||
|
'font-bold': ['bold_italic', 'bold'].includes(this.preferences.font_type),
|
||||||
|
italic: ['bold_italic', 'italic'].includes(this.preferences.font_type)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keys () {
|
||||||
|
return ['font_type', 'font_size', 'color', 'align', 'font']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.preferences = this.keys.reduce((acc, key) => {
|
||||||
|
acc[key] = this.field.preferences?.[key]
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeDropdown () {
|
||||||
|
this.$el.getRootNode().activeElement.blur()
|
||||||
|
},
|
||||||
|
setFontType (value) {
|
||||||
|
if (value === 'bold') {
|
||||||
|
if (this.preferences.font_type === 'bold') {
|
||||||
|
delete this.preferences.font_type
|
||||||
|
} else if (this.preferences.font_type === 'italic') {
|
||||||
|
this.preferences.font_type = 'bold_italic'
|
||||||
|
} else if (this.preferences.font_type === 'bold_italic') {
|
||||||
|
this.preferences.font_type = 'italic'
|
||||||
|
} else {
|
||||||
|
this.preferences.font_type = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === 'italic') {
|
||||||
|
if (this.preferences.font_type === 'italic') {
|
||||||
|
delete this.preferences.font_type
|
||||||
|
} else if (this.preferences.font_type === 'bold') {
|
||||||
|
this.preferences.font_type = 'bold_italic'
|
||||||
|
} else if (this.preferences.font_type === 'bold_italic') {
|
||||||
|
this.preferences.font_type = 'bold'
|
||||||
|
} else {
|
||||||
|
this.preferences.font_type = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveAndClose () {
|
||||||
|
this.field.preferences ||= {}
|
||||||
|
|
||||||
|
this.keys.forEach((key) => delete this.field.preferences[key])
|
||||||
|
|
||||||
|
Object.assign(this.field.preferences, this.preferences)
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
font: 'Font',
|
||||||
party: 'Party',
|
party: 'Party',
|
||||||
method: 'Method',
|
method: 'Method',
|
||||||
reorder_fields: 'Reorder fields',
|
reorder_fields: 'Reorder fields',
|
||||||
@@ -161,6 +162,7 @@ const en = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
|
fuente: 'Fuente',
|
||||||
party: 'Parte',
|
party: 'Parte',
|
||||||
method: 'Método',
|
method: 'Método',
|
||||||
reorder_fields: 'Reordenar campos',
|
reorder_fields: 'Reordenar campos',
|
||||||
@@ -323,6 +325,7 @@ const es = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
|
font: 'Carattere',
|
||||||
party: 'Parte',
|
party: 'Parte',
|
||||||
method: 'Metodo',
|
method: 'Metodo',
|
||||||
reorder_fields: 'Riordina i campi',
|
reorder_fields: 'Riordina i campi',
|
||||||
@@ -485,6 +488,7 @@ const it = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
|
fonte: 'Fonte',
|
||||||
party: 'Parte',
|
party: 'Parte',
|
||||||
method: 'Método',
|
method: 'Método',
|
||||||
reorder_fields: 'Reorganizar campos',
|
reorder_fields: 'Reorganizar campos',
|
||||||
@@ -647,6 +651,7 @@ const pt = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
|
font: 'Police',
|
||||||
party: 'Partie',
|
party: 'Partie',
|
||||||
method: 'Méthode',
|
method: 'Méthode',
|
||||||
reorder_fields: 'Réorganiser les champs',
|
reorder_fields: 'Réorganiser les champs',
|
||||||
@@ -809,6 +814,7 @@ const fr = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
|
font: 'Schriftart',
|
||||||
party: 'Partei',
|
party: 'Partei',
|
||||||
method: 'Verfahren',
|
method: 'Verfahren',
|
||||||
reorder_fields: 'Felder neu anordnen',
|
reorder_fields: 'Felder neu anordnen',
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<span>
|
<span>
|
||||||
<%= t('add_signature_id_to_the_documents') %>
|
<%= t('add_signature_id_to_the_documents') %>
|
||||||
</span>
|
</span>
|
||||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
|
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<span>
|
<span>
|
||||||
<%= t('require_signing_reason') %>
|
<%= t('require_signing_reason') %>
|
||||||
</span>
|
</span>
|
||||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
|
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<% align = field.dig('preferences', 'align') %>
|
<% align = field.dig('preferences', 'align') %>
|
||||||
<% color = field.dig('preferences', 'color') %>
|
<% color = field.dig('preferences', 'color') %>
|
||||||
<% font = field.dig('preferences', 'font') %>
|
<% font = field.dig('preferences', 'font') %>
|
||||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
<% font_type = field.dig('preferences', 'font_type') %>
|
||||||
|
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||||
<% if field['type'] == 'signature' %>
|
<% if field['type'] == 'signature' %>
|
||||||
<div class="flex flex-col justify-between h-full overflow-hidden">
|
<div class="flex flex-col justify-between h-full overflow-hidden">
|
||||||
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<%= svg_icon('external_link', class: 'w-6 h-6') %>
|
<%= svg_icon('external_link', class: 'w-6 h-6') %>
|
||||||
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% elsif signed_in? %>
|
||||||
<%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %>
|
<%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %>
|
||||||
<%= svg_icon('logs', class: 'w-6 h-6') %>
|
<%= svg_icon('logs', class: 'w-6 h-6') %>
|
||||||
<span class="hidden md:block"><%= t('event_log') %></span>
|
<span class="hidden md:block"><%= t('event_log') %></span>
|
||||||
@@ -105,7 +105,18 @@
|
|||||||
<% value = values[field['uuid']] %>
|
<% value = values[field['uuid']] %>
|
||||||
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
||||||
<% next if value.blank? %>
|
<% next if value.blank? %>
|
||||||
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
<% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %>
|
||||||
|
<span class="group">
|
||||||
|
<span class="hidden group-hover:inline">
|
||||||
|
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||||
|
</span>
|
||||||
|
<span class="group-hover:hidden">
|
||||||
|
<%= render 'submissions/value', area:, field:, attachments_index:, value: Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', '), locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<% else %>
|
||||||
|
<%= render 'submissions/value', area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -232,12 +243,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<% elsif field['type'] == 'checkbox' %>
|
<% elsif field['type'] == 'checkbox' %>
|
||||||
<%= svg_icon('check', class: 'w-6 h-6') %>
|
<%= svg_icon('check', class: 'w-6 h-6') %>
|
||||||
<% elsif field['type'] == 'number' %>
|
|
||||||
<%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
|
||||||
<% elsif field['type'] == 'date' %>
|
|
||||||
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="whitespace-pre-wrap"><%= Array.wrap(value).join(', ') %></div>
|
<% if field['type'] == 'number' %>
|
||||||
|
<% value = NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
||||||
|
<% elsif field['type'] == 'date' %>
|
||||||
|
<% value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
||||||
|
<% end %>
|
||||||
|
<% if (mask = field.dig('preferences', 'mask').presence) %>
|
||||||
|
<% if signed_in? && can?(:read, @submission) %>
|
||||||
|
<div class="[&:not(:hover)]:after:content-[attr(data-value)] block whitespace-pre-wrap group" data-value="<%= Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') %>"><span class="hidden group-hover:block"><%= Array.wrap(value).join(', ') %></span></div>
|
||||||
|
<% else %>
|
||||||
|
<div class="whitespace-pre-wrap"><%= Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') %></div>
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<div class="whitespace-pre-wrap"><%= Array.wrap(value).join(', ') %></div>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
|
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
|
||||||
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
|
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||||
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
|
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||||
<%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %>
|
<%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ Devise.setup do |config|
|
|||||||
|
|
||||||
# ==> Configuration for :rememberable
|
# ==> Configuration for :rememberable
|
||||||
# The time the user will be remembered without asking for credentials again.
|
# The time the user will be remembered without asking for credentials again.
|
||||||
config.remember_for = 2.years
|
config.remember_for = ENV.fetch('SESSION_REMEMBER_DAYS', '730').to_i.days
|
||||||
|
|
||||||
# Invalidates all the remember me tokens when the user signs out.
|
# Invalidates all the remember me tokens when the user signs out.
|
||||||
config.expire_all_remember_me_on_sign_out = true
|
config.expire_all_remember_me_on_sign_out = true
|
||||||
|
|||||||
+8
-2
@@ -33,8 +33,14 @@ environment ENV.fetch('RAILS_ENV', 'development')
|
|||||||
# the concurrency of the application would be max `threads` * `workers`.
|
# the concurrency of the application would be max `threads` * `workers`.
|
||||||
# Workers do not work on JRuby or Windows (both of which do not support
|
# Workers do not work on JRuby or Windows (both of which do not support
|
||||||
# processes).
|
# processes).
|
||||||
#
|
|
||||||
workers ENV.fetch('WEB_CONCURRENCY', 0)
|
if ENV['WEB_CONCURRENCY_AUTO'] == 'true'
|
||||||
|
require 'etc'
|
||||||
|
|
||||||
|
workers Etc.nprocessors
|
||||||
|
else
|
||||||
|
workers ENV.fetch('WEB_CONCURRENCY', 0)
|
||||||
|
end
|
||||||
|
|
||||||
# Use the `preload_app!` method when specifying a `workers` number.
|
# Use the `preload_app!` method when specifying a `workers` number.
|
||||||
# This directive tells Puma to first boot the application and load code
|
# This directive tells Puma to first boot the application and load code
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ Rails.application.routes.draw do
|
|||||||
get '/disk/:encoded_key/*filename' => 'active_storage/disk#show', as: :rails_disk_service
|
get '/disk/:encoded_key/*filename' => 'active_storage/disk#show', as: :rails_disk_service
|
||||||
put '/disk/:encoded_token' => 'active_storage/disk#update', as: :update_rails_disk_service
|
put '/disk/:encoded_token' => 'active_storage/disk#update', as: :update_rails_disk_service
|
||||||
post '/direct_uploads' => 'active_storage/direct_uploads#create', as: :rails_direct_uploads
|
post '/direct_uploads' => 'active_storage/direct_uploads#create', as: :rails_direct_uploads
|
||||||
|
|
||||||
|
ActiveSupport.run_load_hooks(:multitenant_routes, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :start_form, only: %i[show update], path: 'd', param: 'slug' do
|
resources :start_form, only: %i[show update], path: 'd', param: 'slug' do
|
||||||
|
|||||||
@@ -359,13 +359,14 @@ module Submissions
|
|||||||
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
|
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
|
||||||
end
|
end
|
||||||
|
|
||||||
if field['type'] == 'number'
|
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'
|
||||||
value = NumberUtils.format_number(value,
|
|
||||||
field.dig('preferences', 'format'))
|
|
||||||
end
|
|
||||||
|
|
||||||
value = value.join(', ') if value.is_a?(Array)
|
value = value.join(', ') if value.is_a?(Array)
|
||||||
|
|
||||||
|
if (mask = field.dig('preferences', 'mask').presence)
|
||||||
|
value = TextUtils.mask_value(value, mask)
|
||||||
|
end
|
||||||
|
|
||||||
composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }],
|
composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }],
|
||||||
text_align: value.to_s.match?(RTL_REGEXP) ? :right : :left,
|
text_align: value.to_s.match?(RTL_REGEXP) ? :right : :left,
|
||||||
padding: [0, 0, 10, 0])
|
padding: [0, 0, 10, 0])
|
||||||
|
|||||||
@@ -4,12 +4,29 @@ module Submissions
|
|||||||
module GenerateResultAttachments
|
module GenerateResultAttachments
|
||||||
FONT_SIZE = 11
|
FONT_SIZE = 11
|
||||||
FONT_PATH = '/fonts/GoNotoKurrent-Regular.ttf'
|
FONT_PATH = '/fonts/GoNotoKurrent-Regular.ttf'
|
||||||
|
FONT_BOLD_PATH = '/fonts/GoNotoKurrent-Bold.ttf'
|
||||||
FONT_NAME = if File.exist?(FONT_PATH)
|
FONT_NAME = if File.exist?(FONT_PATH)
|
||||||
FONT_PATH
|
FONT_PATH
|
||||||
else
|
else
|
||||||
'Helvetica'
|
'Helvetica'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
FONT_BOLD_NAME = if File.exist?(FONT_BOLD_PATH)
|
||||||
|
FONT_BOLD_PATH
|
||||||
|
else
|
||||||
|
'Helvetica'
|
||||||
|
end
|
||||||
|
|
||||||
|
FONT_ITALIC_NAME = 'Helvetica'
|
||||||
|
FONT_BOLD_ITALIC_NAME = 'Helvetica'
|
||||||
|
|
||||||
|
FONT_VARIANS = {
|
||||||
|
none: FONT_NAME,
|
||||||
|
bold: FONT_BOLD_NAME,
|
||||||
|
italic: FONT_ITALIC_NAME,
|
||||||
|
bold_italic: FONT_BOLD_ITALIC_NAME
|
||||||
|
}.freeze
|
||||||
|
|
||||||
SIGN_REASON = 'Signed by %<name>s with DocuSeal.com'
|
SIGN_REASON = 'Signed by %<name>s with DocuSeal.com'
|
||||||
|
|
||||||
RTL_REGEXP = TextUtils::RTL_REGEXP
|
RTL_REGEXP = TextUtils::RTL_REGEXP
|
||||||
@@ -18,12 +35,15 @@ module Submissions
|
|||||||
TEXT_TOP_MARGIN = 1
|
TEXT_TOP_MARGIN = 1
|
||||||
MAX_PAGE_ROTATE = 20
|
MAX_PAGE_ROTATE = 20
|
||||||
|
|
||||||
COURIER_FONT = 'Courier'
|
|
||||||
|
|
||||||
A4_SIZE = [595, 842].freeze
|
A4_SIZE = [595, 842].freeze
|
||||||
|
|
||||||
TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING'
|
TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING'
|
||||||
DEFAULT_FONTS = %w[Times Helvetica Courier].freeze
|
DEFAULT_FONTS = %w[Times Helvetica Courier].freeze
|
||||||
|
FONTS_LINE_HEIGHT = {
|
||||||
|
'Times' => 1.4,
|
||||||
|
'Helvetica' => 1.4,
|
||||||
|
'Courier' => 1.6
|
||||||
|
}.freeze
|
||||||
|
|
||||||
MISSING_GLYPH_REPLACE = {
|
MISSING_GLYPH_REPLACE = {
|
||||||
'▪' => '-',
|
'▪' => '-',
|
||||||
@@ -192,8 +212,16 @@ module Submissions
|
|||||||
fill_color = field.dig('preferences', 'color').presence
|
fill_color = field.dig('preferences', 'color').presence
|
||||||
|
|
||||||
font_name = field.dig('preferences', 'font')
|
font_name = field.dig('preferences', 'font')
|
||||||
|
font_variant = (field.dig('preferences', 'font_type').presence || 'none').to_sym
|
||||||
|
|
||||||
font_name = FONT_NAME unless font_name.in?(DEFAULT_FONTS)
|
font_name = FONT_NAME unless font_name.in?(DEFAULT_FONTS)
|
||||||
font = pdf.fonts.add(font_name)
|
|
||||||
|
if font_variant != :none && font_name == FONT_NAME
|
||||||
|
font_name = FONT_VARIANS[font_variant]
|
||||||
|
font_variant = nil unless font_name.in?(DEFAULT_FONTS)
|
||||||
|
end
|
||||||
|
|
||||||
|
font = pdf.fonts.add(font_name, variant: font_variant)
|
||||||
|
|
||||||
value = submitter.values[field['uuid']]
|
value = submitter.values[field['uuid']]
|
||||||
value = field['default_value'] if field['type'] == 'heading'
|
value = field['default_value'] if field['type'] == 'heading'
|
||||||
@@ -391,6 +419,10 @@ module Submissions
|
|||||||
when ->(type) { type == 'cells' && !area['cell_w'].to_f.zero? }
|
when ->(type) { type == 'cells' && !area['cell_w'].to_f.zero? }
|
||||||
cell_width = area['cell_w'] * width
|
cell_width = area['cell_w'] * width
|
||||||
|
|
||||||
|
if (mask = field.dig('preferences', 'mask').presence)
|
||||||
|
value = TextUtils.mask_value(value, mask)
|
||||||
|
end
|
||||||
|
|
||||||
chars = TextUtils.maybe_rtl_reverse(value).chars
|
chars = TextUtils.maybe_rtl_reverse(value).chars
|
||||||
chars = chars.reverse if field.dig('preferences', 'align') == 'right'
|
chars = chars.reverse if field.dig('preferences', 'align') == 'right'
|
||||||
|
|
||||||
@@ -440,8 +472,12 @@ module Submissions
|
|||||||
|
|
||||||
value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', '))
|
value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', '))
|
||||||
|
|
||||||
|
if (mask = field.dig('preferences', 'mask').presence)
|
||||||
|
value = TextUtils.mask_value(value, mask)
|
||||||
|
end
|
||||||
|
|
||||||
text_params = { font:, fill_color:, font_size: }
|
text_params = { font:, fill_color:, font_size: }
|
||||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1)
|
||||||
|
|
||||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||||
|
|
||||||
@@ -450,7 +486,7 @@ module Submissions
|
|||||||
|
|
||||||
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
||||||
text_params[:font_size] = (font_size / 1.4).to_i
|
text_params[:font_size] = (font_size / 1.4).to_i
|
||||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1)
|
||||||
|
|
||||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||||
|
|
||||||
@@ -461,7 +497,7 @@ module Submissions
|
|||||||
|
|
||||||
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
||||||
text_params[:font_size] = (font_size / 1.9).to_i
|
text_params[:font_size] = (font_size / 1.9).to_i
|
||||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1)
|
||||||
|
|
||||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
module TextUtils
|
module TextUtils
|
||||||
RTL_REGEXP = /[\p{Hebrew}\p{Arabic}]/
|
RTL_REGEXP = /[\p{Hebrew}\p{Arabic}]/
|
||||||
|
MASK_REGEXP = /[^\s\-_\[\]\(\)\+\?\.\,]/
|
||||||
|
MASK_SYMBOL = 'X'
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
@@ -13,6 +15,24 @@ module TextUtils
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mask_value(text, unmask_size = 0)
|
||||||
|
if unmask_size.is_a?(Numeric) && !unmask_size.zero? && unmask_size.abs < text.length
|
||||||
|
if unmask_size.negative?
|
||||||
|
[
|
||||||
|
text.first(text.length + unmask_size).gsub(MASK_REGEXP, MASK_SYMBOL),
|
||||||
|
text.last(-unmask_size)
|
||||||
|
].join
|
||||||
|
elsif unmask_size.positive?
|
||||||
|
[
|
||||||
|
text.first(unmask_size),
|
||||||
|
text.last(text.length - unmask_size).gsub(MASK_REGEXP, MASK_SYMBOL)
|
||||||
|
].join
|
||||||
|
end
|
||||||
|
else
|
||||||
|
text.to_s.gsub(MASK_REGEXP, MASK_SYMBOL)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def maybe_rtl_reverse(text)
|
def maybe_rtl_reverse(text)
|
||||||
if text.match?(RTL_REGEXP)
|
if text.match?(RTL_REGEXP)
|
||||||
TwitterCldr::Shared::Bidi
|
TwitterCldr::Shared::Bidi
|
||||||
|
|||||||
@@ -772,6 +772,30 @@ RSpec.describe 'Signing Form', type: :system do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the masked field' do
|
||||||
|
let(:template) { create(:template, submitter_count: 2, account:, author:, only_field_types: %w[text]) }
|
||||||
|
let(:submission) { create(:submission, template: template) }
|
||||||
|
let!(:first_submitter) { create(:submitter, submission:, uuid: template.submitters[0]['uuid'], account:) }
|
||||||
|
let!(:second_submitter) { create(:submitter, submission:, uuid: template.submitters[1]['uuid'], account:) }
|
||||||
|
|
||||||
|
it 'shows the masked value instead of the real value' do
|
||||||
|
field = submission.template_fields.find do |f|
|
||||||
|
f['name'] == 'First Name' && f['submitter_uuid'] == first_submitter.uuid
|
||||||
|
end
|
||||||
|
field['preferences']['mask'] = true
|
||||||
|
submission.save!
|
||||||
|
|
||||||
|
visit submit_form_path(slug: first_submitter.slug)
|
||||||
|
|
||||||
|
fill_in 'First Name', with: 'Jahn'
|
||||||
|
click_button 'Complete'
|
||||||
|
|
||||||
|
visit submit_form_path(slug: second_submitter.slug)
|
||||||
|
|
||||||
|
expect(page).to have_content('XXXX')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'sends completed email' do
|
it 'sends completed email' do
|
||||||
template = create(:template, account:, author:, only_field_types: %w[text signature])
|
template = create(:template, account:, author:, only_field_types: %w[text signature])
|
||||||
submission = create(:submission, template:)
|
submission = create(:submission, template:)
|
||||||
|
|||||||
Reference in New Issue
Block a user