mirror of
https://github.com/docusealco/docuseal
synced 2026-11-05 06:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2505768e4d | ||
|
|
23a1b70534 | ||
|
|
b34616ecbc | ||
|
|
0111252f6f | ||
|
|
a561cc5f73 | ||
|
|
519e6f1f79 | ||
|
|
da871b5d3a | ||
|
|
ae3f939d23 | ||
|
|
fb6ede564e | ||
|
|
1c701d4fe8 | ||
|
|
c743cc689d | ||
|
|
8432bec3d6 | ||
|
|
8e72b0b0e4 | ||
|
|
19e4bbc01e | ||
|
|
56a9e412c8 | ||
|
|
4b75623b86 | ||
|
|
e1ea42a997 | ||
|
|
ee37710e57 | ||
|
|
ae99218789 | ||
|
|
9fe8214ca0 | ||
|
|
6ca44a5cc3 | ||
|
|
801abd197b | ||
|
|
970977e469 | ||
|
|
f83674b934 | ||
|
|
57c7dc1b5e |
No files matched your search
@@ -11,6 +11,8 @@ class AccountConfigsController < ApplicationController
|
|||||||
AccountConfig::ESIGNING_PREFERENCE_KEY
|
AccountConfig::ESIGNING_PREFERENCE_KEY
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
|
InvalidKey = Class.new(StandardError)
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@account_config.update!(account_config_params)
|
@account_config.update!(account_config_params)
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ class AccountConfigsController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def load_account_config
|
def load_account_config
|
||||||
return head :not_found unless ALLOWED_KEYS.include?(account_config_params[:key])
|
raise InvalidKey unless ALLOWED_KEYS.include?(account_config_params[:key])
|
||||||
|
|
||||||
@account_config =
|
@account_config =
|
||||||
AccountConfig.find_or_initialize_by(account: current_account, key: account_config_params[:key])
|
AccountConfig.find_or_initialize_by(account: current_account, key: account_config_params[:key])
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module Api
|
|||||||
def show
|
def show
|
||||||
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
|
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
|
||||||
|
|
||||||
if blob_uuid.blank? || purp != 'blob' || (exp && exp < Time.current.to_i)
|
if blob_uuid.blank? || (purp.present? && purp != 'blob') || (exp && exp < Time.current.to_i)
|
||||||
Rollbar.error('Blob not found') if defined?(Rollbar)
|
Rollbar.error('Blob not found') if defined?(Rollbar)
|
||||||
|
|
||||||
return head :not_found
|
return head :not_found
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ module Api
|
|||||||
if SELECT_COLUMNS.include?(params[:field])
|
if SELECT_COLUMNS.include?(params[:field])
|
||||||
column = Submitter.arel_table[params[:field].to_sym]
|
column = Submitter.arel_table[params[:field].to_sym]
|
||||||
|
|
||||||
term = "%#{params[:q].downcase}%"
|
term = "#{params[:q].downcase}%"
|
||||||
|
|
||||||
submitters.where(column.lower.matches(term))
|
submitters.where(column.matches(term, false, true))
|
||||||
else
|
else
|
||||||
Submitters.search(submitters, params[:q])
|
Submitters.search(submitters, params[:q])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,16 +20,7 @@ module Api
|
|||||||
|
|
||||||
Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
||||||
|
|
||||||
render json: cloned_template.as_json(serialize_params)
|
render json: Templates::SerializeForApi.call(cloned_template)
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def serialize_params
|
|
||||||
{
|
|
||||||
include: { author: { only: %i[id email first_name last_name] },
|
|
||||||
documents: { only: %i[id uuid], methods: %i[url filename] } }
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -89,6 +89,7 @@ module Api
|
|||||||
submitters: [%i[name uuid]],
|
submitters: [%i[name uuid]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value,
|
fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value,
|
||||||
{ preferences: {},
|
{ preferences: {},
|
||||||
|
conditions: [%i[field_uuid value action]],
|
||||||
options: [%i[value uuid]],
|
options: [%i[value uuid]],
|
||||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -54,10 +54,14 @@ class DashboardController < ApplicationController
|
|||||||
rel = templates.active.preload(:author).order(id: :desc)
|
rel = templates.active.preload(:author).order(id: :desc)
|
||||||
|
|
||||||
if params[:q].blank?
|
if params[:q].blank?
|
||||||
shared_template_ids =
|
if Docuseal.multitenant? && !current_account.testing?
|
||||||
TemplateSharing.where(account_id: [current_account.id, TemplateSharing::ALL_ID]).select(:template_id)
|
rel = rel.where(folder_id: current_account.default_template_folder.id)
|
||||||
|
else
|
||||||
|
shared_template_ids =
|
||||||
|
TemplateSharing.where(account_id: [current_account.id, TemplateSharing::ALL_ID]).select(:template_id)
|
||||||
|
|
||||||
rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids))
|
rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Templates.search(rel, params[:q])
|
Templates.search(rel, params[:q])
|
||||||
|
|||||||
@@ -1,23 +1,42 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PersonalizationSettingsController < ApplicationController
|
class PersonalizationSettingsController < ApplicationController
|
||||||
|
ALLOWED_KEYS = [
|
||||||
|
AccountConfig::FORM_COMPLETED_BUTTON_KEY,
|
||||||
|
AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY,
|
||||||
|
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY,
|
||||||
|
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
InvalidKey = Class.new(StandardError)
|
||||||
|
|
||||||
|
before_action :load_and_authorize_account_config, only: :create
|
||||||
|
|
||||||
def show
|
def show
|
||||||
authorize!(:read, AccountConfig)
|
authorize!(:read, AccountConfig)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
account_config =
|
@account_config.save!
|
||||||
current_account.account_configs.find_or_initialize_by(key: account_config_params[:key])
|
|
||||||
|
|
||||||
authorize!(:create, account_config)
|
|
||||||
|
|
||||||
account_config.update!(account_config_params)
|
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_personalization_path, notice: 'Settings have been saved.')
|
redirect_back(fallback_location: settings_personalization_path, notice: 'Settings have been saved.')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def load_and_authorize_account_config
|
||||||
|
@account_config =
|
||||||
|
current_account.account_configs.find_or_initialize_by(key: account_config_params[:key])
|
||||||
|
|
||||||
|
@account_config.assign_attributes(account_config_params)
|
||||||
|
|
||||||
|
authorize!(:create, @account_config)
|
||||||
|
|
||||||
|
raise InvalidKey unless ALLOWED_KEYS.include?(@account_config.key)
|
||||||
|
|
||||||
|
@account_config
|
||||||
|
end
|
||||||
|
|
||||||
def account_config_params
|
def account_config_params
|
||||||
attrs = params.require(:account_config).permit!
|
attrs = params.require(:account_config).permit!
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class RegistrationsController < Devise::RegistrationsController
|
|
||||||
prepend_before_action :require_no_authentication, only: [:show]
|
|
||||||
prepend_before_action :maybe_redirect_if_signed_in, only: [:show]
|
|
||||||
|
|
||||||
def show; end
|
|
||||||
|
|
||||||
def create
|
|
||||||
super
|
|
||||||
|
|
||||||
Accounts.create_default_template(resource.account) if resource.account.persisted?
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def after_sign_up_path_for(...)
|
|
||||||
if params[:redir].present?
|
|
||||||
return console_redirect_index_path(redir: params[:redir]) if params[:redir].starts_with?(Docuseal::CONSOLE_URL)
|
|
||||||
|
|
||||||
return params[:redir]
|
|
||||||
end
|
|
||||||
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_redirect_if_signed_in
|
|
||||||
return unless signed_in?
|
|
||||||
return if params[:redir].blank?
|
|
||||||
|
|
||||||
redirect_to after_sign_up_path_for(current_user), allow_other_host: true
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_flash_message(key, kind, options = {})
|
|
||||||
return if key == :alert && kind == 'already_authenticated'
|
|
||||||
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_resource(_hash = {})
|
|
||||||
account = Account.new(account_params)
|
|
||||||
account.timezone = Accounts.normalize_timezone(account.timezone)
|
|
||||||
|
|
||||||
self.resource = account.users.new(user_params)
|
|
||||||
|
|
||||||
account.name ||= resource.full_name if params[:action] == 'create'
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_params
|
|
||||||
return {} if params[:user].blank?
|
|
||||||
|
|
||||||
params.require(:user).permit(:first_name, :last_name, :email, :password).compact_blank.tap do |attrs|
|
|
||||||
attrs[:password] ||= SecureRandom.hex if params[:action] == 'create'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_params
|
|
||||||
return {} if params[:account].blank?
|
|
||||||
|
|
||||||
params.require(:account).permit(:name, :timezone).compact_blank
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -8,6 +8,8 @@ class UserConfigsController < ApplicationController
|
|||||||
UserConfig::RECEIVE_COMPLETED_EMAIL
|
UserConfig::RECEIVE_COMPLETED_EMAIL
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
|
InvalidKey = Class.new(StandardError)
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@user_config.update!(user_config_params)
|
@user_config.update!(user_config_params)
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ class UserConfigsController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def load_user_config
|
def load_user_config
|
||||||
return head :not_found unless ALLOWED_KEYS.include?(user_config_params[:key])
|
raise InvalidKey unless ALLOWED_KEYS.include?(user_config_params[:key])
|
||||||
|
|
||||||
@user_config =
|
@user_config =
|
||||||
UserConfig.find_or_initialize_by(user: current_user, key: user_config_params[:key])
|
UserConfig.find_or_initialize_by(user: current_user, key: user_config_params[:key])
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ window.customElements.define('template-builder', class extends HTMLElement {
|
|||||||
editable: this.dataset.editable !== 'false',
|
editable: this.dataset.editable !== 'false',
|
||||||
withPayment: this.dataset.withPayment === 'true',
|
withPayment: this.dataset.withPayment === 'true',
|
||||||
withFormula: this.dataset.withFormula === 'true',
|
withFormula: this.dataset.withFormula === 'true',
|
||||||
|
withConditions: this.dataset.withConditions === 'true',
|
||||||
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
||||||
acceptFileTypes: this.dataset.acceptFileTypes,
|
acceptFileTypes: this.dataset.acceptFileTypes,
|
||||||
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
isDirectUpload: this.dataset.isDirectUpload === 'true'
|
||||||
|
|||||||
@@ -125,3 +125,7 @@ button[disabled] .enabled {
|
|||||||
outline-offset: 3px;
|
outline-offset: 3px;
|
||||||
outline-color: hsl(var(--bc) / 0.2);
|
outline-color: hsl(var(--bc) / 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select:required:invalid {
|
||||||
|
color: gray !important;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ export default class extends HTMLElement {
|
|||||||
preventSubmit: 1,
|
preventSubmit: 1,
|
||||||
minLength: 1,
|
minLength: 1,
|
||||||
showOnFocus: true,
|
showOnFocus: true,
|
||||||
|
debounceWaitMs: 200,
|
||||||
onSelect: this.onSelect,
|
onSelect: this.onSelect,
|
||||||
render: this.render,
|
render: this.render,
|
||||||
fetch: this.fetch
|
fetch: this.fetch
|
||||||
@@ -37,7 +38,9 @@ export default class extends HTMLElement {
|
|||||||
if (q) {
|
if (q) {
|
||||||
const queryParams = new URLSearchParams({ q, field: this.dataset.field })
|
const queryParams = new URLSearchParams({ q, field: this.dataset.field })
|
||||||
|
|
||||||
fetch('/api/submitters_autocomplete?' + queryParams).then(async (resp) => {
|
this.currentFetch ||= fetch('/api/submitters_autocomplete?' + queryParams)
|
||||||
|
|
||||||
|
this.currentFetch.then(async (resp) => {
|
||||||
const items = await resp.json()
|
const items = await resp.json()
|
||||||
|
|
||||||
if (q.length < 3) {
|
if (q.length < 3) {
|
||||||
@@ -47,6 +50,8 @@ export default class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
resolve([])
|
resolve([])
|
||||||
|
}).finally(() => {
|
||||||
|
this.currentFetch = null
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
resolve([])
|
resolve([])
|
||||||
|
|||||||
@@ -19,7 +19,13 @@
|
|||||||
class="btn btn-neutral flex text-white absolute rounded-none border-x-0 md:border md:rounded-full bottom-0 w-full md:mb-4 text-base"
|
class="btn btn-neutral flex text-white absolute rounded-none border-x-0 md:border md:rounded-full bottom-0 w-full md:mb-4 text-base"
|
||||||
@click.prevent="[isFormVisible = true, scrollIntoField(currentField)]"
|
@click.prevent="[isFormVisible = true, scrollIntoField(currentField)]"
|
||||||
>
|
>
|
||||||
{{ t('submit_form') }}
|
<template v-if="['initials', 'signature'].includes(currentField.type)">
|
||||||
|
<IconWritingSign stroke-width="1.5" />
|
||||||
|
{{ t('sign_now') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ t('submit_form') }}
|
||||||
|
</template>
|
||||||
<IconArrowsDiagonal
|
<IconArrowsDiagonal
|
||||||
class="absolute right-0 mr-4"
|
class="absolute right-0 mr-4"
|
||||||
:width="20"
|
:width="20"
|
||||||
@@ -413,7 +419,7 @@ import TextStep from './text_step'
|
|||||||
import NumberStep from './number_step'
|
import NumberStep from './number_step'
|
||||||
import DateStep from './date_step'
|
import DateStep from './date_step'
|
||||||
import FormCompleted from './completed'
|
import FormCompleted from './completed'
|
||||||
import { IconInnerShadowTop, IconArrowsDiagonal, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
import { IconInnerShadowTop, IconArrowsDiagonal, IconWritingSign, IconArrowsDiagonalMinimize2 } from '@tabler/icons-vue'
|
||||||
import AppearsOn from './appears_on'
|
import AppearsOn from './appears_on'
|
||||||
import i18n from './i18n'
|
import i18n from './i18n'
|
||||||
|
|
||||||
@@ -442,6 +448,7 @@ export default {
|
|||||||
ImageStep,
|
ImageStep,
|
||||||
SignatureStep,
|
SignatureStep,
|
||||||
AppearsOn,
|
AppearsOn,
|
||||||
|
IconWritingSign,
|
||||||
AttachmentStep,
|
AttachmentStep,
|
||||||
InitialsStep,
|
InitialsStep,
|
||||||
MultiSelectStep,
|
MultiSelectStep,
|
||||||
@@ -489,7 +496,7 @@ export default {
|
|||||||
expand: {
|
expand: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: null
|
||||||
},
|
},
|
||||||
withConfetti: {
|
withConfetti: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -590,7 +597,7 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isCompleted: false,
|
isCompleted: false,
|
||||||
isFormVisible: this.expand,
|
isFormVisible: this.expand !== false,
|
||||||
showFillAllRequiredFields: false,
|
showFillAllRequiredFields: false,
|
||||||
currentStep: 0,
|
currentStep: 0,
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
@@ -614,6 +621,13 @@ export default {
|
|||||||
submitterSlug () {
|
submitterSlug () {
|
||||||
return this.submitter.slug
|
return this.submitter.slug
|
||||||
},
|
},
|
||||||
|
fieldsUuidIndex () {
|
||||||
|
return this.fields.reduce((acc, f) => {
|
||||||
|
acc[f.uuid] = f
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
previousInitialsValue () {
|
previousInitialsValue () {
|
||||||
const initialsField = [...this.fields].reverse().find((field) => field.type === 'initials' && !!this.values[field.uuid])
|
const initialsField = [...this.fields].reverse().find((field) => field.type === 'initials' && !!this.values[field.uuid])
|
||||||
|
|
||||||
@@ -639,10 +653,12 @@ export default {
|
|||||||
return this.fields.filter((f) => !f.readonly).reduce((acc, f) => {
|
return this.fields.filter((f) => !f.readonly).reduce((acc, f) => {
|
||||||
const prevStep = acc[acc.length - 1]
|
const prevStep = acc[acc.length - 1]
|
||||||
|
|
||||||
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox') {
|
if (this.checkFieldConditions(f)) {
|
||||||
prevStep.push(f)
|
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox') {
|
||||||
} else {
|
prevStep.push(f)
|
||||||
acc.push([f])
|
} else {
|
||||||
|
acc.push([f])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
@@ -699,6 +715,10 @@ export default {
|
|||||||
this.currentStep = Math.min(...indexesList)
|
this.currentStep = Math.min(...indexesList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (document.body?.clientWidth >= 768 && this.expand !== true && ['signature', 'initials', 'file', 'image'].includes(this.currentField?.type)) {
|
||||||
|
this.isFormVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const root = this.$root.$el.parentNode.getRootNode()
|
const root = this.$root.$el.parentNode.getRootNode()
|
||||||
@@ -728,6 +748,40 @@ export default {
|
|||||||
t (key) {
|
t (key) {
|
||||||
return this.i18n[key] || i18n[this.language?.toLowerCase()]?.[key] || i18n[this.browserLanguage]?.[key] || i18n.en[key] || key
|
return this.i18n[key] || i18n[this.language?.toLowerCase()]?.[key] || i18n[this.browserLanguage]?.[key] || i18n.en[key] || key
|
||||||
},
|
},
|
||||||
|
checkFieldConditions (field) {
|
||||||
|
if (field.conditions?.length) {
|
||||||
|
return field.conditions.reduce((acc, c) => {
|
||||||
|
if (['empty', 'unchecked'].includes(c.action)) {
|
||||||
|
return acc && isEmpty(this.values[c.field_uuid])
|
||||||
|
} else if (['not_empty', 'checked'].includes(c.action)) {
|
||||||
|
return acc && !isEmpty(this.values[c.field_uuid])
|
||||||
|
} else if (['equal', 'contains'].includes(c.action)) {
|
||||||
|
const field = this.fieldsUuidIndex[c.field_uuid]
|
||||||
|
const option = field.options.find((o) => o.uuid === c.value)
|
||||||
|
const values = [this.values[c.field_uuid]].flat()
|
||||||
|
|
||||||
|
return acc && values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
|
} else if (['not_equal', 'does_not_contain'].includes(c.action)) {
|
||||||
|
const field = this.fieldsUuidIndex[c.field_uuid]
|
||||||
|
const option = field.options.find((o) => o.uuid === c.value)
|
||||||
|
const values = [this.values[c.field_uuid]].flat()
|
||||||
|
|
||||||
|
return acc && !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
|
} else {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionValue (option, index) {
|
||||||
|
if (option.value) {
|
||||||
|
return option.value
|
||||||
|
} else {
|
||||||
|
return `${this.t('option')} ${index + 1}`
|
||||||
|
}
|
||||||
|
},
|
||||||
maybeTrackEmailClick () {
|
maybeTrackEmailClick () {
|
||||||
const { queryParams } = this
|
const { queryParams } = this
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const en = {
|
|||||||
payment: 'Payment',
|
payment: 'Payment',
|
||||||
phone: 'Phone',
|
phone: 'Phone',
|
||||||
submit_form: 'Submit Form',
|
submit_form: 'Submit Form',
|
||||||
|
sign_now: 'Sign Now',
|
||||||
type_here_: 'Type here...',
|
type_here_: 'Type here...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
option: 'Option',
|
option: 'Option',
|
||||||
@@ -82,6 +83,7 @@ const es = {
|
|||||||
phone: 'Teléfono',
|
phone: 'Teléfono',
|
||||||
take_photo: 'Tomar foto',
|
take_photo: 'Tomar foto',
|
||||||
submit_form: 'Enviar Formulario',
|
submit_form: 'Enviar Formulario',
|
||||||
|
sign_now: 'Firmar ahora',
|
||||||
type_here_: 'Escribe aquí...',
|
type_here_: 'Escribe aquí...',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
appears_on: 'Aparece en',
|
appears_on: 'Aparece en',
|
||||||
@@ -145,6 +147,7 @@ const it = {
|
|||||||
payment: 'Pagamento',
|
payment: 'Pagamento',
|
||||||
phone: 'Telefono',
|
phone: 'Telefono',
|
||||||
submit_form: 'Invia Modulo',
|
submit_form: 'Invia Modulo',
|
||||||
|
sign_now: 'Firma ora',
|
||||||
type_here_: 'Digita qui...',
|
type_here_: 'Digita qui...',
|
||||||
optional: 'opzionale',
|
optional: 'opzionale',
|
||||||
appears_on: 'Compare su',
|
appears_on: 'Compare su',
|
||||||
@@ -209,6 +212,7 @@ const de = {
|
|||||||
payment: 'Zahlung',
|
payment: 'Zahlung',
|
||||||
phone: 'Telefon',
|
phone: 'Telefon',
|
||||||
submit_form: 'Formular absenden',
|
submit_form: 'Formular absenden',
|
||||||
|
sign_now: 'Jetzt unterschreiben',
|
||||||
type_here_: 'Hier eingeben...',
|
type_here_: 'Hier eingeben...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
appears_on: 'Erscheint auf',
|
appears_on: 'Erscheint auf',
|
||||||
@@ -273,6 +277,7 @@ const fr = {
|
|||||||
payment: 'Paiement',
|
payment: 'Paiement',
|
||||||
phone: 'Téléphone',
|
phone: 'Téléphone',
|
||||||
submit_form: 'Envoyer le Formulaire',
|
submit_form: 'Envoyer le Formulaire',
|
||||||
|
sign_now: 'Signer maintenant',
|
||||||
type_here_: 'Tapez ici...',
|
type_here_: 'Tapez ici...',
|
||||||
optional: 'facultatif',
|
optional: 'facultatif',
|
||||||
appears_on: 'Apparaît sur',
|
appears_on: 'Apparaît sur',
|
||||||
@@ -337,6 +342,7 @@ const pl = {
|
|||||||
payment: 'Płatność',
|
payment: 'Płatność',
|
||||||
phone: 'Telefon',
|
phone: 'Telefon',
|
||||||
submit_form: 'Wyślij Formularz',
|
submit_form: 'Wyślij Formularz',
|
||||||
|
sign_now: 'Podpisz teraz',
|
||||||
type_here_: 'Wpisz tutaj...',
|
type_here_: 'Wpisz tutaj...',
|
||||||
optional: 'opcjonalny',
|
optional: 'opcjonalny',
|
||||||
appears_on: 'Pojawia się na',
|
appears_on: 'Pojawia się na',
|
||||||
@@ -401,6 +407,7 @@ const uk = {
|
|||||||
payment: 'Платіж',
|
payment: 'Платіж',
|
||||||
phone: 'Телефон',
|
phone: 'Телефон',
|
||||||
submit_form: 'Надіслати Форму',
|
submit_form: 'Надіслати Форму',
|
||||||
|
sign_now: 'Підписати зараз',
|
||||||
type_here_: 'Введіть тут',
|
type_here_: 'Введіть тут',
|
||||||
optional: 'необов’язково',
|
optional: 'необов’язково',
|
||||||
appears_on: "З'являється на",
|
appears_on: "З'являється на",
|
||||||
@@ -465,6 +472,7 @@ const cs = {
|
|||||||
payment: 'Platba',
|
payment: 'Platba',
|
||||||
phone: 'Telefon',
|
phone: 'Telefon',
|
||||||
submit_form: 'Odeslat formulář',
|
submit_form: 'Odeslat formulář',
|
||||||
|
sign_now: 'Podepsat nyní',
|
||||||
type_here_: 'Zadejte zde',
|
type_here_: 'Zadejte zde',
|
||||||
optional: 'volitelné',
|
optional: 'volitelné',
|
||||||
appears_on: 'Zobrazuje se na',
|
appears_on: 'Zobrazuje se na',
|
||||||
@@ -529,6 +537,7 @@ const pt = {
|
|||||||
payment: 'Pagamento',
|
payment: 'Pagamento',
|
||||||
phone: 'Telefone',
|
phone: 'Telefone',
|
||||||
submit_form: 'Enviar Formulário',
|
submit_form: 'Enviar Formulário',
|
||||||
|
sign_now: 'Assinar agora',
|
||||||
type_here_: 'Digite aqui',
|
type_here_: 'Digite aqui',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
appears_on: 'Aparece em',
|
appears_on: 'Aparece em',
|
||||||
@@ -593,6 +602,7 @@ const he = {
|
|||||||
payment: 'תשלום',
|
payment: 'תשלום',
|
||||||
phone: 'טלפון',
|
phone: 'טלפון',
|
||||||
submit_form: 'שלח טופס',
|
submit_form: 'שלח טופס',
|
||||||
|
sign_now: 'חתום כעת',
|
||||||
type_here_: 'הקלד כאן',
|
type_here_: 'הקלד כאן',
|
||||||
optional: 'אופציונלי',
|
optional: 'אופציונלי',
|
||||||
option: 'אפשרות',
|
option: 'אפשרות',
|
||||||
@@ -659,6 +669,7 @@ const nl = {
|
|||||||
payment: 'Betaling',
|
payment: 'Betaling',
|
||||||
phone: 'Telefoon',
|
phone: 'Telefoon',
|
||||||
submit_form: 'Formulier verzenden',
|
submit_form: 'Formulier verzenden',
|
||||||
|
sign_now: 'Nu ondertekenen',
|
||||||
type_here_: 'Typ hier...',
|
type_here_: 'Typ hier...',
|
||||||
optional: 'Optioneel',
|
optional: 'Optioneel',
|
||||||
option: 'Optie',
|
option: 'Optie',
|
||||||
@@ -724,6 +735,7 @@ const ar = {
|
|||||||
payment: 'الدفع',
|
payment: 'الدفع',
|
||||||
phone: 'هاتف',
|
phone: 'هاتف',
|
||||||
submit_form: 'إرسال النموذج',
|
submit_form: 'إرسال النموذج',
|
||||||
|
sign_now: 'وقع الآن',
|
||||||
type_here_: 'اكتب هنا',
|
type_here_: 'اكتب هنا',
|
||||||
optional: 'اختياري',
|
optional: 'اختياري',
|
||||||
option: 'خيار',
|
option: 'خيار',
|
||||||
|
|||||||
@@ -323,8 +323,10 @@ export default {
|
|||||||
withPhone: this.withPhone,
|
withPhone: this.withPhone,
|
||||||
withPayment: this.withPayment,
|
withPayment: this.withPayment,
|
||||||
withFormula: this.withFormula,
|
withFormula: this.withFormula,
|
||||||
|
withConditions: this.withConditions,
|
||||||
defaultDrawFieldType: this.defaultDrawFieldType,
|
defaultDrawFieldType: this.defaultDrawFieldType,
|
||||||
selectedAreaRef: computed(() => this.selectedAreaRef)
|
selectedAreaRef: computed(() => this.selectedAreaRef),
|
||||||
|
fieldsDragFieldRef: computed(() => this.fieldsDragFieldRef)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -456,6 +458,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
withConditions: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
onlyDefinedFields: {
|
onlyDefinedFields: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -480,6 +487,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
selectedAreaRef: () => ref(),
|
selectedAreaRef: () => ref(),
|
||||||
|
fieldsDragFieldRef: () => ref(),
|
||||||
fieldAreasIndex () {
|
fieldAreasIndex () {
|
||||||
const areas = {}
|
const areas = {}
|
||||||
|
|
||||||
@@ -811,7 +819,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDropfield (area) {
|
onDropfield (area) {
|
||||||
const field = {
|
const field = this.fieldsDragFieldRef.value || {
|
||||||
name: '',
|
name: '',
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
submitter_uuid: this.selectedSubmitter.uuid,
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
@@ -819,17 +827,19 @@ export default {
|
|||||||
...this.dragField
|
...this.dragField
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['select', 'multiple', 'radio'].includes(field.type)) {
|
if (!this.fieldsDragFieldRef.value) {
|
||||||
field.options = [{ value: '', uuid: v4() }]
|
if (['select', 'multiple', 'radio'].includes(field.type)) {
|
||||||
}
|
field.options = [{ value: '', uuid: v4() }]
|
||||||
|
}
|
||||||
|
|
||||||
if (field.type === 'stamp') {
|
if (field.type === 'stamp') {
|
||||||
field.readonly = true
|
field.readonly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'date') {
|
if (field.type === 'date') {
|
||||||
field.preferences = {
|
field.preferences = {
|
||||||
format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'
|
format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -885,11 +895,23 @@ export default {
|
|||||||
fieldArea.cell_w = baseArea.cell_w || (baseArea.w / 5)
|
fieldArea.cell_w = baseArea.cell_w || (baseArea.w / 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
field.areas = [fieldArea]
|
field.areas ||= []
|
||||||
|
|
||||||
|
const lastArea = field.areas[field.areas.length - 1]
|
||||||
|
|
||||||
|
if (lastArea) {
|
||||||
|
fieldArea.x -= lastArea.w / 2
|
||||||
|
fieldArea.w = lastArea.w
|
||||||
|
fieldArea.h = lastArea.h
|
||||||
|
}
|
||||||
|
|
||||||
|
field.areas.push(fieldArea)
|
||||||
|
|
||||||
this.selectedAreaRef.value = fieldArea
|
this.selectedAreaRef.value = fieldArea
|
||||||
|
|
||||||
this.template.fields.push(field)
|
if (this.template.fields.indexOf(field) === -1) {
|
||||||
|
this.template.fields.push(field)
|
||||||
|
}
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="modal modal-open items-start !animate-none overflow-y-auto"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute top-0 bottom-0 right-0 left-0"
|
||||||
|
@click.prevent="$emit('close')"
|
||||||
|
/>
|
||||||
|
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
||||||
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||||
|
<span>
|
||||||
|
{{ t('condition') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-xl"
|
||||||
|
@click.prevent="$emit('close')"
|
||||||
|
>×</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="!withConditions"
|
||||||
|
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://www.docuseal.co/pricing"
|
||||||
|
target="_blank"
|
||||||
|
class="link"
|
||||||
|
>Available in Pro</a>
|
||||||
|
</div>
|
||||||
|
<form @submit.prevent="validateSaveAndClose">
|
||||||
|
<div class="my-4">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<select
|
||||||
|
class="select select-bordered select-sm w-full bg-white h-11 pl-4 text-base font-normal"
|
||||||
|
required
|
||||||
|
@change="[
|
||||||
|
newCondition.field_uuid = $event.target.value,
|
||||||
|
delete newCondition.value,
|
||||||
|
(conditionActions.includes(newCondition.action) ? '' : newCondition.action = conditionActions[0])
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value=""
|
||||||
|
disabled
|
||||||
|
:selected="!newCondition.field_uuid"
|
||||||
|
>
|
||||||
|
{{ t('select_field_') }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="f in fields"
|
||||||
|
:key="f.uuid"
|
||||||
|
:value="f.uuid"
|
||||||
|
:selected="newCondition.field_uuid === f.uuid"
|
||||||
|
>
|
||||||
|
{{ f.name || buildDefaultName(f, template.fields) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
v-model="newCondition.action"
|
||||||
|
class="select select-bordered select-sm w-full h-11 pl-4 text-base font-normal"
|
||||||
|
:class="{ 'bg-white': newCondition.field_uuid, 'bg-base-300': !newCondition.field_uuid }"
|
||||||
|
:required="newCondition.field_uuid"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="action in conditionActions"
|
||||||
|
:key="action"
|
||||||
|
:value="action"
|
||||||
|
>
|
||||||
|
{{ t(action) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
v-if="conditionField?.options?.length"
|
||||||
|
class="select select-bordered select-sm w-full bg-white h-11 pl-4 text-base font-normal"
|
||||||
|
required
|
||||||
|
@change="newCondition.value = $event.target.value"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value=""
|
||||||
|
disabled
|
||||||
|
selected
|
||||||
|
>
|
||||||
|
{{ t('select_value_') }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="(option, index) in conditionField.options"
|
||||||
|
:key="option.uuid"
|
||||||
|
:value="option.uuid"
|
||||||
|
:selected="newCondition.value === option.uuid"
|
||||||
|
>
|
||||||
|
{{ option.value || `${t('option')} ${index + 1}` }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="base-button w-full mt-2"
|
||||||
|
>
|
||||||
|
{{ t('save') }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<div
|
||||||
|
v-if="field.conditions?.[0]?.field_uuid"
|
||||||
|
class="text-center w-full mt-4"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="link"
|
||||||
|
@click="[newCondition.field_uuid = null, delete field.conditions, validateSaveAndClose()]"
|
||||||
|
>
|
||||||
|
{{ t('remove_condition') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ConditionModal',
|
||||||
|
inject: ['t', 'save', 'template', 'withConditions'],
|
||||||
|
props: {
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
buildDefaultName: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['close'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
newCondition: this.field.conditions?.[0] || {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
conditionField () {
|
||||||
|
return this.fields.find((f) => f.uuid === this.newCondition.field_uuid)
|
||||||
|
},
|
||||||
|
conditionActions () {
|
||||||
|
return this.fieldActions(this.conditionField)
|
||||||
|
},
|
||||||
|
fields () {
|
||||||
|
return this.template.fields.reduce((acc, f) => {
|
||||||
|
if (f !== this.field && f.submitter_uuid === this.field.submitter_uuid) {
|
||||||
|
acc.push(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.field.conditions ||= []
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fieldActions (field) {
|
||||||
|
const actions = []
|
||||||
|
|
||||||
|
if (!field) {
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'checkbox') {
|
||||||
|
actions.push('checked', 'unchecked')
|
||||||
|
} else if (['radio', 'select'].includes(field.type)) {
|
||||||
|
actions.push('equal', 'not_equal')
|
||||||
|
} else if (['multiple'].includes(field.type)) {
|
||||||
|
actions.push('contains', 'does_not_contain')
|
||||||
|
} else {
|
||||||
|
actions.push('not_empty', 'empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions
|
||||||
|
},
|
||||||
|
validateSaveAndClose () {
|
||||||
|
if (!this.withConditions) {
|
||||||
|
return alert('Available only in Pro')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.newCondition.field_uuid) {
|
||||||
|
this.field.conditions = [this.newCondition]
|
||||||
|
} else {
|
||||||
|
delete this.field.conditions
|
||||||
|
}
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="field.preferences?.formula"
|
v-if="field.preferences?.formula"
|
||||||
class="relative cursor-pointer text-transparent group-hover:text-base-content"
|
class="relative cursor-pointer text-transparent group-hover:text-base-content"
|
||||||
:disabled="!withFormula"
|
:title="t('formula')"
|
||||||
@click="isShowFormulaModal = true"
|
@click="isShowFormulaModal = true"
|
||||||
>
|
>
|
||||||
<IconMathFunction
|
<IconMathFunction
|
||||||
@@ -76,6 +76,17 @@
|
|||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="field.conditions?.length"
|
||||||
|
class="relative cursor-pointer text-transparent group-hover:text-base-content"
|
||||||
|
:title="t('condition')"
|
||||||
|
@click="isShowConditionsModal = true"
|
||||||
|
>
|
||||||
|
<IconRouteAltLeft
|
||||||
|
:width="18"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
<PaymentSettings
|
<PaymentSettings
|
||||||
v-if="field.type === 'payment'"
|
v-if="field.type === 'payment'"
|
||||||
:field="field"
|
:field="field"
|
||||||
@@ -204,26 +215,6 @@
|
|||||||
<span class="label-text">{{ t('required') }}</span>
|
<span class="label-text">{{ t('required') }}</span>
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li
|
|
||||||
v-if="field.type == 'number'"
|
|
||||||
:class="{'tooltip tooltip-bottom': !withFormula}"
|
|
||||||
:data-tip="withFormula ? '' : 'Available in Pro'"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="label-text cursor-pointer py-1.5 text-center w-full flex items-center"
|
|
||||||
@click="isShowFormulaModal = withFormula"
|
|
||||||
>
|
|
||||||
<IconMathFunction
|
|
||||||
width="18px"
|
|
||||||
height="18px"
|
|
||||||
class="ml-0.5 mr-1"
|
|
||||||
/>
|
|
||||||
<span class="text-sm">
|
|
||||||
{{ t('formula') }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li
|
<li
|
||||||
v-if="field.type == 'checkbox'"
|
v-if="field.type == 'checkbox'"
|
||||||
@click.stop
|
@click.stop
|
||||||
@@ -267,6 +258,33 @@
|
|||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<hr class="pb-0.5 mt-0.5">
|
<hr class="pb-0.5 mt-0.5">
|
||||||
|
<li>
|
||||||
|
<label
|
||||||
|
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||||
|
@click="isShowConditionsModal = !isShowConditionsModal"
|
||||||
|
>
|
||||||
|
<IconRouteAltLeft
|
||||||
|
width="18"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">
|
||||||
|
{{ t('condition') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li v-if="field.type == 'number'">
|
||||||
|
<label
|
||||||
|
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||||
|
@click="isShowFormulaModal = true"
|
||||||
|
>
|
||||||
|
<IconMathFunction
|
||||||
|
width="18"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">
|
||||||
|
{{ t('formula') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<hr class="pb-0.5 mt-0.5">
|
||||||
<li
|
<li
|
||||||
v-for="(area, index) in field.areas || []"
|
v-for="(area, index) in field.areas || []"
|
||||||
:key="index"
|
:key="index"
|
||||||
@@ -280,7 +298,8 @@
|
|||||||
:width="20"
|
:width="20"
|
||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
{{ t('page') }} {{ area.page + 1 }}
|
{{ t('page') }}
|
||||||
|
<template v-if="template.schema.length > 1">{{ template.schema.findIndex((item) => item.attachment_uuid === area.attachment_uuid) + 1 }}-</template>{{ area.page + 1 }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!field.areas?.length || !['radio', 'multiple'].includes(field.type)">
|
<li v-if="!field.areas?.length || !['radio', 'multiple'].includes(field.type)">
|
||||||
@@ -339,7 +358,7 @@
|
|||||||
{{ index + 1 }}.
|
{{ index + 1 }}.
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-if="['radio', 'multiple'].includes(field.type) && (index > 0 || field.areas.find((a) => a.option_uuid) || !field.areas.length) && !field.areas.find((a) => a.option_uuid === option.uuid)"
|
v-if="editable && ['radio', 'multiple'].includes(field.type) && (index > 0 || field.areas.find((a) => a.option_uuid) || !field.areas.length) && !field.areas.find((a) => a.option_uuid === option.uuid)"
|
||||||
class="items-center flex w-full"
|
class="items-center flex w-full"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -368,12 +387,14 @@
|
|||||||
class="w-full input input-primary input-xs text-sm bg-transparent"
|
class="w-full input input-primary input-xs text-sm bg-transparent"
|
||||||
:placeholder="`${t('option')} ${index + 1}`"
|
:placeholder="`${t('option')} ${index + 1}`"
|
||||||
type="text"
|
type="text"
|
||||||
|
:readonly="!editable"
|
||||||
required
|
required
|
||||||
dir="auto"
|
dir="auto"
|
||||||
@focus="maybeFocusOnOptionArea(option)"
|
@focus="maybeFocusOnOptionArea(option)"
|
||||||
@blur="save"
|
@blur="save"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
v-if="editable"
|
||||||
class="text-sm w-3.5"
|
class="text-sm w-3.5"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@click="removeOption(option)"
|
@click="removeOption(option)"
|
||||||
@@ -381,8 +402,12 @@
|
|||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="field.options && !editable"
|
||||||
|
class="pb-1"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="field.options"
|
v-else-if="field.options && editable"
|
||||||
class="text-center text-sm w-full pb-1"
|
class="text-center text-sm w-full pb-1"
|
||||||
@click="addOption"
|
@click="addOption"
|
||||||
>
|
>
|
||||||
@@ -400,6 +425,16 @@
|
|||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowConditionsModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<ConditionsModal
|
||||||
|
:field="field"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
@close="isShowConditionsModal = false"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -408,7 +443,8 @@ import Contenteditable from './contenteditable'
|
|||||||
import FieldType from './field_type'
|
import FieldType from './field_type'
|
||||||
import PaymentSettings from './payment_settings'
|
import PaymentSettings from './payment_settings'
|
||||||
import FormulaModal from './formula_modal'
|
import FormulaModal from './formula_modal'
|
||||||
import { IconMathFunction, IconShape, IconNewSection, IconTrashX, IconCopy, IconSettings } from '@tabler/icons-vue'
|
import ConditionsModal from './conditions_modal'
|
||||||
|
import { IconRouteAltLeft, IconMathFunction, IconShape, IconNewSection, IconTrashX, IconCopy, IconSettings } from '@tabler/icons-vue'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -420,12 +456,14 @@ export default {
|
|||||||
PaymentSettings,
|
PaymentSettings,
|
||||||
IconNewSection,
|
IconNewSection,
|
||||||
FormulaModal,
|
FormulaModal,
|
||||||
|
ConditionsModal,
|
||||||
|
IconRouteAltLeft,
|
||||||
IconTrashX,
|
IconTrashX,
|
||||||
IconMathFunction,
|
IconMathFunction,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
FieldType
|
FieldType
|
||||||
},
|
},
|
||||||
inject: ['template', 'save', 'backgroundColor', 'selectedAreaRef', 't', 'withFormula'],
|
inject: ['template', 'save', 'backgroundColor', 'selectedAreaRef', 't'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -448,6 +486,7 @@ export default {
|
|||||||
isNameFocus: false,
|
isNameFocus: false,
|
||||||
showPaymentModal: false,
|
showPaymentModal: false,
|
||||||
isShowFormulaModal: false,
|
isShowFormulaModal: false,
|
||||||
|
isShowConditionsModal: false,
|
||||||
renderDropdown: false
|
renderDropdown: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,11 +24,11 @@
|
|||||||
: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="fields.filter((f) => f.type === field.type).indexOf(field)"
|
||||||
:editable="editable && (!dragField || dragField !== field)"
|
:editable="editable && (!fieldsDragFieldRef.value || fieldsDragFieldRef.value !== field)"
|
||||||
:default-field="defaultFields.find((f) => f.name === field.name)"
|
:default-field="defaultFields.find((f) => f.name === field.name)"
|
||||||
:draggable="editable"
|
:draggable="editable"
|
||||||
@dragstart="dragField = field"
|
@dragstart="fieldsDragFieldRef.value = field"
|
||||||
@dragend="dragField = null"
|
@dragend="fieldsDragFieldRef.value = null"
|
||||||
@remove="removeField"
|
@remove="removeField"
|
||||||
@scroll-to="$emit('scroll-to-area', $event)"
|
@scroll-to="$emit('scroll-to-area', $event)"
|
||||||
@set-draw="$emit('set-draw', $event)"
|
@set-draw="$emit('set-draw', $event)"
|
||||||
@@ -154,7 +154,7 @@ export default {
|
|||||||
IconDrag,
|
IconDrag,
|
||||||
IconLock
|
IconLock
|
||||||
},
|
},
|
||||||
inject: ['save', 'backgroundColor', 'withPhone', 'withPayment', 't'],
|
inject: ['save', 'backgroundColor', 'withPhone', 'withPayment', 't', 'fieldsDragFieldRef'],
|
||||||
props: {
|
props: {
|
||||||
fields: {
|
fields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -205,11 +205,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
|
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
dragField: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
fieldNames: FieldType.computed.fieldNames,
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldIcons: FieldType.computed.fieldIcons,
|
fieldIcons: FieldType.computed.fieldIcons,
|
||||||
@@ -239,7 +234,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onFieldDragover (e) {
|
onFieldDragover (e) {
|
||||||
const targetField = e.target.closest('[data-uuid]')
|
const targetField = e.target.closest('[data-uuid]')
|
||||||
const dragField = this.$refs.fields.querySelector(`[data-uuid="${this.dragField.uuid}"]`)
|
const dragField = this.$refs.fields.querySelector(`[data-uuid="${this.fieldsDragFieldRef.value.uuid}"]`)
|
||||||
|
|
||||||
if (dragField && targetField && targetField !== dragField) {
|
if (dragField && targetField && targetField !== dragField) {
|
||||||
const fields = Array.from(this.$refs.fields.children)
|
const fields = Array.from(this.$refs.fields.children)
|
||||||
@@ -283,6 +278,14 @@ export default {
|
|||||||
removeField (field) {
|
removeField (field) {
|
||||||
this.fields.splice(this.fields.indexOf(field), 1)
|
this.fields.splice(this.fields.indexOf(field), 1)
|
||||||
|
|
||||||
|
this.fields.forEach((f) => {
|
||||||
|
(f.conditions || []).forEach((c) => {
|
||||||
|
if (c.field_uuid === field.uuid) {
|
||||||
|
f.conditions.splice(f.conditions.indexOf(c), 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
addField (type, area = null) {
|
addField (type, area = null) {
|
||||||
|
|||||||
@@ -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>
|
<span>
|
||||||
{{ t('formula') }}
|
{{ t('formula') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
@@ -18,6 +18,16 @@
|
|||||||
>×</a>
|
>×</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="!withFormula"
|
||||||
|
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://www.docuseal.co/pricing"
|
||||||
|
target="_blank"
|
||||||
|
class="link"
|
||||||
|
>Available in Pro</a>
|
||||||
|
</div>
|
||||||
<div class="flex-inline mb-2 gap-2 space-y-1">
|
<div class="flex-inline mb-2 gap-2 space-y-1">
|
||||||
<button
|
<button
|
||||||
v-for="f in fields"
|
v-for="f in fields"
|
||||||
@@ -118,7 +128,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
IconCodePlus
|
IconCodePlus
|
||||||
},
|
},
|
||||||
inject: ['t', 'save', 'template'],
|
inject: ['t', 'save', 'template', 'withFormula'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -178,6 +188,10 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
validateSaveAndClose () {
|
validateSaveAndClose () {
|
||||||
|
if (!this.withFormula) {
|
||||||
|
return alert('Available only in Pro')
|
||||||
|
}
|
||||||
|
|
||||||
const normalizedFormula = this.normalizeFormula(this.formula)
|
const normalizedFormula = this.normalizeFormula(this.formula)
|
||||||
|
|
||||||
if (normalizedFormula.includes('FIELD NOT FOUND')) {
|
if (normalizedFormula.includes('FIELD NOT FOUND')) {
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
unchecked: 'Unchecked',
|
||||||
|
equal: 'Equal',
|
||||||
|
not_equal: 'Not equal',
|
||||||
|
contains: 'Contains',
|
||||||
|
does_not_contain: 'Does not contain',
|
||||||
|
not_empty: 'Not empty',
|
||||||
|
empty: 'Empty',
|
||||||
|
select_field_: 'Select field...',
|
||||||
|
select_value_: 'Select value...',
|
||||||
|
remove_condition: 'Remove condition',
|
||||||
are_you_sure: 'Are you sure?',
|
are_you_sure: 'Are you sure?',
|
||||||
sign_yourself: 'Sign Yourself',
|
sign_yourself: 'Sign Yourself',
|
||||||
set_signing_date: 'Set signing date',
|
set_signing_date: 'Set signing date',
|
||||||
@@ -31,6 +41,7 @@ const en = {
|
|||||||
copy_to_all_pages: 'Copy to All Pages',
|
copy_to_all_pages: 'Copy to All Pages',
|
||||||
add_option: 'Add option',
|
add_option: 'Add option',
|
||||||
option: 'Option',
|
option: 'Option',
|
||||||
|
condition: 'Condition',
|
||||||
first_party: 'First Party',
|
first_party: 'First Party',
|
||||||
second_party: 'Second Party',
|
second_party: 'Second Party',
|
||||||
third_party: 'Third Party',
|
third_party: 'Third Party',
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="resizeDirection || isMove || isDrag || showMask || (drawField && isMobile)"
|
v-show="resizeDirection || isMove || isDrag || showMask || (drawField && isMobile) || fieldsDragFieldRef.value"
|
||||||
id="mask"
|
id="mask"
|
||||||
ref="mask"
|
ref="mask"
|
||||||
class="top-0 bottom-0 left-0 right-0 absolute"
|
class="top-0 bottom-0 left-0 right-0 absolute"
|
||||||
@@ -61,7 +61,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
FieldArea
|
FieldArea
|
||||||
},
|
},
|
||||||
inject: ['fieldTypes', 'defaultDrawFieldType'],
|
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef'],
|
||||||
props: {
|
props: {
|
||||||
image: {
|
image: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class SendFormCompletedWebhookRequestJob < ApplicationJob
|
|||||||
}.to_json,
|
}.to_json,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::TimeoutError
|
rescue Faraday::Error
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class SendFormStartedWebhookRequestJob < ApplicationJob
|
|||||||
}.to_json,
|
}.to_json,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::TimeoutError
|
rescue Faraday::Error
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class SendFormViewedWebhookRequestJob < ApplicationJob
|
|||||||
}.to_json,
|
}.to_json,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::TimeoutError
|
rescue Faraday::Error
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Account < ApplicationRecord
|
|||||||
has_many :template_folders, dependent: :destroy
|
has_many :template_folders, dependent: :destroy
|
||||||
has_one :default_template_folder, -> { where(name: TemplateFolder::DEFAULT_NAME) },
|
has_one :default_template_folder, -> { where(name: TemplateFolder::DEFAULT_NAME) },
|
||||||
class_name: 'TemplateFolder', dependent: :destroy, inverse_of: :account
|
class_name: 'TemplateFolder', dependent: :destroy, inverse_of: :account
|
||||||
has_many :submissions, through: :templates
|
has_many :submissions, dependent: :destroy
|
||||||
has_many :submitters, through: :submissions
|
has_many :submitters, through: :submissions
|
||||||
has_many :account_linked_accounts, dependent: :destroy
|
has_many :account_linked_accounts, dependent: :destroy
|
||||||
has_many :account_testing_accounts, -> { testing }, dependent: :destroy,
|
has_many :account_testing_accounts, -> { testing }, dependent: :destroy,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
# index_submission_events_on_created_at (created_at)
|
||||||
# index_submission_events_on_submission_id (submission_id)
|
# index_submission_events_on_submission_id (submission_id)
|
||||||
# index_submission_events_on_submitter_id (submitter_id)
|
# index_submission_events_on_submitter_id (submitter_id)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
<div class="max-w-lg mx-auto px-2">
|
|
||||||
<h1 class="text-3xl font-bold text-center mt-8">Profile Details</h1>
|
|
||||||
<%= form_for('', as: resource_name, html: { class: 'space-y-6' }, data: { turbo: params[:redir].blank? }, url: new_registration_path) do |f| %>
|
|
||||||
<% if params[:redir].present? %>
|
|
||||||
<%= hidden_field_tag :redir, params[:redir] %>
|
|
||||||
<% end %>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<%= render 'devise/shared/error_messages', resource: %>
|
|
||||||
<%= f.fields_for resource do |ff| %>
|
|
||||||
<div class="grid gap-2 md:grid-cols-2 md:gap-4">
|
|
||||||
<div class="form-control">
|
|
||||||
<%= ff.label :first_name, class: 'label' %>
|
|
||||||
<%= ff.text_field :first_name, required: true, class: 'base-input' %>
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<%= ff.label :last_name, class: 'label' %>
|
|
||||||
<%= ff.text_field :last_name, required: true, class: 'base-input' %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= f.fields_for resource do |ff| %>
|
|
||||||
<div class="form-control <%= 'hidden' if (params[:oauth_callback] || params[:sign_up]) && resource.email? %>">
|
|
||||||
<%= ff.label :email, class: 'label' %>
|
|
||||||
<%= ff.email_field :email, value: EmailTypo.call(resource.email), required: true, class: 'base-input' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= f.fields_for resource.account do |ff| %>
|
|
||||||
<set-timezone data-input-id="_account_timezone"></set-timezone>
|
|
||||||
<%= ff.hidden_field :timezone %>
|
|
||||||
<div class="form-control">
|
|
||||||
<%= ff.label :name, 'Company name (optional)', class: 'label' %>
|
|
||||||
<%= ff.text_field :name, class: 'base-input' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= f.fields_for resource do |ff| %>
|
|
||||||
<div class="form-control <%= 'hidden' if params[:oauth_callback] %>">
|
|
||||||
<%= ff.label :password, class: 'label' %>
|
|
||||||
<%= ff.password_field :password, required: !params[:oauth_callback], class: 'base-input' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<%= f.button button_title(title: 'Sign up', disabled_with: 'Signing up'), class: 'base-button' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= render 'devise/shared/links' %>
|
|
||||||
</div>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<div class="max-w-lg mx-auto px-2">
|
|
||||||
<% if params[:redir].to_s.ends_with?('on_premise') %>
|
|
||||||
<h1 class="text-3xl font-bold text-center mt-8 mb-6">DocuSeal Console</h1>
|
|
||||||
<div class="alert my-4 text-sm">
|
|
||||||
Sign up in DocuSeal Console to upgrade.
|
|
||||||
On-premises app is completely standalone, Console is used only to manage your license.
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
|
||||||
<h1 class="text-3xl font-bold text-center mt-8 mb-6">Create Free Account</h1>
|
|
||||||
<% end %>
|
|
||||||
<%= form_for(User.new, html: { class: 'space-y-6' }, url: new_registration_path, data: { turbo: params[:redir].blank? }, method: :get) do |f| %>
|
|
||||||
<% if params[:redir].present? %>
|
|
||||||
<%= hidden_field_tag :redir, params[:redir] %>
|
|
||||||
<% end %>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-control">
|
|
||||||
<%= f.label :email, class: 'label' %>
|
|
||||||
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'base-input', placeholder: 'Enter email to continue' %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<%= f.button button_title(title: 'Sign up', disabled_with: 'Sign up'), name: 'sign_up', value: true, class: 'base-button' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% if devise_mapping.omniauthable? %>
|
|
||||||
<div class="space-y-4">
|
|
||||||
<% if User.omniauth_providers.include?(:google_oauth2) %>
|
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
|
||||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
|
||||||
<%= f.button button_title(title: 'Sign up with Google', icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if User.omniauth_providers.include?(:microsoft_office365) %>
|
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
|
||||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
|
||||||
<%= f.button button_title(title: 'Sign up with Microsoft', icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= render 'devise/shared/links' %>
|
|
||||||
</div>
|
|
||||||
@@ -38,7 +38,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[None none], %w[SSL ssl], %w[TLS tls]].each do |(label, val)| %>
|
<% [%w[Auto none], %w[SSL ssl], %w[TLS tls]].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 %>
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
<span>
|
<span>
|
||||||
Timeserver URL
|
Timeserver URL
|
||||||
</span>
|
</span>
|
||||||
<span class="tooltip" data-tip="URL of the trusted timeserver to be used to generate timestamp signatures.">
|
<span class="tooltip" data-tip="URL of the trusted RFC 3161 timeserver to be used to generate timestamp signatures.">
|
||||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Send automatic email reminders to your recipients.
|
Send automatic email reminders to your recipients.
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Display your company name and logo when signing documents.
|
Display your company name and logo when signing documents.
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<p class="text-gray-700">
|
<p class="text-gray-700">
|
||||||
Unlock with DocuSeal Pro
|
Unlock with DocuSeal Pro
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<p class="text-gray-700">
|
<p class="text-gray-700">
|
||||||
Unlock with DocuSeal Pro
|
Unlock with DocuSeal Pro
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% elsif field['type'] == 'date' %>
|
<% elsif field['type'] == 'date' && value != '{{date}}' %>
|
||||||
<autosize-field></autosize-field>
|
<autosize-field></autosize-field>
|
||||||
<div class="flex items-center px-0.5">
|
<div class="flex items-center px-0.5">
|
||||||
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), local_assigns[:locale]) %>
|
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), local_assigns[:locale]) %>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<p class="text-gray-700">
|
<p class="text-gray-700">
|
||||||
Unlock with DocuSeal Pro
|
Unlock with DocuSeal Pro
|
||||||
<br>
|
<br>
|
||||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>">Learn More</a>
|
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,21 +65,21 @@ fr: &fr
|
|||||||
email: Email
|
email: Email
|
||||||
digitally_signed_by: Signé numériquement par
|
digitally_signed_by: Signé numériquement par
|
||||||
role: Rôle
|
role: Rôle
|
||||||
provide_your_email_to_start: Entrez votre email pour commencer
|
provide_your_email_to_start: Entrez votre adresse email pour commencer
|
||||||
start: Démarrer
|
start: Démarrer
|
||||||
starting: Démarrage
|
starting: Démarrage
|
||||||
form_has_been_deleted_by_html: 'Le formulaire a été supprimé par <span class="font-semibold">%{name}</span>.'
|
form_has_been_deleted_by_html: 'Le formulaire a été supprimé par <span class="font-semibold">%{name}</span>.'
|
||||||
invited_by_html: 'Invité par <span class="font-semibold">%{name}</span>'
|
invited_by_html: 'Invité par <span class="font-semibold">%{name}</span>'
|
||||||
you_have_been_invited_to_submit_a_form: Vous avez été invité à soumettre un formulaire
|
you_have_been_invited_to_submit_a_form: Vous avez été invité à remplir un formulaire
|
||||||
signed_on_time: 'Signé à %{time}'
|
signed_on_time: 'Signé le %{time}'
|
||||||
form_has_been_submitted_already: Le formulaire a déjà été soumis
|
form_has_been_submitted_already: Le formulaire a déjà été complété
|
||||||
send_copy_to_email: Envoyer une copie par email
|
send_copy_to_email: Envoyer une copie par e-mail
|
||||||
sending: Envoi
|
sending: Envoi en cours
|
||||||
resubmit: Soumettre à nouveau
|
resubmit: Compléter à nouveau
|
||||||
or: ou
|
or: ou
|
||||||
download_documents: Télécharger des documents
|
download_documents: Télécharger les documents
|
||||||
downloading: Téléchargement
|
downloading: Téléchargement
|
||||||
completed_successfully: Terminé avec succès
|
completed_successfully: Complété avec succès
|
||||||
|
|
||||||
pt: &pt
|
pt: &pt
|
||||||
role: Função
|
role: Função
|
||||||
|
|||||||
@@ -17,15 +17,6 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
|
|
||||||
devise_scope :user do
|
devise_scope :user do
|
||||||
if Docuseal.multitenant?
|
|
||||||
resource :registration, only: %i[show], path: 'sign_up'
|
|
||||||
unauthenticated do
|
|
||||||
resource :registration, only: %i[create], path: 'new' do
|
|
||||||
get '' => :new, as: :new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
resource :invitation, only: %i[update] do
|
resource :invitation, only: %i[update] do
|
||||||
get '' => :edit
|
get '' => :edit
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddIndexOnSubmissionEvents < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
add_index :submission_events, :created_at
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddIndexOnBlobsChecksum < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
add_index :active_storage_blobs, :checksum
|
||||||
|
end
|
||||||
|
end
|
||||||
+3
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2024_02_18_082157) do
|
ActiveRecord::Schema[7.1].define(version: 2024_02_28_083356) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
@@ -75,6 +75,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_02_18_082157) do
|
|||||||
t.string "checksum"
|
t.string "checksum"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.string "uuid"
|
t.string "uuid"
|
||||||
|
t.index ["checksum"], name: "index_active_storage_blobs_on_checksum"
|
||||||
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
||||||
t.index ["uuid"], name: "index_active_storage_blobs_on_uuid", unique: true
|
t.index ["uuid"], name: "index_active_storage_blobs_on_uuid", unique: true
|
||||||
end
|
end
|
||||||
@@ -136,6 +137,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_02_18_082157) do
|
|||||||
t.datetime "event_timestamp", null: false
|
t.datetime "event_timestamp", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["created_at"], name: "index_submission_events_on_created_at"
|
||||||
t.index ["submission_id"], name: "index_submission_events_on_submission_id"
|
t.index ["submission_id"], name: "index_submission_events_on_submission_id"
|
||||||
t.index ["submitter_id"], name: "index_submission_events_on_submitter_id"
|
t.index ["submitter_id"], name: "index_submission_events_on_submitter_id"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ module ReplaceEmailVariables
|
|||||||
text = text.gsub(DOCUMENTS_LINKS, build_documents_links_text(submitter, sig))
|
text = text.gsub(DOCUMENTS_LINKS, build_documents_links_text(submitter, sig))
|
||||||
text = text.gsub(DOCUMENTS_LINK, build_documents_links_text(submitter, sig))
|
text = text.gsub(DOCUMENTS_LINK, build_documents_links_text(submitter, sig))
|
||||||
|
|
||||||
text = text.gsub(ACCOUNT_NAME, submitter.template.account.name) if submitter.template
|
text = text.gsub(ACCOUNT_NAME, submitter.account.name) if submitter.account
|
||||||
|
|
||||||
text
|
text
|
||||||
end
|
end
|
||||||
|
|||||||
+2
-2
@@ -51,7 +51,7 @@ module Submissions
|
|||||||
def create_from_emails(template:, user:, emails:, source:, mark_as_sent: false, params: {})
|
def create_from_emails(template:, user:, emails:, source:, mark_as_sent: false, params: {})
|
||||||
preferences = Submitters.normalize_preferences(user.account, user, params)
|
preferences = Submitters.normalize_preferences(user.account, user, params)
|
||||||
|
|
||||||
parse_emails(emails).uniq.map do |email|
|
parse_emails(emails, user).uniq.map do |email|
|
||||||
submission = template.submissions.new(created_by_user: user,
|
submission = template.submissions.new(created_by_user: user,
|
||||||
account_id: user.account_id,
|
account_id: user.account_id,
|
||||||
source:,
|
source:,
|
||||||
@@ -66,7 +66,7 @@ module Submissions
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_emails(emails)
|
def parse_emails(emails, _user)
|
||||||
emails = emails.to_s.scan(User::EMAIL_REGEXP) unless emails.is_a?(Array)
|
emails = emails.to_s.scan(User::EMAIL_REGEXP) unless emails.is_a?(Array)
|
||||||
|
|
||||||
emails
|
emails
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ module Submissions
|
|||||||
width: PdfIcons::WIDTH * scale,
|
width: PdfIcons::WIDTH * scale,
|
||||||
height: PdfIcons::HEIGHT * scale
|
height: PdfIcons::HEIGHT * scale
|
||||||
)
|
)
|
||||||
when 'cells'
|
when ->(type) { type == 'cells' && area['cell_w'] }
|
||||||
cell_width = area['cell_w'] * width
|
cell_width = area['cell_w'] * width
|
||||||
|
|
||||||
TextUtils.maybe_rtl_reverse(value).chars.each_with_index do |char, index|
|
TextUtils.maybe_rtl_reverse(value).chars.each_with_index do |char, index|
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ module TimeUtils
|
|||||||
|
|
||||||
I18n.l(date, format: i18n_format, locale:)
|
I18n.l(date, format: i18n_format, locale:)
|
||||||
rescue Date::Error => e
|
rescue Date::Error => e
|
||||||
Rollbar.warning(e) if defined?(Rollbar)
|
Rollbar.warning("#{e}: #{string}") if defined?(Rollbar)
|
||||||
|
|
||||||
string
|
string
|
||||||
end
|
end
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
"@hotwired/turbo": "https://github.com/docusealco/turbo#main",
|
"@hotwired/turbo": "https://github.com/docusealco/turbo#main",
|
||||||
"@hotwired/turbo-rails": "^7.3.0",
|
"@hotwired/turbo-rails": "^7.3.0",
|
||||||
"@rails/activestorage": "^7.0.0",
|
"@rails/activestorage": "^7.0.0",
|
||||||
"@tabler/icons-vue": "^2.20.0",
|
"@tabler/icons-vue": "^2.47.0",
|
||||||
"autocompleter": "^9.1.0",
|
"autocompleter": "^9.1.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"babel-loader": "9.1.2",
|
"babel-loader": "9.1.2",
|
||||||
|
|||||||
@@ -1137,17 +1137,17 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718"
|
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718"
|
||||||
integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==
|
integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==
|
||||||
|
|
||||||
"@tabler/icons-vue@^2.20.0":
|
"@tabler/icons-vue@^2.47.0":
|
||||||
version "2.20.0"
|
version "2.47.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tabler/icons-vue/-/icons-vue-2.20.0.tgz#e8ff057727feceb58e480643d9fa0aace135c5b8"
|
resolved "https://registry.yarnpkg.com/@tabler/icons-vue/-/icons-vue-2.47.0.tgz#604608a6df673d035c0e8e33f0565eeffa3adf36"
|
||||||
integrity sha512-KLu2XjeUBScUlTq3GbISinXsolBnEIm/nGdQoUqlWaFH5haBlHYmv/pHTZuBdW32uRxzGnidxfu/LT3DvAU/AQ==
|
integrity sha512-huEDak5xouwsRjbTUZlYZny9kC8ez61vdx/KwhR2Uc5HcUpxpFy+7p+IKH/ZZ48IcTHmCbdBJgbFRUI1mePJog==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@tabler/icons" "2.20.0"
|
"@tabler/icons" "2.47.0"
|
||||||
|
|
||||||
"@tabler/icons@2.20.0":
|
"@tabler/icons@2.47.0":
|
||||||
version "2.20.0"
|
version "2.47.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-2.20.0.tgz#42a11bb91b9d347fdf6b296274cf68998122bb10"
|
resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-2.47.0.tgz#c41c680d1947e3ab2d60af3febc4132287c60596"
|
||||||
integrity sha512-BsUEJoqREs8bqcrf5HfJBq6/rDvsRI3h+T+0X1o7i8LBHonsH0iAngcyL0I82YKoSy9NiVDvM3LV63zDP0nPYQ==
|
integrity sha512-4w5evLh+7FUUiA1GucvGj2ReX2TvOjEr4ejXdwL/bsjoSkof6r1gQmzqI+VHrE2CpJpB3al7bCTulOkFa/RcyA==
|
||||||
|
|
||||||
"@trysound/sax@0.2.0":
|
"@trysound/sax@0.2.0":
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
|
|||||||
Reference in new issue
Block a user