mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5764b4dac1 | ||
|
|
3affd67d24 | ||
|
|
d5a98a0f9e | ||
|
|
9629cc7226 | ||
|
|
06415ad869 | ||
|
|
3f872da435 | ||
|
|
bc1d5a9825 | ||
|
|
72c8ed9a00 | ||
|
|
3fec433846 | ||
|
|
3b572702d7 | ||
|
|
febdc0b48d | ||
|
|
8773bb2f62 | ||
|
|
55ad70fabc | ||
|
|
23dcd62672 | ||
|
|
42371d7ce3 | ||
|
|
4116746be3 | ||
|
|
b2b2856ac0 | ||
|
|
b65bca213f | ||
|
|
7a72e2cbb7 | ||
|
|
3a73398a1e | ||
|
|
cce20d9230 | ||
|
|
8b72c91951 | ||
|
|
b0d8e47a75 | ||
|
|
85824faa2b | ||
|
|
8a507339af | ||
|
|
4636849118 | ||
|
|
cc52cae9fc |
No files matched your search
@@ -0,0 +1,58 @@
|
||||
---
|
||||
:position: before
|
||||
:position_in_additional_file_patterns: before
|
||||
:position_in_class: before
|
||||
:position_in_factory: before
|
||||
:position_in_fixture: before
|
||||
:position_in_routes: before
|
||||
:position_in_serializer: before
|
||||
:position_in_test: before
|
||||
:classified_sort: true
|
||||
:exclude_controllers: true
|
||||
:exclude_factories: true
|
||||
:exclude_fixtures: false
|
||||
:exclude_helpers: true
|
||||
:exclude_scaffolds: true
|
||||
:exclude_serializers: false
|
||||
:exclude_sti_subclasses: false
|
||||
:exclude_tests: false
|
||||
:force: false
|
||||
:format_markdown: false
|
||||
:format_rdoc: false
|
||||
:format_yard: false
|
||||
:frozen: false
|
||||
:ignore_model_sub_dir: false
|
||||
:ignore_unknown_models: false
|
||||
:include_version: false
|
||||
:show_check_constraints: false
|
||||
:show_complete_foreign_keys: false
|
||||
:show_foreign_keys: true
|
||||
:show_indexes: true
|
||||
:simple_indexes: false
|
||||
:sort: false
|
||||
:timestamp: false
|
||||
:trace: false
|
||||
:with_comment: true
|
||||
:with_column_comments: true
|
||||
:with_table_comments: true
|
||||
:active_admin: false
|
||||
:command:
|
||||
:debug: false
|
||||
:hide_default_column_types: ''
|
||||
:hide_limit_column_types: ''
|
||||
:ignore_columns:
|
||||
:ignore_routes:
|
||||
:models: true
|
||||
:routes: false
|
||||
:skip_on_db_migrate: false
|
||||
:target_action: :do_annotations
|
||||
:wrapper:
|
||||
:wrapper_close:
|
||||
:wrapper_open:
|
||||
:classes_default_to_s: []
|
||||
:additional_file_patterns: []
|
||||
:model_dir:
|
||||
- app/models
|
||||
:require: []
|
||||
:root_dir:
|
||||
- ''
|
||||
@@ -77,6 +77,33 @@ jobs:
|
||||
run: |
|
||||
./node_modules/eslint/bin/eslint.js "app/javascript/**/*.js"
|
||||
|
||||
brakeman:
|
||||
name: Brakeman
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.4.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gem-
|
||||
- name: Install gems
|
||||
run: |
|
||||
gem install bundler
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 4
|
||||
yarn install
|
||||
sudo apt-get update
|
||||
sudo apt-get install libvips
|
||||
- name: Run Brakeman
|
||||
run: bundle exec brakeman -q --exit-on-warn
|
||||
|
||||
rspec:
|
||||
name: RSpec
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -46,7 +46,6 @@ gem 'twitter_cldr', require: false
|
||||
gem 'tzinfo-data'
|
||||
|
||||
group :development, :test do
|
||||
gem 'annotate'
|
||||
gem 'better_html'
|
||||
gem 'bullet'
|
||||
gem 'debug'
|
||||
@@ -63,6 +62,9 @@ group :development, :test do
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'annotaterb'
|
||||
gem 'brakeman', require: false
|
||||
gem 'foreman', require: false
|
||||
gem 'letter_opener_web'
|
||||
gem 'web-console'
|
||||
end
|
||||
|
||||
+8
-5
@@ -74,9 +74,7 @@ GEM
|
||||
uri (>= 0.13.1)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
annotate (2.6.5)
|
||||
activerecord (>= 2.3.0)
|
||||
rake (>= 0.8.7)
|
||||
annotaterb (4.14.0)
|
||||
arabic-letter-connector (0.1.1)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.3.0)
|
||||
@@ -120,6 +118,8 @@ GEM
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.18.4)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (7.0.0)
|
||||
racc
|
||||
builder (3.3.0)
|
||||
bullet (8.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
@@ -229,6 +229,7 @@ GEM
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
ffi (1.17.1-x86_64-linux-gnu)
|
||||
ffi (1.17.1-x86_64-linux-musl)
|
||||
foreman (0.88.1)
|
||||
geom2d (0.4.1)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
@@ -558,7 +559,7 @@ GEM
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
uniform_notifier (1.16.0)
|
||||
uri (1.0.2)
|
||||
uri (1.0.3)
|
||||
useragent (0.16.11)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
@@ -588,13 +589,14 @@ PLATFORMS
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
annotate
|
||||
annotaterb
|
||||
arabic-letter-connector
|
||||
aws-sdk-s3
|
||||
aws-sdk-secretsmanager
|
||||
azure-storage-blob
|
||||
better_html
|
||||
bootsnap
|
||||
brakeman
|
||||
bullet
|
||||
cancancan
|
||||
capybara
|
||||
@@ -610,6 +612,7 @@ DEPENDENCIES
|
||||
faker
|
||||
faraday
|
||||
faraday-follow_redirects
|
||||
foreman
|
||||
google-cloud-storage
|
||||
hexapdf
|
||||
image_processing
|
||||
|
||||
@@ -39,7 +39,7 @@ class AccountConfigsController < ApplicationController
|
||||
end
|
||||
|
||||
def account_config_params
|
||||
params.required(:account_config).permit!.tap do |attrs|
|
||||
params.required(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
|
||||
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,7 +39,7 @@ class NotificationsSettingsController < ApplicationController
|
||||
end
|
||||
|
||||
def email_config_params
|
||||
params.require(:account_config).permit!.tap do |attrs|
|
||||
params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
|
||||
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,7 +50,7 @@ class PersonalizationSettingsController < ApplicationController
|
||||
end
|
||||
|
||||
def account_config_params
|
||||
attrs = params.require(:account_config).permit!
|
||||
attrs = params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] })
|
||||
|
||||
return attrs if attrs[:value].is_a?(String)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class SubmissionsPreviewController < ApplicationController
|
||||
|
||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||
|
||||
raise ActionController::RoutingError if @submission.account.archived_at?
|
||||
raise ActionController::RoutingError, I18n.t('not_found') if @submission.account.archived_at?
|
||||
|
||||
if !@submission.submitters.all?(&:completed_at?) && !signature_valid &&
|
||||
(!current_user || !current_ability.can?(:read, @submission))
|
||||
|
||||
@@ -26,7 +26,7 @@ class UserConfigsController < ApplicationController
|
||||
end
|
||||
|
||||
def user_config_params
|
||||
params.required(:user_config).permit!.tap do |attrs|
|
||||
params.required(:user_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
|
||||
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,17 +45,16 @@ class UsersController < ApplicationController
|
||||
return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo?
|
||||
|
||||
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
|
||||
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
|
||||
|
||||
if params.dig(:user, :account_id).present?
|
||||
account = Account.accessible_by(current_ability).find(params[:user][:account_id])
|
||||
account = Account.accessible_by(current_ability).find(params.dig(:user, :account_id))
|
||||
|
||||
authorize!(:manage, account)
|
||||
|
||||
@user.account = account
|
||||
end
|
||||
|
||||
if @user.update(attrs)
|
||||
if @user.update(attrs.except(current_user == @user ? :role : nil))
|
||||
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
|
||||
else
|
||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
||||
@@ -84,8 +83,11 @@ class UsersController < ApplicationController
|
||||
|
||||
def user_params
|
||||
if params.key?(:user)
|
||||
params.require(:user).permit(:email, :first_name, :last_name, :password,
|
||||
:role, :archived_at, :account_id)
|
||||
permitted_params = %i[email first_name last_name password archived_at]
|
||||
|
||||
permitted_params << :role if role_valid?(params.dig(:user, :role))
|
||||
|
||||
params.require(:user).permit(permitted_params)
|
||||
else
|
||||
{}
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
class="field-area flex absolute lg:text-base -outline-offset-1"
|
||||
dir="auto"
|
||||
:style="computedStyle"
|
||||
:class="{ 'font-mono': field.preferences?.font === 'Courier', 'font-serif': field.preferences?.font === 'Times', 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||
:class="{ 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||
>
|
||||
<div
|
||||
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||
@@ -168,7 +168,7 @@
|
||||
<div
|
||||
v-else-if="field.type === 'cells'"
|
||||
class="w-full flex items-center"
|
||||
:class="{ 'justify-end': field.preferences?.align === 'right' }"
|
||||
:class="{ 'justify-end': field.preferences?.align === 'right', ...fontClasses }"
|
||||
>
|
||||
<div
|
||||
v-for="(char, index) in modelValue"
|
||||
@@ -184,7 +184,7 @@
|
||||
ref="textContainer"
|
||||
dir="auto"
|
||||
class="flex items-center px-0.5 w-full"
|
||||
:class="alignClasses[field.preferences?.align]"
|
||||
:class="{ ...alignClasses, ...fontClasses }"
|
||||
>
|
||||
<span
|
||||
v-if="field && field.name && withFieldPlaceholder && !modelValue && modelValue !== 0"
|
||||
@@ -326,10 +326,26 @@ export default {
|
||||
}
|
||||
},
|
||||
alignClasses () {
|
||||
if (!this.field.preferences) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
center: 'text-center',
|
||||
left: 'text-left',
|
||||
right: 'text-right'
|
||||
'text-center': this.field.preferences.align === 'center',
|
||||
'text-left': this.field.preferences.align === 'left',
|
||||
'text-right': this.field.preferences.align === 'right'
|
||||
}
|
||||
},
|
||||
fontClasses () {
|
||||
if (!this.field.preferences) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
'font-mono': this.field.preferences.font === 'Courier',
|
||||
'font-serif': this.field.preferences.font === 'Times',
|
||||
'font-bold': ['bold_italic', 'bold'].includes(this.field.preferences.font_type),
|
||||
italic: ['bold_italic', 'italic'].includes(this.field.preferences.font_type)
|
||||
}
|
||||
},
|
||||
option () {
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
:with-required="false"
|
||||
:with-areas="false"
|
||||
@click-formula="isShowFormulaModal = true"
|
||||
@click-font="isShowFontModal = true"
|
||||
@click-description="isShowDescriptionModal = true"
|
||||
@click-condition="isShowConditionsModal = true"
|
||||
@scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]"
|
||||
@@ -161,7 +162,7 @@
|
||||
ref="touchValueTarget"
|
||||
class="flex items-center h-full w-full"
|
||||
dir="auto"
|
||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
|
||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? fontClasses : 'justify-center']"
|
||||
@click="focusValueInput"
|
||||
>
|
||||
<span
|
||||
@@ -253,6 +254,17 @@
|
||||
@close="isShowFormulaModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowFontModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<FontModal
|
||||
:field="field"
|
||||
:editable="editable && !defaultField"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowFontModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
@@ -283,6 +295,7 @@ import FieldType from './field_type'
|
||||
import Field from './field'
|
||||
import FieldSettings from './field_settings'
|
||||
import FormulaModal from './formula_modal'
|
||||
import FontModal from './font_modal'
|
||||
import ConditionsModal from './conditions_modal'
|
||||
import DescriptionModal from './description_modal'
|
||||
import { IconX, IconCheck, IconDotsVertical } from '@tabler/icons-vue'
|
||||
@@ -295,6 +308,7 @@ export default {
|
||||
IconCheck,
|
||||
FieldSettings,
|
||||
FormulaModal,
|
||||
FontModal,
|
||||
IconDotsVertical,
|
||||
DescriptionModal,
|
||||
ConditionsModal,
|
||||
@@ -352,6 +366,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
isShowFormulaModal: false,
|
||||
isShowFontModal: false,
|
||||
isShowConditionsModal: false,
|
||||
isContenteditable: false,
|
||||
isSettingsFocus: false,
|
||||
@@ -386,11 +401,19 @@ export default {
|
||||
defaultName () {
|
||||
return this.buildDefaultName(this.field, this.template.fields)
|
||||
},
|
||||
alignClasses () {
|
||||
fontClasses () {
|
||||
if (!this.field.preferences) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
center: 'justify-center',
|
||||
left: 'justify-start',
|
||||
right: 'justify-end'
|
||||
'justify-center': this.field.preferences.align === 'center',
|
||||
'justify-start': this.field.preferences.align === 'left',
|
||||
'justify-end': this.field.preferences.align === 'right',
|
||||
'font-mono': this.field.preferences.font === 'Courier',
|
||||
'font-serif': this.field.preferences.font === 'Times',
|
||||
'font-bold': ['bold_italic', 'bold'].includes(this.field.preferences.font_type),
|
||||
italic: ['bold_italic', 'italic'].includes(this.field.preferences.font_type)
|
||||
}
|
||||
},
|
||||
optionIndexText () {
|
||||
@@ -483,6 +506,10 @@ export default {
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
},
|
||||
maybeToggleDefaultValue () {
|
||||
if (!this.editable) {
|
||||
return
|
||||
}
|
||||
|
||||
if (['text', 'number'].includes(this.field.type)) {
|
||||
this.isContenteditable = true
|
||||
|
||||
|
||||
@@ -1316,6 +1316,10 @@ export default {
|
||||
}
|
||||
},
|
||||
onDropfield (area) {
|
||||
if (!this.editable) {
|
||||
return
|
||||
}
|
||||
|
||||
const field = this.fieldsDragFieldRef.value || {
|
||||
name: '',
|
||||
uuid: v4(),
|
||||
@@ -1338,9 +1342,8 @@ export default {
|
||||
}
|
||||
|
||||
if (field.type === 'date') {
|
||||
field.preferences = {
|
||||
format: this.defaultDateFormat
|
||||
}
|
||||
field.preferences ||= {}
|
||||
field.preferences.format ||= this.defaultDateFormat
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
:editable="editable"
|
||||
:background-color="dropdownBgColor"
|
||||
@click-formula="isShowFormulaModal = true"
|
||||
@click-font="isShowFontModal = true"
|
||||
@click-description="isShowDescriptionModal = true"
|
||||
@click-condition="isShowConditionsModal = true"
|
||||
@set-draw="$emit('set-draw', $event)"
|
||||
@@ -231,6 +232,17 @@
|
||||
@close="isShowFormulaModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowFontModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<FontModal
|
||||
:field="field"
|
||||
:editable="editable && !defaultField"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowFontModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
@@ -261,6 +273,7 @@ import FieldType from './field_type'
|
||||
import PaymentSettings from './payment_settings'
|
||||
import FieldSettings from './field_settings'
|
||||
import FormulaModal from './formula_modal'
|
||||
import FontModal from './font_modal'
|
||||
import ConditionsModal from './conditions_modal'
|
||||
import DescriptionModal from './description_modal'
|
||||
import { IconRouteAltLeft, IconMathFunction, IconNewSection, IconTrashX, IconSettings } from '@tabler/icons-vue'
|
||||
@@ -275,6 +288,7 @@ export default {
|
||||
PaymentSettings,
|
||||
IconNewSection,
|
||||
FormulaModal,
|
||||
FontModal,
|
||||
DescriptionModal,
|
||||
ConditionsModal,
|
||||
IconRouteAltLeft,
|
||||
@@ -305,6 +319,7 @@ export default {
|
||||
isNameFocus: false,
|
||||
showPaymentModal: false,
|
||||
isShowFormulaModal: false,
|
||||
isShowFontModal: false,
|
||||
isShowConditionsModal: false,
|
||||
isShowDescriptionModal: false,
|
||||
renderDropdown: false
|
||||
|
||||
@@ -330,6 +330,19 @@
|
||||
v-if="field.type != 'stamp'"
|
||||
class="pb-0.5 mt-0.5"
|
||||
>
|
||||
<li v-if="field.type == 'text'">
|
||||
<label
|
||||
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||
@click="$emit('click-font')"
|
||||
>
|
||||
<IconTypography
|
||||
width="18"
|
||||
/>
|
||||
<span class="text-sm">
|
||||
{{ t('font') }}
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li
|
||||
v-if="field.type != 'stamp'"
|
||||
>
|
||||
@@ -427,7 +440,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconRouteAltLeft, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy } from '@tabler/icons-vue'
|
||||
import { IconRouteAltLeft, IconTypography, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FieldSettings',
|
||||
@@ -438,6 +451,7 @@ export default {
|
||||
IconRouteAltLeft,
|
||||
IconCopy,
|
||||
IconNewSection,
|
||||
IconTypography,
|
||||
IconX
|
||||
},
|
||||
inject: ['template', 'save', 't'],
|
||||
@@ -472,7 +486,7 @@ export default {
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'remove-area'],
|
||||
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'click-font', 'remove-area'],
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<div
|
||||
class="modal modal-open items-start !animate-none overflow-y-auto"
|
||||
>
|
||||
<div
|
||||
class="absolute top-0 bottom-0 right-0 left-0"
|
||||
@click.prevent="$emit('close')"
|
||||
/>
|
||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
||||
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||
<span>
|
||||
{{ t('font') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||
</span>
|
||||
<a
|
||||
href="#"
|
||||
class="text-xl"
|
||||
@click.prevent="$emit('close')"
|
||||
>×</a>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<div>
|
||||
<div class="flex items-center space-x-1.5">
|
||||
<span>
|
||||
<div class="dropdown">
|
||||
<label
|
||||
tabindex="0"
|
||||
class="base-input flex items-center justify-between items-center"
|
||||
style="height: 32px; padding-right: 0; width: 120px"
|
||||
:class="fonts.find((f) => f.value === preferences.font)?.class"
|
||||
>
|
||||
<span style="margin-top: 1px">
|
||||
{{ preferences.font || 'Default' }}
|
||||
</span>
|
||||
<IconChevronDown
|
||||
class="ml-2 mr-2 mt-0.5"
|
||||
width="18"
|
||||
height="18"
|
||||
/>
|
||||
</label>
|
||||
<div
|
||||
tabindex="0"
|
||||
class="dropdown-content p-0 mt-1 block z-10 menu shadow bg-white border border-base-300 rounded-md w-52"
|
||||
>
|
||||
<div
|
||||
v-for="(font, index) in fonts"
|
||||
:key="index"
|
||||
:value="font.value"
|
||||
:class="{ 'bg-base-300': preferences.font == font.value, [font.class]: true }"
|
||||
class="hover:bg-base-300 px-2 py-1.5 cursor-pointer"
|
||||
@click="[font.value ? preferences.font = font.value : delete preferences.font, closeDropdown()]"
|
||||
>
|
||||
{{ font.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<span class="relative">
|
||||
<select
|
||||
class="select input-bordered bg-white select-sm text-center pl-2"
|
||||
style="font-size: 16px; width: 86px; text-align-last: center;"
|
||||
@change="$event.target.value ? preferences.font_size = parseInt($event.target.value) : delete preferences.font_size"
|
||||
>
|
||||
<option
|
||||
:selected="!preferences.font_size"
|
||||
value=""
|
||||
>
|
||||
Auto
|
||||
</option>
|
||||
<option
|
||||
v-for="size in sizes"
|
||||
:key="size"
|
||||
:value="size"
|
||||
:selected="size === preferences.font_size"
|
||||
>
|
||||
{{ size }}
|
||||
</option>
|
||||
</select>
|
||||
<span
|
||||
class="border-l pl-1.5 absolute bg-white absolute bottom-0 pointer-events-none text-sm h-5"
|
||||
style="right: 13px; top: 7px"
|
||||
>
|
||||
pt
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
<div
|
||||
class="join"
|
||||
style="height: 32px"
|
||||
>
|
||||
<button
|
||||
v-for="(type, index) in types"
|
||||
:key="index"
|
||||
class="btn btn-sm join-item bg-white input-bordered hover:border-base-content/20 hover:bg-base-200/50 px-2"
|
||||
:class="{ '!bg-base-300': preferences.font_type?.includes(type.value) }"
|
||||
@click="setFontType(type.value)"
|
||||
>
|
||||
<component :is="type.icon" />
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
<span>
|
||||
<div
|
||||
class="join"
|
||||
style="height: 32px"
|
||||
>
|
||||
<button
|
||||
v-for="(align, index) in aligns"
|
||||
:key="index"
|
||||
class="btn btn-sm join-item bg-white input-bordered hover:border-base-content/20 hover:bg-base-200/50 px-2"
|
||||
:class="{ '!bg-base-300': preferences.align === align.value }"
|
||||
@click="align.value && preferences.align != align.value ? preferences.align = align.value : delete preferences.align"
|
||||
>
|
||||
<component :is="align.icon" />
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
<span>
|
||||
<select
|
||||
class="input input-bordered bg-white input-sm text-lg rounded-md"
|
||||
style="-webkit-appearance: none; -moz-appearance: none; text-indent: 0px; text-overflow: ''; padding: 0px 6px; height: 32px"
|
||||
@change="$event.target.value ? preferences.color = $event.target.value : delete preferences.color"
|
||||
>
|
||||
<option
|
||||
v-for="(color, index) in colors"
|
||||
:key="index"
|
||||
:value="color.value"
|
||||
:selected="color.value == preferences.color"
|
||||
>
|
||||
{{ color.label }}
|
||||
</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<div
|
||||
class="flex items-center border border-base-content/20 rounded-xl bg-white px-4 h-16"
|
||||
:style="{
|
||||
color: preferences.color || 'black',
|
||||
fontSize: (preferences.font_size || 12) + 'pt',
|
||||
}"
|
||||
:class="textClasses"
|
||||
>
|
||||
<span
|
||||
contenteditable="true"
|
||||
class="outline-none"
|
||||
>
|
||||
{{ field.default_value || field.name || buildDefaultName(field, template.fields) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button
|
||||
class="base-button w-full"
|
||||
@click.prevent="saveAndClose"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconChevronDown, IconBold, IconItalic, IconAlignLeft, IconAlignRight, IconAlignCenter } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FontModal',
|
||||
components: {
|
||||
IconChevronDown
|
||||
},
|
||||
inject: ['t', 'save', 'template'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
buildDefaultName: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
data () {
|
||||
return {
|
||||
preferences: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fonts () {
|
||||
return [
|
||||
{ value: null, label: 'Default' },
|
||||
{ value: 'Times', label: 'Times', class: 'font-serif' },
|
||||
{ value: 'Courier', label: 'Courier', class: 'font-mono' }
|
||||
]
|
||||
},
|
||||
types () {
|
||||
return [
|
||||
{ icon: IconBold, value: 'bold' },
|
||||
{ icon: IconItalic, value: 'italic' }
|
||||
]
|
||||
},
|
||||
aligns () {
|
||||
return [
|
||||
{ icon: IconAlignLeft, value: 'left' },
|
||||
{ icon: IconAlignCenter, value: 'center' },
|
||||
{ icon: IconAlignRight, value: 'right' }
|
||||
]
|
||||
},
|
||||
sizes () {
|
||||
return [...Array(23).keys()].map(i => i + 6)
|
||||
},
|
||||
colors () {
|
||||
return [
|
||||
{ label: '⬛', value: 'black' },
|
||||
{ label: '🟦', value: 'blue' },
|
||||
{ label: '🟥', value: 'red' }
|
||||
]
|
||||
},
|
||||
textClasses () {
|
||||
return {
|
||||
'font-mono': this.preferences.font === 'Courier',
|
||||
'font-serif': this.preferences.font === 'Times',
|
||||
'justify-center': this.preferences.align === 'center',
|
||||
'justify-start': this.preferences.align === 'left',
|
||||
'justify-end': this.preferences.align === 'right',
|
||||
'font-bold': ['bold_italic', 'bold'].includes(this.preferences.font_type),
|
||||
italic: ['bold_italic', 'italic'].includes(this.preferences.font_type)
|
||||
}
|
||||
},
|
||||
keys () {
|
||||
return ['font_type', 'font_size', 'color', 'align', 'font']
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.preferences = this.keys.reduce((acc, key) => {
|
||||
acc[key] = this.field.preferences?.[key]
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
methods: {
|
||||
closeDropdown () {
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
},
|
||||
setFontType (value) {
|
||||
if (value === 'bold') {
|
||||
if (this.preferences.font_type === 'bold') {
|
||||
delete this.preferences.font_type
|
||||
} else if (this.preferences.font_type === 'italic') {
|
||||
this.preferences.font_type = 'bold_italic'
|
||||
} else if (this.preferences.font_type === 'bold_italic') {
|
||||
this.preferences.font_type = 'italic'
|
||||
} else {
|
||||
this.preferences.font_type = value
|
||||
}
|
||||
}
|
||||
|
||||
if (value === 'italic') {
|
||||
if (this.preferences.font_type === 'italic') {
|
||||
delete this.preferences.font_type
|
||||
} else if (this.preferences.font_type === 'bold') {
|
||||
this.preferences.font_type = 'bold_italic'
|
||||
} else if (this.preferences.font_type === 'bold_italic') {
|
||||
this.preferences.font_type = 'bold'
|
||||
} else {
|
||||
this.preferences.font_type = value
|
||||
}
|
||||
}
|
||||
},
|
||||
saveAndClose () {
|
||||
this.field.preferences ||= {}
|
||||
|
||||
this.keys.forEach((key) => delete this.field.preferences[key])
|
||||
|
||||
Object.assign(this.field.preferences, this.preferences)
|
||||
|
||||
this.save()
|
||||
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
const en = {
|
||||
font: 'Font',
|
||||
party: 'Party',
|
||||
method: 'Method',
|
||||
reorder_fields: 'Reorder fields',
|
||||
@@ -161,6 +162,7 @@ const en = {
|
||||
}
|
||||
|
||||
const es = {
|
||||
fuente: 'Fuente',
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
reorder_fields: 'Reordenar campos',
|
||||
@@ -323,6 +325,7 @@ const es = {
|
||||
}
|
||||
|
||||
const it = {
|
||||
font: 'Carattere',
|
||||
party: 'Parte',
|
||||
method: 'Metodo',
|
||||
reorder_fields: 'Riordina i campi',
|
||||
@@ -485,6 +488,7 @@ const it = {
|
||||
}
|
||||
|
||||
const pt = {
|
||||
fonte: 'Fonte',
|
||||
party: 'Parte',
|
||||
method: 'Método',
|
||||
reorder_fields: 'Reorganizar campos',
|
||||
@@ -647,6 +651,7 @@ const pt = {
|
||||
}
|
||||
|
||||
const fr = {
|
||||
font: 'Police',
|
||||
party: 'Partie',
|
||||
method: 'Méthode',
|
||||
reorder_fields: 'Réorganiser les champs',
|
||||
@@ -809,6 +814,7 @@ const fr = {
|
||||
}
|
||||
|
||||
const de = {
|
||||
font: 'Schriftart',
|
||||
party: 'Partei',
|
||||
method: 'Verfahren',
|
||||
reorder_fields: 'Felder neu anordnen',
|
||||
|
||||
@@ -55,6 +55,7 @@ class Submitter < ApplicationRecord
|
||||
has_many_attached :attachments
|
||||
has_many_attached :preview_documents
|
||||
has_many :template_accesses, through: :template
|
||||
has_many :email_events, as: :emailable, dependent: (Docuseal.multitenant? ? nil : :destroy)
|
||||
|
||||
has_many :document_generation_events, dependent: :destroy
|
||||
has_many :submission_events, dependent: :destroy
|
||||
@@ -63,6 +64,8 @@ class Submitter < ApplicationRecord
|
||||
|
||||
scope :completed, -> { where.not(completed_at: nil) }
|
||||
|
||||
after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? }
|
||||
|
||||
def status
|
||||
if declined_at?
|
||||
'declined'
|
||||
@@ -108,4 +111,12 @@ class Submitter < ApplicationRecord
|
||||
fields.any? { |f| f['submitter_uuid'] == uuid && signature_field_types.include?(f['type']) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def anonymize_email_events
|
||||
email_events.each do |event|
|
||||
event.update!(email: Digest::MD5.base64digest(event.email))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -63,7 +63,7 @@
|
||||
<span>
|
||||
<%= t('add_signature_id_to_the_documents') %>
|
||||
</span>
|
||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
|
||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -75,7 +75,7 @@
|
||||
<span>
|
||||
<%= t('require_signing_reason') %>
|
||||
</span>
|
||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
|
||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()', disabled: can?(:manage, :cfr) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<% align = field.dig('preferences', 'align') %>
|
||||
<% color = field.dig('preferences', 'color') %>
|
||||
<% font = field.dig('preferences', 'font') %>
|
||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||
<% font_type = field.dig('preferences', 'font_type') %>
|
||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||
<% if field['type'] == 'signature' %>
|
||||
<div class="flex flex-col justify-between h-full overflow-hidden">
|
||||
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<%= svg_icon('external_link', class: 'w-6 h-6') %>
|
||||
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
||||
</a>
|
||||
<% else %>
|
||||
<% elsif signed_in? %>
|
||||
<%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %>
|
||||
<%= svg_icon('logs', class: 'w-6 h-6') %>
|
||||
<span class="hidden md:block"><%= t('event_log') %></span>
|
||||
@@ -105,7 +105,18 @@
|
||||
<% value = values[field['uuid']] %>
|
||||
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
||||
<% next if value.blank? %>
|
||||
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||
<% if (mask = field.dig('preferences', 'mask').presence) && signed_in? && can?(:read, @submission) %>
|
||||
<span class="group">
|
||||
<span class="hidden group-hover:inline">
|
||||
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||
</span>
|
||||
<span class="group-hover:hidden">
|
||||
<%= render 'submissions/value', area:, field:, attachments_index:, value: Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', '), locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||
</span>
|
||||
</span>
|
||||
<% else %>
|
||||
<%= render 'submissions/value', area:, field:, attachments_index:, value: mask.present? ? Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') : value, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,12 +243,21 @@
|
||||
</div>
|
||||
<% elsif field['type'] == 'checkbox' %>
|
||||
<%= svg_icon('check', class: 'w-6 h-6') %>
|
||||
<% elsif field['type'] == 'number' %>
|
||||
<%= NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
||||
<% elsif field['type'] == 'date' %>
|
||||
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
||||
<% else %>
|
||||
<div class="whitespace-pre-wrap"><%= Array.wrap(value).join(', ') %></div>
|
||||
<% if field['type'] == 'number' %>
|
||||
<% value = NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
||||
<% elsif field['type'] == 'date' %>
|
||||
<% value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
||||
<% end %>
|
||||
<% if (mask = field.dig('preferences', 'mask').presence) %>
|
||||
<% if signed_in? && can?(:read, @submission) %>
|
||||
<div class="[&:not(:hover)]:after:content-[attr(data-value)] block whitespace-pre-wrap group" data-value="<%= Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') %>"><span class="hidden group-hover:block"><%= Array.wrap(value).join(', ') %></span></div>
|
||||
<% else %>
|
||||
<div class="whitespace-pre-wrap"><%= Array.wrap(value).map { |e| TextUtils.mask_value(e, mask) }.join(', ') %></div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="whitespace-pre-wrap"><%= Array.wrap(value).join(', ') %></div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
|
||||
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %>
|
||||
<%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value: field.dig('preferences', 'mask').present? ? TextUtils.mask_value(value, field.dig('preferences', 'mask')) : value, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"ignored_warnings": [
|
||||
{
|
||||
"fingerprint": "25f4ce5fee1e1180fa1919dc4ee78db3ab3457a956e4679503aa745771a43836",
|
||||
"note": "Permitted parameters are necessary for creating submitters via API"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -187,7 +187,7 @@ Devise.setup do |config|
|
||||
|
||||
# ==> Configuration for :rememberable
|
||||
# The time the user will be remembered without asking for credentials again.
|
||||
config.remember_for = 2.years
|
||||
config.remember_for = ENV.fetch('SESSION_REMEMBER_DAYS', '730').to_i.days
|
||||
|
||||
# Invalidates all the remember me tokens when the user signs out.
|
||||
config.expire_all_remember_me_on_sign_out = true
|
||||
|
||||
+8
-2
@@ -33,8 +33,14 @@ environment ENV.fetch('RAILS_ENV', 'development')
|
||||
# the concurrency of the application would be max `threads` * `workers`.
|
||||
# Workers do not work on JRuby or Windows (both of which do not support
|
||||
# processes).
|
||||
#
|
||||
workers ENV.fetch('WEB_CONCURRENCY', 0)
|
||||
|
||||
if ENV['WEB_CONCURRENCY_AUTO'] == 'true'
|
||||
require 'etc'
|
||||
|
||||
workers Etc.nprocessors
|
||||
else
|
||||
workers ENV.fetch('WEB_CONCURRENCY', 0)
|
||||
end
|
||||
|
||||
# Use the `preload_app!` method when specifying a `workers` number.
|
||||
# This directive tells Puma to first boot the application and load code
|
||||
|
||||
@@ -122,6 +122,8 @@ Rails.application.routes.draw do
|
||||
get '/disk/:encoded_key/*filename' => 'active_storage/disk#show', as: :rails_disk_service
|
||||
put '/disk/:encoded_token' => 'active_storage/disk#update', as: :update_rails_disk_service
|
||||
post '/direct_uploads' => 'active_storage/direct_uploads#create', as: :rails_direct_uploads
|
||||
|
||||
ActiveSupport.run_load_hooks(:multitenant_routes, self)
|
||||
end
|
||||
|
||||
resources :start_form, only: %i[show update], path: 'd', param: 'slug' do
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateConsole1984Tables < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :console1984_sessions do |t|
|
||||
t.text :reason
|
||||
t.references :user, null: false, index: false
|
||||
t.timestamps
|
||||
|
||||
t.index :created_at
|
||||
t.index %i[user_id created_at]
|
||||
end
|
||||
|
||||
create_table :console1984_users do |t|
|
||||
t.string :username, null: false
|
||||
t.timestamps
|
||||
|
||||
t.index [:username]
|
||||
end
|
||||
|
||||
create_table :console1984_commands do |t|
|
||||
t.text :statements
|
||||
t.references :sensitive_access
|
||||
t.references :session, null: false, index: false
|
||||
t.timestamps
|
||||
|
||||
t.index %i[session_id created_at sensitive_access_id], name: 'on_session_and_sensitive_chronologically'
|
||||
end
|
||||
|
||||
create_table :console1984_sensitive_accesses do |t|
|
||||
t.text :justification
|
||||
t.references :session, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
+35
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2024_12_07_172237) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_02_25_111255) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -112,6 +112,40 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_07_172237) do
|
||||
t.index ["submitter_id"], name: "index_completed_submitters_on_submitter_id", unique: true
|
||||
end
|
||||
|
||||
create_table "console1984_commands", force: :cascade do |t|
|
||||
t.text "statements"
|
||||
t.bigint "sensitive_access_id"
|
||||
t.bigint "session_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["sensitive_access_id"], name: "index_console1984_commands_on_sensitive_access_id"
|
||||
t.index ["session_id", "created_at", "sensitive_access_id"], name: "on_session_and_sensitive_chronologically"
|
||||
end
|
||||
|
||||
create_table "console1984_sensitive_accesses", force: :cascade do |t|
|
||||
t.text "justification"
|
||||
t.bigint "session_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["session_id"], name: "index_console1984_sensitive_accesses_on_session_id"
|
||||
end
|
||||
|
||||
create_table "console1984_sessions", force: :cascade do |t|
|
||||
t.text "reason"
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["created_at"], name: "index_console1984_sessions_on_created_at"
|
||||
t.index ["user_id", "created_at"], name: "index_console1984_sessions_on_user_id_and_created_at"
|
||||
end
|
||||
|
||||
create_table "console1984_users", force: :cascade do |t|
|
||||
t.string "username", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["username"], name: "index_console1984_users_on_username"
|
||||
end
|
||||
|
||||
create_table "document_generation_events", force: :cascade do |t|
|
||||
t.bigint "submitter_id", null: false
|
||||
t.string "event_name", null: false
|
||||
|
||||
@@ -3,10 +3,28 @@
|
||||
module SendWebhookRequest
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
LOCALHOSTS = %w[0.0.0.0 127.0.0.1 localhost].freeze
|
||||
|
||||
HttpsError = Class.new(StandardError)
|
||||
LocalhostError = Class.new(StandardError)
|
||||
|
||||
module_function
|
||||
|
||||
def call(webhook_url, event_type:, data:)
|
||||
Faraday.post(webhook_url.url) do |req|
|
||||
uri = begin
|
||||
URI(webhook_url.url)
|
||||
rescue URI::Error
|
||||
Addressable::URI.parse(webhook_url.url).normalize
|
||||
end
|
||||
|
||||
if Docuseal.multitenant?
|
||||
raise HttpsError, 'Only HTTPS is allowed.' if uri.scheme != 'https' &&
|
||||
!AccountConfig.exists?(key: :allow_http,
|
||||
account_id: webhook_url.account_id)
|
||||
raise LocalhostError, "Can't send to localhost." if uri.host.in?(LOCALHOSTS)
|
||||
end
|
||||
|
||||
Faraday.post(uri) do |req|
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.headers['User-Agent'] = USER_AGENT
|
||||
req.headers.merge!(webhook_url.secret.to_h) if webhook_url.secret.present?
|
||||
|
||||
@@ -107,6 +107,7 @@ module Submissions
|
||||
|
||||
def normalize_email(email)
|
||||
return if email.blank?
|
||||
return if email.is_a?(Numeric)
|
||||
|
||||
return email.downcase if email.to_s.include?(',') ||
|
||||
email.to_s.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie)\z/) ||
|
||||
|
||||
@@ -359,13 +359,14 @@ module Submissions
|
||||
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
|
||||
end
|
||||
|
||||
if field['type'] == 'number'
|
||||
value = NumberUtils.format_number(value,
|
||||
field.dig('preferences', 'format'))
|
||||
end
|
||||
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'
|
||||
|
||||
value = value.join(', ') if value.is_a?(Array)
|
||||
|
||||
if (mask = field.dig('preferences', 'mask').presence)
|
||||
value = TextUtils.mask_value(value, mask)
|
||||
end
|
||||
|
||||
composer.formatted_text_box([{ text: TextUtils.maybe_rtl_reverse(value.to_s.presence || 'n/a') }],
|
||||
text_align: value.to_s.match?(RTL_REGEXP) ? :right : :left,
|
||||
padding: [0, 0, 10, 0])
|
||||
|
||||
@@ -4,12 +4,29 @@ module Submissions
|
||||
module GenerateResultAttachments
|
||||
FONT_SIZE = 11
|
||||
FONT_PATH = '/fonts/GoNotoKurrent-Regular.ttf'
|
||||
FONT_BOLD_PATH = '/fonts/GoNotoKurrent-Bold.ttf'
|
||||
FONT_NAME = if File.exist?(FONT_PATH)
|
||||
FONT_PATH
|
||||
else
|
||||
'Helvetica'
|
||||
end
|
||||
|
||||
FONT_BOLD_NAME = if File.exist?(FONT_BOLD_PATH)
|
||||
FONT_BOLD_PATH
|
||||
else
|
||||
'Helvetica'
|
||||
end
|
||||
|
||||
FONT_ITALIC_NAME = 'Helvetica'
|
||||
FONT_BOLD_ITALIC_NAME = 'Helvetica'
|
||||
|
||||
FONT_VARIANS = {
|
||||
none: FONT_NAME,
|
||||
bold: FONT_BOLD_NAME,
|
||||
italic: FONT_ITALIC_NAME,
|
||||
bold_italic: FONT_BOLD_ITALIC_NAME
|
||||
}.freeze
|
||||
|
||||
SIGN_REASON = 'Signed by %<name>s with DocuSeal.com'
|
||||
|
||||
RTL_REGEXP = TextUtils::RTL_REGEXP
|
||||
@@ -18,12 +35,15 @@ module Submissions
|
||||
TEXT_TOP_MARGIN = 1
|
||||
MAX_PAGE_ROTATE = 20
|
||||
|
||||
COURIER_FONT = 'Courier'
|
||||
|
||||
A4_SIZE = [595, 842].freeze
|
||||
|
||||
TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING'
|
||||
DEFAULT_FONTS = %w[Times Helvetica Courier].freeze
|
||||
FONTS_LINE_HEIGHT = {
|
||||
'Times' => 1.4,
|
||||
'Helvetica' => 1.4,
|
||||
'Courier' => 1.6
|
||||
}.freeze
|
||||
|
||||
MISSING_GLYPH_REPLACE = {
|
||||
'▪' => '-',
|
||||
@@ -192,8 +212,16 @@ module Submissions
|
||||
fill_color = field.dig('preferences', 'color').presence
|
||||
|
||||
font_name = field.dig('preferences', 'font')
|
||||
font_variant = (field.dig('preferences', 'font_type').presence || 'none').to_sym
|
||||
|
||||
font_name = FONT_NAME unless font_name.in?(DEFAULT_FONTS)
|
||||
font = pdf.fonts.add(font_name)
|
||||
|
||||
if font_variant != :none && font_name == FONT_NAME
|
||||
font_name = FONT_VARIANS[font_variant]
|
||||
font_variant = nil unless font_name.in?(DEFAULT_FONTS)
|
||||
end
|
||||
|
||||
font = pdf.fonts.add(font_name, variant: font_variant)
|
||||
|
||||
value = submitter.values[field['uuid']]
|
||||
value = field['default_value'] if field['type'] == 'heading'
|
||||
@@ -391,6 +419,10 @@ module Submissions
|
||||
when ->(type) { type == 'cells' && !area['cell_w'].to_f.zero? }
|
||||
cell_width = area['cell_w'] * width
|
||||
|
||||
if (mask = field.dig('preferences', 'mask').presence)
|
||||
value = TextUtils.mask_value(value, mask)
|
||||
end
|
||||
|
||||
chars = TextUtils.maybe_rtl_reverse(value).chars
|
||||
chars = chars.reverse if field.dig('preferences', 'align') == 'right'
|
||||
|
||||
@@ -440,8 +472,12 @@ module Submissions
|
||||
|
||||
value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', '))
|
||||
|
||||
if (mask = field.dig('preferences', 'mask').presence)
|
||||
value = TextUtils.mask_value(value, mask)
|
||||
end
|
||||
|
||||
text_params = { font:, fill_color:, font_size: }
|
||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
||||
text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1)
|
||||
|
||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||
|
||||
@@ -450,7 +486,7 @@ module Submissions
|
||||
|
||||
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
||||
text_params[:font_size] = (font_size / 1.4).to_i
|
||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
||||
text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1)
|
||||
|
||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||
|
||||
@@ -461,7 +497,7 @@ module Submissions
|
||||
|
||||
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
||||
text_params[:font_size] = (font_size / 1.9).to_i
|
||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
||||
text_params[:line_height] = text_params[:font_size] * (FONTS_LINE_HEIGHT[font_name] || 1)
|
||||
|
||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This rake task was added by annotate_rb gem.
|
||||
|
||||
# Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this
|
||||
if Rails.env.development? && ENV['ANNOTATERB_SKIP_ON_DB_TASKS'].nil?
|
||||
require 'annotate_rb'
|
||||
|
||||
AnnotateRb::Core.load_rake_tasks
|
||||
end
|
||||
@@ -1,57 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
if Rails.env.development?
|
||||
require 'annotate'
|
||||
|
||||
task set_annotation_options: :environment do
|
||||
Annotate.set_defaults(
|
||||
'active_admin' => 'false',
|
||||
'additional_file_patterns' => [],
|
||||
'routes' => 'false',
|
||||
'models' => 'true',
|
||||
'position_in_routes' => 'before',
|
||||
'position_in_class' => 'before',
|
||||
'position_in_test' => 'before',
|
||||
'position_in_fixture' => 'before',
|
||||
'position_in_factory' => 'before',
|
||||
'position_in_serializer' => 'before',
|
||||
'show_foreign_keys' => 'true',
|
||||
'show_complete_foreign_keys' => 'false',
|
||||
'show_indexes' => 'true',
|
||||
'simple_indexes' => 'false',
|
||||
'model_dir' => 'app/models',
|
||||
'root_dir' => '',
|
||||
'include_version' => 'false',
|
||||
'require' => '',
|
||||
'exclude_tests' => 'true',
|
||||
'exclude_fixtures' => 'true',
|
||||
'exclude_factories' => 'true',
|
||||
'exclude_serializers' => 'false',
|
||||
'exclude_scaffolds' => 'true',
|
||||
'exclude_controllers' => 'true',
|
||||
'exclude_helpers' => 'true',
|
||||
'exclude_sti_subclasses' => 'false',
|
||||
'ignore_model_sub_dir' => 'false',
|
||||
'ignore_columns' => nil,
|
||||
'ignore_routes' => nil,
|
||||
'ignore_unknown_models' => 'false',
|
||||
'hide_limit_column_types' => 'integer,bigint,boolean',
|
||||
'hide_default_column_types' => 'json,jsonb,hstore',
|
||||
'skip_on_db_migrate' => 'false',
|
||||
'format_bare' => 'true',
|
||||
'format_rdoc' => 'false',
|
||||
'format_yard' => 'false',
|
||||
'format_markdown' => 'false',
|
||||
'sort' => 'false',
|
||||
'force' => 'false',
|
||||
'frozen' => 'false',
|
||||
'classified_sort' => 'true',
|
||||
'trace' => 'false',
|
||||
'wrapper_open' => nil,
|
||||
'wrapper_close' => nil,
|
||||
'with_comment' => 'true'
|
||||
)
|
||||
end
|
||||
|
||||
Annotate.load_tasks
|
||||
end
|
||||
@@ -101,7 +101,7 @@ module Templates
|
||||
|
||||
{
|
||||
uuid: SecureRandom.uuid,
|
||||
required: false,
|
||||
required: field.flags.include?(:required),
|
||||
preferences: {},
|
||||
areas:,
|
||||
**field_properties
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
module TextUtils
|
||||
RTL_REGEXP = /[\p{Hebrew}\p{Arabic}]/
|
||||
MASK_REGEXP = /[^\s\-_\[\]\(\)\+\?\.\,]/
|
||||
MASK_SYMBOL = 'X'
|
||||
|
||||
module_function
|
||||
|
||||
@@ -13,6 +15,24 @@ module TextUtils
|
||||
false
|
||||
end
|
||||
|
||||
def mask_value(text, unmask_size = 0)
|
||||
if unmask_size.is_a?(Numeric) && !unmask_size.zero? && unmask_size.abs < text.length
|
||||
if unmask_size.negative?
|
||||
[
|
||||
text.first(text.length + unmask_size).gsub(MASK_REGEXP, MASK_SYMBOL),
|
||||
text.last(-unmask_size)
|
||||
].join
|
||||
elsif unmask_size.positive?
|
||||
[
|
||||
text.first(unmask_size),
|
||||
text.last(text.length - unmask_size).gsub(MASK_REGEXP, MASK_SYMBOL)
|
||||
].join
|
||||
end
|
||||
else
|
||||
text.to_s.gsub(MASK_REGEXP, MASK_SYMBOL)
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_rtl_reverse(text)
|
||||
if text.match?(RTL_REGEXP)
|
||||
TwitterCldr::Shared::Bidi
|
||||
|
||||
@@ -772,6 +772,30 @@ RSpec.describe 'Signing Form', type: :system do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the masked field' do
|
||||
let(:template) { create(:template, submitter_count: 2, account:, author:, only_field_types: %w[text]) }
|
||||
let(:submission) { create(:submission, template: template) }
|
||||
let!(:first_submitter) { create(:submitter, submission:, uuid: template.submitters[0]['uuid'], account:) }
|
||||
let!(:second_submitter) { create(:submitter, submission:, uuid: template.submitters[1]['uuid'], account:) }
|
||||
|
||||
it 'shows the masked value instead of the real value' do
|
||||
field = submission.template_fields.find do |f|
|
||||
f['name'] == 'First Name' && f['submitter_uuid'] == first_submitter.uuid
|
||||
end
|
||||
field['preferences']['mask'] = true
|
||||
submission.save!
|
||||
|
||||
visit submit_form_path(slug: first_submitter.slug)
|
||||
|
||||
fill_in 'First Name', with: 'Jahn'
|
||||
click_button 'Complete'
|
||||
|
||||
visit submit_form_path(slug: second_submitter.slug)
|
||||
|
||||
expect(page).to have_content('XXXX')
|
||||
end
|
||||
end
|
||||
|
||||
it 'sends completed email' do
|
||||
template = create(:template, account:, author:, only_field_types: %w[text signature])
|
||||
submission = create(:submission, template:)
|
||||
|
||||
Reference in new issue
Block a user