Merge from docusealco/wip
This commit is contained in:
+2
-2
@@ -51,7 +51,7 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev yaml-dev redis libheif vips-heif gcompat ttf-freefont onnxruntime && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
RUN apk add --no-cache sqlite-dev libpq-dev vips-dev yaml-dev redis libheif vips-heif gcompat ttf-freefont onnxruntime && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||||
|
|
||||||
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ activate = 1' >> /etc/openssl_legacy.cnf
|
|||||||
|
|
||||||
COPY --chown=docuseal:docuseal ./Gemfile ./Gemfile.lock ./
|
COPY --chown=docuseal:docuseal ./Gemfile ./Gemfile.lock ./
|
||||||
|
|
||||||
RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf && ln -sf /usr/lib/libonnxruntime.so.1 $(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first")
|
RUN apk add --no-cache build-base git && bundle install && apk del --no-cache build-base git && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf && ln -sf /usr/lib/libonnxruntime.so.1 $(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first")
|
||||||
|
|
||||||
COPY --chown=docuseal:docuseal ./bin ./bin
|
COPY --chown=docuseal:docuseal ./bin ./bin
|
||||||
COPY --chown=docuseal:docuseal ./app ./app
|
COPY --chown=docuseal:docuseal ./app ./app
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ gem 'hexapdf'
|
|||||||
gem 'image_processing'
|
gem 'image_processing'
|
||||||
gem 'jwt', require: false
|
gem 'jwt', require: false
|
||||||
gem 'lograge'
|
gem 'lograge'
|
||||||
gem 'mysql2', require: false
|
|
||||||
gem 'numo-narray-alt', require: false
|
gem 'numo-narray-alt', require: false
|
||||||
gem 'oj'
|
gem 'oj'
|
||||||
gem 'onnxruntime', require: false
|
gem 'onnxruntime', require: false
|
||||||
@@ -45,6 +44,7 @@ gem 'shakapacker'
|
|||||||
gem 'sidekiq'
|
gem 'sidekiq'
|
||||||
gem 'sqlite3', require: false
|
gem 'sqlite3', require: false
|
||||||
gem 'strip_attributes'
|
gem 'strip_attributes'
|
||||||
|
gem 'trilogy', github: 'trilogy-libraries/trilogy', glob: 'contrib/ruby/*.gemspec', require: false
|
||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
gem 'twitter_cldr', require: false
|
gem 'twitter_cldr', require: false
|
||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
|
|||||||
+9
-3
@@ -1,3 +1,11 @@
|
|||||||
|
GIT
|
||||||
|
remote: https://github.com/trilogy-libraries/trilogy.git
|
||||||
|
revision: 3963d490459df7a2b5bedb42424c3285f25eab22
|
||||||
|
glob: contrib/ruby/*.gemspec
|
||||||
|
specs:
|
||||||
|
trilogy (2.10.0)
|
||||||
|
bigdecimal
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
@@ -318,8 +326,6 @@ GEM
|
|||||||
prism (~> 1.5)
|
prism (~> 1.5)
|
||||||
msgpack (1.8.0)
|
msgpack (1.8.0)
|
||||||
multi_json (1.19.1)
|
multi_json (1.19.1)
|
||||||
mysql2 (0.5.7)
|
|
||||||
bigdecimal
|
|
||||||
net-http (0.9.1)
|
net-http (0.9.1)
|
||||||
uri (>= 0.11.1)
|
uri (>= 0.11.1)
|
||||||
net-imap (0.6.2)
|
net-imap (0.6.2)
|
||||||
@@ -644,7 +650,6 @@ DEPENDENCIES
|
|||||||
jwt
|
jwt
|
||||||
letter_opener_web
|
letter_opener_web
|
||||||
lograge
|
lograge
|
||||||
mysql2
|
|
||||||
numo-narray-alt
|
numo-narray-alt
|
||||||
oj
|
oj
|
||||||
onnxruntime
|
onnxruntime
|
||||||
@@ -673,6 +678,7 @@ DEPENDENCIES
|
|||||||
simplecov
|
simplecov
|
||||||
sqlite3
|
sqlite3
|
||||||
strip_attributes
|
strip_attributes
|
||||||
|
trilogy!
|
||||||
turbo-rails
|
turbo-rails
|
||||||
twitter_cldr
|
twitter_cldr
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ module Api
|
|||||||
message: %i[subject body],
|
message: %i[subject body],
|
||||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||||
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
||||||
:require_phone_2fa, :require_email_2fa, :order, :invite_by,
|
:require_phone_2fa, :require_email_2fa, :order, :index, :invite_by,
|
||||||
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
|
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
|
||||||
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
||||||
:readonly, :required, :validation_pattern, :invalid_message,
|
:readonly, :required, :validation_pattern, :invalid_message,
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redrawCanvas (oldWidth, oldHeight) {
|
redrawCanvas (oldWidth, oldHeight) {
|
||||||
if (this.pad && !this.pad.isEmpty()) {
|
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) {
|
||||||
const sx = this.canvas.width / oldWidth
|
const sx = this.canvas.width / oldWidth
|
||||||
const sy = this.canvas.height / oldHeight
|
const sy = this.canvas.height / oldHeight
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,22 @@ export default class extends HTMLElement {
|
|||||||
if (action === 'empty' || action === 'unchecked') return this.isEmpty(actual)
|
if (action === 'empty' || action === 'unchecked') return this.isEmpty(actual)
|
||||||
if (action === 'not_empty' || action === 'checked') return !this.isEmpty(actual)
|
if (action === 'not_empty' || action === 'checked') return !this.isEmpty(actual)
|
||||||
|
|
||||||
|
if (['equal', 'not_equal', 'greater_than', 'less_than'].includes(action) && this.sourceEl?.getAttribute('type') === 'number') {
|
||||||
|
if (this.isEmpty(actual) || this.isEmpty(expected)) return false
|
||||||
|
|
||||||
|
const actualNumber = parseFloat(actual)
|
||||||
|
const expectedNumber = parseFloat(expected)
|
||||||
|
|
||||||
|
if (Number.isNaN(actualNumber) || Number.isNaN(expectedNumber)) return false
|
||||||
|
|
||||||
|
if (action === 'equal') return Math.abs(actualNumber - expectedNumber) < Number.EPSILON
|
||||||
|
if (action === 'not_equal') return Math.abs(actualNumber - expectedNumber) > Number.EPSILON
|
||||||
|
if (action === 'greater_than') return actualNumber > expectedNumber
|
||||||
|
if (action === 'less_than') return actualNumber < expectedNumber
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (action === 'equal') {
|
if (action === 'equal') {
|
||||||
const list = Array.isArray(actual) ? actual : [actual]
|
const list = Array.isArray(actual) ? actual : [actual]
|
||||||
return list.filter((v) => v !== null && v !== undefined).map(String).includes(String(expected))
|
return list.filter((v) => v !== null && v !== undefined).map(String).includes(String(expected))
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default targetable(class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redrawCanvas (oldWidth, oldHeight) {
|
redrawCanvas (oldWidth, oldHeight) {
|
||||||
if (this.pad && !this.pad.isEmpty()) {
|
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) {
|
||||||
const sx = this.canvas.width / oldWidth
|
const sx = this.canvas.width / oldWidth
|
||||||
const sy = this.canvas.height / oldHeight
|
const sy = this.canvas.height / oldHeight
|
||||||
|
|
||||||
|
|||||||
@@ -182,8 +182,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'cells'"
|
v-else-if="field.type === 'cells'"
|
||||||
class="w-full flex items-center"
|
class="w-full flex"
|
||||||
:class="{ 'justify-end': field.preferences?.align === 'right', ...fontClasses }"
|
:class="{ 'justify-end': field.preferences?.align === 'right', ...alignClasses, ...fontClasses }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(char, index) in modelValue"
|
v-for="(char, index) in modelValue"
|
||||||
|
|||||||
@@ -395,7 +395,9 @@
|
|||||||
v-model="values[currentField.uuid]"
|
v-model="values[currentField.uuid]"
|
||||||
:reason="values[currentField.preferences?.reason_field_uuid]"
|
:reason="values[currentField.preferences?.reason_field_uuid]"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
|
:values="values"
|
||||||
:previous-value="previousSignatureValueFor(currentField) || previousSignatureValue"
|
:previous-value="previousSignatureValueFor(currentField) || previousSignatureValue"
|
||||||
|
:touch-attachment-uuid="previousSignatureValue"
|
||||||
:with-typed-signature="withTypedSignature"
|
:with-typed-signature="withTypedSignature"
|
||||||
:remember-signature="rememberSignature"
|
:remember-signature="rememberSignature"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
@@ -407,6 +409,7 @@
|
|||||||
:submitter="submitter"
|
:submitter="submitter"
|
||||||
:show-field-names="showFieldNames"
|
:show-field-names="showFieldNames"
|
||||||
@update:reason="values[currentField.preferences?.reason_field_uuid] = $event"
|
@update:reason="values[currentField.preferences?.reason_field_uuid] = $event"
|
||||||
|
@touch-attachment="attachmentsIndex[previousSignatureValue] ? attachmentsIndex[previousSignatureValue].created_at = new Date() : null"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@start="scrollIntoField(currentField)"
|
@start="scrollIntoField(currentField)"
|
||||||
@minimize="minimizeForm"
|
@minimize="minimizeForm"
|
||||||
@@ -926,10 +929,12 @@ export default {
|
|||||||
}, {})
|
}, {})
|
||||||
},
|
},
|
||||||
attachmentConditionsIndex () {
|
attachmentConditionsIndex () {
|
||||||
|
const cache = {}
|
||||||
|
|
||||||
return this.schema.reduce((acc, item) => {
|
return this.schema.reduce((acc, item) => {
|
||||||
if (item.conditions?.length) {
|
if (item.conditions?.length) {
|
||||||
if (item.conditions.every((c) => this.fieldsUuidIndex[c.field_uuid])) {
|
if (item.conditions.every((c) => this.fieldsUuidIndex[c.field_uuid])) {
|
||||||
acc[item.attachment_uuid] = this.checkFieldConditions(item)
|
acc[item.attachment_uuid] = this.checkFieldConditions(item, cache)
|
||||||
} else {
|
} else {
|
||||||
acc[item.attachment_uuid] = true
|
acc[item.attachment_uuid] = true
|
||||||
}
|
}
|
||||||
@@ -996,7 +1001,9 @@ export default {
|
|||||||
},
|
},
|
||||||
previousInitialsValue () {
|
previousInitialsValue () {
|
||||||
if (this.reuseSignature !== false) {
|
if (this.reuseSignature !== false) {
|
||||||
const initialsField = [...this.fields].reverse().find((field) => field.type === 'initials' && !!this.values[field.uuid])
|
const initialsField = this.fields.findLast
|
||||||
|
? this.fields.findLast((field) => field.type === 'initials' && !!this.values[field.uuid])
|
||||||
|
: [...this.fields].reverse().find((field) => field.type === 'initials' && !!this.values[field.uuid])
|
||||||
|
|
||||||
return this.values[initialsField?.uuid]
|
return this.values[initialsField?.uuid]
|
||||||
} else {
|
} else {
|
||||||
@@ -1023,7 +1030,9 @@ export default {
|
|||||||
return this.readonlyFields.filter((f) => f.conditions?.length)
|
return this.readonlyFields.filter((f) => f.conditions?.length)
|
||||||
},
|
},
|
||||||
readonlyFields () {
|
readonlyFields () {
|
||||||
return this.fields.filter((f) => f.readonly && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
|
const cache = {}
|
||||||
|
|
||||||
|
return this.fields.filter((f) => f.readonly && this.checkFieldConditions(f, cache) && this.checkFieldDocumentsConditions(f))
|
||||||
},
|
},
|
||||||
stepFields () {
|
stepFields () {
|
||||||
const verificationFields = []
|
const verificationFields = []
|
||||||
@@ -1078,10 +1087,12 @@ export default {
|
|||||||
sortedFields.push(verificationFields.pop())
|
sortedFields.push(verificationFields.pop())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cache = {}
|
||||||
|
|
||||||
return sortedFields.reduce((acc, f) => {
|
return sortedFields.reduce((acc, f) => {
|
||||||
const prevStep = acc[acc.length - 1]
|
const prevStep = acc[acc.length - 1]
|
||||||
|
|
||||||
if (this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) {
|
if (this.checkFieldConditions(f, cache) && this.checkFieldDocumentsConditions(f)) {
|
||||||
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox' && !f.description) {
|
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox' && !f.description) {
|
||||||
prevStep.push(f)
|
prevStep.push(f)
|
||||||
} else {
|
} else {
|
||||||
@@ -1093,7 +1104,9 @@ export default {
|
|||||||
}, [])
|
}, [])
|
||||||
},
|
},
|
||||||
formulaFields () {
|
formulaFields () {
|
||||||
return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment' && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
|
const cache = {}
|
||||||
|
|
||||||
|
return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment' && this.checkFieldConditions(f, cache) && this.checkFieldDocumentsConditions(f))
|
||||||
},
|
},
|
||||||
attachmentsIndex () {
|
attachmentsIndex () {
|
||||||
return this.attachments.reduce((acc, a) => {
|
return this.attachments.reduce((acc, a) => {
|
||||||
@@ -1155,7 +1168,9 @@ export default {
|
|||||||
this.currentStep = Math.max(stepIndex, 0)
|
this.currentStep = Math.max(stepIndex, 0)
|
||||||
} else if (this.goToLast) {
|
} else if (this.goToLast) {
|
||||||
const requiredEmptyStepIndex = this.stepFields.indexOf(this.stepFields.find((fields) => fields.some((f) => f.required && !this.submittedValues[f.uuid])))
|
const requiredEmptyStepIndex = this.stepFields.indexOf(this.stepFields.find((fields) => fields.some((f) => f.required && !this.submittedValues[f.uuid])))
|
||||||
const lastFilledStepIndex = this.stepFields.indexOf([...this.stepFields].reverse().find((fields) => fields.some((f) => !!this.submittedValues[f.uuid]))) + 1
|
const lastFilledStepIndex = this.stepFields.indexOf(this.stepFields.findLast
|
||||||
|
? this.stepFields.findLast((fields) => fields.some((f) => !!this.submittedValues[f.uuid]))
|
||||||
|
: [...this.stepFields].reverse().find((fields) => fields.some((f) => !!this.submittedValues[f.uuid]))) + 1
|
||||||
|
|
||||||
const indexesList = [this.stepFields.length - 1]
|
const indexesList = [this.stepFields.length - 1]
|
||||||
|
|
||||||
@@ -1223,27 +1238,33 @@ export default {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkFieldConditions (field) {
|
checkFieldConditions (field, cache = {}) {
|
||||||
|
if (cache[field.uuid] !== undefined) {
|
||||||
|
return cache[field.uuid]
|
||||||
|
}
|
||||||
|
|
||||||
|
cache[field.uuid] = true
|
||||||
|
|
||||||
if (field.conditions?.length) {
|
if (field.conditions?.length) {
|
||||||
const result = field.conditions.reduce((acc, cond) => {
|
const result = field.conditions.reduce((acc, cond) => {
|
||||||
if (cond.operation === 'or') {
|
if (cond.operation === 'or') {
|
||||||
acc.push(acc.pop() || this.checkFieldCondition(cond))
|
acc.push(acc.pop() || this.checkFieldCondition(cond, cache))
|
||||||
} else {
|
} else {
|
||||||
acc.push(this.checkFieldCondition(cond))
|
acc.push(this.checkFieldCondition(cond, cache))
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return !result.includes(false)
|
cache[field.uuid] = !result.includes(false)
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cache[field.uuid]
|
||||||
},
|
},
|
||||||
checkFieldCondition (condition) {
|
checkFieldCondition (condition, cache = {}) {
|
||||||
const field = this.fieldsUuidIndex[condition.field_uuid]
|
const field = this.fieldsUuidIndex[condition.field_uuid]
|
||||||
|
|
||||||
if (['not_empty', 'checked', 'equal', 'contains'].includes(condition.action) && field && !this.checkFieldConditions(field)) {
|
if (['not_empty', 'checked', 'equal', 'contains', 'greater_than', 'less_than'].includes(condition.action) && field && !this.checkFieldConditions(field, cache)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1253,6 +1274,22 @@ export default {
|
|||||||
return isEmpty(this.values[condition.field_uuid] ?? defaultValue)
|
return isEmpty(this.values[condition.field_uuid] ?? defaultValue)
|
||||||
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
||||||
return !isEmpty(this.values[condition.field_uuid] ?? defaultValue)
|
return !isEmpty(this.values[condition.field_uuid] ?? defaultValue)
|
||||||
|
} else if (field?.type === 'number' && ['equal', 'not_equal', 'greater_than', 'less_than'].includes(condition.action)) {
|
||||||
|
const value = this.values[condition.field_uuid] ?? defaultValue
|
||||||
|
|
||||||
|
if (isEmpty(value) || isEmpty(condition.value)) return false
|
||||||
|
|
||||||
|
const actual = parseFloat(value)
|
||||||
|
const expected = parseFloat(condition.value)
|
||||||
|
|
||||||
|
if (Number.isNaN(actual) || Number.isNaN(expected)) return false
|
||||||
|
|
||||||
|
if (condition.action === 'equal') return Math.abs(actual - expected) < Number.EPSILON
|
||||||
|
if (condition.action === 'not_equal') return Math.abs(actual - expected) > Number.EPSILON
|
||||||
|
if (condition.action === 'greater_than') return actual > expected
|
||||||
|
if (condition.action === 'less_than') return actual < expected
|
||||||
|
|
||||||
|
return false
|
||||||
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
||||||
if (field.options) {
|
if (field.options) {
|
||||||
const option = field.options.find((o) => o.uuid === condition.value)
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
@@ -1353,9 +1390,9 @@ export default {
|
|||||||
},
|
},
|
||||||
previousSignatureValueFor (field) {
|
previousSignatureValueFor (field) {
|
||||||
if (this.reuseSignature !== false) {
|
if (this.reuseSignature !== false) {
|
||||||
const signatureField = [...this.fields].reverse().find((f) =>
|
const signatureField = this.fields.findLast
|
||||||
f.type === 'signature' && field.preferences?.format === f.preferences?.format && !!this.values[f.uuid]
|
? this.fields.findLast((f) => f.type === 'signature' && field.preferences?.format === f.preferences?.format && !!this.values[f.uuid])
|
||||||
)
|
: [...this.fields].reverse().find((f) => f.type === 'signature' && field.preferences?.format === f.preferences?.format && !!this.values[f.uuid])
|
||||||
|
|
||||||
return this.values[signatureField?.uuid]
|
return this.values[signatureField?.uuid]
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -497,10 +497,10 @@ export default {
|
|||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!resp.ok) throw new Error('Failed to start KBA')
|
|
||||||
|
|
||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
|
|
||||||
|
if (!resp.ok) throw new Error(data.error || 'Failed to start KBA')
|
||||||
|
|
||||||
if (data.result && data.result.action === 'FAIL') {
|
if (data.result && data.result.action === 'FAIL') {
|
||||||
if (data.result.detail === 'NO MATCH') {
|
if (data.result.detail === 'NO MATCH') {
|
||||||
throw new Error('Unfortunately, we were unable to start Knowledge Based Authentication with the details provided. Please review and confirm that all your personal details are correct.')
|
throw new Error('Unfortunately, we were unable to start Knowledge Based Authentication with the details provided. Please review and confirm that all your personal details are correct.')
|
||||||
@@ -555,7 +555,11 @@ export default {
|
|||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
|
|
||||||
if (data.result?.action !== 'PASS') {
|
if (data.result?.action !== 'PASS') {
|
||||||
this.error = 'Knowledge Based Authentication Failed - make sure you provide correct answers for the Knowledge Based authentication.'
|
if (data.result?.issues?.length) {
|
||||||
|
this.error = `Knowledge Based Authentication Failed - make sure you provide correct details for the Knowledge Based authentication: ${data.result.issues.join(', ')}`
|
||||||
|
} else {
|
||||||
|
this.error = 'Knowledge Based Authentication Failed - make sure you provide correct answers for the Knowledge Based authentication.'
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error('Knowledge Based Authentication Failed')
|
throw new Error('Knowledge Based Authentication Failed')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,12 @@
|
|||||||
type="hidden"
|
type="hidden"
|
||||||
:name="`values[${field.uuid}]`"
|
:name="`values[${field.uuid}]`"
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
v-if="isTouchAttachment"
|
||||||
|
:value="touchAttachmentUuid"
|
||||||
|
type="hidden"
|
||||||
|
name="touch_attachment_uuid"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
v-if="modelValue || computedPreviousValue"
|
v-if="modelValue || computedPreviousValue"
|
||||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||||
@@ -334,6 +340,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
values: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
requireSigningReason: {
|
requireSigningReason: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -388,6 +398,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
touchAttachmentUuid: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
reason: {
|
reason: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -399,13 +414,14 @@ export default {
|
|||||||
default: ''
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['attached', 'update:model-value', 'start', 'minimize', 'update:reason'],
|
emits: ['attached', 'update:model-value', 'start', 'minimize', 'update:reason', 'touch-attachment'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isSignatureStarted: false,
|
isSignatureStarted: false,
|
||||||
isShowQr: false,
|
isShowQr: false,
|
||||||
isOtherReason: false,
|
isOtherReason: false,
|
||||||
isUsePreviousValue: true,
|
isUsePreviousValue: true,
|
||||||
|
isTouchAttachment: false,
|
||||||
isTextSignature: this.field.preferences?.format === 'typed' || this.field.preferences?.format === 'typed_or_upload',
|
isTextSignature: this.field.preferences?.format === 'typed' || this.field.preferences?.format === 'typed_or_upload',
|
||||||
uploadImageInputKey: Math.random().toString()
|
uploadImageInputKey: Math.random().toString()
|
||||||
}
|
}
|
||||||
@@ -514,7 +530,7 @@ export default {
|
|||||||
redrawCanvas (oldWidth, oldHeight) {
|
redrawCanvas (oldWidth, oldHeight) {
|
||||||
const canvas = this.$refs.canvas
|
const canvas = this.$refs.canvas
|
||||||
|
|
||||||
if (this.pad && !this.isTextSignature && !this.pad.isEmpty()) {
|
if (this.pad && !this.isTextSignature && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) {
|
||||||
const sx = canvas.width / oldWidth
|
const sx = canvas.width / oldWidth
|
||||||
const sy = canvas.height / oldHeight
|
const sy = canvas.height / oldHeight
|
||||||
|
|
||||||
@@ -731,6 +747,13 @@ export default {
|
|||||||
},
|
},
|
||||||
async submit () {
|
async submit () {
|
||||||
if (this.modelValue || this.computedPreviousValue) {
|
if (this.modelValue || this.computedPreviousValue) {
|
||||||
|
if (this.touchAttachmentUuid && this.computedPreviousValue === this.touchAttachmentUuid && !Object.values(this.values).includes(this.touchAttachmentUuid)) {
|
||||||
|
this.isTouchAttachment = true
|
||||||
|
this.$emit('touch-attachment', this.touchAttachmentUuid)
|
||||||
|
} else {
|
||||||
|
this.isTouchAttachment = false
|
||||||
|
}
|
||||||
|
|
||||||
if (this.computedPreviousValue) {
|
if (this.computedPreviousValue) {
|
||||||
this.$emit('update:model-value', this.computedPreviousValue)
|
this.$emit('update:model-value', this.computedPreviousValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export default {
|
|||||||
docId: this.eidEasyData.doc_id,
|
docId: this.eidEasyData.doc_id,
|
||||||
language: this.locale,
|
language: this.locale,
|
||||||
countryCode: this.countryCode,
|
countryCode: this.countryCode,
|
||||||
|
sandbox: ['demo.docuseal.tech'].includes(location.host),
|
||||||
enabledMethods: {
|
enabledMethods: {
|
||||||
signature: this.eidEasyData.available_methods
|
signature: this.eidEasyData.available_methods
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -134,6 +134,7 @@
|
|||||||
@focusout="maybeBlurSettings"
|
@focusout="maybeBlurSettings"
|
||||||
>
|
>
|
||||||
<FieldSettings
|
<FieldSettings
|
||||||
|
v-if="isMobile"
|
||||||
:field="field"
|
:field="field"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
@@ -150,6 +151,12 @@
|
|||||||
@save="save"
|
@save="save"
|
||||||
@scroll-to="[selectedAreasRef.value = [$event], $emit('scroll-to', $event)]"
|
@scroll-to="[selectedAreasRef.value = [$event], $emit('scroll-to', $event)]"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="whitespace-normal"
|
||||||
|
>
|
||||||
|
The dots menu is retired in favor of the field context menu. Right-click the field to access field settings. Double-click the field to set a default value.
|
||||||
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -252,7 +259,8 @@
|
|||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'cells' && field.default_value"
|
v-else-if="field.type === 'cells' && field.default_value"
|
||||||
class="w-full flex items-center"
|
class="w-full flex"
|
||||||
|
:class="fontClasses"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(char, index) in field.default_value"
|
v-for="(char, index) in field.default_value"
|
||||||
@@ -334,6 +342,7 @@
|
|||||||
:editable="editable && !defaultField"
|
:editable="editable && !defaultField"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="save"
|
||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -346,6 +355,7 @@
|
|||||||
:editable="editable && !defaultField"
|
:editable="editable && !defaultField"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="save"
|
||||||
@close="isShowFontModal = false"
|
@close="isShowFontModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -357,6 +367,7 @@
|
|||||||
:item="field"
|
:item="field"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
|
@save="save"
|
||||||
@close="isShowConditionsModal = false"
|
@close="isShowConditionsModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -369,6 +380,7 @@
|
|||||||
:editable="editable && !defaultField"
|
:editable="editable && !defaultField"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="save"
|
||||||
@close="isShowDescriptionModal = false"
|
@close="isShowDescriptionModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -401,7 +413,7 @@ export default {
|
|||||||
FieldSubmitter,
|
FieldSubmitter,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['template', 'save', 't', 'isInlineSize', 'selectedAreasRef', 'isCmdKeyRef'],
|
inject: ['template', 'save', 't', 'isInlineSize', 'selectedAreasRef', 'isCmdKeyRef', 'getFieldTypeIndex'],
|
||||||
props: {
|
props: {
|
||||||
area: {
|
area: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -467,6 +479,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
isMobile: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
isSelectMode: {
|
isSelectMode: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -579,7 +596,7 @@ export default {
|
|||||||
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||||
},
|
},
|
||||||
defaultName () {
|
defaultName () {
|
||||||
return this.buildDefaultName(this.field, this.template.fields)
|
return this.buildDefaultName(this.field)
|
||||||
},
|
},
|
||||||
fontClasses () {
|
fontClasses () {
|
||||||
if (!this.field.preferences) {
|
if (!this.field.preferences) {
|
||||||
|
|||||||
@@ -374,6 +374,7 @@
|
|||||||
:draw-field-type="drawFieldType"
|
:draw-field-type="drawFieldType"
|
||||||
:draw-custom-field="drawCustomField"
|
:draw-custom-field="drawCustomField"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
|
:is-mobile="isMobile"
|
||||||
:base-url="baseUrl"
|
:base-url="baseUrl"
|
||||||
:with-fields-detection="withFieldsDetection"
|
:with-fields-detection="withFieldsDetection"
|
||||||
@draw="[onDraw($event), withSelectedFieldType ? '' : drawFieldType = '', drawCustomField = null, showDrawField = false]"
|
@draw="[onDraw($event), withSelectedFieldType ? '' : drawFieldType = '', drawCustomField = null, showDrawField = false]"
|
||||||
@@ -382,9 +383,9 @@
|
|||||||
@paste-field="pasteField"
|
@paste-field="pasteField"
|
||||||
@copy-field="copyField"
|
@copy-field="copyField"
|
||||||
@add-custom-field="addCustomField"
|
@add-custom-field="addCustomField"
|
||||||
|
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
||||||
@copy-selected-areas="copySelectedAreas"
|
@copy-selected-areas="copySelectedAreas"
|
||||||
@delete-selected-areas="deleteSelectedAreas"
|
@delete-selected-areas="deleteSelectedAreas"
|
||||||
@align-selected-areas="alignSelectedAreas"
|
|
||||||
@autodetect-fields="detectFieldsForPage"
|
@autodetect-fields="detectFieldsForPage"
|
||||||
/>
|
/>
|
||||||
<DocumentControls
|
<DocumentControls
|
||||||
@@ -645,7 +646,8 @@ export default {
|
|||||||
fieldsDragFieldRef: computed(() => this.fieldsDragFieldRef),
|
fieldsDragFieldRef: computed(() => this.fieldsDragFieldRef),
|
||||||
customDragFieldRef: computed(() => this.customDragFieldRef),
|
customDragFieldRef: computed(() => this.customDragFieldRef),
|
||||||
isSelectModeRef: computed(() => this.isSelectModeRef),
|
isSelectModeRef: computed(() => this.isSelectModeRef),
|
||||||
isCmdKeyRef: computed(() => this.isCmdKeyRef)
|
isCmdKeyRef: computed(() => this.isCmdKeyRef),
|
||||||
|
getFieldTypeIndex: this.getFieldTypeIndex
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -989,6 +991,18 @@ export default {
|
|||||||
|
|
||||||
return areas
|
return areas
|
||||||
},
|
},
|
||||||
|
fieldTypeIndexMap () {
|
||||||
|
const map = {}
|
||||||
|
const typeCounters = {}
|
||||||
|
|
||||||
|
this.template.fields.forEach((f) => {
|
||||||
|
typeCounters[f.type] ||= 0
|
||||||
|
map[f.uuid] = typeCounters[f.type]
|
||||||
|
typeCounters[f.type]++
|
||||||
|
})
|
||||||
|
|
||||||
|
return map
|
||||||
|
},
|
||||||
isAllRequiredFieldsAdded () {
|
isAllRequiredFieldsAdded () {
|
||||||
return !this.defaultRequiredFields?.some((f) => {
|
return !this.defaultRequiredFields?.some((f) => {
|
||||||
return !this.template.fields?.some((field) => field.name === f.name)
|
return !this.template.fields?.some((field) => field.name === f.name)
|
||||||
@@ -1085,6 +1099,9 @@ export default {
|
|||||||
addCustomField (field) {
|
addCustomField (field) {
|
||||||
return this.$refs.fields.addCustomField(field)
|
return this.$refs.fields.addCustomField(field)
|
||||||
},
|
},
|
||||||
|
getFieldTypeIndex (field) {
|
||||||
|
return this.fieldTypeIndexMap[field.uuid]
|
||||||
|
},
|
||||||
addCustomFieldWithoutDraw () {
|
addCustomFieldWithoutDraw () {
|
||||||
const customField = this.drawCustomField
|
const customField = this.drawCustomField
|
||||||
|
|
||||||
@@ -1160,27 +1177,6 @@ export default {
|
|||||||
|
|
||||||
this.debouncedSave()
|
this.debouncedSave()
|
||||||
},
|
},
|
||||||
alignSelectedAreas (direction) {
|
|
||||||
const areas = this.selectedAreasRef.value
|
|
||||||
|
|
||||||
let targetValue
|
|
||||||
|
|
||||||
if (direction === 'left') {
|
|
||||||
targetValue = Math.min(...areas.map(a => a.x))
|
|
||||||
areas.forEach((area) => { area.x = targetValue })
|
|
||||||
} else if (direction === 'right') {
|
|
||||||
targetValue = Math.max(...areas.map(a => a.x + a.w))
|
|
||||||
areas.forEach((area) => { area.x = targetValue - area.w })
|
|
||||||
} else if (direction === 'top') {
|
|
||||||
targetValue = Math.min(...areas.map(a => a.y))
|
|
||||||
areas.forEach((area) => { area.y = targetValue })
|
|
||||||
} else if (direction === 'bottom') {
|
|
||||||
targetValue = Math.max(...areas.map(a => a.y + a.h))
|
|
||||||
areas.forEach((area) => { area.y = targetValue - area.h })
|
|
||||||
}
|
|
||||||
|
|
||||||
this.save()
|
|
||||||
},
|
|
||||||
download () {
|
download () {
|
||||||
this.isDownloading = true
|
this.isDownloading = true
|
||||||
|
|
||||||
@@ -2065,7 +2061,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'checkbox' && !this.drawFieldType && (this.template.fields[this.template.fields.length - 1]?.type === 'checkbox' || area.w)) {
|
if (type === 'checkbox' && !this.drawFieldType && (this.template.fields[this.template.fields.length - 1]?.type === 'checkbox' || area.w)) {
|
||||||
const previousField = [...this.template.fields].reverse().find((f) => f.type === type)
|
const previousField = this.template.fields.findLast
|
||||||
|
? this.template.fields.findLast((f) => f.type === type)
|
||||||
|
: [...this.template.fields].reverse().find((f) => f.type === type)
|
||||||
const previousArea = previousField?.areas?.[previousField.areas.length - 1]
|
const previousArea = previousField?.areas?.[previousField.areas.length - 1]
|
||||||
|
|
||||||
if (previousArea || area.w) {
|
if (previousArea || area.w) {
|
||||||
@@ -2329,7 +2327,9 @@ export default {
|
|||||||
assignDropAreaSize (fieldArea, field, area) {
|
assignDropAreaSize (fieldArea, field, area) {
|
||||||
const fieldType = field.type || 'text'
|
const fieldType = field.type || 'text'
|
||||||
|
|
||||||
const previousField = [...this.template.fields].reverse().find((f) => f.type === fieldType)
|
const previousField = this.template.fields.findLast
|
||||||
|
? this.template.fields.findLast((f) => f.type === fieldType)
|
||||||
|
: [...this.template.fields].reverse().find((f) => f.type === fieldType)
|
||||||
|
|
||||||
let baseArea
|
let baseArea
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
<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">
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
<span class="modal-title">
|
<span class="modal-title">
|
||||||
{{ t('condition') }} - {{ (defaultField ? (defaultField.title || item.title || item.name) : item.name) || buildDefaultName(item, template.fields) }}
|
{{ t('condition') }} - {{ (defaultField ? (defaultField.title || item.title || item.name) : item.name) || buildDefaultName(item) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
class="text-base-content"
|
class="text-base-content"
|
||||||
:selected="condition.field_uuid === f.uuid"
|
:selected="condition.field_uuid === f.uuid"
|
||||||
>
|
>
|
||||||
{{ f.name || buildDefaultName(f, template.fields) }}
|
{{ f.name || buildDefaultName(f) }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<select
|
<select
|
||||||
@@ -124,6 +124,16 @@
|
|||||||
{{ option.value || `${t('option')} ${index + 1}` }}
|
{{ option.value || `${t('option')} ${index + 1}` }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
<input
|
||||||
|
v-else-if="conditionField(condition)?.type === 'number' && ['equal', 'not_equal', 'greater_than', 'less_than'].includes(condition.action)"
|
||||||
|
v-model="condition.value"
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
class="input input-bordered input-sm w-full bg-white h-11 pl-4 text-base font-normal"
|
||||||
|
:class="{ 'text-gray-300': !condition.value }"
|
||||||
|
:placeholder="t('type_value')"
|
||||||
|
required
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
@@ -154,7 +164,7 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'ConditionModal',
|
name: 'ConditionModal',
|
||||||
inject: ['t', 'save', 'template', 'withConditions'],
|
inject: ['t', 'template', 'withConditions'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -169,18 +179,13 @@ export default {
|
|||||||
type: Function,
|
type: Function,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
withClickSaveEvent: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
excludeFieldUuids: {
|
excludeFieldUuids: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['close', 'click-save'],
|
emits: ['close', 'save'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
conditions: this.item.conditions?.[0] ? JSON.parse(JSON.stringify(this.item.conditions)) : [{}]
|
conditions: this.item.conditions?.[0] ? JSON.parse(JSON.stringify(this.item.conditions)) : [{}]
|
||||||
@@ -227,6 +232,8 @@ export default {
|
|||||||
actions.push('equal', 'not_equal')
|
actions.push('equal', 'not_equal')
|
||||||
} else if (['multiple'].includes(field.type)) {
|
} else if (['multiple'].includes(field.type)) {
|
||||||
actions.push('contains', 'does_not_contain')
|
actions.push('contains', 'does_not_contain')
|
||||||
|
} else if (field.type === 'number') {
|
||||||
|
actions.push('not_empty', 'empty', 'equal', 'not_equal', 'greater_than', 'less_than')
|
||||||
} else {
|
} else {
|
||||||
actions.push('not_empty', 'empty')
|
actions.push('not_empty', 'empty')
|
||||||
}
|
}
|
||||||
@@ -244,12 +251,7 @@ export default {
|
|||||||
delete this.item.conditions
|
delete this.item.conditions
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.withClickSaveEvent) {
|
this.$emit('save')
|
||||||
this.$emit('click-save')
|
|
||||||
} else {
|
|
||||||
this.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,8 +179,11 @@ export default {
|
|||||||
},
|
},
|
||||||
onBlur (e) {
|
onBlur (e) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.value = this.$refs.contenteditable.innerText.trim() || this.modelValue
|
if (this.$refs.contenteditable) {
|
||||||
this.$emit('update:model-value', this.value)
|
this.value = this.$refs.contenteditable.innerText.trim() || this.modelValue
|
||||||
|
this.$emit('update:model-value', this.value)
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('blur', e)
|
this.$emit('blur', e)
|
||||||
|
|
||||||
this.isEditable = false
|
this.isEditable = false
|
||||||
|
|||||||
@@ -1,546 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
v-if="!isShowFormulaModal && !isShowFontModal && !isShowConditionsModal && !isShowDescriptionModal"
|
|
||||||
ref="menu"
|
|
||||||
class="fixed z-50 p-1 bg-white shadow-lg rounded-lg border border-base-300 min-w-[170px] cursor-default"
|
|
||||||
:style="menuStyle"
|
|
||||||
@mousedown.stop
|
|
||||||
@pointerdown.stop
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
v-if="showRequired"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm cursor-pointer"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
:checked="isRequired"
|
|
||||||
type="checkbox"
|
|
||||||
class="toggle toggle-xs"
|
|
||||||
@change="handleToggleRequired($event.target.checked)"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<span>{{ t('required') }}</span>
|
|
||||||
</label>
|
|
||||||
<label
|
|
||||||
v-if="showReadOnly"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm cursor-pointer"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
:checked="isReadOnly"
|
|
||||||
type="checkbox"
|
|
||||||
class="toggle toggle-xs"
|
|
||||||
@change="handleToggleReadOnly($event.target.checked)"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<span>{{ t('read_only') }}</span>
|
|
||||||
</label>
|
|
||||||
<hr
|
|
||||||
v-if="(showRequired || showReadOnly) && (showFont || showDescription || showCondition || showFormula)"
|
|
||||||
class="my-1 border-base-300"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="showFont && !isMultiSelection"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="openFontModal"
|
|
||||||
>
|
|
||||||
<IconTypography class="w-4 h-4" />
|
|
||||||
<span>{{ t('font') }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="showDescription"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="openDescriptionModal"
|
|
||||||
>
|
|
||||||
<IconInfoCircle class="w-4 h-4" />
|
|
||||||
<span>{{ t('description') }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="showCondition && !isMultiSelection"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="openConditionModal"
|
|
||||||
>
|
|
||||||
<IconRouteAltLeft class="w-4 h-4" />
|
|
||||||
<span>{{ t('condition') }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="showFormula"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="openFormulaModal"
|
|
||||||
>
|
|
||||||
<IconMathFunction class="w-4 h-4" />
|
|
||||||
<span>{{ t('formula') }}</span>
|
|
||||||
</button>
|
|
||||||
<hr
|
|
||||||
v-if="((showFont && !isMultiSelection) || showDescription || (showCondition && !isMultiSelection) || showFormula) && (showCopy || showDelete || showPaste)"
|
|
||||||
class="my-1 border-base-300"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="isMultiSelection"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="$emit('align', 'left')"
|
|
||||||
>
|
|
||||||
<IconLayoutAlignLeft class="w-4 h-4" />
|
|
||||||
<span>{{ t('align_left') }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="isMultiSelection"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="$emit('align', 'right')"
|
|
||||||
>
|
|
||||||
<IconLayoutAlignRight class="w-4 h-4" />
|
|
||||||
<span>{{ t('align_right') }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="isMultiSelection"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="$emit('align', 'top')"
|
|
||||||
>
|
|
||||||
<IconLayoutAlignTop class="w-4 h-4" />
|
|
||||||
<span>{{ t('align_top') }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="isMultiSelection"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="$emit('align', 'bottom')"
|
|
||||||
>
|
|
||||||
<IconLayoutAlignBottom class="w-4 h-4" />
|
|
||||||
<span>{{ t('align_bottom') }}</span>
|
|
||||||
</button>
|
|
||||||
<hr
|
|
||||||
v-if="isMultiSelection && (showFont || showCondition)"
|
|
||||||
class="my-1 border-base-300"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="showFont && isMultiSelection"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="openFontModal"
|
|
||||||
>
|
|
||||||
<IconTypography class="w-4 h-4" />
|
|
||||||
<span>{{ t('font') }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="showCondition && isMultiSelection"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="openConditionModal"
|
|
||||||
>
|
|
||||||
<IconRouteAltLeft class="w-4 h-4" />
|
|
||||||
<span>{{ t('condition') }}</span>
|
|
||||||
</button>
|
|
||||||
<hr
|
|
||||||
v-if="isMultiSelection"
|
|
||||||
class="my-1 border-base-300"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="showCopy"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center justify-between text-sm"
|
|
||||||
@click.stop="$emit('copy')"
|
|
||||||
>
|
|
||||||
<span class="flex items-center space-x-2">
|
|
||||||
<IconCopy class="w-4 h-4" />
|
|
||||||
<span>{{ t('copy') }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="text-xs text-base-content/60 ml-4">{{ isMac ? '⌘C' : 'Ctrl+C' }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="showDelete"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center justify-between text-sm text-red-600"
|
|
||||||
@click.stop="$emit('delete')"
|
|
||||||
>
|
|
||||||
<span class="flex items-center space-x-2">
|
|
||||||
<IconTrashX class="w-4 h-4" />
|
|
||||||
<span>{{ t('remove') }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="text-xs text-base-content/60 ml-4">Del</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="showPaste"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center justify-between text-sm"
|
|
||||||
:class="!hasClipboardData ? 'opacity-50 cursor-not-allowed' : 'hover:bg-base-100'"
|
|
||||||
:disabled="!hasClipboardData"
|
|
||||||
@click.stop="!hasClipboardData ? null : $emit('paste')"
|
|
||||||
>
|
|
||||||
<span class="flex items-center space-x-2">
|
|
||||||
<IconClipboard class="w-4 h-4" />
|
|
||||||
<span>{{ t('paste') }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="text-xs text-base-content/60 ml-4">{{ isMac ? '⌘V' : 'Ctrl+V' }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="showSelectFields"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center justify-between text-sm"
|
|
||||||
@click.stop="handleToggleSelectMode"
|
|
||||||
>
|
|
||||||
<span class="flex items-center space-x-2">
|
|
||||||
<IconClick
|
|
||||||
v-if="!isSelectModeRef.value"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<IconNewSection
|
|
||||||
v-else
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span>{{ isSelectModeRef.value ? t('draw_fields') : t('select_fields') }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="text-xs text-base-content/60 ml-4">Tab</span>
|
|
||||||
</button>
|
|
||||||
<hr
|
|
||||||
v-if="showAutodetectFields"
|
|
||||||
class="my-1 border-base-300"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="showAutodetectFields"
|
|
||||||
class="w-full px-2 py-1 rounded-md hover:bg-base-100 flex items-center space-x-2 text-sm"
|
|
||||||
@click.stop="$emit('autodetect-fields')"
|
|
||||||
>
|
|
||||||
<IconSparkles class="w-4 h-4" />
|
|
||||||
<span>{{ t('autodetect_fields') }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Teleport
|
|
||||||
v-if="isShowFormulaModal"
|
|
||||||
:to="modalContainerEl"
|
|
||||||
>
|
|
||||||
<FormulaModal
|
|
||||||
:field="field"
|
|
||||||
:editable="editable"
|
|
||||||
:build-default-name="buildDefaultName"
|
|
||||||
@close="closeModal"
|
|
||||||
/>
|
|
||||||
</Teleport>
|
|
||||||
<Teleport
|
|
||||||
v-if="isShowFontModal"
|
|
||||||
:to="modalContainerEl"
|
|
||||||
>
|
|
||||||
<FontModal
|
|
||||||
:field="multiSelectField || field"
|
|
||||||
:area="contextMenu.area"
|
|
||||||
:editable="editable"
|
|
||||||
:build-default-name="buildDefaultName"
|
|
||||||
:with-click-save-event="isMultiSelection"
|
|
||||||
@click-save="handleSaveMultiSelectFontModal"
|
|
||||||
@close="closeModal"
|
|
||||||
/>
|
|
||||||
</Teleport>
|
|
||||||
<Teleport
|
|
||||||
v-if="isShowConditionsModal"
|
|
||||||
:to="modalContainerEl"
|
|
||||||
>
|
|
||||||
<ConditionsModal
|
|
||||||
:item="multiSelectField || field"
|
|
||||||
:build-default-name="buildDefaultName"
|
|
||||||
:exclude-field-uuids="isMultiSelection ? selectedFields.map(f => f.uuid) : []"
|
|
||||||
:with-click-save-event="isMultiSelection"
|
|
||||||
@click-save="handleSaveMultiSelectConditionsModal"
|
|
||||||
@close="closeModal"
|
|
||||||
/>
|
|
||||||
</Teleport>
|
|
||||||
<Teleport
|
|
||||||
v-if="isShowDescriptionModal"
|
|
||||||
:to="modalContainerEl"
|
|
||||||
>
|
|
||||||
<DescriptionModal
|
|
||||||
:field="field"
|
|
||||||
:editable="editable"
|
|
||||||
:build-default-name="buildDefaultName"
|
|
||||||
@close="closeModal"
|
|
||||||
/>
|
|
||||||
</Teleport>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { IconCopy, IconClipboard, IconTrashX, IconTypography, IconInfoCircle, IconRouteAltLeft, IconMathFunction, IconClick, IconNewSection, IconLayoutAlignLeft, IconLayoutAlignRight, IconLayoutAlignTop, IconLayoutAlignBottom, IconSparkles } from '@tabler/icons-vue'
|
|
||||||
import FormulaModal from './formula_modal'
|
|
||||||
import FontModal from './font_modal'
|
|
||||||
import ConditionsModal from './conditions_modal'
|
|
||||||
import DescriptionModal from './description_modal'
|
|
||||||
import Field from './field'
|
|
||||||
import FieldType from './field_type.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ContextMenu',
|
|
||||||
components: {
|
|
||||||
IconCopy,
|
|
||||||
IconClipboard,
|
|
||||||
IconTrashX,
|
|
||||||
IconTypography,
|
|
||||||
IconInfoCircle,
|
|
||||||
IconRouteAltLeft,
|
|
||||||
IconMathFunction,
|
|
||||||
IconClick,
|
|
||||||
IconNewSection,
|
|
||||||
IconLayoutAlignLeft,
|
|
||||||
IconLayoutAlignRight,
|
|
||||||
IconLayoutAlignTop,
|
|
||||||
IconLayoutAlignBottom,
|
|
||||||
IconSparkles,
|
|
||||||
FormulaModal,
|
|
||||||
FontModal,
|
|
||||||
ConditionsModal,
|
|
||||||
DescriptionModal
|
|
||||||
},
|
|
||||||
inject: ['t', 'save', 'selectedAreasRef', 'isSelectModeRef'],
|
|
||||||
props: {
|
|
||||||
contextMenu: {
|
|
||||||
type: Object,
|
|
||||||
default: null,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
field: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
editable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
isMultiSelection: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
selectedAreas: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
template: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
withFieldsDetection: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emits: ['copy', 'paste', 'delete', 'close', 'align', 'autodetect-fields'],
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isShowFormulaModal: false,
|
|
||||||
isShowFontModal: false,
|
|
||||||
isShowConditionsModal: false,
|
|
||||||
isShowDescriptionModal: false,
|
|
||||||
multiSelectField: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
fieldNames: FieldType.computed.fieldNames,
|
|
||||||
fieldLabels: FieldType.computed.fieldLabels,
|
|
||||||
modalContainerEl () {
|
|
||||||
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
|
||||||
},
|
|
||||||
selectedFields () {
|
|
||||||
if (!this.isMultiSelection) return []
|
|
||||||
|
|
||||||
return this.selectedAreasRef.value.map((area) => {
|
|
||||||
return this.template.fields.find((f) => f.areas?.includes(area))
|
|
||||||
}).filter(Boolean)
|
|
||||||
},
|
|
||||||
isMac () {
|
|
||||||
return (navigator.userAgentData?.platform || navigator.platform)?.toLowerCase()?.includes('mac')
|
|
||||||
},
|
|
||||||
hasClipboardData () {
|
|
||||||
try {
|
|
||||||
const clipboard = localStorage.getItem('docuseal_clipboard')
|
|
||||||
|
|
||||||
if (clipboard) {
|
|
||||||
const data = JSON.parse(clipboard)
|
|
||||||
|
|
||||||
return Date.now() - data.timestamp < 3600000
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
menuStyle () {
|
|
||||||
return {
|
|
||||||
left: this.contextMenu.x + 'px',
|
|
||||||
top: this.contextMenu.y + 'px'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showCopy () {
|
|
||||||
return !!this.contextMenu.area || this.isMultiSelection
|
|
||||||
},
|
|
||||||
showPaste () {
|
|
||||||
return !this.contextMenu.area && !this.isMultiSelection
|
|
||||||
},
|
|
||||||
showDelete () {
|
|
||||||
return !!this.contextMenu.area || this.isMultiSelection
|
|
||||||
},
|
|
||||||
showFont () {
|
|
||||||
if (this.isMultiSelection) return true
|
|
||||||
if (!this.field) return false
|
|
||||||
|
|
||||||
return ['text', 'number', 'date', 'select', 'heading'].includes(this.field.type)
|
|
||||||
},
|
|
||||||
showDescription () {
|
|
||||||
if (!this.field) return false
|
|
||||||
|
|
||||||
return !['stamp', 'heading', 'strikethrough'].includes(this.field.type)
|
|
||||||
},
|
|
||||||
showCondition () {
|
|
||||||
if (this.isMultiSelection) return true
|
|
||||||
if (!this.field) return false
|
|
||||||
|
|
||||||
return !['stamp', 'heading'].includes(this.field.type)
|
|
||||||
},
|
|
||||||
showFormula () {
|
|
||||||
if (!this.field) return false
|
|
||||||
|
|
||||||
return this.field.type === 'number'
|
|
||||||
},
|
|
||||||
showRequired () {
|
|
||||||
if (!this.field) return false
|
|
||||||
|
|
||||||
return !['phone', 'stamp', 'verification', 'strikethrough', 'heading'].includes(this.field.type)
|
|
||||||
},
|
|
||||||
showReadOnly () {
|
|
||||||
if (!this.field) return false
|
|
||||||
|
|
||||||
return ['text', 'number'].includes(this.field.type)
|
|
||||||
},
|
|
||||||
isRequired () {
|
|
||||||
return this.field?.required || false
|
|
||||||
},
|
|
||||||
isReadOnly () {
|
|
||||||
return this.field?.readonly || false
|
|
||||||
},
|
|
||||||
showSelectFields () {
|
|
||||||
return !this.contextMenu.area && !this.isMultiSelection
|
|
||||||
},
|
|
||||||
showAutodetectFields () {
|
|
||||||
return this.withFieldsDetection && this.editable && !this.contextMenu.area && !this.isMultiSelection
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
document.addEventListener('keydown', this.onKeyDown)
|
|
||||||
document.addEventListener('mousedown', this.handleClickOutside)
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.checkMenuPosition()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
beforeUnmount () {
|
|
||||||
document.removeEventListener('keydown', this.onKeyDown)
|
|
||||||
document.removeEventListener('mousedown', this.handleClickOutside)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
buildDefaultName: Field.methods.buildDefaultName,
|
|
||||||
checkMenuPosition () {
|
|
||||||
if (this.$refs.menu) {
|
|
||||||
const rect = this.$refs.menu.getBoundingClientRect()
|
|
||||||
|
|
||||||
if (rect.bottom > window.innerHeight) {
|
|
||||||
this.contextMenu.y = this.contextMenu.y - rect.height
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rect.right > window.innerWidth) {
|
|
||||||
this.contextMenu.x = this.contextMenu.x - rect.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleToggleRequired (value) {
|
|
||||||
if (this.field) {
|
|
||||||
this.field.required = value
|
|
||||||
this.save()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleToggleReadOnly (value) {
|
|
||||||
if (this.field) {
|
|
||||||
this.field.readonly = value
|
|
||||||
this.save()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyDown (event) {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
|
|
||||||
this.$emit('close')
|
|
||||||
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v' && this.hasClipboardData) {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
|
|
||||||
this.$emit('paste')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleClickOutside (event) {
|
|
||||||
if (this.$refs.menu && !this.$refs.menu.contains(event.target)) {
|
|
||||||
this.$emit('close')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openFontModal () {
|
|
||||||
if (this.isMultiSelection) {
|
|
||||||
this.multiSelectField = {
|
|
||||||
name: this.t('fields_selected').replace('{count}', this.selectedFields.length),
|
|
||||||
preferences: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const preferencesStrings = this.selectedFields.map((f) => JSON.stringify(f.preferences || {}))
|
|
||||||
|
|
||||||
if (preferencesStrings.every((s) => s === preferencesStrings[0])) {
|
|
||||||
this.multiSelectField.preferences = JSON.parse(preferencesStrings[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isShowFontModal = true
|
|
||||||
},
|
|
||||||
openDescriptionModal () {
|
|
||||||
this.isShowDescriptionModal = true
|
|
||||||
},
|
|
||||||
openConditionModal () {
|
|
||||||
if (this.isMultiSelection) {
|
|
||||||
this.multiSelectField = {
|
|
||||||
name: this.t('fields_selected').replace('{count}', this.selectedFields.length),
|
|
||||||
conditions: []
|
|
||||||
}
|
|
||||||
|
|
||||||
const conditionStrings = this.selectedFields.map((f) => JSON.stringify(f.conditions || []))
|
|
||||||
|
|
||||||
if (conditionStrings.every((s) => s === conditionStrings[0])) {
|
|
||||||
this.multiSelectField.conditions = JSON.parse(conditionStrings[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isShowConditionsModal = true
|
|
||||||
},
|
|
||||||
openFormulaModal () {
|
|
||||||
this.isShowFormulaModal = true
|
|
||||||
},
|
|
||||||
closeModal () {
|
|
||||||
this.isShowFormulaModal = false
|
|
||||||
this.isShowFontModal = false
|
|
||||||
this.isShowConditionsModal = false
|
|
||||||
this.isShowDescriptionModal = false
|
|
||||||
this.multiSelectField = null
|
|
||||||
|
|
||||||
this.$emit('close')
|
|
||||||
},
|
|
||||||
handleSaveMultiSelectFontModal () {
|
|
||||||
this.selectedFields.forEach((field) => {
|
|
||||||
field.preferences = { ...field.preferences, ...this.multiSelectField.preferences }
|
|
||||||
})
|
|
||||||
|
|
||||||
this.save()
|
|
||||||
|
|
||||||
this.closeModal()
|
|
||||||
},
|
|
||||||
handleSaveMultiSelectConditionsModal () {
|
|
||||||
this.selectedFields.forEach((field) => {
|
|
||||||
field.conditions = JSON.parse(JSON.stringify(this.multiSelectField.conditions))
|
|
||||||
})
|
|
||||||
|
|
||||||
this.save()
|
|
||||||
|
|
||||||
this.closeModal()
|
|
||||||
},
|
|
||||||
handleToggleSelectMode () {
|
|
||||||
this.isSelectModeRef.value = !this.isSelectModeRef.value
|
|
||||||
|
|
||||||
this.$emit('close')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -125,6 +125,7 @@
|
|||||||
:field="field"
|
:field="field"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="$emit('save')"
|
||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -136,6 +137,7 @@
|
|||||||
:field="field"
|
:field="field"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="$emit('save')"
|
||||||
@close="isShowFontModal = false"
|
@close="isShowFontModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -147,6 +149,7 @@
|
|||||||
:field="field"
|
:field="field"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="$emit('save')"
|
||||||
@close="isShowDescriptionModal = false"
|
@close="isShowDescriptionModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
<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">
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
<span class="modal-title">
|
<span class="modal-title">
|
||||||
{{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || buildDefaultName(field, template.fields) }}
|
{{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || buildDefaultName(field) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'DescriptionModal',
|
name: 'DescriptionModal',
|
||||||
inject: ['t', 'save', 'template'],
|
inject: ['t', 'template'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -88,7 +88,7 @@ export default {
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['close'],
|
emits: ['close', 'save'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
description: this.field.description,
|
description: this.field.description,
|
||||||
@@ -103,7 +103,7 @@ export default {
|
|||||||
this.field.description = this.description
|
this.field.description = this.description
|
||||||
this.field.title = this.title
|
this.field.title = this.title
|
||||||
|
|
||||||
this.save()
|
this.$emit('save')
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
},
|
},
|
||||||
resizeTextarea () {
|
resizeTextarea () {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
:with-signature-id="withSignatureId"
|
:with-signature-id="withSignatureId"
|
||||||
:with-prefillable="withPrefillable"
|
:with-prefillable="withPrefillable"
|
||||||
:is-drag="isDrag"
|
:is-drag="isDrag"
|
||||||
|
:is-mobile="isMobile"
|
||||||
:with-field-placeholder="withFieldPlaceholder"
|
:with-field-placeholder="withFieldPlaceholder"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
:drag-field-placeholder="dragFieldPlaceholder"
|
:drag-field-placeholder="dragFieldPlaceholder"
|
||||||
@@ -30,9 +31,9 @@
|
|||||||
@copy-field="$emit('copy-field', $event)"
|
@copy-field="$emit('copy-field', $event)"
|
||||||
@paste-field="$emit('paste-field', { ...$event, attachment_uuid: document.uuid })"
|
@paste-field="$emit('paste-field', { ...$event, attachment_uuid: document.uuid })"
|
||||||
@add-custom-field="$emit('add-custom-field', $event)"
|
@add-custom-field="$emit('add-custom-field', $event)"
|
||||||
|
@set-draw="$emit('set-draw', $event)"
|
||||||
@copy-selected-areas="$emit('copy-selected-areas')"
|
@copy-selected-areas="$emit('copy-selected-areas')"
|
||||||
@delete-selected-areas="$emit('delete-selected-areas')"
|
@delete-selected-areas="$emit('delete-selected-areas')"
|
||||||
@align-selected-areas="$emit('align-selected-areas', $event)"
|
|
||||||
@autodetect-fields="$emit('autodetect-fields', $event)"
|
@autodetect-fields="$emit('autodetect-fields', $event)"
|
||||||
@scroll-to="scrollToArea"
|
@scroll-to="scrollToArea"
|
||||||
@draw="$emit('draw', { area: {...$event.area, attachment_uuid: document.uuid }, isTooSmall: $event.isTooSmall })"
|
@draw="$emit('draw', { area: {...$event.area, attachment_uuid: document.uuid }, isTooSmall: $event.isTooSmall })"
|
||||||
@@ -98,6 +99,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
isMobile: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
allowDraw: {
|
allowDraw: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -138,7 +144,7 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['draw', 'drop-field', 'remove-area', 'paste-field', 'copy-field', 'copy-selected-areas', 'delete-selected-areas', 'align-selected-areas', 'autodetect-fields', 'add-custom-field'],
|
emits: ['draw', 'drop-field', 'remove-area', 'paste-field', 'copy-field', 'copy-selected-areas', 'delete-selected-areas', 'autodetect-fields', 'add-custom-field', 'set-draw'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
pageRefs: []
|
pageRefs: []
|
||||||
@@ -180,7 +186,15 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
scrollToArea (area) {
|
scrollToArea (area) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.pageRefs[area.page].areaRefs.find((e) => e.area === area).$el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
const pageRef = this.pageRefs[area.page]
|
||||||
|
|
||||||
|
if (pageRef && pageRef.areaRefs) {
|
||||||
|
const areaRef = pageRef.areaRefs.find((e) => e.area === area)
|
||||||
|
|
||||||
|
if (areaRef && areaRef.$el) {
|
||||||
|
areaRef.$el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setPageRefs (el) {
|
setPageRefs (el) {
|
||||||
|
|||||||
@@ -269,6 +269,7 @@
|
|||||||
:editable="editable && !defaultField"
|
:editable="editable && !defaultField"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="$emit('save')"
|
||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -281,6 +282,7 @@
|
|||||||
:editable="editable && !defaultField"
|
:editable="editable && !defaultField"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="$emit('save')"
|
||||||
@close="isShowFontModal = false"
|
@close="isShowFontModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -292,6 +294,7 @@
|
|||||||
:item="field"
|
:item="field"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="$emit('save')"
|
||||||
@close="isShowConditionsModal = false"
|
@close="isShowConditionsModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -304,6 +307,7 @@
|
|||||||
:editable="editable && !defaultField"
|
:editable="editable && !defaultField"
|
||||||
:default-field="defaultField"
|
:default-field="defaultField"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="$emit('save')"
|
||||||
@close="isShowDescriptionModal = false"
|
@close="isShowDescriptionModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -340,7 +344,7 @@ export default {
|
|||||||
IconMathFunction,
|
IconMathFunction,
|
||||||
FieldType
|
FieldType
|
||||||
},
|
},
|
||||||
inject: ['template', 'backgroundColor', 'selectedAreasRef', 't', 'locale'],
|
inject: ['template', 'backgroundColor', 'selectedAreasRef', 't', 'locale', 'getFieldTypeIndex'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -412,7 +416,7 @@ export default {
|
|||||||
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||||
},
|
},
|
||||||
defaultName () {
|
defaultName () {
|
||||||
return this.buildDefaultName(this.field, this.template.fields)
|
return this.buildDefaultName(this.field)
|
||||||
},
|
},
|
||||||
areas () {
|
areas () {
|
||||||
return this.field.areas || []
|
return this.field.areas || []
|
||||||
@@ -432,7 +436,7 @@ export default {
|
|||||||
|
|
||||||
this.$emit('save')
|
this.$emit('save')
|
||||||
},
|
},
|
||||||
buildDefaultName (field, fields) {
|
buildDefaultName (field) {
|
||||||
if (field.type === 'payment' && field.preferences?.price && !field.preferences?.formula) {
|
if (field.type === 'payment' && field.preferences?.price && !field.preferences?.formula) {
|
||||||
const { price, currency } = field.preferences || {}
|
const { price, currency } = field.preferences || {}
|
||||||
|
|
||||||
@@ -443,7 +447,7 @@ export default {
|
|||||||
|
|
||||||
return `${this.fieldNames[field.type]} ${formattedPrice}`
|
return `${this.fieldNames[field.type]} ${formattedPrice}`
|
||||||
} else {
|
} else {
|
||||||
const typeIndex = fields.filter((f) => f.type === field.type).indexOf(field)
|
const typeIndex = this.getFieldTypeIndex(field)
|
||||||
|
|
||||||
if (field.type === 'heading' || field.type === 'strikethrough') {
|
if (field.type === 'heading' || field.type === 'strikethrough') {
|
||||||
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
|
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<Teleport :to="modalContainerEl">
|
||||||
|
<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 class="modal-title">
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-xl modal-close-button"
|
||||||
|
@click.prevent="$emit('close')"
|
||||||
|
>×</a>
|
||||||
|
</div>
|
||||||
|
<form @submit.prevent="$emit('save')">
|
||||||
|
<slot />
|
||||||
|
<button
|
||||||
|
class="base-button w-full mt-4 modal-save-button"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{{ t('save') }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ContextModal',
|
||||||
|
inject: ['t'],
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
modalContainerEl: {
|
||||||
|
type: Element,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['close', 'save']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="relative"
|
||||||
|
@mouseenter="handleMouseEnter"
|
||||||
|
@mouseleave="handleMouseLeave"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between text-sm"
|
||||||
|
@click.stop="isOpen ? close() : open()"
|
||||||
|
>
|
||||||
|
<span class="flex items-center space-x-2">
|
||||||
|
<component
|
||||||
|
:is="icon || 'span'"
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
</span>
|
||||||
|
<IconChevronRight class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-if="isOpen"
|
||||||
|
ref="submenu"
|
||||||
|
class="absolute p-1 z-50 left-full bg-white shadow-lg rounded-lg border border-neutral-200 cursor-default"
|
||||||
|
style="min-width: 170px"
|
||||||
|
:style="submenuStyle"
|
||||||
|
:class="menuClass"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<button
|
||||||
|
v-for="option in options"
|
||||||
|
:key="option.value"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between space-x-2 text-sm cursor-pointer"
|
||||||
|
@click="handleSelect(option.value)"
|
||||||
|
>
|
||||||
|
<span class="whitespace-nowrap">{{ option.label }}</span>
|
||||||
|
<IconCheck
|
||||||
|
v-if="modelValue === option.value"
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IconChevronRight, IconCheck } from '@tabler/icons-vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ContextSubmenu',
|
||||||
|
components: {
|
||||||
|
IconChevronRight,
|
||||||
|
IconCheck
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
icon: {
|
||||||
|
type: [Function],
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
menuClass: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['select', 'update:modelValue'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isOpen: false,
|
||||||
|
topOffset: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
submenuStyle () {
|
||||||
|
return {
|
||||||
|
top: this.topOffset + 'px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
this.clearTimeout()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleMouseEnter () {
|
||||||
|
clearTimeout(this.closeTimeout)
|
||||||
|
|
||||||
|
this.openTimeout = setTimeout(() => this.open(), 200)
|
||||||
|
},
|
||||||
|
handleMouseLeave () {
|
||||||
|
clearTimeout(this.openTimeout)
|
||||||
|
|
||||||
|
this.closeTimeout = setTimeout(() => this.close(), 200)
|
||||||
|
},
|
||||||
|
open () {
|
||||||
|
this.clearTimeout()
|
||||||
|
|
||||||
|
this.isOpen = true
|
||||||
|
this.topOffset = 0
|
||||||
|
|
||||||
|
this.$nextTick(() => setTimeout(() => this.adjustPosition(), 0))
|
||||||
|
},
|
||||||
|
clearTimeout () {
|
||||||
|
if (this.openTimeout) {
|
||||||
|
clearTimeout(this.openTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.closeTimeout) {
|
||||||
|
clearTimeout(this.closeTimeout)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close () {
|
||||||
|
this.clearTimeout()
|
||||||
|
|
||||||
|
this.isOpen = false
|
||||||
|
},
|
||||||
|
handleSelect (value) {
|
||||||
|
this.$emit('select', value)
|
||||||
|
this.$emit('update:modelValue', value)
|
||||||
|
},
|
||||||
|
adjustPosition () {
|
||||||
|
if (!this.$refs.submenu) return
|
||||||
|
|
||||||
|
const rect = this.$refs.submenu.getBoundingClientRect()
|
||||||
|
const overflow = rect.bottom - window.innerHeight
|
||||||
|
|
||||||
|
if (overflow > 0) {
|
||||||
|
this.topOffset = -overflow - 4
|
||||||
|
} else {
|
||||||
|
this.topOffset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
@change="[field.preferences ||= {}, field.preferences.method = $event.target.value, $emit('save')]"
|
@change="[field.preferences ||= {}, field.preferences.method = $event.target.value, $emit('save')]"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="method in ['QeS', 'AeS']"
|
v-for="method in verificationMethods"
|
||||||
:key="method"
|
:key="method"
|
||||||
:value="method.toLowerCase()"
|
:value="method.toLowerCase()"
|
||||||
:selected="method.toLowerCase() === field.preferences?.method || (method === 'QeS' && !field.preferences?.method)"
|
:selected="method.toLowerCase() === field.preferences?.method || (method === 'QeS' && !field.preferences?.method)"
|
||||||
@@ -26,32 +26,6 @@
|
|||||||
{{ t('method') }}
|
{{ t('method') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="['cells'].includes(field.type)"
|
|
||||||
class="py-1.5 px-1 relative"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
class="select select-bordered select-xs w-full max-w-xs h-7 !outline-0 font-normal bg-transparent"
|
|
||||||
@change="[field.preferences ||= {}, field.preferences.align = $event.target.value, $emit('save')]"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="value in ['left', 'right', field.type === 'cells' ? null : 'center'].filter(Boolean)"
|
|
||||||
:key="value"
|
|
||||||
:selected="field.preferences?.align ? value === field.preferences.align : value === 'left'"
|
|
||||||
:value="value"
|
|
||||||
>
|
|
||||||
{{ t(value) }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<label
|
|
||||||
:style="{ backgroundColor }"
|
|
||||||
class="absolute -top-1 left-2.5 px-1 h-4"
|
|
||||||
style="font-size: 8px"
|
|
||||||
>
|
|
||||||
{{ t('align') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-if="['select', 'radio'].includes(field.type) && !defaultField"
|
v-if="['select', 'radio'].includes(field.type) && !defaultField"
|
||||||
class="py-1.5 px-1 relative"
|
class="py-1.5 px-1 relative"
|
||||||
@@ -348,7 +322,7 @@
|
|||||||
{{ t('any') }}
|
{{ t('any') }}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
v-for="type in ['drawn', 'typed', 'drawn_or_typed', 'drawn_or_upload', 'upload']"
|
v-for="type in signatureFormats"
|
||||||
:key="type"
|
:key="type"
|
||||||
:value="type"
|
:value="type"
|
||||||
:selected="field.preferences?.format === type"
|
:selected="field.preferences?.format === type"
|
||||||
@@ -437,7 +411,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="['text', 'number'].includes(field.type)"
|
v-if="['text', 'number', 'radio', 'multiple', 'select'].includes(field.type)"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<label class="cursor-pointer py-1.5">
|
<label class="cursor-pointer py-1.5">
|
||||||
@@ -452,7 +426,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="withPrefillable && ['text', 'number', 'cells', 'date', 'checkbox', 'select', 'radio', 'phone'].includes(field['type'])"
|
v-if="withPrefillable && prefillableFieldTypes.includes(field['type'])"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<label class="cursor-pointer py-1.5">
|
<label class="cursor-pointer py-1.5">
|
||||||
@@ -470,7 +444,7 @@
|
|||||||
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="['text', 'number', 'date', 'select', 'heading'].includes(field.type)">
|
<li v-if="['text', 'number', 'date', 'select', 'heading', 'cells'].includes(field.type)">
|
||||||
<label
|
<label
|
||||||
class="label-text cursor-pointer text-center w-full flex items-center"
|
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||||
@click="$emit('click-font')"
|
@click="$emit('click-font')"
|
||||||
@@ -718,7 +692,7 @@ export default {
|
|||||||
},
|
},
|
||||||
lengthValidation () {
|
lengthValidation () {
|
||||||
if (this.field.validation?.pattern && this.selectedValidation !== 'custom') {
|
if (this.field.validation?.pattern && this.selectedValidation !== 'custom') {
|
||||||
return this.field.validation.pattern.match(/^\.{(?<min>\d+),(?<max>\d+)?}$/)?.groups
|
return this.parseLengthPattern(this.field.validation.pattern)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -735,6 +709,15 @@ export default {
|
|||||||
'^[a-zA-Z]+$': 'letters_only'
|
'^[a-zA-Z]+$': 'letters_only'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
signatureFormats () {
|
||||||
|
return ['drawn', 'typed', 'drawn_or_typed', 'drawn_or_upload', 'upload']
|
||||||
|
},
|
||||||
|
verificationMethods () {
|
||||||
|
return ['QeS', 'AeS']
|
||||||
|
},
|
||||||
|
prefillableFieldTypes () {
|
||||||
|
return ['text', 'number', 'cells', 'date', 'checkbox', 'select', 'radio', 'phone']
|
||||||
|
},
|
||||||
sortedAreas () {
|
sortedAreas () {
|
||||||
return (this.field.areas || []).sort((a, b) => {
|
return (this.field.areas || []).sort((a, b) => {
|
||||||
return this.schemaAttachmentsIndexes[a.attachment_uuid] - this.schemaAttachmentsIndexes[b.attachment_uuid]
|
return this.schemaAttachmentsIndexes[a.attachment_uuid] - this.schemaAttachmentsIndexes[b.attachment_uuid]
|
||||||
@@ -798,6 +781,9 @@ export default {
|
|||||||
return number
|
return number
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
parseLengthPattern (pattern) {
|
||||||
|
return pattern?.match(/^\.{(?<min>\d+),(?<max>\d+)?}$/)?.groups || null
|
||||||
|
},
|
||||||
formatDate (date, format) {
|
formatDate (date, format) {
|
||||||
const monthFormats = {
|
const monthFormats = {
|
||||||
M: 'numeric',
|
M: 'numeric',
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
:key="field.uuid"
|
:key="field.uuid"
|
||||||
:data-uuid="field.uuid"
|
:data-uuid="field.uuid"
|
||||||
:field="field"
|
:field="field"
|
||||||
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
:type-index="getFieldTypeIndex(field)"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:with-signature-id="withSignatureId"
|
:with-signature-id="withSignatureId"
|
||||||
:with-prefillable="withPrefillable"
|
:with-prefillable="withPrefillable"
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
>{{ t('custom') }}</a>
|
>{{ t('custom') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="showCustomTab && editable"
|
v-if="showCustomTab && editable && (customFields.length || newCustomField)"
|
||||||
ref="customFields"
|
ref="customFields"
|
||||||
class="custom-fields"
|
class="custom-fields"
|
||||||
@dragover.prevent="onCustomFieldDragover"
|
@dragover.prevent="onCustomFieldDragover"
|
||||||
@@ -376,7 +376,7 @@ export default {
|
|||||||
IconDrag,
|
IconDrag,
|
||||||
IconLock
|
IconLock
|
||||||
},
|
},
|
||||||
inject: ['save', 'backgroundColor', 'withPhone', 'withVerification', 'withKba', 'withPayment', 't', 'fieldsDragFieldRef', 'customDragFieldRef', 'baseFetch', 'selectedAreasRef'],
|
inject: ['save', 'backgroundColor', 'withPhone', 'withVerification', 'withKba', 'withPayment', 't', 'fieldsDragFieldRef', 'customDragFieldRef', 'baseFetch', 'selectedAreasRef', 'getFieldTypeIndex'],
|
||||||
props: {
|
props: {
|
||||||
fields: {
|
fields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
<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">
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
<span class="modal-title">
|
<span class="modal-title">
|
||||||
{{ t('font') }} - {{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || buildDefaultName(field, template.fields) }}
|
{{ t('font') }} - {{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || buildDefaultName(field) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
class="outline-none whitespace-nowrap truncate"
|
class="outline-none whitespace-nowrap truncate"
|
||||||
>
|
>
|
||||||
{{ field.default_value || field.name || buildDefaultName(field, template.fields) }}
|
{{ field.default_value || field.name || buildDefaultName(field) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -196,7 +196,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
IconChevronDown
|
IconChevronDown
|
||||||
},
|
},
|
||||||
inject: ['t', 'save', 'template'],
|
inject: ['t', 'template'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -212,17 +212,12 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
withClickSaveEvent: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
buildDefaultName: {
|
buildDefaultName: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['close', 'click-save'],
|
emits: ['close', 'save'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
preferences: {}
|
preferences: {}
|
||||||
@@ -262,6 +257,7 @@ export default {
|
|||||||
colors () {
|
colors () {
|
||||||
return [
|
return [
|
||||||
{ label: '⬛', value: 'black' },
|
{ label: '⬛', value: 'black' },
|
||||||
|
{ label: '⬜', value: 'white' },
|
||||||
{ label: '🟦', value: 'blue' },
|
{ label: '🟦', value: 'blue' },
|
||||||
{ label: '🟥', value: 'red' }
|
{ label: '🟥', value: 'red' }
|
||||||
]
|
]
|
||||||
@@ -276,6 +272,7 @@ export default {
|
|||||||
'items-center': !this.preferences.valign || this.preferences.valign === 'center',
|
'items-center': !this.preferences.valign || this.preferences.valign === 'center',
|
||||||
'items-start': this.preferences.valign === 'top',
|
'items-start': this.preferences.valign === 'top',
|
||||||
'items-end': this.preferences.valign === 'bottom',
|
'items-end': this.preferences.valign === 'bottom',
|
||||||
|
'bg-black': this.preferences.color === 'white',
|
||||||
'font-bold': ['bold_italic', 'bold'].includes(this.preferences.font_type),
|
'font-bold': ['bold_italic', 'bold'].includes(this.preferences.font_type),
|
||||||
italic: ['bold_italic', 'italic'].includes(this.preferences.font_type)
|
italic: ['bold_italic', 'italic'].includes(this.preferences.font_type)
|
||||||
}
|
}
|
||||||
@@ -327,12 +324,7 @@ export default {
|
|||||||
|
|
||||||
Object.assign(this.field.preferences, this.preferences)
|
Object.assign(this.field.preferences, this.preferences)
|
||||||
|
|
||||||
if (this.withClickSaveEvent) {
|
this.$emit('save')
|
||||||
this.$emit('click-save')
|
|
||||||
} else {
|
|
||||||
this.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
<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">
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
<span class="modal-title">
|
<span class="modal-title">
|
||||||
{{ t('formula') }} - {{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || buildDefaultName(field, template.fields) }}
|
{{ t('formula') }} - {{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || buildDefaultName(field) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
v-for="f in fields"
|
v-for="f in fields"
|
||||||
:key="f.uuid"
|
:key="f.uuid"
|
||||||
class="mr-1 flex btn btn-neutral btn-outline border-base-content/20 btn-sm normal-case font-normal bg-white !rounded-xl"
|
class="mr-1 flex btn btn-neutral btn-outline border-base-content/20 btn-sm normal-case font-normal bg-white !rounded-xl"
|
||||||
@click.prevent="insertTextUnderCursor(`{{${f.name || buildDefaultName(f, template.fields)}}}`)"
|
@click.prevent="insertTextUnderCursor(`{{${f.name || buildDefaultName(f)}}}`)"
|
||||||
>
|
>
|
||||||
<IconMathFunction
|
<IconMathFunction
|
||||||
v-if="f.preferences?.formula"
|
v-if="f.preferences?.formula"
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
height="20"
|
height="20"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
/>
|
/>
|
||||||
{{ f.name || buildDefaultName(f, template.fields) }}
|
{{ f.name || buildDefaultName(f) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -131,7 +131,7 @@ export default {
|
|||||||
IconCodePlus,
|
IconCodePlus,
|
||||||
IconMathFunction
|
IconMathFunction
|
||||||
},
|
},
|
||||||
inject: ['t', 'save', 'template', 'withFormula'],
|
inject: ['t', 'template', 'withFormula'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -152,7 +152,7 @@ export default {
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['close'],
|
emits: ['close', 'save'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
formula: ''
|
formula: ''
|
||||||
@@ -181,7 +181,7 @@ export default {
|
|||||||
const foundField = this.fields.find((f) => f.uuid === uuid)
|
const foundField = this.fields.find((f) => f.uuid === uuid)
|
||||||
|
|
||||||
if (foundField) {
|
if (foundField) {
|
||||||
return `{{${foundField.name || this.buildDefaultName(foundField, this.template.fields)}}}`
|
return `{{${foundField.name || this.buildDefaultName(foundField)}}}`
|
||||||
} else {
|
} else {
|
||||||
return '{{FIELD NOT FOUND}}'
|
return '{{FIELD NOT FOUND}}'
|
||||||
}
|
}
|
||||||
@@ -190,7 +190,7 @@ export default {
|
|||||||
normalizeFormula (text) {
|
normalizeFormula (text) {
|
||||||
return text.replace(/{{(.*?)}}/g, (match, name) => {
|
return text.replace(/{{(.*?)}}/g, (match, name) => {
|
||||||
const foundField = this.fields.find((f) => {
|
const foundField = this.fields.find((f) => {
|
||||||
return (f.name || this.buildDefaultName(f, this.template.fields)).trim() === name.trim()
|
return (f.name || this.buildDefaultName(f)).trim() === name.trim()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (foundField) {
|
if (foundField) {
|
||||||
@@ -212,11 +212,14 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.field.preferences.formula = normalizedFormula
|
this.field.preferences.formula = normalizedFormula
|
||||||
|
|
||||||
if (this.field.type !== 'payment') {
|
if (this.field.type === 'payment') {
|
||||||
|
delete this.field.preferences.price
|
||||||
|
delete this.field.preferences.payment_link_id
|
||||||
|
} else {
|
||||||
this.field.readonly = !!normalizedFormula
|
this.field.readonly = !!normalizedFormula
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save()
|
this.$emit('save')
|
||||||
|
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
fixed: 'Fixed',
|
||||||
default: 'Default',
|
default: 'Default',
|
||||||
save_as_custom_field: 'Save as Custom Field',
|
save_as_custom_field: 'Save as custom field',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
analyzing_: 'Analyzing...',
|
analyzing_: 'Analyzing...',
|
||||||
download: 'Download',
|
download: 'Download',
|
||||||
@@ -31,6 +32,9 @@ const en = {
|
|||||||
field_not_found: 'Field not found',
|
field_not_found: 'Field not found',
|
||||||
clear: 'Clear',
|
clear: 'Clear',
|
||||||
align: 'Align',
|
align: 'Align',
|
||||||
|
resize: 'Resize',
|
||||||
|
width: 'Width',
|
||||||
|
height: 'Height',
|
||||||
add_all_required_fields_to_continue: 'Add all required fields to continue',
|
add_all_required_fields_to_continue: 'Add all required fields to continue',
|
||||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Uploaded PDF contains form fields. Keep or remove them?',
|
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Uploaded PDF contains form fields. Keep or remove them?',
|
||||||
keep: 'Keep',
|
keep: 'Keep',
|
||||||
@@ -48,6 +52,8 @@ const en = {
|
|||||||
type_value: 'Type value',
|
type_value: 'Type value',
|
||||||
equal: 'Equal',
|
equal: 'Equal',
|
||||||
not_equal: 'Not equal',
|
not_equal: 'Not equal',
|
||||||
|
greater_than: 'Greater than',
|
||||||
|
less_than: 'Less than',
|
||||||
contains: 'Contains',
|
contains: 'Contains',
|
||||||
does_not_contain: 'Does not contain',
|
does_not_contain: 'Does not contain',
|
||||||
not_empty: 'Not empty',
|
not_empty: 'Not empty',
|
||||||
@@ -91,8 +97,9 @@ const en = {
|
|||||||
format: 'Format',
|
format: 'Format',
|
||||||
read_only: 'Read-only',
|
read_only: 'Read-only',
|
||||||
page: 'Page',
|
page: 'Page',
|
||||||
draw_new_area: 'Draw New Area',
|
draw_new_area: 'Draw new area',
|
||||||
copy_to_all_pages: 'Copy to All Pages',
|
copy_to_all_pages: 'Copy to all pages',
|
||||||
|
more: 'More',
|
||||||
add_option: 'Add option',
|
add_option: 'Add option',
|
||||||
option: 'Option',
|
option: 'Option',
|
||||||
options: 'Options',
|
options: 'Options',
|
||||||
@@ -173,6 +180,9 @@ const en = {
|
|||||||
numbers_only: 'Numbers only',
|
numbers_only: 'Numbers only',
|
||||||
letters_only: 'Letters only',
|
letters_only: 'Letters only',
|
||||||
regexp_validation: 'Regexp validation',
|
regexp_validation: 'Regexp validation',
|
||||||
|
custom_validation: 'Custom Validation',
|
||||||
|
length_validation: 'Length Validation',
|
||||||
|
number_range: 'Number Range',
|
||||||
enter_pdf_password: 'Enter PDF password',
|
enter_pdf_password: 'Enter PDF password',
|
||||||
wrong_password: 'Wrong password',
|
wrong_password: 'Wrong password',
|
||||||
currency: 'Currency',
|
currency: 'Currency',
|
||||||
@@ -202,6 +212,7 @@ const en = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
|
fixed: 'Fijo',
|
||||||
default: 'Predeterminado',
|
default: 'Predeterminado',
|
||||||
save_as_custom_field: 'Guardar como personalizado',
|
save_as_custom_field: 'Guardar como personalizado',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
@@ -235,6 +246,9 @@ const es = {
|
|||||||
clear: 'Borrar',
|
clear: 'Borrar',
|
||||||
type_value: 'Escriba valor',
|
type_value: 'Escriba valor',
|
||||||
align: 'Alinear',
|
align: 'Alinear',
|
||||||
|
resize: 'Redimensionar',
|
||||||
|
width: 'Ancho',
|
||||||
|
height: 'Alto',
|
||||||
add_all_required_fields_to_continue: 'Agregar todos los campos requeridos para continuar',
|
add_all_required_fields_to_continue: 'Agregar todos los campos requeridos para continuar',
|
||||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'El PDF cargado tiene campos. ¿Mantenerlos o eliminarlos?',
|
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'El PDF cargado tiene campos. ¿Mantenerlos o eliminarlos?',
|
||||||
keep: 'Mantener',
|
keep: 'Mantener',
|
||||||
@@ -252,6 +266,8 @@ const es = {
|
|||||||
price: 'Precio',
|
price: 'Precio',
|
||||||
equal: 'Igual',
|
equal: 'Igual',
|
||||||
not_equal: 'No es igual',
|
not_equal: 'No es igual',
|
||||||
|
greater_than: 'Mayor que',
|
||||||
|
less_than: 'Menor que',
|
||||||
contains: 'Contiene',
|
contains: 'Contiene',
|
||||||
does_not_contain: 'No contiene',
|
does_not_contain: 'No contiene',
|
||||||
not_empty: 'No vacío',
|
not_empty: 'No vacío',
|
||||||
@@ -292,6 +308,7 @@ const es = {
|
|||||||
page: 'Página',
|
page: 'Página',
|
||||||
draw_new_area: 'Dibujar nueva área',
|
draw_new_area: 'Dibujar nueva área',
|
||||||
copy_to_all_pages: 'Copiar a todas las páginas',
|
copy_to_all_pages: 'Copiar a todas las páginas',
|
||||||
|
more: 'Más',
|
||||||
add_option: 'Agregar opción',
|
add_option: 'Agregar opción',
|
||||||
option: 'Opción',
|
option: 'Opción',
|
||||||
options: 'Opciones',
|
options: 'Opciones',
|
||||||
@@ -376,6 +393,9 @@ const es = {
|
|||||||
numbers_only: 'Solo números',
|
numbers_only: 'Solo números',
|
||||||
letters_only: 'Solo letras',
|
letters_only: 'Solo letras',
|
||||||
regexp_validation: 'Validación de expresión regular',
|
regexp_validation: 'Validación de expresión regular',
|
||||||
|
custom_validation: 'Validación Personalizada',
|
||||||
|
length_validation: 'Validación de Longitud',
|
||||||
|
number_range: 'Rango de Números',
|
||||||
enter_pdf_password: 'Ingrese la contraseña del PDF',
|
enter_pdf_password: 'Ingrese la contraseña del PDF',
|
||||||
wrong_password: 'Contraseña incorrecta',
|
wrong_password: 'Contraseña incorrecta',
|
||||||
currency: 'Moneda',
|
currency: 'Moneda',
|
||||||
@@ -405,6 +425,7 @@ const es = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
|
fixed: 'Fisso',
|
||||||
default: 'Predefinito',
|
default: 'Predefinito',
|
||||||
save_as_custom_field: 'Salva come personalizzato',
|
save_as_custom_field: 'Salva come personalizzato',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
@@ -437,6 +458,9 @@ const it = {
|
|||||||
field_not_found: 'Campo non trovato',
|
field_not_found: 'Campo non trovato',
|
||||||
clear: 'Cancella',
|
clear: 'Cancella',
|
||||||
align: 'Allinea',
|
align: 'Allinea',
|
||||||
|
resize: 'Ridimensiona',
|
||||||
|
width: 'Larghezza',
|
||||||
|
height: 'Altezza',
|
||||||
add_all_required_fields_to_continue: 'Aggiungi tutti i campi obbligatori per continuare',
|
add_all_required_fields_to_continue: 'Aggiungi tutti i campi obbligatori per continuare',
|
||||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Il PDF caricato contiene campi del modulo. Mantenerli o rimuoverli?',
|
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Il PDF caricato contiene campi del modulo. Mantenerli o rimuoverli?',
|
||||||
keep: 'Mantieni',
|
keep: 'Mantieni',
|
||||||
@@ -454,6 +478,8 @@ const it = {
|
|||||||
type_value: 'Inserisci valore',
|
type_value: 'Inserisci valore',
|
||||||
equal: 'Uguale',
|
equal: 'Uguale',
|
||||||
not_equal: 'Non uguale',
|
not_equal: 'Non uguale',
|
||||||
|
greater_than: 'Maggiore di',
|
||||||
|
less_than: 'Minore di',
|
||||||
contains: 'Contiene',
|
contains: 'Contiene',
|
||||||
does_not_contain: 'Non contiene',
|
does_not_contain: 'Non contiene',
|
||||||
not_empty: 'Non vuoto',
|
not_empty: 'Non vuoto',
|
||||||
@@ -499,6 +525,7 @@ const it = {
|
|||||||
page: 'Pagina',
|
page: 'Pagina',
|
||||||
draw_new_area: 'Disegna nuova area',
|
draw_new_area: 'Disegna nuova area',
|
||||||
copy_to_all_pages: 'Copia in tutte le pagine',
|
copy_to_all_pages: 'Copia in tutte le pagine',
|
||||||
|
more: 'Altro',
|
||||||
add_option: 'Aggiungi opzione',
|
add_option: 'Aggiungi opzione',
|
||||||
option: 'Opzione',
|
option: 'Opzione',
|
||||||
options: 'Opzioni',
|
options: 'Opzioni',
|
||||||
@@ -579,6 +606,9 @@ const it = {
|
|||||||
numbers_only: 'Solo numeri',
|
numbers_only: 'Solo numeri',
|
||||||
letters_only: 'Solo lettere',
|
letters_only: 'Solo lettere',
|
||||||
regexp_validation: 'Validazione regexp',
|
regexp_validation: 'Validazione regexp',
|
||||||
|
custom_validation: 'Validazione Personalizzata',
|
||||||
|
length_validation: 'Validazione Lunghezza',
|
||||||
|
number_range: 'Intervallo Numerico',
|
||||||
enter_pdf_password: 'Inserisci password PDF',
|
enter_pdf_password: 'Inserisci password PDF',
|
||||||
wrong_password: 'Password errata',
|
wrong_password: 'Password errata',
|
||||||
currency: 'Valuta',
|
currency: 'Valuta',
|
||||||
@@ -608,6 +638,7 @@ const it = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
|
fixed: 'Fixo',
|
||||||
default: 'Padrão',
|
default: 'Padrão',
|
||||||
save_as_custom_field: 'Salvar como personalizado',
|
save_as_custom_field: 'Salvar como personalizado',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
@@ -644,6 +675,9 @@ const pt = {
|
|||||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'O PDF carregado contém campos. Manter ou removê-los?',
|
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'O PDF carregado contém campos. Manter ou removê-los?',
|
||||||
keep: 'Manter',
|
keep: 'Manter',
|
||||||
align: 'Alinhar',
|
align: 'Alinhar',
|
||||||
|
resize: 'Redimensionar',
|
||||||
|
width: 'Largura',
|
||||||
|
height: 'Altura',
|
||||||
left: 'Esquerda',
|
left: 'Esquerda',
|
||||||
heading: 'Cabeçalho',
|
heading: 'Cabeçalho',
|
||||||
validation: 'Validação',
|
validation: 'Validação',
|
||||||
@@ -658,6 +692,8 @@ const pt = {
|
|||||||
price: 'Preço',
|
price: 'Preço',
|
||||||
equal: 'Igual',
|
equal: 'Igual',
|
||||||
not_equal: 'Não é igual',
|
not_equal: 'Não é igual',
|
||||||
|
greater_than: 'Maior que',
|
||||||
|
less_than: 'Menor que',
|
||||||
contains: 'Contém',
|
contains: 'Contém',
|
||||||
does_not_contain: 'Não contém',
|
does_not_contain: 'Não contém',
|
||||||
not_empty: 'Não vazio',
|
not_empty: 'Não vazio',
|
||||||
@@ -698,6 +734,7 @@ const pt = {
|
|||||||
page: 'Página',
|
page: 'Página',
|
||||||
draw_new_area: 'Desenhar nova área',
|
draw_new_area: 'Desenhar nova área',
|
||||||
copy_to_all_pages: 'Copiar para todas as páginas',
|
copy_to_all_pages: 'Copiar para todas as páginas',
|
||||||
|
more: 'Mais',
|
||||||
add_option: 'Adicionar opção',
|
add_option: 'Adicionar opção',
|
||||||
option: 'Opção',
|
option: 'Opção',
|
||||||
options: 'Opções',
|
options: 'Opções',
|
||||||
@@ -782,6 +819,9 @@ const pt = {
|
|||||||
numbers_only: 'Somente números',
|
numbers_only: 'Somente números',
|
||||||
letters_only: 'Somente letras',
|
letters_only: 'Somente letras',
|
||||||
regexp_validation: 'Validação de expressão regular',
|
regexp_validation: 'Validação de expressão regular',
|
||||||
|
custom_validation: 'Validação Personalizada',
|
||||||
|
length_validation: 'Validação de Comprimento',
|
||||||
|
number_range: 'Intervalo de Números',
|
||||||
enter_pdf_password: 'Digite a senha do PDF',
|
enter_pdf_password: 'Digite a senha do PDF',
|
||||||
wrong_password: 'Senha incorreta',
|
wrong_password: 'Senha incorreta',
|
||||||
currency: 'Moeda',
|
currency: 'Moeda',
|
||||||
@@ -811,6 +851,7 @@ const pt = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
|
fixed: 'Fixe',
|
||||||
default: 'Par défaut',
|
default: 'Par défaut',
|
||||||
save_as_custom_field: 'Enregistrer comme personnalisé',
|
save_as_custom_field: 'Enregistrer comme personnalisé',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
@@ -843,6 +884,9 @@ const fr = {
|
|||||||
field_not_found: 'Champ introuvable',
|
field_not_found: 'Champ introuvable',
|
||||||
clear: 'Effacer',
|
clear: 'Effacer',
|
||||||
align: 'Aligner',
|
align: 'Aligner',
|
||||||
|
resize: 'Redimensionner',
|
||||||
|
width: 'Largeur',
|
||||||
|
height: 'Hauteur',
|
||||||
add_all_required_fields_to_continue: 'Ajoutez tous les champs obligatoires pour continuer',
|
add_all_required_fields_to_continue: 'Ajoutez tous les champs obligatoires pour continuer',
|
||||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Le PDF téléversé contient des champs de formulaire. Les conserver ou les supprimer ?',
|
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Le PDF téléversé contient des champs de formulaire. Les conserver ou les supprimer ?',
|
||||||
keep: 'Conserver',
|
keep: 'Conserver',
|
||||||
@@ -860,6 +904,8 @@ const fr = {
|
|||||||
type_value: 'Saisir une valeur',
|
type_value: 'Saisir une valeur',
|
||||||
equal: 'Égal',
|
equal: 'Égal',
|
||||||
not_equal: 'Différent',
|
not_equal: 'Différent',
|
||||||
|
greater_than: 'Supérieur à',
|
||||||
|
less_than: 'Inférieur à',
|
||||||
contains: 'Contient',
|
contains: 'Contient',
|
||||||
does_not_contain: 'Ne contient pas',
|
does_not_contain: 'Ne contient pas',
|
||||||
not_empty: 'Non vide',
|
not_empty: 'Non vide',
|
||||||
@@ -905,6 +951,7 @@ const fr = {
|
|||||||
page: 'Page',
|
page: 'Page',
|
||||||
draw_new_area: 'Dessiner une zone',
|
draw_new_area: 'Dessiner une zone',
|
||||||
copy_to_all_pages: 'Copier sur toutes les pages',
|
copy_to_all_pages: 'Copier sur toutes les pages',
|
||||||
|
more: 'Plus',
|
||||||
add_option: 'Ajouter une option',
|
add_option: 'Ajouter une option',
|
||||||
option: 'Option',
|
option: 'Option',
|
||||||
options: 'Options',
|
options: 'Options',
|
||||||
@@ -985,6 +1032,9 @@ const fr = {
|
|||||||
numbers_only: 'Chiffres uniquement',
|
numbers_only: 'Chiffres uniquement',
|
||||||
letters_only: 'Lettres uniquement',
|
letters_only: 'Lettres uniquement',
|
||||||
regexp_validation: 'Validation par expression régulière',
|
regexp_validation: 'Validation par expression régulière',
|
||||||
|
custom_validation: 'Validation Personnalisée',
|
||||||
|
length_validation: 'Validation de Longueur',
|
||||||
|
number_range: 'Plage de Nombres',
|
||||||
enter_pdf_password: 'Saisir le mot de passe du PDF',
|
enter_pdf_password: 'Saisir le mot de passe du PDF',
|
||||||
wrong_password: 'Mot de passe incorrect',
|
wrong_password: 'Mot de passe incorrect',
|
||||||
currency: 'Devise',
|
currency: 'Devise',
|
||||||
@@ -1014,6 +1064,7 @@ const fr = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
|
fixed: 'Fest',
|
||||||
default: 'Standard',
|
default: 'Standard',
|
||||||
save_as_custom_field: 'Als benutzerdefiniert speichern',
|
save_as_custom_field: 'Als benutzerdefiniert speichern',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
@@ -1046,6 +1097,9 @@ const de = {
|
|||||||
field_not_found: 'Feld nicht gefunden',
|
field_not_found: 'Feld nicht gefunden',
|
||||||
clear: 'Leeren',
|
clear: 'Leeren',
|
||||||
align: 'Ausrichten',
|
align: 'Ausrichten',
|
||||||
|
resize: 'Größe ändern',
|
||||||
|
width: 'Breite',
|
||||||
|
height: 'Höhe',
|
||||||
add_all_required_fields_to_continue: 'Fügen Sie alle erforderlichen Felder hinzu, um fortzufahren',
|
add_all_required_fields_to_continue: 'Fügen Sie alle erforderlichen Felder hinzu, um fortzufahren',
|
||||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Das hochgeladene PDF enthält Formularfelder. Beibehalten oder entfernen?',
|
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Das hochgeladene PDF enthält Formularfelder. Beibehalten oder entfernen?',
|
||||||
keep: 'Beibehalten',
|
keep: 'Beibehalten',
|
||||||
@@ -1063,6 +1117,8 @@ const de = {
|
|||||||
type_value: 'Wert eingeben',
|
type_value: 'Wert eingeben',
|
||||||
equal: 'Gleich',
|
equal: 'Gleich',
|
||||||
not_equal: 'Ungleich',
|
not_equal: 'Ungleich',
|
||||||
|
greater_than: 'Größer als',
|
||||||
|
less_than: 'Kleiner als',
|
||||||
contains: 'Enthält',
|
contains: 'Enthält',
|
||||||
does_not_contain: 'Enthält nicht',
|
does_not_contain: 'Enthält nicht',
|
||||||
not_empty: 'Nicht leer',
|
not_empty: 'Nicht leer',
|
||||||
@@ -1108,6 +1164,7 @@ const de = {
|
|||||||
page: 'Seite',
|
page: 'Seite',
|
||||||
draw_new_area: 'Bereich zeichnen',
|
draw_new_area: 'Bereich zeichnen',
|
||||||
copy_to_all_pages: 'Auf alle Seiten kopieren',
|
copy_to_all_pages: 'Auf alle Seiten kopieren',
|
||||||
|
more: 'Mehr',
|
||||||
add_option: 'Option hinzufügen',
|
add_option: 'Option hinzufügen',
|
||||||
option: 'Option',
|
option: 'Option',
|
||||||
options: 'Optionen',
|
options: 'Optionen',
|
||||||
@@ -1188,6 +1245,9 @@ const de = {
|
|||||||
numbers_only: 'Nur Zahlen',
|
numbers_only: 'Nur Zahlen',
|
||||||
letters_only: 'Nur Buchstaben',
|
letters_only: 'Nur Buchstaben',
|
||||||
regexp_validation: 'RegExp-Validierung',
|
regexp_validation: 'RegExp-Validierung',
|
||||||
|
custom_validation: 'Benutzerdefinierte Validierung',
|
||||||
|
length_validation: 'Längenvalidierung',
|
||||||
|
number_range: 'Zahlenbereich',
|
||||||
enter_pdf_password: 'PDF-Passwort eingeben',
|
enter_pdf_password: 'PDF-Passwort eingeben',
|
||||||
wrong_password: 'Falsches Passwort',
|
wrong_password: 'Falsches Passwort',
|
||||||
currency: 'Währung',
|
currency: 'Währung',
|
||||||
@@ -1217,6 +1277,7 @@ const de = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nl = {
|
const nl = {
|
||||||
|
fixed: 'Vast',
|
||||||
default: 'Standaard',
|
default: 'Standaard',
|
||||||
save_as_custom_field: 'Opslaan als aangepast',
|
save_as_custom_field: 'Opslaan als aangepast',
|
||||||
kba: 'KBA',
|
kba: 'KBA',
|
||||||
@@ -1249,6 +1310,9 @@ const nl = {
|
|||||||
field_not_found: 'Veld niet gevonden',
|
field_not_found: 'Veld niet gevonden',
|
||||||
clear: 'Wissen',
|
clear: 'Wissen',
|
||||||
align: 'Uitlijnen',
|
align: 'Uitlijnen',
|
||||||
|
resize: 'Formaat wijzigen',
|
||||||
|
width: 'Breedte',
|
||||||
|
height: 'Hoogte',
|
||||||
add_all_required_fields_to_continue: 'Voeg alle vereiste velden toe om door te gaan',
|
add_all_required_fields_to_continue: 'Voeg alle vereiste velden toe om door te gaan',
|
||||||
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Geüploade PDF bevat formuliervelden. Behouden of verwijderen?',
|
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Geüploade PDF bevat formuliervelden. Behouden of verwijderen?',
|
||||||
keep: 'Behouden',
|
keep: 'Behouden',
|
||||||
@@ -1266,6 +1330,8 @@ const nl = {
|
|||||||
type_value: 'Typ waarde',
|
type_value: 'Typ waarde',
|
||||||
equal: 'Gelijk aan',
|
equal: 'Gelijk aan',
|
||||||
not_equal: 'Niet gelijk aan',
|
not_equal: 'Niet gelijk aan',
|
||||||
|
greater_than: 'Groter dan',
|
||||||
|
less_than: 'Kleiner dan',
|
||||||
contains: 'Bevat',
|
contains: 'Bevat',
|
||||||
does_not_contain: 'Bevat niet',
|
does_not_contain: 'Bevat niet',
|
||||||
not_empty: 'Niet leeg',
|
not_empty: 'Niet leeg',
|
||||||
@@ -1311,6 +1377,7 @@ const nl = {
|
|||||||
page: 'Pagina',
|
page: 'Pagina',
|
||||||
draw_new_area: 'Nieuw gebied tekenen',
|
draw_new_area: 'Nieuw gebied tekenen',
|
||||||
copy_to_all_pages: 'Kopieer naar alle pag.',
|
copy_to_all_pages: 'Kopieer naar alle pag.',
|
||||||
|
more: 'Meer',
|
||||||
add_option: 'Optie toevoegen',
|
add_option: 'Optie toevoegen',
|
||||||
option: 'Optie',
|
option: 'Optie',
|
||||||
options: 'Opties',
|
options: 'Opties',
|
||||||
@@ -1391,6 +1458,9 @@ const nl = {
|
|||||||
numbers_only: 'Alleen cijfers',
|
numbers_only: 'Alleen cijfers',
|
||||||
letters_only: 'Alleen letters',
|
letters_only: 'Alleen letters',
|
||||||
regexp_validation: 'Regex validatie',
|
regexp_validation: 'Regex validatie',
|
||||||
|
custom_validation: 'Aangepaste Validatie',
|
||||||
|
length_validation: 'Lengte Validatie',
|
||||||
|
number_range: 'Getalbereik',
|
||||||
enter_pdf_password: 'Voer PDF-wachtwoord in',
|
enter_pdf_password: 'Voer PDF-wachtwoord in',
|
||||||
wrong_password: 'Onjuist wachtwoord',
|
wrong_password: 'Onjuist wachtwoord',
|
||||||
currency: 'Valuta',
|
currency: 'Valuta',
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
:is-drag="isDrag"
|
:is-drag="isDrag"
|
||||||
@move="onSelectionBoxMove"
|
@move="onSelectionBoxMove"
|
||||||
@contextmenu="openSelectionContextMenu"
|
@contextmenu="openSelectionContextMenu"
|
||||||
@close-context-menu="closeSelectionContextMenu"
|
@close-context-menu="closeContextMenu"
|
||||||
/>
|
/>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
v-for="(item, i) in areas"
|
v-for="(item, i) in areas"
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
:default-submitters="defaultSubmitters"
|
:default-submitters="defaultSubmitters"
|
||||||
:max-page="totalPages - 1"
|
:max-page="totalPages - 1"
|
||||||
:is-select-mode="isSelectMode"
|
:is-select-mode="isSelectMode"
|
||||||
|
:is-mobile="isMobile"
|
||||||
@start-resize="resizeDirection = $event"
|
@start-resize="resizeDirection = $event"
|
||||||
@stop-resize="resizeDirection = null"
|
@stop-resize="resizeDirection = null"
|
||||||
@remove="$emit('remove-area', item.area)"
|
@remove="$emit('remove-area', item.area)"
|
||||||
@@ -74,29 +75,39 @@
|
|||||||
class="absolute outline-dashed outline-gray-400 pointer-events-none z-20"
|
class="absolute outline-dashed outline-gray-400 pointer-events-none z-20"
|
||||||
:style="selectionRectStyle"
|
:style="selectionRectStyle"
|
||||||
/>
|
/>
|
||||||
<ContextMenu
|
<FieldContextMenu
|
||||||
v-if="contextMenu"
|
v-if="contextMenu && contextMenu.field"
|
||||||
:context-menu="contextMenu"
|
:context-menu="contextMenu"
|
||||||
:field="contextMenu.field"
|
:field="contextMenu.field"
|
||||||
|
:with-signature-id="withSignatureId"
|
||||||
|
:with-prefillable="withPrefillable"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:with-fields-detection="withFieldsDetection"
|
:default-field="defaultFieldsIndex[contextMenu.field.name]"
|
||||||
@copy="handleCopy"
|
@copy="handleCopy"
|
||||||
@delete="handleDelete"
|
@delete="handleDelete"
|
||||||
@paste="handlePaste"
|
|
||||||
@autodetect-fields="handleAutodetectFields"
|
|
||||||
@close="closeContextMenu"
|
@close="closeContextMenu"
|
||||||
|
@set-draw="$emit('set-draw', $event)"
|
||||||
|
@scroll-to="$emit('scroll-to', $event)"
|
||||||
|
@save="save"
|
||||||
|
@add-custom-field="$emit('add-custom-field', $event)"
|
||||||
/>
|
/>
|
||||||
<ContextMenu
|
<SelectionContextMenu
|
||||||
v-if="selectionContextMenu"
|
v-else-if="contextMenu && contextMenu.areas"
|
||||||
:context-menu="selectionContextMenu"
|
:context-menu="contextMenu"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:is-multi-selection="true"
|
|
||||||
:selected-areas="selectedAreasRef.value"
|
|
||||||
:template="template"
|
:template="template"
|
||||||
@copy="handleSelectionCopy"
|
@copy="handleSelectionCopy"
|
||||||
@delete="handleSelectionDelete"
|
@delete="handleSelectionDelete"
|
||||||
@align="handleSelectionAlign"
|
@close="closeContextMenu"
|
||||||
@close="closeSelectionContextMenu"
|
/>
|
||||||
|
<PageContextMenu
|
||||||
|
v-else-if="contextMenu && !contextMenu.field && !contextMenu.areas"
|
||||||
|
:context-menu="contextMenu"
|
||||||
|
:editable="editable"
|
||||||
|
:with-fields-detection="withFieldsDetection"
|
||||||
|
@paste="handlePaste"
|
||||||
|
@autodetect-fields="handleAutodetectFields"
|
||||||
|
@close="closeContextMenu"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -119,17 +130,21 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FieldArea from './area'
|
import FieldArea from './area'
|
||||||
import ContextMenu from './context_menu'
|
import FieldContextMenu from './field_context_menu'
|
||||||
|
import SelectionContextMenu from './selection_context_menu'
|
||||||
|
import PageContextMenu from './page_context_menu'
|
||||||
import SelectionBox from './selection_box'
|
import SelectionBox from './selection_box'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplatePage',
|
name: 'TemplatePage',
|
||||||
components: {
|
components: {
|
||||||
FieldArea,
|
FieldArea,
|
||||||
ContextMenu,
|
FieldContextMenu,
|
||||||
|
SelectionContextMenu,
|
||||||
|
PageContextMenu,
|
||||||
SelectionBox
|
SelectionBox
|
||||||
},
|
},
|
||||||
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'customDragFieldRef', 'assignDropAreaSize', 'selectedAreasRef', 'template', 'isSelectModeRef'],
|
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'customDragFieldRef', 'assignDropAreaSize', 'selectedAreasRef', 'template', 'isSelectModeRef', 'save'],
|
||||||
props: {
|
props: {
|
||||||
image: {
|
image: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -140,6 +155,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
isMobile: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
withSignatureId: {
|
withSignatureId: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -228,7 +248,7 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['draw', 'drop-field', 'remove-area', 'copy-field', 'paste-field', 'scroll-to', 'copy-selected-areas', 'delete-selected-areas', 'align-selected-areas', 'autodetect-fields', 'add-custom-field'],
|
emits: ['draw', 'drop-field', 'remove-area', 'copy-field', 'paste-field', 'scroll-to', 'copy-selected-areas', 'delete-selected-areas', 'autodetect-fields', 'add-custom-field', 'set-draw'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
areaRefs: [],
|
areaRefs: [],
|
||||||
@@ -236,8 +256,7 @@ export default {
|
|||||||
resizeDirection: null,
|
resizeDirection: null,
|
||||||
newAreas: [],
|
newAreas: [],
|
||||||
contextMenu: null,
|
contextMenu: null,
|
||||||
selectionRect: null,
|
selectionRect: null
|
||||||
selectionContextMenu: null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -305,11 +324,6 @@ export default {
|
|||||||
return 'text'
|
return 'text'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isMobile () {
|
|
||||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
|
||||||
|
|
||||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
|
||||||
},
|
|
||||||
resizeDirectionClasses () {
|
resizeDirectionClasses () {
|
||||||
return {
|
return {
|
||||||
nwse: 'cursor-nwse-resize',
|
nwse: 'cursor-nwse-resize',
|
||||||
@@ -399,29 +413,32 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
openSelectionContextMenu (event) {
|
openSelectionContextMenu (event) {
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
const rect = this.$el.getBoundingClientRect()
|
const rect = this.$el.getBoundingClientRect()
|
||||||
|
|
||||||
this.selectionContextMenu = {
|
this.contextMenu = {
|
||||||
x: event.clientX,
|
x: event.clientX,
|
||||||
y: event.clientY,
|
y: event.clientY,
|
||||||
relativeX: (event.clientX - rect.left) / rect.width,
|
relativeX: (event.clientX - rect.left) / rect.width,
|
||||||
relativeY: (event.clientY - rect.top) / rect.height
|
relativeY: (event.clientY - rect.top) / rect.height,
|
||||||
|
areas: this.selectedAreasRef.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closeSelectionContextMenu () {
|
|
||||||
this.selectionContextMenu = null
|
|
||||||
},
|
|
||||||
handleSelectionCopy () {
|
handleSelectionCopy () {
|
||||||
this.$emit('copy-selected-areas')
|
this.$emit('copy-selected-areas')
|
||||||
this.closeSelectionContextMenu()
|
|
||||||
|
this.closeContextMenu()
|
||||||
},
|
},
|
||||||
handleSelectionDelete () {
|
handleSelectionDelete () {
|
||||||
this.$emit('delete-selected-areas')
|
this.$emit('delete-selected-areas')
|
||||||
this.closeSelectionContextMenu()
|
|
||||||
},
|
this.closeContextMenu()
|
||||||
handleSelectionAlign (direction) {
|
|
||||||
this.$emit('align-selected-areas', direction)
|
|
||||||
this.closeSelectionContextMenu()
|
|
||||||
},
|
},
|
||||||
closeContextMenu () {
|
closeContextMenu () {
|
||||||
this.contextMenu = null
|
this.contextMenu = null
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="menu"
|
||||||
|
class="fixed z-50 p-1 bg-white shadow-lg rounded-lg border border-neutral-200 cursor-default"
|
||||||
|
style="min-width: 170px"
|
||||||
|
:style="menuStyle"
|
||||||
|
@mousedown.stop
|
||||||
|
@pointerdown.stop
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between text-sm"
|
||||||
|
:class="!hasClipboardData ? 'opacity-50 cursor-not-allowed' : 'hover:bg-neutral-100'"
|
||||||
|
:disabled="!hasClipboardData"
|
||||||
|
@click.stop="!hasClipboardData ? null : $emit('paste')"
|
||||||
|
>
|
||||||
|
<span class="flex items-center space-x-2">
|
||||||
|
<IconClipboard class="w-4 h-4" />
|
||||||
|
<span>{{ t('paste') }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-base-content/60 ml-4">{{ isMac ? '⌘V' : 'Ctrl+V' }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between text-sm"
|
||||||
|
@click.stop="handleToggleSelectMode"
|
||||||
|
>
|
||||||
|
<span class="flex items-center space-x-2">
|
||||||
|
<IconClick
|
||||||
|
v-if="!isSelectModeRef.value"
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
|
<IconNewSection
|
||||||
|
v-else
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
|
<span>{{ isSelectModeRef.value ? t('draw_fields') : t('select_fields') }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-base-content/60 ml-4">Tab</span>
|
||||||
|
</button>
|
||||||
|
<hr
|
||||||
|
v-if="showAutodetectFields"
|
||||||
|
class="my-1 border-neutral-200"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="showAutodetectFields"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="$emit('autodetect-fields')"
|
||||||
|
>
|
||||||
|
<IconSparkles class="w-4 h-4" />
|
||||||
|
<span>{{ t('autodetect_fields') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IconClipboard, IconClick, IconNewSection, IconSparkles } from '@tabler/icons-vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PageContextMenu',
|
||||||
|
components: {
|
||||||
|
IconClipboard,
|
||||||
|
IconClick,
|
||||||
|
IconNewSection,
|
||||||
|
IconSparkles
|
||||||
|
},
|
||||||
|
inject: ['t', 'isSelectModeRef'],
|
||||||
|
props: {
|
||||||
|
contextMenu: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
withFieldsDetection: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['paste', 'close', 'autodetect-fields'],
|
||||||
|
computed: {
|
||||||
|
isMac () {
|
||||||
|
return (navigator.userAgentData?.platform || navigator.platform)?.toLowerCase()?.includes('mac')
|
||||||
|
},
|
||||||
|
hasClipboardData () {
|
||||||
|
try {
|
||||||
|
const clipboard = localStorage.getItem('docuseal_clipboard')
|
||||||
|
|
||||||
|
if (clipboard) {
|
||||||
|
const data = JSON.parse(clipboard)
|
||||||
|
|
||||||
|
return Date.now() - data.timestamp < 3600000
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
menuStyle () {
|
||||||
|
return {
|
||||||
|
left: this.contextMenu.x + 'px',
|
||||||
|
top: this.contextMenu.y + 'px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showAutodetectFields () {
|
||||||
|
return this.withFieldsDetection && this.editable
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
document.addEventListener('keydown', this.onKeyDown)
|
||||||
|
document.addEventListener('mousedown', this.handleClickOutside)
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.checkMenuPosition()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
document.removeEventListener('keydown', this.onKeyDown)
|
||||||
|
document.removeEventListener('mousedown', this.handleClickOutside)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkMenuPosition () {
|
||||||
|
if (this.$refs.menu) {
|
||||||
|
const rect = this.$refs.menu.getBoundingClientRect()
|
||||||
|
const overflow = rect.bottom - window.innerHeight
|
||||||
|
|
||||||
|
if (overflow > 0) {
|
||||||
|
this.contextMenu.y = this.contextMenu.y - overflow - 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyDown (event) {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
this.$emit('close')
|
||||||
|
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v' && this.hasClipboardData) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
this.$emit('paste')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClickOutside (event) {
|
||||||
|
if (this.$refs.menu && !this.$refs.menu.contains(event.target)) {
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleToggleSelectMode () {
|
||||||
|
this.isSelectModeRef.value = !this.isSelectModeRef.value
|
||||||
|
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -119,6 +119,7 @@
|
|||||||
<ConditionsModal
|
<ConditionsModal
|
||||||
:item="item"
|
:item="item"
|
||||||
:build-default-name="buildDefaultName"
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="$emit('change')"
|
||||||
@close="isShowConditionsModal = false"
|
@close="isShowConditionsModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -145,7 +146,7 @@ export default {
|
|||||||
GoogleDriveDocumentSettings,
|
GoogleDriveDocumentSettings,
|
||||||
IconSortDescending2
|
IconSortDescending2
|
||||||
},
|
},
|
||||||
inject: ['t'],
|
inject: ['t', 'getFieldTypeIndex'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -0,0 +1,348 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="!isShowFontModal && !isShowConditionsModal"
|
||||||
|
ref="menu"
|
||||||
|
class="fixed z-50 p-1 bg-white shadow-lg rounded-lg border border-neutral-200 cursor-default"
|
||||||
|
style="min-width: 170px"
|
||||||
|
:style="menuStyle"
|
||||||
|
@mousedown.stop
|
||||||
|
@pointerdown.stop
|
||||||
|
>
|
||||||
|
<ContextSubmenu
|
||||||
|
:icon="IconLayoutAlignMiddle"
|
||||||
|
:label="t('align')"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="alignSelectedAreas('left')"
|
||||||
|
>
|
||||||
|
<IconLayoutAlignLeft class="w-4 h-4" />
|
||||||
|
<span>{{ t('align_left') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="alignSelectedAreas('right')"
|
||||||
|
>
|
||||||
|
<IconLayoutAlignRight class="w-4 h-4" />
|
||||||
|
<span>{{ t('align_right') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="alignSelectedAreas('top')"
|
||||||
|
>
|
||||||
|
<IconLayoutAlignTop class="w-4 h-4" />
|
||||||
|
<span>{{ t('align_top') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="alignSelectedAreas('bottom')"
|
||||||
|
>
|
||||||
|
<IconLayoutAlignBottom class="w-4 h-4" />
|
||||||
|
<span>{{ t('align_bottom') }}</span>
|
||||||
|
</button>
|
||||||
|
</ContextSubmenu>
|
||||||
|
<ContextSubmenu
|
||||||
|
:icon="IconAspectRatio"
|
||||||
|
:label="t('resize')"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="resizeSelectedAreas('width')"
|
||||||
|
>
|
||||||
|
<IconArrowsHorizontal class="w-4 h-4" />
|
||||||
|
<span>{{ t('width') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="resizeSelectedAreas('height')"
|
||||||
|
>
|
||||||
|
<IconArrowsVertical class="w-4 h-4" />
|
||||||
|
<span>{{ t('height') }}</span>
|
||||||
|
</button>
|
||||||
|
</ContextSubmenu>
|
||||||
|
<hr
|
||||||
|
v-if="showFont || showCondition"
|
||||||
|
class="my-1 border-neutral-200"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="showFont"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="openFontModal"
|
||||||
|
>
|
||||||
|
<IconTypography class="w-4 h-4" />
|
||||||
|
<span>{{ t('font') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="showCondition"
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center space-x-2 text-sm"
|
||||||
|
@click.stop="openConditionModal"
|
||||||
|
>
|
||||||
|
<IconRouteAltLeft class="w-4 h-4" />
|
||||||
|
<span>{{ t('condition') }}</span>
|
||||||
|
</button>
|
||||||
|
<hr class="my-1 border-neutral-200">
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between text-sm"
|
||||||
|
@click.stop="$emit('copy')"
|
||||||
|
>
|
||||||
|
<span class="flex items-center space-x-2">
|
||||||
|
<IconCopy class="w-4 h-4" />
|
||||||
|
<span>{{ t('copy') }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-base-content/60 ml-4">{{ isMac ? '⌘C' : 'Ctrl+C' }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between text-sm text-red-600"
|
||||||
|
@click.stop="$emit('delete')"
|
||||||
|
>
|
||||||
|
<span class="flex items-center space-x-2">
|
||||||
|
<IconTrashX class="w-4 h-4" />
|
||||||
|
<span>{{ t('remove') }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-base-content/60 ml-4">Del</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowFontModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<FontModal
|
||||||
|
:field="multiSelectField"
|
||||||
|
:area="contextMenu.area"
|
||||||
|
:editable="editable"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
@save="handleSaveMultiSelectFontModal"
|
||||||
|
@close="closeModal"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowConditionsModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<ConditionsModal
|
||||||
|
:item="multiSelectField"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
:exclude-field-uuids="selectedFields.map(f => f.uuid)"
|
||||||
|
@save="handleSaveMultiSelectConditionsModal"
|
||||||
|
@close="closeModal"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IconCopy, IconTrashX, IconTypography, IconRouteAltLeft, IconLayoutAlignLeft, IconLayoutAlignRight, IconLayoutAlignTop, IconLayoutAlignBottom, IconLayoutAlignMiddle, IconAspectRatio, IconArrowsHorizontal, IconArrowsVertical } from '@tabler/icons-vue'
|
||||||
|
import FontModal from './font_modal'
|
||||||
|
import ConditionsModal from './conditions_modal'
|
||||||
|
import ContextSubmenu from './field_context_submenu'
|
||||||
|
import Field from './field'
|
||||||
|
import FieldType from './field_type'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SelectionContextMenu',
|
||||||
|
components: {
|
||||||
|
IconCopy,
|
||||||
|
IconTrashX,
|
||||||
|
IconTypography,
|
||||||
|
IconRouteAltLeft,
|
||||||
|
IconLayoutAlignLeft,
|
||||||
|
IconLayoutAlignRight,
|
||||||
|
IconLayoutAlignTop,
|
||||||
|
IconLayoutAlignBottom,
|
||||||
|
FontModal,
|
||||||
|
IconArrowsHorizontal,
|
||||||
|
IconArrowsVertical,
|
||||||
|
ConditionsModal,
|
||||||
|
ContextSubmenu
|
||||||
|
},
|
||||||
|
inject: ['t', 'save', 'selectedAreasRef', 'getFieldTypeIndex'],
|
||||||
|
props: {
|
||||||
|
contextMenu: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
withCondition: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['copy', 'delete', 'close'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isShowFontModal: false,
|
||||||
|
isShowConditionsModal: false,
|
||||||
|
multiSelectField: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
modalContainerEl () {
|
||||||
|
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||||
|
},
|
||||||
|
selectedFields () {
|
||||||
|
return this.selectedAreasRef.value.map((area) => {
|
||||||
|
return this.template.fields.find((f) => f.areas?.includes(area))
|
||||||
|
}).filter(Boolean)
|
||||||
|
},
|
||||||
|
isMac () {
|
||||||
|
return (navigator.userAgentData?.platform || navigator.platform)?.toLowerCase()?.includes('mac')
|
||||||
|
},
|
||||||
|
menuStyle () {
|
||||||
|
return {
|
||||||
|
left: this.contextMenu.x + 'px',
|
||||||
|
top: this.contextMenu.y + 'px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showFont () {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
showCondition () {
|
||||||
|
return this.withCondition
|
||||||
|
},
|
||||||
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
|
fieldLabels: FieldType.computed.fieldLabels
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
document.addEventListener('keydown', this.onKeyDown)
|
||||||
|
document.addEventListener('mousedown', this.handleClickOutside)
|
||||||
|
|
||||||
|
this.$nextTick(() => this.checkMenuPosition())
|
||||||
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
document.removeEventListener('keydown', this.onKeyDown)
|
||||||
|
document.removeEventListener('mousedown', this.handleClickOutside)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
IconLayoutAlignMiddle,
|
||||||
|
IconAspectRatio,
|
||||||
|
buildDefaultName: Field.methods.buildDefaultName,
|
||||||
|
checkMenuPosition () {
|
||||||
|
if (this.$refs.menu) {
|
||||||
|
const rect = this.$refs.menu.getBoundingClientRect()
|
||||||
|
const overflow = rect.bottom - window.innerHeight
|
||||||
|
|
||||||
|
if (overflow > 0) {
|
||||||
|
this.contextMenu.y = this.contextMenu.y - overflow - 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyDown (event) {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClickOutside (event) {
|
||||||
|
if (this.$refs.menu && !this.$refs.menu.contains(event.target)) {
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openFontModal () {
|
||||||
|
this.multiSelectField = {
|
||||||
|
name: this.t('fields_selected').replace('{count}', this.selectedFields.length),
|
||||||
|
preferences: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferencesStrings = this.selectedFields.map((f) => JSON.stringify(f.preferences || {}))
|
||||||
|
|
||||||
|
if (preferencesStrings.every((s) => s === preferencesStrings[0])) {
|
||||||
|
this.multiSelectField.preferences = JSON.parse(preferencesStrings[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isShowFontModal = true
|
||||||
|
},
|
||||||
|
openConditionModal () {
|
||||||
|
this.multiSelectField = {
|
||||||
|
name: this.t('fields_selected').replace('{count}', this.selectedFields.length),
|
||||||
|
conditions: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionStrings = this.selectedFields.map((f) => JSON.stringify(f.conditions || []))
|
||||||
|
|
||||||
|
if (conditionStrings.every((s) => s === conditionStrings[0])) {
|
||||||
|
this.multiSelectField.conditions = JSON.parse(conditionStrings[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isShowConditionsModal = true
|
||||||
|
},
|
||||||
|
closeModal () {
|
||||||
|
this.isShowFontModal = false
|
||||||
|
this.isShowConditionsModal = false
|
||||||
|
this.multiSelectField = null
|
||||||
|
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
alignSelectedAreas (direction) {
|
||||||
|
const areas = this.selectedAreasRef.value
|
||||||
|
|
||||||
|
let targetValue
|
||||||
|
|
||||||
|
if (direction === 'left') {
|
||||||
|
targetValue = Math.min(...areas.map(a => a.x))
|
||||||
|
areas.forEach((area) => { area.x = targetValue })
|
||||||
|
} else if (direction === 'right') {
|
||||||
|
targetValue = Math.max(...areas.map(a => a.x + a.w))
|
||||||
|
areas.forEach((area) => { area.x = targetValue - area.w })
|
||||||
|
} else if (direction === 'top') {
|
||||||
|
targetValue = Math.min(...areas.map(a => a.y))
|
||||||
|
areas.forEach((area) => { area.y = targetValue })
|
||||||
|
} else if (direction === 'bottom') {
|
||||||
|
targetValue = Math.max(...areas.map(a => a.y + a.h))
|
||||||
|
areas.forEach((area) => { area.y = targetValue - area.h })
|
||||||
|
}
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
resizeSelectedAreas (dimension) {
|
||||||
|
const areas = this.selectedAreasRef.value
|
||||||
|
|
||||||
|
const values = areas.map(a => dimension === 'width' ? a.w : a.h).sort((a, b) => a - b)
|
||||||
|
const medianValue = values[Math.floor(values.length / 2)]
|
||||||
|
|
||||||
|
if (dimension === 'width') {
|
||||||
|
areas.forEach((area) => { area.w = medianValue })
|
||||||
|
} else if (dimension === 'height') {
|
||||||
|
areas.forEach((area) => {
|
||||||
|
const diff = medianValue - area.h
|
||||||
|
area.y = area.y - diff
|
||||||
|
area.h = medianValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
handleSaveMultiSelectFontModal () {
|
||||||
|
this.selectedFields.forEach((field) => {
|
||||||
|
field.preferences = { ...field.preferences, ...this.multiSelectField.preferences }
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
|
||||||
|
this.closeModal()
|
||||||
|
},
|
||||||
|
handleSaveMultiSelectConditionsModal () {
|
||||||
|
this.selectedFields.forEach((field) => {
|
||||||
|
field.conditions = JSON.parse(JSON.stringify(this.multiSelectField.conditions))
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
|
||||||
|
this.closeModal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -290,7 +290,11 @@ export default {
|
|||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
resp.json().then((data) => {
|
resp.json().then((data) => {
|
||||||
this.$emit('success', data)
|
this.$emit('success', data)
|
||||||
this.$refs.input.value = ''
|
|
||||||
|
if (this.$refs.input) {
|
||||||
|
this.$refs.input.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
})
|
})
|
||||||
} else if (resp.status === 422) {
|
} else if (resp.status === 422) {
|
||||||
|
|||||||
@@ -164,11 +164,13 @@ class ProcessSubmitterCompletionJob
|
|||||||
next_submitter_items =
|
next_submitter_items =
|
||||||
if submission.template_submitters.any? { |s| s['order'] }
|
if submission.template_submitters.any? { |s| s['order'] }
|
||||||
submitter_groups =
|
submitter_groups =
|
||||||
submission.template_submitters.group_by.with_index { |s, index| s['order'] || index }
|
submission.template_submitters
|
||||||
|
.group_by.with_index { |s, index| s['order'] || index }
|
||||||
|
.sort_by(&:first).pluck(1)
|
||||||
|
|
||||||
current_group_index = submitter_groups.find { |_, group| group.any? { |s| s['uuid'] == submitter.uuid } }&.first
|
current_group_index = submitter_groups.index { |group| group.any? { |s| s['uuid'] == submitter.uuid } }
|
||||||
|
|
||||||
if submitter_groups[current_group_index + 1] &&
|
if current_group_index && submitter_groups[current_group_index + 1] &&
|
||||||
submitters_index.values_at(*submitter_groups[current_group_index].pluck('uuid'))
|
submitters_index.values_at(*submitter_groups[current_group_index].pluck('uuid'))
|
||||||
.compact.all?(&:completed_at?)
|
.compact.all?(&:completed_at?)
|
||||||
submitter_groups[current_group_index + 1]
|
submitter_groups[current_group_index + 1]
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :security_label, 'SMTP Security', class: 'label' %>
|
<%= ff.label :security_label, 'SMTP Security', class: 'label' %>
|
||||||
<div class="flex items-center space-x-6">
|
<div class="flex items-center space-x-6">
|
||||||
<% [%w[Auto none], %w[SSL ssl], %w[TLS tls], %w[Noverify noverify]].each do |(label, val)| %>
|
<% [%w[STARTTLS none], %w[TLS tls], %w[SSL ssl], %w[Noverify noverify]].each do |(label, val)| %>
|
||||||
<%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %>
|
<%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %>
|
||||||
<%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
|
<%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
|
||||||
<%= label %>
|
<%= label %>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% elsif field['type'] == 'cells' && area['cell_w'].to_f > 0.0 %>
|
<% elsif field['type'] == 'cells' && area['cell_w'].to_f > 0.0 %>
|
||||||
<% cell_width = area['cell_w'] / area['w'] * 100 %>
|
<% cell_width = area['cell_w'] / area['w'] * 100 %>
|
||||||
<div class="w-full flex items-center <%= 'justify-end' if align == 'right' %>">
|
<div class="w-full flex <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %> <%= 'justify-end' if align == 'right' %>">
|
||||||
<% (0..(area['w'] / area['cell_w']).ceil).each do |index| %>
|
<% (0..(area['w'] / area['cell_w']).ceil).each do |index| %>
|
||||||
<% if value[index] %>
|
<% if value[index] %>
|
||||||
<div class="text-center flex-none" style="width: <%= cell_width %>%;"><%= value[index] %></div>
|
<div class="text-center flex-none" style="width: <%= cell_width %>%;"><%= value[index] %></div>
|
||||||
|
|||||||
+2
-3
@@ -30,9 +30,8 @@ production:
|
|||||||
<% elsif ENV['DATABASE_URL'].match?(/\Apostgres/) %>
|
<% elsif ENV['DATABASE_URL'].match?(/\Apostgres/) %>
|
||||||
<<: *default
|
<<: *default
|
||||||
url: <%= ENV['DATABASE_URL'] %>
|
url: <%= ENV['DATABASE_URL'] %>
|
||||||
<% elsif ENV['DATABASE_URL'].match?(/\Amysql/) %>
|
<% elsif ENV['DATABASE_URL'].match?(/\Amysql|\Atrilogy/) %>
|
||||||
adapter: mysql2
|
|
||||||
encoding: utf8mb4
|
encoding: utf8mb4
|
||||||
pool: <%= ENV.fetch('RAILS_MAX_THREADS', 15).to_i + ENV.fetch('SIDEKIQ_THREADS', 5).to_i %>
|
pool: <%= ENV.fetch('RAILS_MAX_THREADS', 15).to_i + ENV.fetch('SIDEKIQ_THREADS', 5).to_i %>
|
||||||
url: <%= ENV['DATABASE_URL'] %>
|
url: <%= ENV['DATABASE_URL'].sub(/\Amysql2?/, 'trilogy') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ Rails.application.configure do
|
|||||||
domain: ENV.fetch('SMTP_DOMAIN', nil),
|
domain: ENV.fetch('SMTP_DOMAIN', nil),
|
||||||
user_name: ENV.fetch('SMTP_USERNAME', nil),
|
user_name: ENV.fetch('SMTP_USERNAME', nil),
|
||||||
password: ENV.fetch('SMTP_PASSWORD', nil),
|
password: ENV.fetch('SMTP_PASSWORD', nil),
|
||||||
|
openssl_verify_mode: ENV['SMTP_SSL_VERIFY'] == 'false' ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER,
|
||||||
authentication: ENV.fetch('SMTP_PASSWORD', nil).present? ? ENV.fetch('SMTP_AUTHENTICATION', 'plain') : nil,
|
authentication: ENV.fetch('SMTP_PASSWORD', nil).present? ? ENV.fetch('SMTP_AUTHENTICATION', 'plain') : nil,
|
||||||
enable_starttls: ENV['SMTP_ENABLE_STARTTLS'] != 'false'
|
enable_starttls: ENV['SMTP_ENABLE_STARTTLS'] != 'false'
|
||||||
}.compact
|
}.compact
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ module ActionMailerConfigsInterceptor
|
|||||||
|
|
||||||
is_tls = value['security'] == 'tls' || (value['security'].blank? && value['port'].to_s == '465')
|
is_tls = value['security'] == 'tls' || (value['security'].blank? && value['port'].to_s == '465')
|
||||||
is_ssl = value['security'] == 'ssl'
|
is_ssl = value['security'] == 'ssl'
|
||||||
|
is_noverify = value['security'] == 'noverify'
|
||||||
|
|
||||||
|
enable_starttls = is_noverify ? :enable_starttls_auto : :enable_starttls
|
||||||
|
|
||||||
{
|
{
|
||||||
user_name: value['username'],
|
user_name: value['username'],
|
||||||
@@ -54,9 +57,9 @@ module ActionMailerConfigsInterceptor
|
|||||||
address: value['host'],
|
address: value['host'],
|
||||||
port: value['port'],
|
port: value['port'],
|
||||||
domain: value['domain'],
|
domain: value['domain'],
|
||||||
openssl_verify_mode: value['security'] == 'noverify' ? OpenSSL::SSL::VERIFY_NONE : nil,
|
openssl_verify_mode: is_noverify ? OpenSSL::SSL::VERIFY_NONE : nil,
|
||||||
authentication: value['password'].present? ? value.fetch('authentication', 'plain') : nil,
|
authentication: value['password'].present? ? value.fetch('authentication', 'plain') : nil,
|
||||||
enable_starttls: !is_tls && !is_ssl,
|
enable_starttls => !is_tls && !is_ssl,
|
||||||
open_timeout: OPEN_TIMEOUT,
|
open_timeout: OPEN_TIMEOUT,
|
||||||
read_timeout: READ_TIMEOUT,
|
read_timeout: READ_TIMEOUT,
|
||||||
ssl: is_ssl,
|
ssl: is_ssl,
|
||||||
|
|||||||
@@ -6,9 +6,16 @@ class NormalizeClientIpMiddleware
|
|||||||
end
|
end
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
if env['HTTP_CLIENT_IP'].present? && env['HTTP_X_CLIENT_IP'].present? &&
|
if env['HTTP_CLIENT_IP'].present?
|
||||||
env['HTTP_CLIENT_IP'].starts_with?("#{env['HTTP_X_CLIENT_IP']}:")
|
if env['HTTP_X_CLIENT_IP'].present? &&
|
||||||
env['HTTP_CLIENT_IP'] = env['HTTP_X_CLIENT_IP']
|
env['HTTP_CLIENT_IP'].starts_with?("#{env['HTTP_X_CLIENT_IP']}:")
|
||||||
|
env['HTTP_CLIENT_IP'] = env['HTTP_X_CLIENT_IP']
|
||||||
|
end
|
||||||
|
|
||||||
|
if env['HTTP_X_FORWARDED_FOR'].present? &&
|
||||||
|
env['HTTP_X_FORWARDED_FOR'].sub(/:\d+\z/, '') == env['HTTP_CLIENT_IP']
|
||||||
|
env['HTTP_X_FORWARDED_FOR'] = env['HTTP_CLIENT_IP']
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@app.call(env)
|
@app.call(env)
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ module Submissions
|
|||||||
|
|
||||||
submission.template_submitters << template_submitter
|
submission.template_submitters << template_submitter
|
||||||
|
|
||||||
is_order_sent = submitters_order == 'random' || (template_submitter['order'] || index).zero?
|
is_order_sent = submitters_order == 'random' ||
|
||||||
|
(template_submitter['order'] || submitter_attrs[:index] || index).zero?
|
||||||
|
|
||||||
build_submitter(submission:, attrs: submitter_attrs,
|
build_submitter(submission:, attrs: submitter_attrs,
|
||||||
uuid:, is_order_sent:, user:, params:,
|
uuid:, is_order_sent:, user:, params:,
|
||||||
@@ -308,7 +309,7 @@ module Submissions
|
|||||||
uuid = attrs[:uuid].presence
|
uuid = attrs[:uuid].presence
|
||||||
uuid ||= submitters.find { |e| e['name'].to_s.casecmp(attrs[:role].to_s).zero? }&.dig('uuid')
|
uuid ||= submitters.find { |e| e['name'].to_s.casecmp(attrs[:role].to_s).zero? }&.dig('uuid')
|
||||||
|
|
||||||
uuid || submitters[index]&.dig('uuid')
|
uuid || submitters[attrs[:index] || index]&.dig('uuid')
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_submitter(submission:, attrs:, uuid:, is_order_sent:, user:, preferences:, params:)
|
def build_submitter(submission:, attrs:, uuid:, is_order_sent:, user:, preferences:, params:)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ module Submissions
|
|||||||
total_wait_time ||= 0
|
total_wait_time ||= 0
|
||||||
key = ['result_attachments', submitter.id].join(':')
|
key = ['result_attachments', submitter.id].join(':')
|
||||||
|
|
||||||
return submitter.documents.reset if ApplicationRecord.uncached { LockEvent.exists?(key:, event_name: :complete) }
|
return submitter.documents.reload if ApplicationRecord.uncached { LockEvent.exists?(key:, event_name: :complete) }
|
||||||
|
|
||||||
events = ApplicationRecord.uncached { LockEvent.where(key:).order(:id).to_a }
|
events = ApplicationRecord.uncached { LockEvent.where(key:).order(:id).to_a }
|
||||||
|
|
||||||
@@ -32,6 +32,8 @@ module Submissions
|
|||||||
|
|
||||||
LockEvent.create!(key:, event_name: :complete)
|
LockEvent.create!(key:, event_name: :complete)
|
||||||
|
|
||||||
|
submitter.documents.reset
|
||||||
|
|
||||||
documents
|
documents
|
||||||
end
|
end
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
@@ -60,7 +62,7 @@ module Submissions
|
|||||||
LockEvent.where(key: ['result_attachments', submitter.id].join(':')).order(:id).last
|
LockEvent.where(key: ['result_attachments', submitter.id].join(':')).order(:id).last
|
||||||
end
|
end
|
||||||
|
|
||||||
break submitter.documents.reset if last_event.event_name.in?(%w[complete fail])
|
break submitter.documents.reload if last_event.event_name.in?(%w[complete fail])
|
||||||
|
|
||||||
raise WaitForCompleteTimeout if total_wait_time > CHECK_COMPLETE_TIMEOUT
|
raise WaitForCompleteTimeout if total_wait_time > CHECK_COMPLETE_TIMEOUT
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -200,7 +200,9 @@ module Submissions
|
|||||||
|
|
||||||
def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:, with_headings: nil,
|
def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:, with_headings: nil,
|
||||||
with_submitter_timezone: false, with_signature_id_reason: true, with_file_links: nil)
|
with_submitter_timezone: false, with_signature_id_reason: true, with_file_links: nil)
|
||||||
cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center)
|
cell_layouters = Hash.new do |hash, valign|
|
||||||
|
hash[valign] = HexaPDF::Layout::TextLayouter.new(text_valign: valign.to_sym, text_align: :center)
|
||||||
|
end
|
||||||
|
|
||||||
attachments_data_cache = {}
|
attachments_data_cache = {}
|
||||||
|
|
||||||
@@ -550,6 +552,8 @@ 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
|
||||||
|
cell_valign = field.dig('preferences', 'valign').to_s.presence || 'center'
|
||||||
|
cell_layouter = cell_layouters[cell_valign]
|
||||||
|
|
||||||
if (mask = field.dig('preferences', 'mask').presence)
|
if (mask = field.dig('preferences', 'mask').presence)
|
||||||
value = TextUtils.mask_value(value, mask)
|
value = TextUtils.mask_value(value, mask)
|
||||||
@@ -790,10 +794,10 @@ module Submissions
|
|||||||
def build_pdfs_index(submission, submitter: nil, flatten: true)
|
def build_pdfs_index(submission, submitter: nil, flatten: true)
|
||||||
latest_submitter = find_last_submitter(submission, submitter:)
|
latest_submitter = find_last_submitter(submission, submitter:)
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter
|
documents = Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter
|
||||||
|
documents ||= submission.schema_documents
|
||||||
|
|
||||||
documents = latest_submitter&.documents&.preload(:blob).to_a.presence
|
ActiveRecord::Associations::Preloader.new(records: documents, associations: [:blob]).call
|
||||||
documents ||= submission.schema_documents.preload(:blob)
|
|
||||||
|
|
||||||
attachment_uuids = Submissions.filtered_conditions_schema(submission).pluck('attachment_uuid')
|
attachment_uuids = Submissions.filtered_conditions_schema(submission).pluck('attachment_uuid')
|
||||||
attachments_index = documents.index_by { |a| a.metadata['original_uuid'] || a.uuid }
|
attachments_index = documents.index_by { |a| a.metadata['original_uuid'] || a.uuid }
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ module Submissions
|
|||||||
|
|
||||||
Array.wrap(submissions_params).each do |submission|
|
Array.wrap(submissions_params).each do |submission|
|
||||||
submission[:submitters].each_with_index do |submitter, index|
|
submission[:submitters].each_with_index do |submitter, index|
|
||||||
_, new_attachments, new_fields = normalize_submitter_params!(submitter, template, index, add_fields:)
|
_, new_attachments, new_fields =
|
||||||
|
normalize_submitter_params!(submitter, template, submitter[:index] || index, add_fields:)
|
||||||
|
|
||||||
attachments.push(*new_attachments)
|
attachments.push(*new_attachments)
|
||||||
fields.push(*new_fields)
|
fields.push(*new_fields)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ module Submissions
|
|||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
|
# rubocop:disable Metrics
|
||||||
def call(submission, submitters = nil, params = {}, with_events: true, with_documents: true, with_values: true,
|
def call(submission, submitters = nil, params = {}, with_events: true, with_documents: true, with_values: true,
|
||||||
expires_at: Accounts.link_expires_at(Account.new(id: submission.account_id)))
|
expires_at: Accounts.link_expires_at(Account.new(id: submission.account_id)))
|
||||||
submitters ||= submission.submitters.preload(documents_attachments: :blob, attachments_attachments: :blob)
|
submitters ||= submission.submitters.preload(documents_attachments: :blob, attachments_attachments: :blob)
|
||||||
@@ -32,6 +33,10 @@ module Submissions
|
|||||||
json['submission_events'] = Submitters::SerializeForApi.serialize_events(submission.submission_events)
|
json['submission_events'] = Submitters::SerializeForApi.serialize_events(submission.submission_events)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if params[:include].to_s.include?('fields')
|
||||||
|
json['fields'] = submission.template_fields || submission.template&.fields
|
||||||
|
end
|
||||||
|
|
||||||
if submitters.all?(&:completed_at?)
|
if submitters.all?(&:completed_at?)
|
||||||
last_submitter = submitters.max_by(&:completed_at)
|
last_submitter = submitters.max_by(&:completed_at)
|
||||||
|
|
||||||
@@ -57,6 +62,7 @@ module Submissions
|
|||||||
|
|
||||||
json
|
json
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Metrics
|
||||||
|
|
||||||
def build_status(submission, submitters)
|
def build_status(submission, submitters)
|
||||||
if submitters.any?(&:declined_at?)
|
if submitters.any?(&:declined_at?)
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ module Submitters
|
|||||||
maybe_set_signature_reason!(values, submitter, params)
|
maybe_set_signature_reason!(values, submitter, params)
|
||||||
validate_values!(values, submitter, params, request)
|
validate_values!(values, submitter, params, request)
|
||||||
|
|
||||||
|
if (touch_attachment_uuid = params[:touch_attachment_uuid].presence)
|
||||||
|
ActiveStorage::Attachment.where(uuid: touch_attachment_uuid, record: submitter).touch_all(:created_at)
|
||||||
|
end
|
||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request) if params[:completed] == 'true'
|
SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request) if params[:completed] == 'true'
|
||||||
|
|
||||||
submitter.save!
|
submitter.save!
|
||||||
@@ -317,17 +321,33 @@ module Submitters
|
|||||||
end.exclude?(false)
|
end.exclude?(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# rubocop:disable Metrics
|
||||||
def check_field_condition(condition, submitter_values, fields_uuid_index)
|
def check_field_condition(condition, submitter_values, fields_uuid_index)
|
||||||
value = submitter_values[condition['field_uuid']]
|
value = submitter_values[condition['field_uuid']]
|
||||||
|
field = fields_uuid_index[condition['field_uuid']]
|
||||||
|
|
||||||
case condition['action']
|
case condition['action']
|
||||||
when 'empty', 'unchecked'
|
when 'empty', 'unchecked'
|
||||||
value.blank?
|
value.blank?
|
||||||
when 'not_empty', 'checked'
|
when 'not_empty', 'checked'
|
||||||
value.present?
|
value.present?
|
||||||
when 'equal', 'contains'
|
when ->(action) { action == 'equal' && field&.dig('type') == 'number' }
|
||||||
field = fields_uuid_index[condition['field_uuid']]
|
return false if value.blank? || condition['value'].blank?
|
||||||
|
|
||||||
|
(value.to_f - condition['value'].to_f).abs < Float::EPSILON
|
||||||
|
when ->(action) { action == 'not_equal' && field&.dig('type') == 'number' }
|
||||||
|
return false if value.blank? || condition['value'].blank?
|
||||||
|
|
||||||
|
(value.to_f - condition['value'].to_f).abs > Float::EPSILON
|
||||||
|
when 'greater_than'
|
||||||
|
return false if field.nil? || value.blank? || condition['value'].blank?
|
||||||
|
|
||||||
|
value.to_f > condition['value'].to_f
|
||||||
|
when 'less_than'
|
||||||
|
return false if field.nil? || value.blank? || condition['value'].blank?
|
||||||
|
|
||||||
|
value.to_f < condition['value'].to_f
|
||||||
|
when 'equal', 'contains'
|
||||||
return true unless field
|
return true unless field
|
||||||
|
|
||||||
values = Array.wrap(value)
|
values = Array.wrap(value)
|
||||||
@@ -340,8 +360,6 @@ module Submitters
|
|||||||
|
|
||||||
values.include?(option['value'].presence || "#{I18n.t('option')} #{field['options'].index(option) + 1}")
|
values.include?(option['value'].presence || "#{I18n.t('option')} #{field['options'].index(option) + 1}")
|
||||||
when 'not_equal', 'does_not_contain'
|
when 'not_equal', 'does_not_contain'
|
||||||
field = fields_uuid_index[condition['field_uuid']]
|
|
||||||
|
|
||||||
return true unless field
|
return true unless field
|
||||||
return false unless field['options']
|
return false unless field['options']
|
||||||
|
|
||||||
@@ -356,6 +374,7 @@ module Submitters
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Metrics
|
||||||
|
|
||||||
def replace_default_variables(value, attrs, submission, with_time: false)
|
def replace_default_variables(value, attrs, submission, with_time: false)
|
||||||
return value if value.in?([true, false]) || value.is_a?(Numeric) || value.is_a?(Array)
|
return value if value.in?([true, false]) || value.is_a?(Numeric) || value.is_a?(Array)
|
||||||
|
|||||||
Reference in New Issue
Block a user