Skip to content

Commit daaa289

Browse files
authored
Merge from docusealco/wip
2 parents eea44bd + b93c7cd commit daaa289

32 files changed

Lines changed: 877 additions & 179 deletions

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ GEM
179179
dotenv (3.2.0)
180180
drb (2.2.3)
181181
email_typo (0.2.3)
182-
erb (6.0.2)
182+
erb (6.0.4)
183183
erb_lint (0.9.0)
184184
activesupport
185185
better_html (>= 2.0.1)

app/controllers/api/submitters_controller.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,7 @@ def maybe_assign_completed_attributes(submitter, attrs)
161161
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
162162
end
163163

164-
submitter.values = submitter.values.transform_values do |v|
165-
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
166-
end
164+
submitter.values = Submitters::SubmitValues.replace_current_date_placeholders(submitter)
167165
end
168166

169167
submitter

app/javascript/application.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
169169
withKba: ['true', 'false'].includes(this.dataset.withKba) ? this.dataset.withKba === 'true' : null,
170170
withLogo: this.dataset.withLogo !== 'false',
171171
withFieldsDetection: this.dataset.withFieldsDetection === 'true',
172+
withDetectExistingFields: this.dataset.withDetectExistingFields === 'true',
172173
editable: this.dataset.editable !== 'false',
173174
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
174175
withCustomFields: true,
@@ -180,6 +181,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
180181
withConditions: this.dataset.withConditions === 'true',
181182
withDynamicDocuments: this.dataset.withDynamicDocuments === 'true',
182183
withGoogleDrive: this.dataset.withGoogleDrive === 'true',
184+
pagePreviewFormat: this.dataset.pagePreviewFormat || '.jpg',
183185
withReplaceAndCloneUpload: true,
184186
withDownload: true,
185187
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),

app/javascript/submission_form/area.vue

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,8 @@ export default {
527527
try {
528528
return this.formatDate(
529529
this.modelValue === '{{date}}' ? new Date() : new Date(this.modelValue),
530-
this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY')
530+
this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'),
531+
{ withTimePlaceholders: this.modelValue === '{{date}}' }
531532
)
532533
} catch {
533534
return this.modelValue
@@ -646,36 +647,55 @@ export default {
646647
return number
647648
}
648649
},
649-
formatDate (date, format) {
650-
const monthFormats = {
651-
M: 'numeric',
652-
MM: '2-digit',
653-
MMM: 'short',
654-
MMMM: 'long'
655-
}
650+
formatDate (date, format, { withTimePlaceholders = false } = {}) {
651+
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
652+
const dayFormats = { D: 'numeric', DD: '2-digit' }
653+
const yearFormats = { YYYY: 'numeric', YYY: 'numeric', YY: '2-digit' }
654+
const hourFormats = { H: 'numeric', HH: '2-digit', h: 'numeric', hh: '2-digit' }
655+
const minuteFormats = { m: 'numeric', mm: '2-digit' }
656+
const secondFormats = { s: 'numeric', ss: '2-digit' }
657+
658+
const hasTime = /[HhAasz]/.test(format)
656659
657-
const dayFormats = {
658-
D: 'numeric',
659-
DD: '2-digit'
660+
const opts = {
661+
day: dayFormats[format.match(/D+/)],
662+
month: monthFormats[format.match(/M+/)],
663+
year: yearFormats[format.match(/Y+/)]
660664
}
661665
662-
const yearFormats = {
663-
YYYY: 'numeric',
664-
YYY: 'numeric',
665-
YY: '2-digit'
666+
if (format.match(/H+/)) { opts.hour = hourFormats[format.match(/H+/)[0]]; opts.hour12 = false }
667+
if (format.match(/h+/)) { opts.hour = hourFormats[format.match(/h+/)[0]]; opts.hour12 = true }
668+
if (/[Aa]/.test(format) && opts.hour12 === undefined) opts.hour12 = true
669+
if (format.match(/m+/)) opts.minute = minuteFormats[format.match(/m+/)[0]]
670+
if (format.match(/s+/)) opts.second = secondFormats[format.match(/s+/)[0]]
671+
if (/z/.test(format)) opts.timeZoneName = 'short'
672+
if (!hasTime) opts.timeZone = 'UTC'
673+
674+
const partTypes = {
675+
M: 'month',
676+
D: 'day',
677+
Y: 'year',
678+
H: 'hour',
679+
h: 'hour',
680+
m: 'minute',
681+
s: 'second',
682+
z: 'timeZoneName',
683+
A: 'dayPeriod',
684+
a: 'dayPeriod'
666685
}
667686
668-
const parts = new Intl.DateTimeFormat([], {
669-
day: dayFormats[format.match(/D+/)],
670-
month: monthFormats[format.match(/M+/)],
671-
year: yearFormats[format.match(/Y+/)],
672-
timeZone: 'UTC'
673-
}).formatToParts(date)
687+
const parts = new Intl.DateTimeFormat([], opts).formatToParts(date)
688+
689+
return format.replace(/MMMM|MMM|MM|M|DD|D|YYYY|YYY|YY|HH|hh|H|h|mm|m|ss|s|A|a|z/g, (token) => {
690+
if (withTimePlaceholders && /^(HH|hh|H|h|mm|m|ss|s|A|a)$/.test(token)) return '--'
691+
692+
const value = parts.find((p) => p.type === partTypes[token[0]])?.value
674693
675-
return format
676-
.replace(/D+/, parts.find((p) => p.type === 'day').value)
677-
.replace(/M+/, parts.find((p) => p.type === 'month').value)
678-
.replace(/Y+/, parts.find((p) => p.type === 'year').value)
694+
if (token === 'A') return (value || '').toUpperCase()
695+
if (token === 'a') return (value || '').toLowerCase()
696+
697+
return value
698+
})
679699
},
680700
updateMultipleSelectValue (value) {
681701
if (this.modelValue?.includes(value)) {

app/javascript/submission_form/date_step.vue

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,18 @@
5656
class="base-input !text-2xl text-center w-full"
5757
:required="field.required"
5858
:aria-describedby="field.description ? field.uuid + '-desc' : undefined"
59-
type="date"
60-
:name="`values[${field.uuid}]`"
59+
:type="inputType"
60+
:name="formatType === 'datetime' ? undefined : `values[${field.uuid}]`"
6161
@keydown.enter="onEnter"
6262
@focus="$emit('focus')"
6363
@paste="onPaste"
6464
>
65+
<input
66+
v-if="formatType === 'datetime'"
67+
type="hidden"
68+
:name="`values[${field.uuid}]`"
69+
:value="modelValue"
70+
>
6571
</div>
6672
</div>
6773
</template>
@@ -97,14 +103,19 @@ export default {
97103
},
98104
emits: ['update:model-value', 'focus', 'submit'],
99105
computed: {
100-
dateNowString () {
101-
const today = new Date()
106+
formatType () {
107+
const format = this.field.preferences?.format || ''
102108
103-
const yyyy = today.getFullYear()
104-
const mm = String(today.getMonth() + 1).padStart(2, '0')
105-
const dd = String(today.getDate()).padStart(2, '0')
109+
if (/[HhAasz]/.test(format)) return 'datetime'
110+
if (format && !/[Dd]/.test(format)) return 'month'
106111
107-
return `${yyyy}-${mm}-${dd}`
112+
return 'date'
113+
},
114+
inputType () {
115+
return { datetime: 'datetime-local', month: 'month', date: 'date' }[this.formatType]
116+
},
117+
dateNowString () {
118+
return this.formatDateValue(new Date())
108119
},
109120
validationMin () {
110121
if (this.field.validation?.min) {
@@ -121,6 +132,8 @@ export default {
121132
}
122133
},
123134
withToday () {
135+
if (this.formatType === 'datetime') return false
136+
124137
const todayDate = new Date().setHours(0, 0, 0, 0)
125138
126139
if (this.validationMin) {
@@ -137,9 +150,25 @@ export default {
137150
},
138151
value: {
139152
set (value) {
153+
if (this.formatType === 'datetime' && value) {
154+
const d = new Date(value)
155+
156+
if (!isNaN(d)) {
157+
this.$emit('update:model-value', d.toISOString())
158+
159+
return
160+
}
161+
}
162+
140163
this.$emit('update:model-value', value)
141164
},
142165
get () {
166+
if (this.formatType === 'datetime') {
167+
const d = new Date(this.modelValue)
168+
169+
return isNaN(d) ? '' : this.formatDateValue(d)
170+
}
171+
143172
return this.modelValue
144173
}
145174
}
@@ -163,20 +192,32 @@ export default {
163192
164193
const parsedDate = new Date(pasteData)
165194
166-
if (!isNaN(parsedDate)) {
167-
const inputEl = this.$refs.input
168-
169-
inputEl.valueAsDate = new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000)
195+
if (isNaN(parsedDate)) return
170196
171-
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
172-
}
197+
this.setInputValue(parsedDate)
173198
},
174199
setCurrentDate () {
200+
this.setInputValue(new Date())
201+
},
202+
setInputValue (date) {
175203
const inputEl = this.$refs.input
176204
177-
inputEl.valueAsDate = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60000)
205+
if (this.formatType === 'date') {
206+
inputEl.valueAsDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000)
207+
} else {
208+
inputEl.value = this.formatDateValue(date)
209+
}
178210
179211
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
212+
},
213+
formatDateValue (date) {
214+
const pad = (n) => String(n).padStart(2, '0')
215+
const ymd = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
216+
217+
if (this.formatType === 'month') return ymd.slice(0, 7)
218+
if (this.formatType === 'datetime') return `${ymd}T${pad(date.getHours())}:${pad(date.getMinutes())}`
219+
220+
return ymd
180221
}
181222
}
182223
}

app/javascript/template_builder/area.vue

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
<div
114114
ref="textContainer"
115115
class="flex items-center px-0.5"
116-
:style="{ color: field.preferences?.color }"
116+
:style="{ color: isConditionMatch ? field.preferences?.color : '#9ca3af' }"
117117
:class="{ 'w-full h-full': isWFullType }"
118118
>
119119
<IconCheck
@@ -131,13 +131,13 @@
131131
/>
132132
</template>
133133
<span
134-
v-else-if="field.type === 'number' && !isValueInput && (field.default_value || field.default_value == 0)"
134+
v-else-if="field.type === 'number' && !isContenteditable && (displayValue || displayValue == 0)"
135135
class="whitespace-pre-wrap"
136-
>{{ formatNumber(field.default_value, field.preferences?.format) }}</span>
136+
>{{ formatNumber(displayValue, field.preferences?.format) }}</span>
137137
<span
138138
v-else-if="field.default_value === '{{date}}'"
139139
>
140-
{{ t('signing_date') }}
140+
{{ /[HhAasz]/.test(field.preferences?.format || '') ? t('signing_date_and_time') : t('signing_date') }}
141141
</span>
142142
<div
143143
v-else-if="field.type === 'cells' && field.default_value"
@@ -183,12 +183,12 @@
183183
:contenteditable="isValueInput"
184184
class="whitespace-pre-wrap outline-none empty:before:content-[attr(placeholder)] before:text-base-content/30"
185185
:class="{ 'cursor-text': isValueInput }"
186-
:placeholder="withFieldPlaceholder && !isValueInput ? defaultField?.title || field.title || field.name || defaultName : (field.type === 'date' ? field.preferences?.format || t('type_value') : t('type_value'))"
186+
:placeholder="withFieldPlaceholder && !isValueInput ? defaultField?.title || field.title || field.name || defaultName : (isConditionMatch ? (field.type === 'date' ? field.preferences?.format || t('type_value') : t('type_value')) : '')"
187187
@blur="onDefaultValueBlur"
188188
@focus="selectedAreasRef.value = [area]"
189189
@paste.prevent="onPaste"
190190
@keydown.enter="onDefaultValueEnter"
191-
>{{ field.default_value }}</span>
191+
>{{ displayValue }}</span>
192192
</div>
193193
</div>
194194
<component
@@ -241,6 +241,16 @@ export default {
241241
required: false,
242242
default: false
243243
},
244+
conditionalFieldIndex: {
245+
type: Object,
246+
required: false,
247+
default: () => ({})
248+
},
249+
formulaValuesIndex: {
250+
type: Object,
251+
required: false,
252+
default: () => ({})
253+
},
244254
isDraw: {
245255
type: Boolean,
246256
required: false,
@@ -323,11 +333,27 @@ export default {
323333
fieldNames: FieldType.computed.fieldNames,
324334
fieldLabels: FieldType.computed.fieldLabels,
325335
fieldIcons: FieldType.computed.fieldIcons,
336+
isConditionMatch () {
337+
return !this.inputMode || this.conditionalFieldIndex[this.field.uuid] !== false
338+
},
339+
displayValue () {
340+
if (this.field.preferences?.formula && this.field.type !== 'payment') {
341+
const computed = this.formulaValuesIndex[this.field.uuid]
342+
343+
if (computed != null) {
344+
return computed
345+
}
346+
}
347+
348+
return this.field.default_value
349+
},
326350
bgClasses () {
327351
if (this.field.type === 'heading') {
328352
return 'bg-gray-50'
329353
} else if (this.field.type === 'strikethrough') {
330354
return 'bg-transparent'
355+
} else if (!this.isConditionMatch) {
356+
return 'bg-gray-100'
331357
} else {
332358
return this.bgColors[this.submitterIndex % this.bgColors.length]
333359
}
@@ -337,6 +363,8 @@ export default {
337363
return ''
338364
} else if (this.field.type === 'strikethrough') {
339365
return 'border-dashed border-gray-300'
366+
} else if (!this.isConditionMatch) {
367+
return 'border-gray-300'
340368
} else {
341369
return this.borderColors[this.submitterIndex % this.borderColors.length]
342370
}
@@ -390,7 +418,7 @@ export default {
390418
return this.basePageWidth / 612.0
391419
},
392420
isDefaultValuePresent () {
393-
return this.field?.default_value || this.field?.default_value === 0
421+
return this.field?.default_value || this.field?.default_value === 0 || this.displayValue || this.displayValue === 0
394422
},
395423
isSelectInput () {
396424
return this.inputMode && (this.field.type === 'select' || (this.field.type === 'radio' && this.field.areas?.length < 2))
@@ -399,6 +427,8 @@ export default {
399427
return this.inputMode && (this.field.type === 'checkbox' || (['radio', 'multiple'].includes(this.field.type) && this.area.option_uuid))
400428
},
401429
isValueInput () {
430+
if (this.inputMode && this.field.preferences?.formula) return false
431+
402432
return (this.field.type === 'heading' && this.isHeadingSelected) || this.isContenteditable ||
403433
(this.inputMode && (['text', 'number'].includes(this.field.type) || (this.field.type === 'date' && this.field.default_value !== '{{date}}')))
404434
},
@@ -511,7 +541,7 @@ export default {
511541
return option?.value || `${this.t('option')} ${this.field.options.indexOf(option) + 1}`
512542
},
513543
maybeToggleDefaultValue () {
514-
if (!this.editable || this.isCmdKeyRef.value) {
544+
if (!this.editable || this.isCmdKeyRef.value || this.field.preferences?.formula) {
515545
return
516546
}
517547
@@ -559,6 +589,10 @@ export default {
559589
}
560590
},
561591
focusValueInput (e) {
592+
if (this.inputMode && this.field.type === 'number' && !this.isContenteditable && !this.field.preferences?.formula) {
593+
this.isContenteditable = true
594+
}
595+
562596
this.$nextTick(() => {
563597
if (this.$refs.defaultValue && this.$refs.defaultValue !== document.activeElement) {
564598
this.$refs.defaultValue.focus()
@@ -624,6 +658,12 @@ export default {
624658
}
625659
},
626660
onDefaultValueBlur (e) {
661+
if (this.field.preferences?.formula) {
662+
this.isContenteditable = false
663+
664+
return
665+
}
666+
627667
const text = this.$refs.defaultValue.innerText.trim()
628668
629669
this.isContenteditable = false

0 commit comments

Comments
 (0)