mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6eae66127 | ||
|
|
08aa902e07 | ||
|
|
1690337699 | ||
|
|
f78cf8cc6e | ||
|
|
343118d00d | ||
|
|
a3bd37c9a2 | ||
|
|
8bc13a5900 | ||
|
|
21138c1d02 | ||
|
|
38674a9fc9 | ||
|
|
a7ed0b86a4 | ||
|
|
91a96f06c8 | ||
|
|
23631b75f2 | ||
|
|
0fb9c2bbf6 | ||
|
|
14fa8fc8e7 | ||
|
|
047c192988 | ||
|
|
bfa244c8fa | ||
|
|
019222ae40 | ||
|
|
8a10852fe2 | ||
|
|
01232aed5a | ||
|
|
82f3ff4824 | ||
|
|
7be3f5a931 | ||
|
|
9d013a7384 | ||
|
|
0c71d7bff8 | ||
|
|
fdd030652e | ||
|
|
f2bf1461f0 | ||
|
|
fc6f796f30 | ||
|
|
5f44806287 | ||
|
|
318b65af18 | ||
|
|
91b182b04a | ||
|
|
15b886d4ff | ||
|
|
0122a237e3 | ||
|
|
2dca7d8a8e | ||
|
|
87265e2f22 | ||
|
|
3166defa1a | ||
|
|
93ba17f42e | ||
|
|
751be1dae5 | ||
|
|
3f8d2831e2 | ||
|
|
4d8942803e | ||
|
|
46d9bb2c87 | ||
|
|
34433c0575 | ||
|
|
544306eac4 | ||
|
|
0eeebfd3cf | ||
|
|
050224a10d | ||
|
|
7b6fc7d06f | ||
|
|
7fb148c85c | ||
|
|
dffa59fecd | ||
|
|
24fe2a1192 | ||
|
|
97b8ac4444 | ||
|
|
e34a763dd9 | ||
|
|
298b1d3e1e | ||
|
|
d9978519cc | ||
|
|
b4a68cf79e | ||
|
|
5663e60f92 | ||
|
|
19e8835809 | ||
|
|
330fae927f | ||
|
|
c5a5212947 | ||
|
|
21d81e38bf | ||
|
|
5b1a4ebffd | ||
|
|
5f5dabd0e5 | ||
|
|
ff46993de6 | ||
|
|
7676db2196 | ||
|
|
3ef0d28ffe | ||
|
|
231177f057 | ||
|
|
e5f7739952 | ||
|
|
4a8e2a82e5 | ||
|
|
6efe8027f1 | ||
|
|
96bb4eb6e5 | ||
|
|
38e68814cf | ||
|
|
786c80bee7 | ||
|
|
98dcce299e | ||
|
|
cbff242a5d | ||
|
|
1876b3efba | ||
|
|
51091f05f0 | ||
|
|
d76eee2ae4 | ||
|
|
d7bfa96d57 | ||
|
|
be60608211 | ||
|
|
4e5abb3c09 | ||
|
|
7fee82ded2 | ||
|
|
e1cd62ff40 | ||
|
|
b498291b62 | ||
|
|
def3009846 | ||
|
|
9d43fd9aa5 | ||
|
|
22a21fb037 | ||
|
|
2a9c375273 | ||
|
|
847e70ec1b | ||
|
|
7964d6e0f0 | ||
|
|
d0259791bf | ||
|
|
a9dcaeccbc | ||
|
|
501b91a603 | ||
|
|
2b5f772feb | ||
|
|
7cada0af8b | ||
|
|
67be785e69 | ||
|
|
d1cfc64adc | ||
|
|
4e5157d1ee | ||
|
|
5e27cc36c6 | ||
|
|
6e81a3be3f | ||
|
|
858b2f33fd | ||
|
|
6adcc87b2b | ||
|
|
e1d5a5b6a6 | ||
|
|
f090e79002 | ||
|
|
56b32e35a2 | ||
|
|
b40c27cdbf | ||
|
|
c4505e67f0 | ||
|
|
d0799b8cc1 | ||
|
|
889fcad859 | ||
|
|
c133fef99d | ||
|
|
cfdc978a2a | ||
|
|
bff1651966 | ||
|
|
b97854f2fe | ||
|
|
7a4629bf79 | ||
|
|
2573d8cb19 | ||
|
|
dd559e90f1 | ||
|
|
e5db61e358 | ||
|
|
69376c0faf | ||
|
|
1f48be135c | ||
|
|
1cae12b728 | ||
|
|
86d2cd93f8 | ||
|
|
b0110d9340 | ||
|
|
7e70f4fb14 | ||
|
|
28e3d65e63 | ||
|
|
f1a2b71b59 | ||
|
|
47c7270911 | ||
|
|
4e1fa336a2 | ||
|
|
244bc32136 | ||
|
|
42c30bdfd0 | ||
|
|
0abf48ddba | ||
|
|
ef05f030de | ||
|
|
b8c36ff09c | ||
|
|
1ba8a9a374 | ||
|
|
661e9b69fb | ||
|
|
a1aeabccff | ||
|
|
0e5e0e7772 | ||
|
|
2a74fdd095 | ||
|
|
ac426e55b2 | ||
|
|
ad2b35673a | ||
|
|
af1e8fa6f2 | ||
|
|
5800cb6c5a | ||
|
|
64d3360e82 | ||
|
|
3ae98bf03e | ||
|
|
65e81d90f4 | ||
|
|
4fb9637e2d | ||
|
|
6b3726dbd5 | ||
|
|
c5b0f2c76c | ||
|
|
ea18f9e28a | ||
|
|
d229c75978 | ||
|
|
ecbe3c3e4f | ||
|
|
38a5cb968b | ||
|
|
a30ee9e961 | ||
|
|
eba27004dc | ||
|
|
09849acd2f | ||
|
|
19e9047a39 | ||
|
|
82b6bdd9ad | ||
|
|
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 | ||
|
|
b6bb4d2c8b | ||
|
|
5032676bd2 | ||
|
|
146b37f8cd | ||
|
|
47b3a2d09e | ||
|
|
f2306b334b | ||
|
|
3affa65571 | ||
|
|
6f3be16826 | ||
|
|
07ce76537b | ||
|
|
a1fb38b8cb | ||
|
|
1b52a99662 | ||
|
|
f5facfa6b9 | ||
|
|
21c6144dc6 | ||
|
|
2c155f0e42 | ||
|
|
33cc0a8ab5 | ||
|
|
1e65a7e52b | ||
|
|
14e2dbe1aa | ||
|
|
a823a6ac56 | ||
|
|
cfc4e001ba | ||
|
|
496cc855f6 | ||
|
|
bfb2db07a5 | ||
|
|
9f4f868246 | ||
|
|
f91f02f786 | ||
|
|
1da72e3e7a | ||
|
|
2e2c0e087d | ||
|
|
0c72f58ab0 | ||
|
|
d9d969cb79 | ||
|
|
8d685d1f73 | ||
|
|
e406620044 | ||
|
|
a315674421 | ||
|
|
8d953f210f | ||
|
|
7439ef07ad | ||
|
|
fc4b4de6ed | ||
|
|
96e4733f87 | ||
|
|
8b6731a289 | ||
|
|
f93ac68e9c | ||
|
|
439df4879c | ||
|
|
83b6ce8574 | ||
|
|
6ea07bc13b | ||
|
|
a15624690b | ||
|
|
974bb85fb7 | ||
|
|
5dc80601b2 | ||
|
|
df2e53bc40 | ||
|
|
6e685bbc32 | ||
|
|
0b3ca0de96 | ||
|
|
aed3f1f141 | ||
|
|
1a7981e919 | ||
|
|
4add805371 | ||
|
|
66c18317b7 | ||
|
|
9edbb885b4 | ||
|
|
c67c80d6bc | ||
|
|
0399531e0c | ||
|
|
54426556d9 | ||
|
|
fa74ee1755 | ||
|
|
eb53af12c4 | ||
|
|
38efcb99c0 | ||
|
|
a92d914674 | ||
|
|
219a698aa5 | ||
|
|
5bed6448dc | ||
|
|
6d2a8ca3d3 | ||
|
|
bfb2e0043d | ||
|
|
922101b12c | ||
|
|
9ff41a0c4a | ||
|
|
071f187605 | ||
|
|
ab6cd0e89c | ||
|
|
97c0dad7ff | ||
|
|
bc0917a190 | ||
|
|
5718acecff | ||
|
|
395d43dbeb | ||
|
|
37de5e0d0d | ||
|
|
44e848a1f0 | ||
|
|
f6d59d731c | ||
|
|
87b039e709 | ||
|
|
e0c6a4e08b | ||
|
|
80b06550c2 | ||
|
|
ec8e35ed7a | ||
|
|
4dc325a6bb | ||
|
|
d52c57df72 | ||
|
|
0cb2557447 | ||
|
|
cd05c1fd19 | ||
|
|
5458e82b6e | ||
|
|
c59c58624b | ||
|
|
2da059d440 | ||
|
|
b0aabd9e79 | ||
|
|
f1cd60dfad | ||
|
|
a264a8522a | ||
|
|
794e339425 | ||
|
|
9aaf61e658 | ||
|
|
37faa7ece6 | ||
|
|
666e3fde4c | ||
|
|
995e040a4e | ||
|
|
185ac2cbee | ||
|
|
a4e372ff5c | ||
|
|
b4834c7674 | ||
|
|
c24ae1751e | ||
|
|
9bd0ad70c4 | ||
|
|
f210f1d8bf | ||
|
|
92b61992f2 | ||
|
|
e3f3895aac | ||
|
|
c696890cf5 | ||
|
|
288d204d8e | ||
|
|
5a96f4f1eb | ||
|
|
5ff4f9889e | ||
|
|
78fb93fd02 | ||
|
|
3c83d459bd | ||
|
|
6cdb8e7132 | ||
|
|
e2b93cbcab | ||
|
|
a59cc7c44e | ||
|
|
1984b99e04 | ||
|
|
fe3e8fba3e | ||
|
|
97ecdf2ff0 | ||
|
|
e4c62087d0 | ||
|
|
1f9faddfd0 | ||
|
|
6dff6483bf | ||
|
|
2e43eb189f | ||
|
|
cee471bfa5 | ||
|
|
81521dee81 | ||
|
|
3cac62d0fd | ||
|
|
24ed3f2e7c | ||
|
|
02ad620c9f | ||
|
|
9051d5a835 | ||
|
|
0abd270f56 | ||
|
|
fde9affd70 | ||
|
|
4edee2971a | ||
|
|
313d7f8408 | ||
|
|
db60810272 | ||
|
|
b3507c1a3a | ||
|
|
19b5a07bac | ||
|
|
616aced4e4 | ||
|
|
cc218fc0b0 | ||
|
|
c59f2a022a | ||
|
|
2a240cebeb | ||
|
|
b950b1c63e | ||
|
|
4ad0b04b7f | ||
|
|
8edf6405b0 | ||
|
|
8e3caae849 | ||
|
|
ac9a5c9556 | ||
|
|
20d09f6dff | ||
|
|
333a758688 | ||
|
|
2c8b4d5816 | ||
|
|
9ce822bce8 | ||
|
|
0a382a688c | ||
|
|
bd3008a35a | ||
|
|
e9bd11cb2e | ||
|
|
ca429bc445 | ||
|
|
21a0c54843 | ||
|
|
2f41d13009 | ||
|
|
f69dcf77de | ||
|
|
32596d738c | ||
|
|
e7e5d0e776 |
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:
|
||||||
|
- ''
|
||||||
File renamed without changes.
+44
-14
@@ -7,13 +7,13 @@ jobs:
|
|||||||
name: Rubocop
|
name: Rubocop
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.3.4
|
ruby-version: 3.4.2
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: vendor/bundle
|
path: vendor/bundle
|
||||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||||
@@ -31,13 +31,13 @@ jobs:
|
|||||||
name: Erblint
|
name: Erblint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.3.4
|
ruby-version: 3.4.2
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: vendor/bundle
|
path: vendor/bundle
|
||||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||||
@@ -49,13 +49,13 @@ jobs:
|
|||||||
bundle config path vendor/bundle
|
bundle config path vendor/bundle
|
||||||
bundle install --jobs 4 --retry 4
|
bundle install --jobs 4 --retry 4
|
||||||
- name: Run Erblint
|
- name: Run Erblint
|
||||||
run: bundle exec erblint ./app
|
run: bundle exec erb_lint ./app
|
||||||
|
|
||||||
eslint:
|
eslint:
|
||||||
name: ESLint
|
name: ESLint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
- name: Cache directory path
|
- name: Cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v4
|
||||||
id: yarn-cache
|
id: yarn-cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
@@ -77,6 +77,33 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./node_modules/eslint/bin/eslint.js "app/javascript/**/*.js"
|
./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.2
|
||||||
|
- 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:
|
rspec:
|
||||||
name: RSpec
|
name: RSpec
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -96,11 +123,11 @@ jobs:
|
|||||||
--health-retries 5
|
--health-retries 5
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.3.4
|
ruby-version: 3.4.2
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
@@ -110,12 +137,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
chrome-version: 125
|
chrome-version: 125
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: vendor/bundle
|
path: vendor/bundle
|
||||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||||
@@ -130,7 +157,10 @@ jobs:
|
|||||||
bundle install --jobs 4 --retry 4
|
bundle install --jobs 4 --retry 4
|
||||||
yarn install
|
yarn install
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libvips
|
sudo apt-get install -y libvips
|
||||||
|
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz"
|
||||||
|
sudo tar -xzf pdfium-linux.tgz --strip-components=1 -C /usr/lib lib/libpdfium.so
|
||||||
|
rm -f pdfium-linux.tgz
|
||||||
- name: Run
|
- name: Run
|
||||||
env:
|
env:
|
||||||
RAILS_ENV: test
|
RAILS_ENV: test
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -25,22 +25,22 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Create .version file
|
- name: Create .version file
|
||||||
run: echo ${{ github.ref_name }} > .version
|
run: echo ${{ github.ref_name }} > .version
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ RSpec/MultipleMemoizedHelpers:
|
|||||||
Max: 9
|
Max: 9
|
||||||
|
|
||||||
Metrics/BlockNesting:
|
Metrics/BlockNesting:
|
||||||
Max: 4
|
Max: 5
|
||||||
|
|
||||||
Rails/I18nLocaleTexts:
|
Rails/I18nLocaleTexts:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|||||||
+19
-8
@@ -1,12 +1,21 @@
|
|||||||
FROM ruby:3.3.4-alpine as fonts
|
FROM ruby:3.4.2-alpine AS download
|
||||||
|
|
||||||
WORKDIR /fonts
|
WORKDIR /fonts
|
||||||
|
|
||||||
RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && wget https://github.com/impallari/DancingScript/raw/master/OFL.txt
|
RUN apk --no-cache add fontforge wget && \
|
||||||
|
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && \
|
||||||
|
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && \
|
||||||
|
wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && \
|
||||||
|
wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && \
|
||||||
|
wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && \
|
||||||
|
wget https://github.com/impallari/DancingScript/raw/master/OFL.txt && \
|
||||||
|
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
|
||||||
|
mkdir -p /pdfium-linux && \
|
||||||
|
tar -xzf pdfium-linux.tgz -C /pdfium-linux
|
||||||
|
|
||||||
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
||||||
|
|
||||||
FROM ruby:3.3.4-alpine as webpack
|
FROM ruby:3.4.2-alpine AS webpack
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
@@ -32,7 +41,7 @@ COPY ./app/views ./app/views
|
|||||||
|
|
||||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||||
|
|
||||||
FROM ruby:3.3.4-alpine as app
|
FROM ruby:3.4.2-alpine AS app
|
||||||
|
|
||||||
ENV RAILS_ENV=production
|
ENV RAILS_ENV=production
|
||||||
ENV BUNDLE_WITHOUT="development:test"
|
ENV BUNDLE_WITHOUT="development:test"
|
||||||
@@ -41,7 +50,7 @@ ENV OPENSSL_CONF=/app/openssl_legacy.cnf
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils redis libheif@edge vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev@edge redis libheif@edge vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||||
|
|
||||||
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
||||||
\n\
|
\n\
|
||||||
@@ -62,7 +71,7 @@ RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-
|
|||||||
COPY ./bin ./bin
|
COPY ./bin ./bin
|
||||||
COPY ./app ./app
|
COPY ./app ./app
|
||||||
COPY ./config ./config
|
COPY ./config ./config
|
||||||
COPY ./db ./db
|
COPY ./db/migrate ./db/migrate
|
||||||
COPY ./log ./log
|
COPY ./log ./log
|
||||||
COPY ./lib ./lib
|
COPY ./lib ./lib
|
||||||
COPY ./public ./public
|
COPY ./public ./public
|
||||||
@@ -70,8 +79,10 @@ COPY ./tmp ./tmp
|
|||||||
COPY LICENSE README.md Rakefile config.ru .version ./
|
COPY LICENSE README.md Rakefile config.ru .version ./
|
||||||
COPY .version ./public/version
|
COPY .version ./public/version
|
||||||
|
|
||||||
COPY --from=fonts /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
|
COPY --from=download /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
|
||||||
COPY --from=fonts /fonts/FreeSans.ttf /usr/share/fonts/freefont
|
COPY --from=download /fonts/FreeSans.ttf /usr/share/fonts/freefont
|
||||||
|
COPY --from=download /pdfium-linux/lib/libpdfium.so /usr/lib/libpdfium.so
|
||||||
|
COPY --from=download /pdfium-linux/licenses/pdfium.txt /usr/lib/libpdfium-LICENSE.txt
|
||||||
COPY --from=webpack /app/public/packs ./public/packs
|
COPY --from=webpack /app/public/packs ./public/packs
|
||||||
|
|
||||||
RUN ln -s /fonts /app/public/fonts
|
RUN ln -s /fonts /app/public/fonts
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
ruby '3.3.4'
|
ruby '3.4.2'
|
||||||
|
|
||||||
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
||||||
gem 'aws-sdk-s3', require: false
|
gem 'aws-sdk-s3', require: false
|
||||||
@@ -46,7 +46,6 @@ gem 'twitter_cldr', require: false
|
|||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'annotate'
|
|
||||||
gem 'better_html'
|
gem 'better_html'
|
||||||
gem 'bullet'
|
gem 'bullet'
|
||||||
gem 'debug'
|
gem 'debug'
|
||||||
@@ -63,6 +62,9 @@ group :development, :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
gem 'annotaterb'
|
||||||
|
gem 'brakeman', require: false
|
||||||
|
gem 'foreman', require: false
|
||||||
gem 'letter_opener_web'
|
gem 'letter_opener_web'
|
||||||
gem 'web-console'
|
gem 'web-console'
|
||||||
end
|
end
|
||||||
|
|||||||
+181
-157
@@ -1,67 +1,67 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.2.1.1)
|
actioncable (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (7.2.1.1)
|
actionmailbox (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activejob (= 7.2.1.1)
|
activejob (= 8.0.1)
|
||||||
activerecord (= 7.2.1.1)
|
activerecord (= 8.0.1)
|
||||||
activestorage (= 7.2.1.1)
|
activestorage (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
actionmailer (7.2.1.1)
|
actionmailer (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
actionview (= 7.2.1.1)
|
actionview (= 8.0.1)
|
||||||
activejob (= 7.2.1.1)
|
activejob (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (7.2.1.1)
|
actionpack (8.0.1)
|
||||||
actionview (= 7.2.1.1)
|
actionview (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
racc
|
rack (>= 2.2.4)
|
||||||
rack (>= 2.2.4, < 3.2)
|
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
useragent (~> 0.16)
|
useragent (~> 0.16)
|
||||||
actiontext (7.2.1.1)
|
actiontext (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activerecord (= 7.2.1.1)
|
activerecord (= 8.0.1)
|
||||||
activestorage (= 7.2.1.1)
|
activestorage (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.2.1.1)
|
actionview (8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activejob (7.2.1.1)
|
activejob (8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.2.1.1)
|
activemodel (8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
activerecord (7.2.1.1)
|
activerecord (8.0.1)
|
||||||
activemodel (= 7.2.1.1)
|
activemodel (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (7.2.1.1)
|
activestorage (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activejob (= 7.2.1.1)
|
activejob (= 8.0.1)
|
||||||
activerecord (= 7.2.1.1)
|
activerecord (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (7.2.1.1)
|
activesupport (8.0.1)
|
||||||
base64
|
base64
|
||||||
|
benchmark (>= 0.3)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
connection_pool (>= 2.2.5)
|
connection_pool (>= 2.2.5)
|
||||||
@@ -71,31 +71,30 @@ GEM
|
|||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
securerandom (>= 0.3)
|
securerandom (>= 0.3)
|
||||||
tzinfo (~> 2.0, >= 2.0.5)
|
tzinfo (~> 2.0, >= 2.0.5)
|
||||||
|
uri (>= 0.13.1)
|
||||||
addressable (2.8.7)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
annotate (3.2.0)
|
annotaterb (4.14.0)
|
||||||
activerecord (>= 3.2, < 8.0)
|
|
||||||
rake (>= 10.4, < 14.0)
|
|
||||||
arabic-letter-connector (0.1.1)
|
arabic-letter-connector (0.1.1)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.985.0)
|
aws-partitions (1.1027.0)
|
||||||
aws-sdk-core (3.209.1)
|
aws-sdk-core (3.214.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.9)
|
aws-sigv4 (~> 1.9)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.94.0)
|
aws-sdk-kms (1.96.0)
|
||||||
aws-sdk-core (~> 3, >= 3.207.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.167.0)
|
aws-sdk-s3 (1.176.1)
|
||||||
aws-sdk-core (~> 3, >= 3.207.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-secretsmanager (1.108.0)
|
aws-sdk-secretsmanager (1.110.0)
|
||||||
aws-sdk-core (~> 3, >= 3.207.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.10.0)
|
aws-sigv4 (1.10.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
azure-storage-blob (2.0.3)
|
azure-storage-blob (2.0.3)
|
||||||
azure-storage-common (~> 2.0)
|
azure-storage-common (~> 2.0)
|
||||||
@@ -107,6 +106,7 @@ GEM
|
|||||||
nokogiri (~> 1, >= 1.10.8)
|
nokogiri (~> 1, >= 1.10.8)
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
bcrypt (3.1.20)
|
bcrypt (3.1.20)
|
||||||
|
benchmark (0.4.0)
|
||||||
better_html (2.1.1)
|
better_html (2.1.1)
|
||||||
actionview (>= 6.0)
|
actionview (>= 6.0)
|
||||||
activesupport (>= 6.0)
|
activesupport (>= 6.0)
|
||||||
@@ -118,8 +118,10 @@ GEM
|
|||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.18.4)
|
bootsnap (1.18.4)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
|
brakeman (7.0.0)
|
||||||
|
racc
|
||||||
builder (3.3.0)
|
builder (3.3.0)
|
||||||
bullet (7.2.0)
|
bullet (8.0.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
camertron-eprun (1.1.1)
|
camertron-eprun (1.1.1)
|
||||||
@@ -145,14 +147,14 @@ GEM
|
|||||||
bigdecimal
|
bigdecimal
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
css_parser (1.19.0)
|
css_parser (1.21.0)
|
||||||
addressable
|
addressable
|
||||||
csv (3.3.0)
|
csv (3.3.2)
|
||||||
cuprite (0.15.1)
|
cuprite (0.15.1)
|
||||||
capybara (~> 3.0)
|
capybara (~> 3.0)
|
||||||
ferrum (~> 0.15.0)
|
ferrum (~> 0.15.0)
|
||||||
date (3.3.4)
|
date (3.4.1)
|
||||||
debug (1.9.2)
|
debug (1.10.0)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
@@ -162,32 +164,32 @@ GEM
|
|||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise-two-factor (6.0.0)
|
devise-two-factor (6.1.0)
|
||||||
activesupport (~> 7.0)
|
activesupport (>= 7.0, < 8.1)
|
||||||
devise (~> 4.0)
|
devise (~> 4.0)
|
||||||
railties (~> 7.0)
|
railties (>= 7.0, < 8.1)
|
||||||
rotp (~> 6.0)
|
rotp (~> 6.0)
|
||||||
diff-lcs (1.5.1)
|
diff-lcs (1.5.1)
|
||||||
digest-crc (0.6.5)
|
digest-crc (0.6.5)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
docile (1.4.1)
|
docile (1.4.1)
|
||||||
dotenv (3.1.4)
|
dotenv (3.1.7)
|
||||||
drb (2.2.1)
|
drb (2.2.1)
|
||||||
email_typo (0.2.3)
|
email_typo (0.2.3)
|
||||||
erb_lint (0.6.0)
|
erb_lint (0.7.0)
|
||||||
activesupport
|
activesupport
|
||||||
better_html (>= 2.0.1)
|
better_html (>= 2.0.1)
|
||||||
parser (>= 2.7.1.4)
|
parser (>= 2.7.1.4)
|
||||||
rainbow
|
rainbow
|
||||||
rubocop (>= 1)
|
rubocop (>= 1)
|
||||||
smart_properties
|
smart_properties
|
||||||
erubi (1.13.0)
|
erubi (1.13.1)
|
||||||
factory_bot (6.5.0)
|
factory_bot (6.5.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
factory_bot_rails (6.4.3)
|
factory_bot_rails (6.4.4)
|
||||||
factory_bot (~> 6.4)
|
factory_bot (~> 6.5)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
faker (3.4.2)
|
faker (3.5.1)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (1.10.4)
|
faraday (1.10.4)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
@@ -207,8 +209,8 @@ GEM
|
|||||||
faraday-follow_redirects (0.3.0)
|
faraday-follow_redirects (0.3.0)
|
||||||
faraday (>= 1, < 3)
|
faraday (>= 1, < 3)
|
||||||
faraday-httpclient (1.0.1)
|
faraday-httpclient (1.0.1)
|
||||||
faraday-multipart (1.0.4)
|
faraday-multipart (1.1.0)
|
||||||
multipart-post (~> 2)
|
multipart-post (~> 2.0)
|
||||||
faraday-net_http (1.0.2)
|
faraday-net_http (1.0.2)
|
||||||
faraday-net_http_persistent (1.2.0)
|
faraday-net_http_persistent (1.2.0)
|
||||||
faraday-patron (1.0.0)
|
faraday-patron (1.0.0)
|
||||||
@@ -221,9 +223,13 @@ GEM
|
|||||||
concurrent-ruby (~> 1.1)
|
concurrent-ruby (~> 1.1)
|
||||||
webrick (~> 1.7)
|
webrick (~> 1.7)
|
||||||
websocket-driver (~> 0.7)
|
websocket-driver (~> 0.7)
|
||||||
ffi (1.17.0)
|
ffi (1.17.1)
|
||||||
ffi (1.17.0-aarch64-linux-gnu)
|
ffi (1.17.1-aarch64-linux-gnu)
|
||||||
ffi (1.17.0-x86_64-linux-gnu)
|
ffi (1.17.1-aarch64-linux-musl)
|
||||||
|
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)
|
geom2d (0.4.1)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
@@ -235,9 +241,9 @@ GEM
|
|||||||
mutex_m
|
mutex_m
|
||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
google-apis-iamcredentials_v1 (0.21.0)
|
google-apis-iamcredentials_v1 (0.22.0)
|
||||||
google-apis-core (>= 0.15.0, < 2.a)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-apis-storage_v1 (0.46.0)
|
google-apis-storage_v1 (0.49.0)
|
||||||
google-apis-core (>= 0.15.0, < 2.a)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-cloud-core (1.7.1)
|
google-cloud-core (1.7.1)
|
||||||
google-cloud-env (>= 1.0, < 3.a)
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
@@ -245,7 +251,7 @@ GEM
|
|||||||
google-cloud-env (2.2.1)
|
google-cloud-env (2.2.1)
|
||||||
faraday (>= 1.0, < 3.a)
|
faraday (>= 1.0, < 3.a)
|
||||||
google-cloud-errors (1.4.0)
|
google-cloud-errors (1.4.0)
|
||||||
google-cloud-storage (1.52.0)
|
google-cloud-storage (1.54.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-core (~> 0.13)
|
google-apis-core (~> 0.13)
|
||||||
@@ -254,15 +260,17 @@ GEM
|
|||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (~> 1.9)
|
googleauth (~> 1.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.11.1)
|
google-logging-utils (0.1.0)
|
||||||
|
googleauth (1.12.2)
|
||||||
faraday (>= 1.0, < 3.a)
|
faraday (>= 1.0, < 3.a)
|
||||||
google-cloud-env (~> 2.1)
|
google-cloud-env (~> 2.2)
|
||||||
|
google-logging-utils (~> 0.1)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
hashdiff (1.1.1)
|
hashdiff (1.1.2)
|
||||||
hexapdf (0.47.0)
|
hexapdf (1.0.3)
|
||||||
cmdparse (~> 3.0, >= 3.0.3)
|
cmdparse (~> 3.0, >= 3.0.3)
|
||||||
geom2d (~> 0.4, >= 0.4.1)
|
geom2d (~> 0.4, >= 0.4.1)
|
||||||
openssl (>= 2.2.1)
|
openssl (>= 2.2.1)
|
||||||
@@ -273,12 +281,12 @@ GEM
|
|||||||
image_processing (1.13.0)
|
image_processing (1.13.0)
|
||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
ruby-vips (>= 2.0.17, < 3)
|
ruby-vips (>= 2.0.17, < 3)
|
||||||
io-console (0.7.2)
|
io-console (0.8.0)
|
||||||
irb (1.14.1)
|
irb (1.14.3)
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.7.2)
|
json (2.9.1)
|
||||||
jwt (2.9.3)
|
jwt (2.9.3)
|
||||||
base64
|
base64
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
@@ -292,7 +300,7 @@ GEM
|
|||||||
letter_opener (~> 1.9)
|
letter_opener (~> 1.9)
|
||||||
railties (>= 6.1)
|
railties (>= 6.1)
|
||||||
rexml
|
rexml
|
||||||
logger (1.6.1)
|
logger (1.6.4)
|
||||||
lograge (0.14.0)
|
lograge (0.14.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
@@ -312,15 +320,15 @@ GEM
|
|||||||
mini_magick (4.13.2)
|
mini_magick (4.13.2)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.8)
|
mini_portile2 (2.8.8)
|
||||||
minitest (5.25.1)
|
minitest (5.25.4)
|
||||||
msgpack (1.7.3)
|
msgpack (1.7.5)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.4.1)
|
multipart-post (2.4.1)
|
||||||
mutex_m (0.2.0)
|
mutex_m (0.3.0)
|
||||||
mysql2 (0.5.6)
|
mysql2 (0.5.6)
|
||||||
net-http-persistent (4.0.4)
|
net-http-persistent (4.0.5)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
net-imap (0.4.17)
|
net-imap (0.5.6)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
@@ -329,28 +337,34 @@ GEM
|
|||||||
timeout
|
timeout
|
||||||
net-smtp (0.5.0)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.3)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.16.8)
|
nokogiri (1.18.8)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.8-aarch64-linux)
|
nokogiri (1.18.8-aarch64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.8-x86_64-linux)
|
nokogiri (1.18.8-aarch64-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oj (3.16.6)
|
nokogiri (1.18.8-arm64-darwin)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.18.8-x86_64-linux-musl)
|
||||||
|
racc (~> 1.4)
|
||||||
|
oj (3.16.8)
|
||||||
bigdecimal (>= 3.0)
|
bigdecimal (>= 3.0)
|
||||||
ostruct (>= 0.2)
|
ostruct (>= 0.2)
|
||||||
openssl (3.2.0)
|
openssl (3.3.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
ostruct (0.6.0)
|
ostruct (0.6.1)
|
||||||
package_json (0.1.0)
|
package_json (0.1.0)
|
||||||
pagy (9.1.0)
|
pagy (9.3.3)
|
||||||
parallel (1.26.3)
|
parallel (1.26.3)
|
||||||
parser (3.3.5.0)
|
parser (3.3.6.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pg (1.5.8)
|
pg (1.5.9)
|
||||||
premailer (1.27.0)
|
premailer (1.27.0)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.19.0)
|
css_parser (>= 1.19.0)
|
||||||
@@ -361,58 +375,58 @@ GEM
|
|||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
pretender (0.5.0)
|
pretender (0.5.0)
|
||||||
actionpack (>= 6.1)
|
actionpack (>= 6.1)
|
||||||
pry (0.14.2)
|
pry (0.15.0)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
pry-rails (0.3.11)
|
pry-rails (0.3.11)
|
||||||
pry (>= 0.13.0)
|
pry (>= 0.13.0)
|
||||||
psych (5.1.2)
|
psych (5.2.2)
|
||||||
|
date
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
puma (6.4.3)
|
puma (6.5.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.8)
|
rack (3.1.14)
|
||||||
rack-proxy (0.7.7)
|
rack-proxy (0.7.7)
|
||||||
rack
|
rack
|
||||||
rack-session (2.0.0)
|
rack-session (2.0.0)
|
||||||
rack (>= 3.0.0)
|
rack (>= 3.0.0)
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.1.0)
|
rackup (2.2.1)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
webrick (~> 1.8)
|
rails (8.0.1)
|
||||||
rails (7.2.1.1)
|
actioncable (= 8.0.1)
|
||||||
actioncable (= 7.2.1.1)
|
actionmailbox (= 8.0.1)
|
||||||
actionmailbox (= 7.2.1.1)
|
actionmailer (= 8.0.1)
|
||||||
actionmailer (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actiontext (= 8.0.1)
|
||||||
actiontext (= 7.2.1.1)
|
actionview (= 8.0.1)
|
||||||
actionview (= 7.2.1.1)
|
activejob (= 8.0.1)
|
||||||
activejob (= 7.2.1.1)
|
activemodel (= 8.0.1)
|
||||||
activemodel (= 7.2.1.1)
|
activerecord (= 8.0.1)
|
||||||
activerecord (= 7.2.1.1)
|
activestorage (= 8.0.1)
|
||||||
activestorage (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.2.1.1)
|
railties (= 8.0.1)
|
||||||
rails-dom-testing (2.2.0)
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.1)
|
rails-html-sanitizer (1.6.2)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||||
rails-i18n (7.0.9)
|
rails-i18n (8.0.1)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 8)
|
railties (>= 8.0.0, < 9)
|
||||||
rails_autolink (1.1.8)
|
rails_autolink (1.1.8)
|
||||||
actionview (> 3.1)
|
actionview (> 3.1)
|
||||||
activesupport (> 3.1)
|
activesupport (> 3.1)
|
||||||
railties (> 3.1)
|
railties (> 3.1)
|
||||||
railties (7.2.1.1)
|
railties (8.0.1)
|
||||||
actionpack (= 7.2.1.1)
|
actionpack (= 8.0.1)
|
||||||
activesupport (= 7.2.1.1)
|
activesupport (= 8.0.1)
|
||||||
irb (~> 1.13)
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
@@ -420,12 +434,12 @@ GEM
|
|||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.2.1)
|
rake (13.2.1)
|
||||||
rdoc (6.7.0)
|
rdoc (6.10.0)
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
redis-client (0.22.2)
|
redis-client (0.23.0)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.9.2)
|
regexp_parser (2.9.3)
|
||||||
reline (0.5.10)
|
reline (0.6.0)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
@@ -437,13 +451,13 @@ GEM
|
|||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.3.9)
|
rexml (3.4.0)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 1.0)
|
rqrcode_core (~> 1.0)
|
||||||
rqrcode_core (1.2.0)
|
rqrcode_core (1.2.0)
|
||||||
rspec-core (3.13.1)
|
rspec-core (3.13.2)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-expectations (3.13.3)
|
rspec-expectations (3.13.3)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
@@ -451,7 +465,7 @@ GEM
|
|||||||
rspec-mocks (3.13.2)
|
rspec-mocks (3.13.2)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-rails (7.0.1)
|
rspec-rails (7.1.0)
|
||||||
actionpack (>= 7.0)
|
actionpack (>= 7.0)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
railties (>= 7.0)
|
railties (>= 7.0)
|
||||||
@@ -459,48 +473,47 @@ GEM
|
|||||||
rspec-expectations (~> 3.13)
|
rspec-expectations (~> 3.13)
|
||||||
rspec-mocks (~> 3.13)
|
rspec-mocks (~> 3.13)
|
||||||
rspec-support (~> 3.13)
|
rspec-support (~> 3.13)
|
||||||
rspec-support (3.13.1)
|
rspec-support (3.13.2)
|
||||||
rubocop (1.66.1)
|
rubocop (1.69.2)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.4, < 3.0)
|
regexp_parser (>= 2.9.3, < 3.0)
|
||||||
rubocop-ast (>= 1.32.2, < 2.0)
|
rubocop-ast (>= 1.36.2, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.32.3)
|
rubocop-ast (1.37.0)
|
||||||
parser (>= 3.3.1.0)
|
parser (>= 3.3.1.0)
|
||||||
rubocop-performance (1.22.1)
|
rubocop-performance (1.23.0)
|
||||||
rubocop (>= 1.48.1, < 2.0)
|
rubocop (>= 1.48.1, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rails (2.26.2)
|
rubocop-rails (2.27.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.52.0, < 2.0)
|
rubocop (>= 1.52.0, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rspec (3.1.0)
|
rubocop-rspec (3.3.0)
|
||||||
rubocop (~> 1.61)
|
rubocop (~> 1.61)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-vips (2.2.2)
|
ruby-vips (2.2.2)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
logger
|
logger
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyXL (3.4.27)
|
rubyXL (3.4.33)
|
||||||
nokogiri (>= 1.10.8)
|
nokogiri (>= 1.10.8)
|
||||||
rubyzip (>= 1.3.0)
|
rubyzip (>= 1.3.0)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
securerandom (0.3.1)
|
securerandom (0.4.1)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.1.0)
|
||||||
shakapacker (8.0.2)
|
shakapacker (8.0.2)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
package_json
|
package_json
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
semantic_range (>= 2.3.0)
|
semantic_range (>= 2.3.0)
|
||||||
sidekiq (7.3.2)
|
sidekiq (7.3.7)
|
||||||
concurrent-ruby (< 2)
|
|
||||||
connection_pool (>= 2.3.0)
|
connection_pool (>= 2.3.0)
|
||||||
logger
|
logger
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
@@ -517,17 +530,20 @@ GEM
|
|||||||
simplecov-html (0.13.1)
|
simplecov-html (0.13.1)
|
||||||
simplecov_json_formatter (0.1.4)
|
simplecov_json_formatter (0.1.4)
|
||||||
smart_properties (1.17.0)
|
smart_properties (1.17.0)
|
||||||
sqlite3 (2.1.0)
|
sqlite3 (2.5.0)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
sqlite3 (2.1.0-aarch64-linux-gnu)
|
sqlite3 (2.5.0-aarch64-linux-gnu)
|
||||||
sqlite3 (2.1.0-x86_64-linux-gnu)
|
sqlite3 (2.5.0-aarch64-linux-musl)
|
||||||
stringio (3.1.1)
|
sqlite3 (2.5.0-arm64-darwin)
|
||||||
strip_attributes (1.13.0)
|
sqlite3 (2.5.0-x86_64-linux-gnu)
|
||||||
activemodel (>= 3.0, < 8.0)
|
sqlite3 (2.5.0-x86_64-linux-musl)
|
||||||
|
stringio (3.1.2)
|
||||||
|
strip_attributes (1.14.1)
|
||||||
|
activemodel (>= 3.0, < 9.0)
|
||||||
thor (1.3.2)
|
thor (1.3.2)
|
||||||
timeout (0.4.1)
|
timeout (0.4.3)
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
turbo-rails (2.0.10)
|
turbo-rails (2.0.11)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
twitter_cldr (6.12.1)
|
twitter_cldr (6.12.1)
|
||||||
@@ -539,9 +555,12 @@ GEM
|
|||||||
tzinfo-data (1.2024.2)
|
tzinfo-data (1.2024.2)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unicode-display_width (2.6.0)
|
unicode-display_width (3.1.2)
|
||||||
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
|
unicode-emoji (4.0.4)
|
||||||
uniform_notifier (1.16.0)
|
uniform_notifier (1.16.0)
|
||||||
useragent (0.16.10)
|
uri (1.0.3)
|
||||||
|
useragent (0.16.11)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
web-console (4.2.1)
|
web-console (4.2.1)
|
||||||
@@ -553,27 +572,31 @@ GEM
|
|||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webrick (1.8.2)
|
webrick (1.9.1)
|
||||||
websocket-driver (0.7.6)
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.7.0)
|
zeitwerk (2.7.1)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux
|
||||||
|
aarch64-linux-musl
|
||||||
|
arm64-darwin
|
||||||
ruby
|
ruby
|
||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
x86_64-linux-musl
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
annotate
|
annotaterb
|
||||||
arabic-letter-connector
|
arabic-letter-connector
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
aws-sdk-secretsmanager
|
aws-sdk-secretsmanager
|
||||||
azure-storage-blob
|
azure-storage-blob
|
||||||
better_html
|
better_html
|
||||||
bootsnap
|
bootsnap
|
||||||
|
brakeman
|
||||||
bullet
|
bullet
|
||||||
cancancan
|
cancancan
|
||||||
capybara
|
capybara
|
||||||
@@ -589,6 +612,7 @@ DEPENDENCIES
|
|||||||
faker
|
faker
|
||||||
faraday
|
faraday
|
||||||
faraday-follow_redirects
|
faraday-follow_redirects
|
||||||
|
foreman
|
||||||
google-cloud-storage
|
google-cloud-storage
|
||||||
hexapdf
|
hexapdf
|
||||||
image_processing
|
image_processing
|
||||||
@@ -628,7 +652,7 @@ DEPENDENCIES
|
|||||||
webmock
|
webmock
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 3.3.4p94
|
ruby 3.4.2p28
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.3
|
2.5.3
|
||||||
@@ -42,7 +42,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
|||||||
- PDF signature verification
|
- PDF signature verification
|
||||||
- Users management
|
- Users management
|
||||||
- Mobile-optimized
|
- Mobile-optimized
|
||||||
- 6 UI languages with signing available in 13 languages
|
- 6 UI languages with signing available in 14 languages
|
||||||
- API and Webhooks for integrations
|
- API and Webhooks for integrations
|
||||||
- Easy to deploy in minutes
|
- Easy to deploy in minutes
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AccountConfigsController < ApplicationController
|
class AccountConfigsController < ApplicationController
|
||||||
before_action :load_account_config
|
before_action :load_account_config, only: :create
|
||||||
authorize_resource :account_config
|
authorize_resource :account_config, only: :create
|
||||||
|
|
||||||
|
load_and_authorize_resource :account_config, only: :destroy
|
||||||
|
|
||||||
ALLOWED_KEYS = [
|
ALLOWED_KEYS = [
|
||||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||||
@@ -15,6 +17,7 @@ class AccountConfigsController < ApplicationController
|
|||||||
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
||||||
AccountConfig::FORCE_SSO_AUTH_KEY,
|
AccountConfig::FORCE_SSO_AUTH_KEY,
|
||||||
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
||||||
|
AccountConfig::ENFORCE_SIGNING_ORDER_KEY,
|
||||||
AccountConfig::WITH_SIGNATURE_ID,
|
AccountConfig::WITH_SIGNATURE_ID,
|
||||||
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
||||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
||||||
@@ -29,6 +32,14 @@ class AccountConfigsController < ApplicationController
|
|||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
raise InvalidKey unless ALLOWED_KEYS.include?(@account_config.key)
|
||||||
|
|
||||||
|
@account_config.destroy!
|
||||||
|
|
||||||
|
redirect_back(fallback_location: root_path)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_account_config
|
def load_account_config
|
||||||
@@ -39,7 +50,7 @@ class AccountConfigsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def account_config_params
|
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])
|
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ class AccountsController < ApplicationController
|
|||||||
def destroy
|
def destroy
|
||||||
authorize!(:manage, current_account)
|
authorize!(:manage, current_account)
|
||||||
|
|
||||||
true_user.update!(locked_at: Time.current)
|
true_user.update!(locked_at: Time.current, email: true_user.email.sub('@', '+removed@'))
|
||||||
|
|
||||||
# rubocop:disable Layout/LineLength
|
# rubocop:disable Layout/LineLength
|
||||||
render turbo_stream: turbo_stream.replace(
|
render turbo_stream: turbo_stream.replace(
|
||||||
:account_delete_button,
|
:account_delete_button,
|
||||||
html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account'))
|
html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_months_please_contact_us_if_you_want_to_keep_your_account'))
|
||||||
)
|
)
|
||||||
# rubocop:enable Layout/LineLength
|
# rubocop:enable Layout/LineLength
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ module Api
|
|||||||
|
|
||||||
blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid)
|
blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid)
|
||||||
|
|
||||||
authorization_check!(blob) if exp.blank?
|
attachment = blob.attachments.take
|
||||||
|
|
||||||
|
@record = attachment.record
|
||||||
|
|
||||||
|
authorization_check!(attachment) if exp.blank?
|
||||||
|
|
||||||
if request.headers['Range'].present?
|
if request.headers['Range'].present?
|
||||||
send_blob_byte_range_data blob, request.headers['Range']
|
send_blob_byte_range_data blob, request.headers['Range']
|
||||||
@@ -37,11 +41,10 @@ module Api
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def authorization_check!(blob)
|
def authorization_check!(attachment)
|
||||||
attachment = blob.attachments.take
|
|
||||||
|
|
||||||
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
|
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
|
||||||
(current_user && attachment.record.account.id == current_user.account_id) ||
|
(current_user && attachment.record.account.id == current_user.account_id) ||
|
||||||
|
(current_user && !Docuseal.multitenant? && current_user.role == 'superadmin') ||
|
||||||
!attachment.record.account.account_configs
|
!attachment.record.account.account_configs
|
||||||
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module Api
|
|||||||
last_submitter = @submission.submitters.max_by(&:completed_at)
|
last_submitter = @submission.submitters.max_by(&:completed_at)
|
||||||
|
|
||||||
if last_submitter.documents_attachments.blank?
|
if last_submitter.documents_attachments.blank?
|
||||||
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(submitter)
|
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(last_submitter)
|
||||||
end
|
end
|
||||||
|
|
||||||
last_submitter.documents_attachments
|
last_submitter.documents_attachments
|
||||||
|
|||||||
@@ -11,11 +11,7 @@ module Api
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
submissions = Submissions.search(@submissions, params[:q])
|
submissions = Submissions.search(@submissions, params[:q])
|
||||||
submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present?
|
submissions = filter_submissions(submissions, params)
|
||||||
|
|
||||||
if params[:template_folder].present?
|
|
||||||
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
|
||||||
end
|
|
||||||
|
|
||||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||||
template: :folder,
|
template: :folder,
|
||||||
@@ -109,6 +105,21 @@ module Api
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def filter_submissions(submissions, params)
|
||||||
|
submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present?
|
||||||
|
submissions = submissions.where(slug: params[:slug]) if params[:slug].present?
|
||||||
|
|
||||||
|
if params[:template_folder].present?
|
||||||
|
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
||||||
|
end
|
||||||
|
|
||||||
|
if params.key?(:archived)
|
||||||
|
submissions = params[:archived].in?(['true', true]) ? submissions.archived : submissions.active
|
||||||
|
end
|
||||||
|
|
||||||
|
Submissions::Filter.call(submissions, current_user, params)
|
||||||
|
end
|
||||||
|
|
||||||
def build_create_json(submissions)
|
def build_create_json(submissions)
|
||||||
json = submissions.flat_map do |submission|
|
json = submissions.flat_map do |submission|
|
||||||
submission.submitters.map do |s|
|
submission.submitters.map do |s|
|
||||||
@@ -116,7 +127,19 @@ module Api
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
json = { submitters: json } if request.path.ends_with?('/init')
|
if request.path.ends_with?('/init')
|
||||||
|
json =
|
||||||
|
if submissions.size == 1
|
||||||
|
{
|
||||||
|
id: submissions.first.id,
|
||||||
|
submitters: json,
|
||||||
|
expire_at: submissions.first.expire_at,
|
||||||
|
created_at: submissions.first.created_at
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ submitters: json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
json
|
json
|
||||||
end
|
end
|
||||||
@@ -165,7 +188,7 @@ module Api
|
|||||||
message: %i[subject body],
|
message: %i[subject body],
|
||||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||||
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
||||||
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
|
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
|
||||||
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
||||||
:readonly, :validation_pattern, :invalid_message,
|
:readonly, :validation_pattern, :invalid_message,
|
||||||
{ default_value: [], value: [], preferences: {} }] }]]
|
{ default_value: [], value: [], preferences: {} }] }]]
|
||||||
@@ -178,7 +201,7 @@ module Api
|
|||||||
key = params.key?(:submission) ? :submission : :submissions
|
key = params.key?(:submission) ? :submission : :submissions
|
||||||
|
|
||||||
params.permit(
|
params.permit(
|
||||||
key => [permitted_attrs]
|
{ key => [permitted_attrs] }, { key => permitted_attrs }
|
||||||
).fetch(key, [])
|
).fetch(key, [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ module Api
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
def create
|
def create
|
||||||
submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
|
|
||||||
if params[:t] == SubmissionEvents.build_tracking_param(submitter, 'click_email')
|
if params[:t] == SubmissionEvents.build_tracking_param(@submitter, 'click_email')
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'click_email', request)
|
SubmissionEvents.create_with_tracking_data(@submitter, 'click_email', request)
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: {}
|
render json: {}
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ module Api
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
def create
|
def create
|
||||||
submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
|
|
||||||
submitter.opened_at = Time.current
|
@submitter.opened_at = Time.current
|
||||||
submitter.save
|
@submitter.save
|
||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
SubmissionEvents.create_with_tracking_data(@submitter, 'view_form', request)
|
||||||
|
|
||||||
WebhookUrls.for_account_id(submitter.account_id, 'form.viewed').each do |webhook_url|
|
WebhookUrls.for_account_id(@submitter.account_id, 'form.viewed').each do |webhook_url|
|
||||||
SendFormViewedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
SendFormViewedWebhookRequestJob.perform_async('submitter_id' => @submitter.id,
|
||||||
'webhook_url_id' => webhook_url.id)
|
'webhook_url_id' => webhook_url.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,7 @@ module Api
|
|||||||
def index
|
def index
|
||||||
submitters = Submitters.search(@submitters, params[:q])
|
submitters = Submitters.search(@submitters, params[:q])
|
||||||
|
|
||||||
submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present?
|
submitters = filter_submitters(submitters, params)
|
||||||
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
|
||||||
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
|
||||||
|
|
||||||
if params[:template_id].present?
|
|
||||||
submitters = submitters.joins(:submission).where(submission: { template_id: params[:template_id] })
|
|
||||||
end
|
|
||||||
|
|
||||||
submitters = maybe_filder_by_completed_at(submitters, params)
|
|
||||||
|
|
||||||
submitters = paginate(
|
submitters = paginate(
|
||||||
submitters.preload(:template, :submission, :submission_events,
|
submitters.preload(:template, :submission, :submission_events,
|
||||||
@@ -146,9 +138,15 @@ module Api
|
|||||||
|
|
||||||
if attrs[:completed]
|
if attrs[:completed]
|
||||||
submitter.values = Submitters::SubmitValues.merge_default_values(submitter)
|
submitter.values = Submitters::SubmitValues.merge_default_values(submitter)
|
||||||
submitter.values = Submitters::SubmitValues.merge_formula_values(submitter)
|
|
||||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||||
|
|
||||||
|
formula_values = Submitters::SubmitValues.build_formula_values(submitter)
|
||||||
|
|
||||||
|
if formula_values.present?
|
||||||
|
submitter.values = submitter.values.merge(formula_values)
|
||||||
|
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||||
|
end
|
||||||
|
|
||||||
submitter.values = submitter.values.transform_values do |v|
|
submitter.values = submitter.values.transform_values do |v|
|
||||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
||||||
end
|
end
|
||||||
@@ -157,6 +155,19 @@ module Api
|
|||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter_submitters(submitters, params)
|
||||||
|
submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
|
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
|
submitters = submitters.where(slug: params[:slug]) if params[:slug].present?
|
||||||
|
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
|
||||||
|
|
||||||
|
if params[:template_id].present?
|
||||||
|
submitters = submitters.joins(:submission).where(submissions: { template_id: params[:template_id] })
|
||||||
|
end
|
||||||
|
|
||||||
|
maybe_filder_by_completed_at(submitters, params)
|
||||||
|
end
|
||||||
|
|
||||||
def assign_external_id(submitter, attrs)
|
def assign_external_id(submitter, attrs)
|
||||||
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
|
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
|
||||||
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
|
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module Api
|
|||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def create
|
def create
|
||||||
authorize!(:manage, @template)
|
authorize!(:create, @template)
|
||||||
|
|
||||||
ActiveRecord::Associations::Preloader.new(
|
ActiveRecord::Associations::Preloader.new(
|
||||||
records: [@template],
|
records: [@template],
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ module Api
|
|||||||
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
||||||
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
|
||||||
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
|
||||||
|
templates = templates.where(slug: params[:slug]) if params[:slug].present?
|
||||||
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
|
||||||
|
|
||||||
templates
|
templates
|
||||||
@@ -100,7 +101,7 @@ module Api
|
|||||||
:name,
|
:name,
|
||||||
:external_id,
|
:external_id,
|
||||||
{
|
{
|
||||||
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email]],
|
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||||
:required, :readonly, :default_value,
|
:required, :readonly, :default_value,
|
||||||
:title, :description,
|
:title, :description,
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ module Api
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
rescue HexaPDF::MalformedPDFError
|
||||||
|
render json: { error: 'Malformed PDF' }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -30,7 +30,7 @@ class ApplicationController < ActionController::Base
|
|||||||
redirect_to request.referer, alert: 'Too many requests', status: :too_many_requests
|
redirect_to request.referer, alert: 'Too many requests', status: :too_many_requests
|
||||||
end
|
end
|
||||||
|
|
||||||
if Rails.env.production?
|
if Rails.env.production? || Rails.env.test?
|
||||||
rescue_from CanCan::AccessDenied do |e|
|
rescue_from CanCan::AccessDenied do |e|
|
||||||
Rollbar.warning(e) if defined?(Rollbar)
|
Rollbar.warning(e) if defined?(Rollbar)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class ConsoleRedirectController < ApplicationController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
if request.path == '/upgrade'
|
if request.path == '/upgrade'
|
||||||
params[:redir] = Docuseal.multitenant? ? "#{Docuseal::CONSOLE_URL}/plans" : "#{Docuseal::CONSOLE_URL}/on_premise"
|
params[:redir] = Docuseal.multitenant? ? "#{Docuseal::CONSOLE_URL}/plans" : "#{Docuseal::CONSOLE_URL}/on_premises"
|
||||||
end
|
end
|
||||||
|
|
||||||
params[:redir] = "#{Docuseal::CONSOLE_URL}/manage" if request.path == '/manage'
|
params[:redir] = "#{Docuseal::CONSOLE_URL}/manage" if request.path == '/manage'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class EmbedScriptsController < ActionController::Metal
|
|||||||
<h2>Upgrade to Pro</h2>
|
<h2>Upgrade to Pro</h2>
|
||||||
<p>Unlock embedded components by upgrading to Pro</p>
|
<p>Unlock embedded components by upgrading to Pro</p>
|
||||||
<div style="margin-top: 40px;">
|
<div style="margin-top: 40px;">
|
||||||
<a href="#{Docuseal::CONSOLE_URL}/on_premise" target="_blank" style="padding: 15px 25px; background-color: #222; color: white; text-decoration: none; border-radius: 5px; font-size: 16px; cursor: pointer;">
|
<a href="#{Docuseal::CONSOLE_URL}/on_premises" target="_blank" style="padding: 15px 25px; background-color: #222; color: white; text-decoration: none; border-radius: 5px; font-size: 16px; cursor: pointer;">
|
||||||
Learn More
|
Learn More
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class NotificationsSettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def email_config_params
|
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])
|
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class PersonalizationSettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def account_config_params
|
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)
|
return attrs if attrs[:value].is_a?(String)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class PreviewDocumentPageController < ActionController::API
|
|||||||
|
|
||||||
return head :not_found unless attachment
|
return head :not_found unless attachment
|
||||||
|
|
||||||
|
@template = attachment.record
|
||||||
|
|
||||||
preview_image = attachment.preview_images.joins(:blob)
|
preview_image = attachment.preview_images.joins(:blob)
|
||||||
.find_by(blob: { filename: ["#{params[:id]}.png", "#{params[:id]}.jpg"] })
|
.find_by(blob: { filename: ["#{params[:id]}.png", "#{params[:id]}.jpg"] })
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PwaController < ActionController::Base
|
||||||
|
end
|
||||||
@@ -9,19 +9,18 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
|
|
||||||
SEND_DURATION = 30.minutes
|
SEND_DURATION = 30.minutes
|
||||||
|
|
||||||
def success; end
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@submitter =
|
if params[:template_slug]
|
||||||
if params[:template_slug]
|
@submitter = Submitter.joins(submission: :template).find_by!(email: params[:email].to_s.downcase,
|
||||||
Submitter.joins(submission: :template).find_by!(email: params[:email].to_s.downcase,
|
template: { slug: params[:template_slug] })
|
||||||
template: { slug: params[:template_slug] })
|
elsif params[:submission_slug]
|
||||||
elsif params[:submission_slug]
|
@submitter = Submitter.joins(:submission).find_by(email: params[:email].to_s.downcase,
|
||||||
Submitter.joins(:submission).find_by!(email: params[:email].to_s.downcase,
|
submission: { slug: params[:submission_slug] })
|
||||||
submission: { slug: params[:submission_slug] })
|
|
||||||
else
|
return redirect_to submissions_preview_completed_path(params[:submission_slug], status: :error) unless @submitter
|
||||||
Submitter.find_by!(slug: params[:submitter_slug])
|
else
|
||||||
end
|
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
|
end
|
||||||
|
|
||||||
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
respond_to do |f|
|
respond_to do |f|
|
||||||
f.html { redirect_to success_send_submission_email_index_path }
|
f.html { render :success }
|
||||||
f.json { head :ok }
|
f.json { head :ok }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ class StartFormController < ApplicationController
|
|||||||
before_action :load_template
|
before_action :load_template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
raise ActionController::RoutingError, I18n.t('not_found') if @template.preferences['require_phone_2fa'] == true
|
||||||
|
|
||||||
@submitter = @template.submissions.new(account_id: @template.account_id)
|
@submitter = @template.submissions.new(account_id: @template.account_id)
|
||||||
.submitters.new(uuid: (filter_undefined_submitters(@template).first ||
|
.submitters.new(account_id: @template.account_id,
|
||||||
|
uuid: (filter_undefined_submitters(@template).first ||
|
||||||
@template.submitters.first)['uuid'])
|
@template.submitters.first)['uuid'])
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -25,7 +28,7 @@ class StartFormController < ApplicationController
|
|||||||
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
|
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
|
||||||
else
|
else
|
||||||
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
|
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
|
||||||
@error_message = I18n.t('not_found')
|
@error_message = multiple_submitters_error_message
|
||||||
|
|
||||||
return render :show
|
return render :show
|
||||||
end
|
end
|
||||||
@@ -34,13 +37,17 @@ class StartFormController < ApplicationController
|
|||||||
assign_submission_attributes(@submitter, @template)
|
assign_submission_attributes(@submitter, @template)
|
||||||
|
|
||||||
Submissions::AssignDefinedSubmitters.call(@submitter.submission)
|
Submissions::AssignDefinedSubmitters.call(@submitter.submission)
|
||||||
|
else
|
||||||
|
@submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent)
|
||||||
end
|
end
|
||||||
|
|
||||||
if @submitter.save
|
if @submitter.save
|
||||||
if is_new_record
|
if is_new_record
|
||||||
WebhookUrls.for_account_id(@submitter.account_id, 'submission.created').each do |webhook_url|
|
enqueue_submission_create_webhooks(@submitter)
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => @submitter.submission_id,
|
|
||||||
'webhook_url_id' => webhook_url.id)
|
if @submitter.submission.expire_at?
|
||||||
|
ProcessSubmissionExpiredJob.perform_at(@submitter.submission.expire_at,
|
||||||
|
'submission_id' => @submitter.submission_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -59,13 +66,22 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def enqueue_submission_create_webhooks(submitter)
|
||||||
|
WebhookUrls.for_account_id(submitter.account_id, 'submission.created').each do |webhook_url|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def find_or_initialize_submitter(template, submitter_params)
|
def find_or_initialize_submitter(template, submitter_params)
|
||||||
Submitter.where(submission: template.submissions.where(expire_at: Time.current..)
|
Submitter.where(submission: template.submissions.where(expire_at: Time.current..)
|
||||||
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
||||||
.order(id: :desc)
|
.order(id: :desc)
|
||||||
.where(declined_at: nil)
|
.where(declined_at: nil)
|
||||||
|
.where(external_id: nil)
|
||||||
|
.where(ip: [nil, request.remote_ip])
|
||||||
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
||||||
.find_or_initialize_by(**submitter_params.compact_blank)
|
.find_or_initialize_by(email: submitter_params[:email], **submitter_params.compact_blank)
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_submission_attributes(submitter, template)
|
def assign_submission_attributes(submitter, template)
|
||||||
@@ -90,6 +106,7 @@ class StartFormController < ApplicationController
|
|||||||
submitter.submission ||= Submission.new(template:,
|
submitter.submission ||= Submission.new(template:,
|
||||||
account_id: template.account_id,
|
account_id: template.account_id,
|
||||||
template_submitters: template.submitters,
|
template_submitters: template.submitters,
|
||||||
|
expire_at: Templates.build_default_expire_at(template),
|
||||||
submitters: [submitter],
|
submitters: [submitter],
|
||||||
source: :link)
|
source: :link)
|
||||||
|
|
||||||
@@ -113,4 +130,12 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
@template = Template.find_by!(slug:)
|
@template = Template.find_by!(slug:)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def multiple_submitters_error_message
|
||||||
|
if current_user&.account_id == @template.account_id
|
||||||
|
helpers.t('this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html')
|
||||||
|
else
|
||||||
|
I18n.t('not_found')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmissionEventsController < ApplicationController
|
||||||
|
SUBMISSION_EVENT_ICONS = {
|
||||||
|
'view_form' => 'eye',
|
||||||
|
'start_form' => 'player_play',
|
||||||
|
'complete_form' => 'check',
|
||||||
|
'send_email' => 'mail_forward',
|
||||||
|
'click_email' => 'hand_click',
|
||||||
|
'api_complete_form' => 'check',
|
||||||
|
'send_reminder_email' => 'mail_forward',
|
||||||
|
'send_2fa_sms' => '2fa',
|
||||||
|
'send_sms' => 'send',
|
||||||
|
'phone_verified' => 'phone_check',
|
||||||
|
'click_sms' => 'hand_click',
|
||||||
|
'decline_form' => 'x',
|
||||||
|
'start_verification' => 'player_play',
|
||||||
|
'complete_verification' => 'check',
|
||||||
|
'invite_party' => 'user_plus'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
load_and_authorize_resource :submission
|
||||||
|
|
||||||
|
def index; end
|
||||||
|
end
|
||||||
@@ -7,9 +7,7 @@ class SubmissionsArchivedController < ApplicationController
|
|||||||
@submissions = @submissions.joins(:template)
|
@submissions = @submissions.joins(:template)
|
||||||
@submissions = @submissions.where.not(archived_at: nil)
|
@submissions = @submissions.where.not(archived_at: nil)
|
||||||
.or(@submissions.where.not(templates: { archived_at: nil }))
|
.or(@submissions.where.not(templates: { archived_at: nil }))
|
||||||
.preload(:created_by_user, template: :author)
|
.preload(:template_accesses, :created_by_user, template: :author)
|
||||||
|
|
||||||
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
|
||||||
|
|
||||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ class SubmissionsController < ApplicationController
|
|||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
|
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
|
||||||
|
rescue Submissions::CreateFromSubmitters::BaseError => e
|
||||||
|
render turbo_stream: turbo_stream.replace(:submitters_error,
|
||||||
|
partial: 'submissions/error',
|
||||||
|
locals: { error: e.message }),
|
||||||
|
status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ class SubmissionsDashboardController < ApplicationController
|
|||||||
|
|
||||||
@submissions = @submissions.where(archived_at: nil)
|
@submissions = @submissions.where(archived_at: nil)
|
||||||
.where(templates: { archived_at: nil })
|
.where(templates: { archived_at: nil })
|
||||||
.preload(:created_by_user, template: :author)
|
.preload(:template_accesses, :created_by_user, template: :author)
|
||||||
|
|
||||||
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
|
||||||
|
|
||||||
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
|
||||||
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
@submissions = Submissions::Filter.call(@submissions, current_user, params)
|
||||||
|
|||||||
@@ -8,25 +8,25 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
FILES_TTL = 5.minutes
|
FILES_TTL = 5.minutes
|
||||||
|
|
||||||
def index
|
def index
|
||||||
submitter = Submitter.find_signed(params[:sig], purpose: :download_completed) if params[:sig].present?
|
@submitter = Submitter.find_signed(params[:sig], purpose: :download_completed) if params[:sig].present?
|
||||||
|
|
||||||
signature_valid =
|
signature_valid =
|
||||||
if submitter&.slug == params[:submitter_slug]
|
if @submitter&.slug == params[:submitter_slug]
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
submitter = nil
|
@submitter = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter ||= Submitter.find_by!(slug: params[:submitter_slug])
|
@submitter ||= Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(submitter)
|
Submissions::EnsureResultGenerated.call(@submitter)
|
||||||
|
|
||||||
last_submitter = submitter.submission.submitters.where.not(completed_at: nil).order(:completed_at).last
|
last_submitter = @submitter.submission.submitters.where.not(completed_at: nil).order(:completed_at).last
|
||||||
|
|
||||||
|
return head :not_found unless last_submitter
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(last_submitter)
|
Submissions::EnsureResultGenerated.call(last_submitter)
|
||||||
|
|
||||||
return head :not_found unless last_submitter.completed_at?
|
|
||||||
|
|
||||||
if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter)
|
if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter)
|
||||||
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
if params[:combined] == 'true'
|
if params[:combined] == 'true'
|
||||||
url = build_combined_url(submitter)
|
url = build_combined_url(@submitter)
|
||||||
|
|
||||||
if url
|
if url
|
||||||
render json: [url]
|
render json: [url]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class SubmissionsPreviewController < ApplicationController
|
class SubmissionsPreviewController < ApplicationController
|
||||||
|
around_action :with_browser_locale
|
||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
@@ -20,7 +21,10 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
|
|
||||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||||
|
|
||||||
if @submission.account.archived_at? || (!@submission.submitters.all?(&:completed_at?) && current_user.blank?)
|
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))
|
||||||
raise ActionController::RoutingError, I18n.t('not_found')
|
raise ActionController::RoutingError, I18n.t('not_found')
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -37,6 +41,7 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
|
|
||||||
def completed
|
def completed
|
||||||
@submission = Submission.find_by!(slug: params[:submissions_preview_slug])
|
@submission = Submission.find_by!(slug: params[:submissions_preview_slug])
|
||||||
|
@template = @submission.template
|
||||||
|
|
||||||
render :completed, layout: 'form'
|
render :completed, layout: 'form'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ class SubmitFormController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
|
before_action :load_submitter, only: %i[show update completed]
|
||||||
|
|
||||||
CONFIG_KEYS = [].freeze
|
CONFIG_KEYS = [].freeze
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@submitter = Submitter.find_by!(slug: params[:slug])
|
|
||||||
|
|
||||||
submission = @submitter.submission
|
submission = @submitter.submission
|
||||||
|
|
||||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||||
@@ -20,7 +20,11 @@ class SubmitFormController < ApplicationController
|
|||||||
@submitter.account.archived_at?
|
@submitter.account.archived_at?
|
||||||
return render :expired if submission.expired?
|
return render :expired if submission.expired?
|
||||||
return render :declined if @submitter.declined_at?
|
return render :declined if @submitter.declined_at?
|
||||||
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
|
|
||||||
|
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||||
|
|
||||||
|
return render :awaiting if (@form_configs[:enforce_signing_order] ||
|
||||||
|
submission.template.preferences['submitters_order'] == 'preserved') &&
|
||||||
!Submitters.current_submitter_order?(@submitter)
|
!Submitters.current_submitter_order?(@submitter)
|
||||||
|
|
||||||
Submitters.preload_with_pages(@submitter)
|
Submitters.preload_with_pages(@submitter)
|
||||||
@@ -29,8 +33,6 @@ class SubmitFormController < ApplicationController
|
|||||||
|
|
||||||
@attachments_index = build_attachments_index(submission)
|
@attachments_index = build_attachments_index(submission)
|
||||||
|
|
||||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
|
||||||
|
|
||||||
return unless @form_configs[:prefill_signature]
|
return unless @form_configs[:prefill_signature]
|
||||||
|
|
||||||
if (user_signature = UserConfigs.load_signature(current_user))
|
if (user_signature = UserConfigs.load_signature(current_user))
|
||||||
@@ -48,40 +50,44 @@ class SubmitFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
submitter = Submitter.find_by!(slug: params[:slug])
|
if @submitter.completed_at?
|
||||||
|
|
||||||
if submitter.completed_at?
|
|
||||||
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
|
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if submitter.template.archived_at? || submitter.submission.archived_at?
|
if @submitter.template.archived_at? || @submitter.submission.archived_at?
|
||||||
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
|
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if submitter.submission.expired?
|
if @submitter.submission.expired?
|
||||||
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity
|
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if submitter.declined_at?
|
if @submitter.declined_at?
|
||||||
return render json: { error: I18n.t('form_has_been_declined') },
|
return render json: { error: I18n.t('form_has_been_declined') },
|
||||||
status: :unprocessable_entity
|
status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
Submitters::SubmitValues.call(submitter, params, request)
|
Submitters::SubmitValues.call(@submitter, params, request)
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
|
rescue Submitters::SubmitValues::RequiredFieldError => e
|
||||||
|
Rollbar.warning("Required field #{@submitter.id}: #{e.message}") if defined?(Rollbar)
|
||||||
|
|
||||||
|
render json: { field_uuid: e.message }, status: :unprocessable_entity
|
||||||
rescue Submitters::SubmitValues::ValidationError => e
|
rescue Submitters::SubmitValues::ValidationError => e
|
||||||
render json: { error: e.message }, status: :unprocessable_entity
|
render json: { error: e.message }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
def completed
|
def completed; end
|
||||||
@submitter = Submitter.completed.find_by!(slug: params[:submit_form_slug])
|
|
||||||
end
|
|
||||||
|
|
||||||
def success; end
|
def success; end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def load_submitter
|
||||||
|
@submitter = Submitter.find_by!(slug: params[:slug] || params[:submit_form_slug])
|
||||||
|
end
|
||||||
|
|
||||||
def build_attachments_index(submission)
|
def build_attachments_index(submission)
|
||||||
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
||||||
.preload(:blob).index_by(&:uuid)
|
.preload(:blob).index_by(&:uuid)
|
||||||
|
|||||||
@@ -7,25 +7,25 @@ class SubmitFormDownloadController < ApplicationController
|
|||||||
FILES_TTL = 5.minutes
|
FILES_TTL = 5.minutes
|
||||||
|
|
||||||
def index
|
def index
|
||||||
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
@submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||||
|
|
||||||
return redirect_to submitter_download_index_path(submitter.slug) if submitter.completed_at?
|
return redirect_to submitter_download_index_path(@submitter.slug) if @submitter.completed_at?
|
||||||
|
|
||||||
return head :unprocessable_entity if submitter.declined_at? ||
|
return head :unprocessable_entity if @submitter.declined_at? ||
|
||||||
submitter.submission.archived_at? ||
|
@submitter.submission.archived_at? ||
|
||||||
submitter.submission.expired? ||
|
@submitter.submission.expired? ||
|
||||||
submitter.submission.template.archived_at?
|
@submitter.submission.template.archived_at?
|
||||||
|
|
||||||
last_completed_submitter = submitter.submission.submitters
|
last_completed_submitter = @submitter.submission.submitters
|
||||||
.where.not(id: submitter.id)
|
.where.not(id: @submitter.id)
|
||||||
.where.not(completed_at: nil)
|
.where.not(completed_at: nil)
|
||||||
.max_by(&:completed_at)
|
.max_by(&:completed_at)
|
||||||
|
|
||||||
attachments =
|
attachments =
|
||||||
if last_completed_submitter
|
if last_completed_submitter
|
||||||
Submitters.select_attachments_for_download(last_completed_submitter)
|
Submitters.select_attachments_for_download(last_completed_submitter)
|
||||||
else
|
else
|
||||||
submitter.submission.template.schema_documents.preload(:blob)
|
@submitter.submission.template.schema_documents.preload(:blob)
|
||||||
end
|
end
|
||||||
|
|
||||||
urls = attachments.map do |attachment|
|
urls = attachments.map do |attachment|
|
||||||
|
|||||||
@@ -9,13 +9,15 @@ class SubmitFormInviteController < ApplicationController
|
|||||||
|
|
||||||
return head :unprocessable_entity unless can_invite?(submitter)
|
return head :unprocessable_entity unless can_invite?(submitter)
|
||||||
|
|
||||||
invite_submitters = filter_invite_submitters(submitter)
|
invite_submitters = filter_invite_submitters(submitter, 'invite_by_uuid')
|
||||||
|
optional_invite_submitters = filter_invite_submitters(submitter, 'optional_invite_by_uuid')
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
invite_submitters.each do |item|
|
(invite_submitters + optional_invite_submitters).each do |item|
|
||||||
attrs = submitters_attributes.find { |e| e[:uuid] == item['uuid'] }
|
attrs = submitters_attributes.find { |e| e[:uuid] == item['uuid'] }
|
||||||
|
|
||||||
next unless attrs
|
next unless attrs
|
||||||
|
next if attrs[:email].blank?
|
||||||
|
|
||||||
submitter.submission.submitters.create!(**attrs, account_id: submitter.account_id)
|
submitter.submission.submitters.create!(**attrs, account_id: submitter.account_id)
|
||||||
|
|
||||||
@@ -46,9 +48,9 @@ class SubmitFormInviteController < ApplicationController
|
|||||||
!submitter.submission.template.archived_at?
|
!submitter.submission.template.archived_at?
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_invite_submitters(submitter)
|
def filter_invite_submitters(submitter, key = 'invite_by_uuid')
|
||||||
(submitter.submission.template_submitters || submitter.submission.template.submitters).select do |s|
|
(submitter.submission.template_submitters || submitter.submission.template.submitters).select do |s|
|
||||||
s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] }
|
s[key] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmittersResubmitController < ApplicationController
|
||||||
|
load_and_authorize_resource :submitter, parent: false
|
||||||
|
|
||||||
|
def update
|
||||||
|
return redirect_to submit_form_path(slug: @submitter.slug) if @submitter.email != current_user.email
|
||||||
|
|
||||||
|
submission = @submitter.template.submissions.new(created_by_user: current_user,
|
||||||
|
submitters_order: :preserved,
|
||||||
|
**@submitter.submission.slice(:template_fields,
|
||||||
|
:account_id,
|
||||||
|
:template_schema,
|
||||||
|
:template_submitters,
|
||||||
|
:preferences))
|
||||||
|
|
||||||
|
@submitter.submission.submitters.each do |submitter|
|
||||||
|
new_submitter = submission.submitters.new(submitter.slice(:uuid, :email, :phone, :name,
|
||||||
|
:preferences, :metadata, :account_id))
|
||||||
|
|
||||||
|
next unless submitter.uuid == @submitter.uuid
|
||||||
|
|
||||||
|
assign_submitter_values(new_submitter, submitter)
|
||||||
|
|
||||||
|
@new_submitter ||= new_submitter
|
||||||
|
end
|
||||||
|
|
||||||
|
submission.save!
|
||||||
|
|
||||||
|
redirect_to submit_form_path(slug: @new_submitter.slug)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def assign_submitter_values(new_submitter, submitter)
|
||||||
|
attachments_index = submitter.attachments.index_by(&:uuid)
|
||||||
|
|
||||||
|
submitter.submission.template_fields.each do |field|
|
||||||
|
next if field['submitter_uuid'] != submitter.uuid
|
||||||
|
next if field['default_value'] == '{{date}}'
|
||||||
|
next if field['type'] == 'stamp'
|
||||||
|
next if field['type'] == 'signature'
|
||||||
|
next if field.dig('preferences', 'formula').present?
|
||||||
|
|
||||||
|
value = submitter.values[field['uuid']]
|
||||||
|
|
||||||
|
next if value.blank?
|
||||||
|
|
||||||
|
if field['type'].in?(%w[image file initials])
|
||||||
|
Array.wrap(value).each do |attachment_uuid|
|
||||||
|
new_submitter.attachments << attachments_index[attachment_uuid].dup
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
new_submitter.values[field['uuid']] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -5,8 +5,9 @@ class TemplateFoldersController < ApplicationController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@templates = @template_folder.templates.active.accessible_by(current_ability)
|
@templates = @template_folder.templates.active.accessible_by(current_ability)
|
||||||
.preload(:author, :template_accesses).order(id: :desc)
|
.preload(:author, :template_accesses)
|
||||||
@templates = Templates.search(@templates, params[:q])
|
@templates = Templates.search(@templates, params[:q])
|
||||||
|
@templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order])
|
||||||
|
|
||||||
@pagy, @templates = pagy(@templates, limit: 12)
|
@pagy, @templates = pagy(@templates, limit: 12)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesCloneAndReplaceController < ApplicationController
|
||||||
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
|
def create
|
||||||
|
return head :unprocessable_entity if params[:files].blank?
|
||||||
|
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: [@template],
|
||||||
|
associations: [schema_documents: :preview_images_attachments]
|
||||||
|
).call
|
||||||
|
|
||||||
|
cloned_template = Templates::Clone.call(@template, author: current_user)
|
||||||
|
cloned_template.name = File.basename(params[:files].first.original_filename, '.*')
|
||||||
|
cloned_template.save!
|
||||||
|
|
||||||
|
documents = Templates::ReplaceAttachments.call(cloned_template, params, extract_fields: true)
|
||||||
|
|
||||||
|
cloned_template.save!
|
||||||
|
|
||||||
|
Templates::CloneAttachments.call(template: cloned_template, original_template: @template,
|
||||||
|
excluded_attachment_uuids: documents.map(&:uuid))
|
||||||
|
|
||||||
|
respond_to do |f|
|
||||||
|
f.html { redirect_to edit_template_path(cloned_template) }
|
||||||
|
f.json { render json: { id: cloned_template.id } }
|
||||||
|
end
|
||||||
|
rescue Templates::CreateAttachments::PdfEncrypted
|
||||||
|
respond_to do |f|
|
||||||
|
f.html { render turbo_stream: turbo_stream.append(params[:form_id], html: helpers.tag.prompt_password) }
|
||||||
|
f.json { render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -21,9 +21,7 @@ class TemplatesController < ApplicationController
|
|||||||
submissions.order(id: :desc)
|
submissions.order(id: :desc)
|
||||||
end
|
end
|
||||||
|
|
||||||
submissions = submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
@pagy, @submissions = pagy(submissions.preload(:template_accesses, submitters: :start_form_submission_events))
|
||||||
|
|
||||||
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events))
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
@@ -111,7 +109,7 @@ class TemplatesController < ApplicationController
|
|||||||
params.require(:template).permit(
|
params.require(:template).permit(
|
||||||
:name,
|
:name,
|
||||||
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
|
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
|
||||||
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid email]],
|
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||||
:required, :readonly, :default_value,
|
:required, :readonly, :default_value,
|
||||||
:title, :description,
|
:title, :description,
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
FOLDERS_PER_PAGE = 18
|
FOLDERS_PER_PAGE = 18
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@template_folders = @template_folders.where(id: @templates.active.select(:folder_id)).order(id: :desc)
|
@template_folders = @template_folders.where(id: @templates.active.select(:folder_id))
|
||||||
|
|
||||||
@template_folders = TemplateFolders.search(@template_folders, params[:q])
|
@template_folders = TemplateFolders.search(@template_folders, params[:q])
|
||||||
|
@template_folders = sort_template_folders(@template_folders, current_user,
|
||||||
|
cookies.permanent[:dashboard_templates_order])
|
||||||
|
|
||||||
@pagy, @template_folders = pagy(
|
@pagy, @template_folders = pagy(
|
||||||
@template_folders,
|
@template_folders,
|
||||||
@@ -24,6 +26,7 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
else
|
else
|
||||||
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
|
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
|
||||||
@templates = filter_templates(@templates)
|
@templates = filter_templates(@templates)
|
||||||
|
@templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order])
|
||||||
|
|
||||||
limit =
|
limit =
|
||||||
if @template_folders.size < 4
|
if @template_folders.size < 4
|
||||||
@@ -39,19 +42,55 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def filter_templates(templates)
|
def filter_templates(templates)
|
||||||
rel = templates.active.preload(:author, :template_accesses).order(id: :desc)
|
rel = templates.active.preload(:author, :template_accesses)
|
||||||
|
|
||||||
if params[:q].blank?
|
if params[:q].blank?
|
||||||
if Docuseal.multitenant? && !current_account.testing?
|
if Docuseal.multitenant? ? current_account.testing? : current_account.linked_account_account
|
||||||
rel = rel.where(folder_id: current_account.default_template_folder.id)
|
shared_account_ids = [current_user.account_id]
|
||||||
else
|
shared_account_ids << TemplateSharing::ALL_ID if !Docuseal.multitenant? && !current_account.testing?
|
||||||
shared_template_ids =
|
|
||||||
TemplateSharing.where(account_id: [current_account.id, TemplateSharing::ALL_ID]).select(:template_id)
|
shared_template_ids = TemplateSharing.where(account_id: shared_account_ids).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))
|
||||||
|
else
|
||||||
|
rel = rel.where(folder_id: current_account.default_template_folder.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Templates.search(rel, params[:q])
|
Templates.search(rel, params[:q])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sort_template_folders(template_folders, current_user, order)
|
||||||
|
case order
|
||||||
|
when 'used_at'
|
||||||
|
subquery =
|
||||||
|
Template.left_joins(:submissions)
|
||||||
|
.group(:folder_id)
|
||||||
|
.where(account_id: current_user.account_id)
|
||||||
|
.select(
|
||||||
|
:folder_id,
|
||||||
|
Template.arel_table[:updated_at].maximum.as('updated_at_max'),
|
||||||
|
Submission.arel_table[:created_at].maximum.as('submission_created_at_max')
|
||||||
|
)
|
||||||
|
|
||||||
|
template_folders = template_folders.joins(
|
||||||
|
Template.arel_table
|
||||||
|
.join(subquery.arel.as('templates'), Arel::Nodes::OuterJoin)
|
||||||
|
.on(TemplateFolder.arel_table[:id].eq(Template.arel_table[:folder_id]))
|
||||||
|
.join_sources
|
||||||
|
)
|
||||||
|
|
||||||
|
template_folders.order(
|
||||||
|
Arel::Nodes::Case.new
|
||||||
|
.when(Template.arel_table[:submission_created_at_max].gt(Template.arel_table[:updated_at_max]))
|
||||||
|
.then(Template.arel_table[:submission_created_at_max])
|
||||||
|
.else(Template.arel_table[:updated_at_max])
|
||||||
|
.desc
|
||||||
|
)
|
||||||
|
when 'name'
|
||||||
|
template_folders.order(name: :asc)
|
||||||
|
else
|
||||||
|
template_folders.order(id: :desc)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -6,9 +6,10 @@ class TemplatesDebugController < ApplicationController
|
|||||||
def show
|
def show
|
||||||
attachment = @template.documents.first
|
attachment = @template.documents.first
|
||||||
|
|
||||||
pdf = HexaPDF::Document.new(io: StringIO.new(attachment.download))
|
data = attachment.download
|
||||||
|
pdf = HexaPDF::Document.new(io: StringIO.new(data))
|
||||||
|
|
||||||
fields = Templates::FindAcroFields.call(pdf, attachment)
|
fields = Templates::FindAcroFields.call(pdf, attachment, data)
|
||||||
|
|
||||||
attachment.metadata['pdf'] ||= {}
|
attachment.metadata['pdf'] ||= {}
|
||||||
attachment.metadata['pdf']['fields'] = fields
|
attachment.metadata['pdf']['fields'] = fields
|
||||||
|
|||||||
@@ -22,14 +22,24 @@ class TemplatesPreferencesController < ApplicationController
|
|||||||
preferences: %i[bcc_completed request_email_subject request_email_body
|
preferences: %i[bcc_completed request_email_subject request_email_body
|
||||||
documents_copy_email_subject documents_copy_email_body
|
documents_copy_email_subject documents_copy_email_body
|
||||||
documents_copy_email_enabled documents_copy_email_attach_audit
|
documents_copy_email_enabled documents_copy_email_attach_audit
|
||||||
documents_copy_email_attach_documents
|
documents_copy_email_attach_documents documents_copy_email_reply_to
|
||||||
completed_notification_email_attach_documents
|
completed_notification_email_attach_documents
|
||||||
completed_redirect_url
|
completed_redirect_url validate_unique_submitters
|
||||||
submitters_order
|
submitters_order require_phone_2fa
|
||||||
|
default_expire_at_duration
|
||||||
|
default_expire_at
|
||||||
completed_notification_email_subject completed_notification_email_body
|
completed_notification_email_subject completed_notification_email_body
|
||||||
completed_notification_email_enabled completed_notification_email_attach_audit] +
|
completed_notification_email_enabled completed_notification_email_attach_audit] +
|
||||||
[completed_message: %i[title body]]
|
[completed_message: %i[title body],
|
||||||
|
submitters: [%i[uuid request_email_subject request_email_body]]]
|
||||||
).tap do |attrs|
|
).tap do |attrs|
|
||||||
|
attrs[:preferences].delete(:submitters) if params[:request_email_per_submitter] != '1'
|
||||||
|
|
||||||
|
if (default_expire_at = attrs.dig(:preferences, :default_expire_at).presence)
|
||||||
|
attrs[:preferences][:default_expire_at] =
|
||||||
|
(ActiveSupport::TimeZone[current_account.timezone] || Time.zone).parse(default_expire_at).utc
|
||||||
|
end
|
||||||
|
|
||||||
attrs[:preferences] = attrs[:preferences].transform_values do |value|
|
attrs[:preferences] = attrs[:preferences].transform_values do |value|
|
||||||
if %w[true false].include?(value)
|
if %w[true false].include?(value)
|
||||||
value == 'true'
|
value == 'true'
|
||||||
|
|||||||
@@ -17,38 +17,47 @@ class TemplatesRecipientsController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def submitters_params
|
def submitters_params
|
||||||
params.require(:template).permit(
|
permit_params = { submitters: [%i[name uuid is_requester optional_invite_by_uuid
|
||||||
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email option]]
|
invite_by_uuid linked_to_uuid email option]] }
|
||||||
).fetch(:submitters, {}).values.filter_map do |s|
|
|
||||||
|
params.require(:template).permit(permit_params).fetch(:submitters, {}).values.filter_map do |s|
|
||||||
next if s[:uuid].blank?
|
next if s[:uuid].blank?
|
||||||
|
|
||||||
if s[:is_requester] == '1' && s[:invite_by_uuid].blank?
|
if s[:is_requester] == '1' && s[:invite_by_uuid].blank? && s[:optional_invite_by_uuid].blank?
|
||||||
s[:is_requester] = true
|
s[:is_requester] = true
|
||||||
else
|
else
|
||||||
s.delete(:is_requester)
|
s.delete(:is_requester)
|
||||||
end
|
end
|
||||||
|
|
||||||
s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank?
|
s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank?
|
||||||
|
s.delete(:optional_invite_by_uuid) if s[:optional_invite_by_uuid].blank?
|
||||||
|
|
||||||
option = s.delete(:option)
|
normalize_option_value(s)
|
||||||
|
|
||||||
if option.present?
|
|
||||||
case option
|
|
||||||
when 'is_requester'
|
|
||||||
s[:is_requester] = true
|
|
||||||
when 'not_set'
|
|
||||||
s.delete(:is_requester)
|
|
||||||
s.delete(:email)
|
|
||||||
s.delete(:linked_to_uuid)
|
|
||||||
s.delete(:invite_by_uuid)
|
|
||||||
when /\Alinked_to_(.*)\z/
|
|
||||||
s[:linked_to_uuid] = ::Regexp.last_match(-1)
|
|
||||||
when /\Ainvite_by_(.*)\z/
|
|
||||||
s[:invite_by_uuid] = ::Regexp.last_match(-1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
s
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def normalize_option_value(attrs)
|
||||||
|
option = attrs.delete(:option)
|
||||||
|
|
||||||
|
if option.present?
|
||||||
|
case option
|
||||||
|
when 'is_requester'
|
||||||
|
attrs[:is_requester] = true
|
||||||
|
when 'not_set'
|
||||||
|
attrs.delete(:is_requester)
|
||||||
|
attrs.delete(:email)
|
||||||
|
attrs.delete(:linked_to_uuid)
|
||||||
|
attrs.delete(:invite_by_uuid)
|
||||||
|
attrs.delete(:optional_invite_by_uuid)
|
||||||
|
when /\Alinked_to_(.*)\z/
|
||||||
|
attrs[:linked_to_uuid] = ::Regexp.last_match(-1)
|
||||||
|
when /\Aoptional_invite_by_(.*)\z/
|
||||||
|
attrs[:optional_invite_by_uuid] = ::Regexp.last_match(-1)
|
||||||
|
when /\Ainvite_by_(.*)\z/
|
||||||
|
attrs[:invite_by_uuid] = ::Regexp.last_match(-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attrs
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -5,7 +5,8 @@ class UserConfigsController < ApplicationController
|
|||||||
authorize_resource :user_config
|
authorize_resource :user_config
|
||||||
|
|
||||||
ALLOWED_KEYS = [
|
ALLOWED_KEYS = [
|
||||||
UserConfig::RECEIVE_COMPLETED_EMAIL
|
UserConfig::RECEIVE_COMPLETED_EMAIL,
|
||||||
|
UserConfig::SHOW_APP_TOUR
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
InvalidKey = Class.new(StandardError)
|
InvalidKey = Class.new(StandardError)
|
||||||
@@ -26,8 +27,9 @@ class UserConfigsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def user_config_params
|
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])
|
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
||||||
|
attrs[:value] = attrs[:value] == 'true' if attrs[:value].in?(%w[true false])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -16,7 +16,7 @@ class UsersController < ApplicationController
|
|||||||
@users.active.where.not(role: 'integration')
|
@users.active.where.not(role: 'integration')
|
||||||
end
|
end
|
||||||
|
|
||||||
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
|
@pagy, @users = pagy(@users.preload(account: :account_accesses).where(account: current_account).order(id: :desc))
|
||||||
end
|
end
|
||||||
|
|
||||||
def new; end
|
def new; end
|
||||||
@@ -45,17 +45,16 @@ class UsersController < ApplicationController
|
|||||||
return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo?
|
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 = 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?
|
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)
|
authorize!(:manage, account)
|
||||||
|
|
||||||
@user.account = account
|
@user.account = account
|
||||||
end
|
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')
|
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
|
||||||
else
|
else
|
||||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
||||||
@@ -84,8 +83,11 @@ class UsersController < ApplicationController
|
|||||||
|
|
||||||
def user_params
|
def user_params
|
||||||
if params.key?(:user)
|
if params.key?(:user)
|
||||||
params.require(:user).permit(:email, :first_name, :last_name, :password,
|
permitted_params = %i[email first_name last_name password archived_at]
|
||||||
:role, :archived_at, :account_id)
|
|
||||||
|
permitted_params << :role if role_valid?(params.dig(:user, :role))
|
||||||
|
|
||||||
|
params.require(:user).permit(permitted_params)
|
||||||
else
|
else
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -31,11 +31,12 @@ import LinkedInput from './elements/linked_input'
|
|||||||
import CheckboxGroup from './elements/checkbox_group'
|
import CheckboxGroup from './elements/checkbox_group'
|
||||||
import MaskedInput from './elements/masked_input'
|
import MaskedInput from './elements/masked_input'
|
||||||
import SetDateButton from './elements/set_date_button'
|
import SetDateButton from './elements/set_date_button'
|
||||||
|
import IndeterminateCheckbox from './elements/indeterminate_checkbox'
|
||||||
|
import AppTour from './elements/app_tour'
|
||||||
|
import DashboardDropzone from './elements/dashboard_dropzone'
|
||||||
|
|
||||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
import './images/preview.png'
|
|
||||||
|
|
||||||
TurboInstantClick.start()
|
TurboInstantClick.start()
|
||||||
|
|
||||||
document.addEventListener('turbo:before-cache', () => {
|
document.addEventListener('turbo:before-cache', () => {
|
||||||
@@ -99,6 +100,9 @@ safeRegisterElement('linked-input', LinkedInput)
|
|||||||
safeRegisterElement('checkbox-group', CheckboxGroup)
|
safeRegisterElement('checkbox-group', CheckboxGroup)
|
||||||
safeRegisterElement('masked-input', MaskedInput)
|
safeRegisterElement('masked-input', MaskedInput)
|
||||||
safeRegisterElement('set-date-button', SetDateButton)
|
safeRegisterElement('set-date-button', SetDateButton)
|
||||||
|
safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
|
||||||
|
safeRegisterElement('app-tour', AppTour)
|
||||||
|
safeRegisterElement('dashboard-dropzone', DashboardDropzone)
|
||||||
|
|
||||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
@@ -123,8 +127,10 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
|||||||
withSendButton: this.dataset.withSendButton !== 'false',
|
withSendButton: this.dataset.withSendButton !== 'false',
|
||||||
withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false',
|
withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false',
|
||||||
withConditions: this.dataset.withConditions === 'true',
|
withConditions: this.dataset.withConditions === 'true',
|
||||||
|
withReplaceAndCloneUpload: true,
|
||||||
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
|
||||||
acceptFileTypes: this.dataset.acceptFileTypes
|
acceptFileTypes: this.dataset.acceptFileTypes,
|
||||||
|
showTourStartForm: this.dataset.showTourStartForm === 'true'
|
||||||
})
|
})
|
||||||
|
|
||||||
this.component = this.app.mount(this.appElem)
|
this.component = this.app.mount(this.appElem)
|
||||||
|
|||||||
+20
-17
@@ -1,5 +1,6 @@
|
|||||||
import SignaturePad from 'signature_pad'
|
import SignaturePad from 'signature_pad'
|
||||||
import { cropCanvasAndExportToPNG } from './submission_form/crop_canvas'
|
import { cropCanvasAndExportToPNG } from './submission_form/crop_canvas'
|
||||||
|
import { isValidSignatureCanvas } from './submission_form/validate_signature'
|
||||||
|
|
||||||
window.customElements.define('draw-signature', class extends HTMLElement {
|
window.customElements.define('draw-signature', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
@@ -43,6 +44,8 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
})
|
})
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.submitButton.disabled = false
|
this.submitButton.disabled = false
|
||||||
})
|
})
|
||||||
@@ -65,26 +68,26 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async submitImage () {
|
async submitImage () {
|
||||||
return new Promise((resolve, reject) => {
|
if (!isValidSignatureCanvas(this.pad.toData())) {
|
||||||
cropCanvasAndExportToPNG(this.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
alert('Signature is too small or simple. Please redraw.')
|
||||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
|
||||||
|
|
||||||
const formData = new FormData()
|
return Promise.reject(new Error('Image too small or simple'))
|
||||||
|
}
|
||||||
|
|
||||||
formData.append('file', file)
|
return cropCanvasAndExportToPNG(this.canvas).then(async (blob) => {
|
||||||
formData.append('submitter_slug', this.dataset.slug)
|
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||||
formData.append('name', 'attachments')
|
|
||||||
formData.append('remember_signature', 'true')
|
|
||||||
|
|
||||||
return fetch('/api/attachments', {
|
const formData = new FormData()
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
formData.append('file', file)
|
||||||
}).then((resp) => resp.json()).then((attachment) => {
|
formData.append('submitter_slug', this.dataset.slug)
|
||||||
return resolve(attachment)
|
formData.append('name', 'attachments')
|
||||||
})
|
formData.append('remember_signature', 'true')
|
||||||
}).catch((error) => {
|
|
||||||
return reject(error)
|
return fetch('/api/attachments', {
|
||||||
})
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(resp => resp.json())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,338 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
async connectedCallback () {
|
||||||
|
this.tourType = this.dataset.type
|
||||||
|
this.nextPagePath = this.dataset.nextPagePath
|
||||||
|
this.I18n = JSON.parse(this.dataset.i18n || '{}')
|
||||||
|
|
||||||
|
if (this.dataset.showTour === 'true') this.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
async start () {
|
||||||
|
if (window.innerWidth < 768) return
|
||||||
|
|
||||||
|
const [{ driver }] = await Promise.all([
|
||||||
|
import('driver.js'),
|
||||||
|
import('driver.js/dist/driver.css')
|
||||||
|
])
|
||||||
|
|
||||||
|
this.driverObj = driver({
|
||||||
|
showProgress: true,
|
||||||
|
nextBtnText: this.I18n.next,
|
||||||
|
prevBtnText: this.I18n.previous,
|
||||||
|
doneBtnText: this.I18n.done,
|
||||||
|
onDestroyStarted: () => {
|
||||||
|
this.disableAppGuide().finally(() => { this.destroy() })
|
||||||
|
},
|
||||||
|
onHighlightStarted: (element) => {
|
||||||
|
if (element) {
|
||||||
|
const clickHandler = () => {
|
||||||
|
this.disableAppGuide().finally(() => { this.destroy() })
|
||||||
|
element.removeEventListener('click', clickHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
element.addEventListener('click', clickHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.tourType === 'dashboard') {
|
||||||
|
this.showDashboardTour()
|
||||||
|
} else if (this.tourType === 'builder') {
|
||||||
|
this.showTemplateBuilderTour()
|
||||||
|
} else if (this.tourType === 'account') {
|
||||||
|
this.showAccountTour()
|
||||||
|
} else if (this.tourType === 'template') {
|
||||||
|
this.showTemplateTour()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
if (this.driverObj) this.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
if (this.builderTemplate) this.builderTemplate.fields.shift()
|
||||||
|
if (this.driverObj) this.driverObj.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
showTemplateTour () {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
element: '#share_link_clipboard',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.copy_and_share_link,
|
||||||
|
description: this.I18n.copy_and_share_link_description,
|
||||||
|
side: 'bottom',
|
||||||
|
align: 'end'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#sign_yourself_button',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.sign_the_document,
|
||||||
|
description: this.I18n.sign_the_document_description,
|
||||||
|
side: 'top',
|
||||||
|
align: 'center'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#send_to_recipients_button',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.send_for_signing,
|
||||||
|
description: this.I18n.add_recipients_description,
|
||||||
|
side: 'top',
|
||||||
|
align: 'center'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#add_recipients_button',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.add_recipients,
|
||||||
|
description: this.I18n.add_recipients_description,
|
||||||
|
side: 'bottom',
|
||||||
|
align: 'end'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#account_settings_button',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.settings,
|
||||||
|
description: this.I18n.settings_template_description,
|
||||||
|
side: 'right',
|
||||||
|
align: 'start',
|
||||||
|
showButtons: this.nextPagePath ? ['next', 'previous', 'close'] : ['previous', 'close'],
|
||||||
|
onNextClick: () => {
|
||||||
|
if (this.nextPagePath) {
|
||||||
|
window.Turbo.visit(this.nextPagePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
].filter((step) => document.querySelector(step.element))
|
||||||
|
|
||||||
|
this.driverObj.setSteps(steps)
|
||||||
|
this.driverObj.drive()
|
||||||
|
}
|
||||||
|
|
||||||
|
showDashboardTour () {
|
||||||
|
this.driverObj.setSteps([
|
||||||
|
{
|
||||||
|
element: '#templates_submissions_toggle',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.template_and_submissions,
|
||||||
|
description: this.I18n.template_and_submissions_description,
|
||||||
|
side: 'right',
|
||||||
|
align: 'start'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#templates_upload_button',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.upload_a_pdf_file,
|
||||||
|
description: this.I18n.upload_a_pdf_file_description,
|
||||||
|
side: 'left',
|
||||||
|
align: 'start',
|
||||||
|
showButtons: this.nextPagePath ? ['next', 'previous', 'close'] : ['previous', 'close'],
|
||||||
|
onNextClick: () => {
|
||||||
|
if (this.nextPagePath) {
|
||||||
|
window.Turbo.visit(this.nextPagePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onHighlightStarted: () => {}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
this.driverObj.drive()
|
||||||
|
}
|
||||||
|
|
||||||
|
showAccountTour () {
|
||||||
|
this.driverObj.setSteps([
|
||||||
|
{
|
||||||
|
element: '#account_settings_menu',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.settings,
|
||||||
|
description: this.I18n.settings_account_description,
|
||||||
|
side: 'right',
|
||||||
|
align: 'start'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#support_channels',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.support,
|
||||||
|
description: this.I18n.support_description,
|
||||||
|
side: 'left',
|
||||||
|
align: 'start'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
].filter((step) => document.querySelector(step.element)))
|
||||||
|
|
||||||
|
this.driverObj.drive()
|
||||||
|
}
|
||||||
|
|
||||||
|
showTemplateBuilderTour () {
|
||||||
|
const builderComponent = document.querySelector('template-builder')?.component
|
||||||
|
|
||||||
|
this.builderTemplate = builderComponent?.template
|
||||||
|
|
||||||
|
if (this.builderTemplate) {
|
||||||
|
this.builderTemplate.fields.unshift({
|
||||||
|
uuid: 'b387399b-88dc-4345-9d37-743e97a9b2b3',
|
||||||
|
submitter_uuid: this.builderTemplate.submitters[0].uuid,
|
||||||
|
name: 'First Name',
|
||||||
|
type: 'text'
|
||||||
|
})
|
||||||
|
|
||||||
|
builderComponent.$nextTick(() => {
|
||||||
|
this.driverObj.setSteps([
|
||||||
|
{
|
||||||
|
element: '.roles-dropdown',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.select_a_signer_party,
|
||||||
|
description: this.I18n.select_a_signer_party_description,
|
||||||
|
side: 'left',
|
||||||
|
align: 'start',
|
||||||
|
onPopoverRender: () => {
|
||||||
|
const rolesDropdown = document.querySelector('.roles-dropdown')
|
||||||
|
|
||||||
|
rolesDropdown.dispatchEvent(new Event('mouseenter', { bubbles: true, cancelable: true }))
|
||||||
|
rolesDropdown.classList.add('dropdown-open')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.roles-dropdown .dropdown-content',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.available_parties,
|
||||||
|
description: this.I18n.available_parties_description,
|
||||||
|
side: 'left',
|
||||||
|
align: 'start',
|
||||||
|
onPopoverRender: () => {
|
||||||
|
document.querySelector('.roles-dropdown .dropdown-content').classList.remove('driver-active-element')
|
||||||
|
},
|
||||||
|
onNextClick: () => {
|
||||||
|
document.querySelector('.roles-dropdown').classList.remove('dropdown-open')
|
||||||
|
this.driverObj.moveNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#field-types-grid',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.available_field_types,
|
||||||
|
description: this.I18n.available_field_types_description,
|
||||||
|
side: 'right',
|
||||||
|
align: 'start',
|
||||||
|
onPrevClick: () => {
|
||||||
|
document.querySelector('.roles-dropdown').classList.add('dropdown-open')
|
||||||
|
this.driverObj.movePrevious()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#text_type_field_button',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.text_input_field,
|
||||||
|
description: this.I18n.text_input_field_description,
|
||||||
|
side: 'left',
|
||||||
|
align: 'start'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#signature_type_field_button',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.signature_field,
|
||||||
|
description: this.I18n.signature_field_description,
|
||||||
|
side: 'left',
|
||||||
|
align: 'start'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.fields',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.added_fields,
|
||||||
|
description: this.I18n.added_fields_description,
|
||||||
|
side: 'right',
|
||||||
|
align: 'start'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.list-field label:has(svg.tabler-icon-settings)',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.open_field_settings,
|
||||||
|
description: this.I18n.open_field_settings_description,
|
||||||
|
side: 'bottom',
|
||||||
|
align: 'end',
|
||||||
|
onPopoverRender: () => {
|
||||||
|
const settingsDropdown = document.querySelector('.list-field div:first-child span:has(svg.tabler-icon-settings)')
|
||||||
|
|
||||||
|
document.querySelectorAll('.list-field div:first-child .text-transparent').forEach((e) => e.classList.remove('text-transparent'))
|
||||||
|
settingsDropdown.dispatchEvent(new Event('mouseenter', { bubbles: true, cancelable: true }))
|
||||||
|
settingsDropdown.classList.add('dropdown-open')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '.list-field div:first-child span:has(svg.tabler-icon-settings) .dropdown-content',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.field_settings,
|
||||||
|
description: this.I18n.field_settings_description,
|
||||||
|
side: 'left',
|
||||||
|
align: 'start',
|
||||||
|
onPopoverRender: () => {
|
||||||
|
document.querySelector('.list-field div:first-child span:has(svg.tabler-icon-settings) .dropdown-content').classList.remove('driver-active-element')
|
||||||
|
},
|
||||||
|
onNextClick: () => {
|
||||||
|
document.querySelector('.list-field div:first-child span:has(svg.tabler-icon-settings)').classList.remove('dropdown-open')
|
||||||
|
this.driverObj.moveNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#send_button',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.send_document,
|
||||||
|
description: this.I18n.send_document_description,
|
||||||
|
side: 'bottom',
|
||||||
|
align: 'end',
|
||||||
|
onPrevClick: () => {
|
||||||
|
document.querySelector('.list-field div:first-child span:has(svg.tabler-icon-settings)').classList.add('dropdown-open')
|
||||||
|
this.driverObj.movePrevious()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: '#sign_yourself_button',
|
||||||
|
popover: {
|
||||||
|
title: this.I18n.sign_yourself,
|
||||||
|
description: this.I18n.sign_yourself_description,
|
||||||
|
side: 'bottom',
|
||||||
|
align: 'end',
|
||||||
|
onNextClick: () => {
|
||||||
|
if (this.nextPagePath) {
|
||||||
|
window.Turbo.visit(this.nextPagePath)
|
||||||
|
} else {
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
this.driverObj.drive()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async disableAppGuide () {
|
||||||
|
return fetch('/user_configs', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ key: 'show_app_tour', value: false })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ export default class extends HTMLElement {
|
|||||||
this.resize()
|
this.resize()
|
||||||
|
|
||||||
this.textarea.addEventListener('input', () => this.resize())
|
this.textarea.addEventListener('input', () => this.resize())
|
||||||
|
|
||||||
|
this.observeVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
resize () {
|
resize () {
|
||||||
@@ -11,6 +13,28 @@ export default class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observeVisibility () {
|
||||||
|
this.observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
this.resize()
|
||||||
|
this.observer.unobserve(this.textarea)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0.1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
this.observer.observe(this.textarea)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
this.observer.unobserve(this.textarea)
|
||||||
|
}
|
||||||
|
|
||||||
get textarea () {
|
get textarea () {
|
||||||
return this.querySelector('textarea')
|
return this.querySelector('textarea')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
import { target, targets, targetable } from '@github/catalyst/lib/targetable'
|
||||||
|
|
||||||
|
const loadingIconHtml = `<svg xmlns="http://www.w3.org/2000/svg" class="animate-spin" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M12 3a9 9 0 1 0 9 9" />
|
||||||
|
</svg>`
|
||||||
|
|
||||||
|
export default targetable(class extends HTMLElement {
|
||||||
|
static [targets.static] = [
|
||||||
|
'hiddenOnDrag',
|
||||||
|
'folderCards',
|
||||||
|
'templateCards'
|
||||||
|
]
|
||||||
|
|
||||||
|
static [target.static] = [
|
||||||
|
'form',
|
||||||
|
'fileDropzone',
|
||||||
|
'fileDropzoneLoading'
|
||||||
|
]
|
||||||
|
|
||||||
|
connectedCallback () {
|
||||||
|
document.addEventListener('drop', this.onWindowDragdrop)
|
||||||
|
document.addEventListener('dragover', this.onWindowDropover)
|
||||||
|
|
||||||
|
window.addEventListener('dragleave', this.onWindowDragleave)
|
||||||
|
|
||||||
|
this.fileDropzone?.addEventListener('drop', this.onDropFile)
|
||||||
|
|
||||||
|
this.folderCards.forEach((el) => el.addEventListener('drop', (e) => this.onDropFolder(e, el)))
|
||||||
|
this.templateCards.forEach((el) => el.addEventListener('drop', this.onDropTemplate))
|
||||||
|
this.templateCards.forEach((el) => el.addEventListener('dragstart', this.onTemplateDragStart))
|
||||||
|
|
||||||
|
return [this.fileDropzone, ...this.folderCards, ...this.templateCards].forEach((el) => {
|
||||||
|
el?.addEventListener('dragover', this.onDragover)
|
||||||
|
el?.addEventListener('dragleave', this.onDragleave)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
document.removeEventListener('drop', this.onWindowDragdrop)
|
||||||
|
document.removeEventListener('dragover', this.onWindowDropover)
|
||||||
|
|
||||||
|
window.removeEventListener('dragleave', this.onWindowDragleave)
|
||||||
|
}
|
||||||
|
|
||||||
|
onTemplateDragStart = (e) => {
|
||||||
|
const id = e.target.href.split('/').pop()
|
||||||
|
|
||||||
|
e.dataTransfer.effectAllowed = 'move'
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
e.dataTransfer.setData('template_id', id)
|
||||||
|
|
||||||
|
const dragPreview = e.target.cloneNode(true)
|
||||||
|
const rect = e.target.getBoundingClientRect()
|
||||||
|
|
||||||
|
const height = e.target.children[0].getBoundingClientRect().height + 50
|
||||||
|
|
||||||
|
dragPreview.children[1].remove()
|
||||||
|
dragPreview.style.width = `${rect.width}px`
|
||||||
|
dragPreview.style.height = `${height}px`
|
||||||
|
dragPreview.style.position = 'absolute'
|
||||||
|
dragPreview.style.top = '-1000px'
|
||||||
|
dragPreview.style.pointerEvents = 'none'
|
||||||
|
dragPreview.style.opacity = '0.9'
|
||||||
|
|
||||||
|
document.body.appendChild(dragPreview)
|
||||||
|
|
||||||
|
e.dataTransfer.setDragImage(dragPreview, rect.width / 2, height / 2)
|
||||||
|
|
||||||
|
setTimeout(() => document.body.removeChild(dragPreview), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropFile = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
this.fileDropzoneLoading.classList.remove('hidden')
|
||||||
|
this.fileDropzoneLoading.previousElementSibling.classList.add('hidden')
|
||||||
|
this.fileDropzoneLoading.classList.add('opacity-50')
|
||||||
|
|
||||||
|
this.uploadFiles(e.dataTransfer.files, '/templates_upload')
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropFolder = (e, el) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const templateId = e.dataTransfer.getData('template_id')
|
||||||
|
|
||||||
|
if (e.dataTransfer.files.length || templateId) {
|
||||||
|
const loading = document.createElement('div')
|
||||||
|
const svg = el.querySelector('svg')
|
||||||
|
|
||||||
|
loading.innerHTML = loadingIconHtml
|
||||||
|
loading.children[0].classList.add(...svg.classList)
|
||||||
|
|
||||||
|
el.replaceChild(loading.children[0], svg)
|
||||||
|
el.classList.add('opacity-50')
|
||||||
|
|
||||||
|
if (e.dataTransfer.files.length) {
|
||||||
|
const params = new URLSearchParams({ folder_name: el.innerText }).toString()
|
||||||
|
|
||||||
|
this.uploadFiles(e.dataTransfer.files, `/templates_upload?${params}`)
|
||||||
|
} else {
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
formData.append('name', el.innerText)
|
||||||
|
|
||||||
|
fetch(`/templates/${templateId}/folder`, {
|
||||||
|
method: 'PUT',
|
||||||
|
redirect: 'manual',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
window.Turbo.cache.clear()
|
||||||
|
window.Turbo.visit(location.href)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropTemplate = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (e.dataTransfer.files.length) {
|
||||||
|
const loading = document.createElement('div')
|
||||||
|
loading.classList.add('bottom-5', 'left-0', 'flex', 'justify-center', 'w-full', 'absolute')
|
||||||
|
loading.innerHTML = loadingIconHtml
|
||||||
|
|
||||||
|
e.target.appendChild(loading)
|
||||||
|
e.target.classList.add('opacity-50')
|
||||||
|
|
||||||
|
const id = e.target.href.split('/').pop()
|
||||||
|
|
||||||
|
this.uploadFiles(e.dataTransfer.files, `/templates/${id}/clone_and_replace`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowDragdrop = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (!this.isLoading) this.hideDraghover()
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadFiles (files, url) {
|
||||||
|
this.isLoading = true
|
||||||
|
|
||||||
|
this.form.action = url
|
||||||
|
|
||||||
|
this.form.querySelector('[type="file"]').files = files
|
||||||
|
|
||||||
|
this.form.querySelector('[type="submit"]').click()
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowDropover = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (e.dataTransfer?.types?.includes('Files')) {
|
||||||
|
this.showDraghover()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragover (e) {
|
||||||
|
if (e.dataTransfer?.types?.includes('Files') || this.dataset.targets !== 'dashboard-dropzone.templateCards') {
|
||||||
|
this.style.backgroundColor = '#F7F3F0'
|
||||||
|
|
||||||
|
if (this.classList.contains('before:border-base-300')) {
|
||||||
|
this.classList.remove('before:border-base-300')
|
||||||
|
this.classList.add('before:border-base-content/30')
|
||||||
|
} else if (this.classList.contains('border-base-300')) {
|
||||||
|
this.classList.remove('border-base-300')
|
||||||
|
this.classList.add('border-base-content/30')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragleave () {
|
||||||
|
this.style.backgroundColor = null
|
||||||
|
|
||||||
|
if (this.classList.contains('before:border-base-content/30')) {
|
||||||
|
this.classList.remove('before:border-base-content/30')
|
||||||
|
this.classList.add('before:border-base-300')
|
||||||
|
} else if (this.classList.contains('border-base-content/30')) {
|
||||||
|
this.classList.remove('border-base-content/30')
|
||||||
|
this.classList.add('border-base-300')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowDragleave = (e) => {
|
||||||
|
if (e.clientX <= 0 || e.clientY <= 0 || e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) {
|
||||||
|
this.hideDraghover()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showDraghover = () => {
|
||||||
|
if (this.isDrag) return
|
||||||
|
|
||||||
|
this.isDrag = true
|
||||||
|
|
||||||
|
this.fileDropzone?.classList?.remove('hidden')
|
||||||
|
|
||||||
|
this.hiddenOnDrag.forEach((el) => { el.style.display = 'none' })
|
||||||
|
|
||||||
|
return [...this.folderCards, ...this.templateCards].forEach((el) => {
|
||||||
|
el.classList.remove('bg-base-200', 'before:hidden')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
hideDraghover = () => {
|
||||||
|
this.isDrag = false
|
||||||
|
|
||||||
|
this.fileDropzone?.classList?.add('hidden')
|
||||||
|
|
||||||
|
this.hiddenOnDrag.forEach((el) => { el.style.display = null })
|
||||||
|
|
||||||
|
return [...this.folderCards, ...this.templateCards].forEach((el) => {
|
||||||
|
el.classList.add('bg-base-200', 'before:hidden')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -20,7 +20,8 @@ export default targetable(class extends HTMLElement {
|
|||||||
fetch(this.dataset.src).then(async (response) => {
|
fetch(this.dataset.src).then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const urls = await response.json()
|
const urls = await response.json()
|
||||||
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||||
|
const isSafariIos = isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||||
|
|
||||||
if (isSafariIos && urls.length > 1) {
|
if (isSafariIos && urls.length > 1) {
|
||||||
this.downloadSafariIos(urls)
|
this.downloadSafariIos(urls)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.form.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
this.submit()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.dataset.onload === 'true') {
|
||||||
|
this.form.querySelector('button').click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit () {
|
||||||
|
fetch(this.form.action, {
|
||||||
|
method: this.form.method,
|
||||||
|
body: new FormData(this.form)
|
||||||
|
}).then(async (resp) => {
|
||||||
|
if (!resp.ok) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(await resp.text())
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
alert(data.error)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get form () {
|
||||||
|
return this.querySelector('form')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,19 +5,37 @@ export default actionable(targetable(class extends HTMLElement {
|
|||||||
static [target.static] = [
|
static [target.static] = [
|
||||||
'loading',
|
'loading',
|
||||||
'icon',
|
'icon',
|
||||||
'input'
|
'input',
|
||||||
|
'area'
|
||||||
]
|
]
|
||||||
|
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
this.addEventListener('drop', this.onDrop)
|
|
||||||
|
|
||||||
this.addEventListener('dragover', (e) => e.preventDefault())
|
this.addEventListener('dragover', (e) => e.preventDefault())
|
||||||
|
this.addEventListener('drop', this.onDrop)
|
||||||
document.addEventListener('turbo:submit-end', this.toggleLoading)
|
document.addEventListener('turbo:submit-end', this.toggleLoading)
|
||||||
|
this.area?.addEventListener('dragover', this.onDragover)
|
||||||
|
this.area?.addEventListener('dragleave', this.onDragleave)
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback () {
|
disconnectedCallback () {
|
||||||
|
this.removeEventListener('drop', this.onDrop)
|
||||||
document.removeEventListener('turbo:submit-end', this.toggleLoading)
|
document.removeEventListener('turbo:submit-end', this.toggleLoading)
|
||||||
|
this.area?.removeEventListener('dragover', this.onDragover)
|
||||||
|
this.area?.removeEventListener('dragleave', this.onDragleave)
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragover (e) {
|
||||||
|
if (e.dataTransfer?.types?.includes('Files')) {
|
||||||
|
this.style.backgroundColor = '#F7F3F0'
|
||||||
|
this.classList.remove('border-base-300', 'hover:bg-base-200/30')
|
||||||
|
this.classList.add('border-base-content/30')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragleave () {
|
||||||
|
this.style.backgroundColor = null
|
||||||
|
this.classList.remove('border-base-content/30')
|
||||||
|
this.classList.add('border-base-300', 'hover:bg-base-200/30')
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrop (e) {
|
onDrop (e) {
|
||||||
@@ -35,7 +53,7 @@ export default actionable(targetable(class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleLoading = (e) => {
|
toggleLoading = (e) => {
|
||||||
if (e && e.target && !e.target.contains(this)) {
|
if (e && e.target && (!e.target.contains(this) || !e.detail?.formSubmission?.formElement?.contains(this))) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
if (this.dataset.indeterminate === 'true') {
|
||||||
|
this.checkbox.indeterminate = true
|
||||||
|
this.checkbox.readOnly = true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkbox.addEventListener('click', () => {
|
||||||
|
this.checkbox.setAttribute('name', this.dataset.name)
|
||||||
|
|
||||||
|
if (this.showIndeterminateEl) {
|
||||||
|
this.showIndeterminateEl.classList.add('hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.checkbox.readOnly) {
|
||||||
|
this.checkbox.checked = this.checkbox.readOnly = false
|
||||||
|
} else if (!this.checkbox.checked) {
|
||||||
|
if (this.showIndeterminateEl) {
|
||||||
|
this.showIndeterminateEl.classList.remove('hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkbox.setAttribute('name', this.dataset.indeterminateName)
|
||||||
|
this.checkbox.checked = this.checkbox.readOnly = this.checkbox.indeterminate = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get checkbox () {
|
||||||
|
return this.querySelector('input[type="checkbox"]')
|
||||||
|
}
|
||||||
|
|
||||||
|
get showIndeterminateEl () {
|
||||||
|
return document.getElementById(this.dataset.showIndeterminateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.header = document.querySelector('#signing_form_header')
|
||||||
|
|
||||||
|
window.addEventListener('scroll', this.onScroll)
|
||||||
|
window.addEventListener('resize', this.onResize)
|
||||||
|
|
||||||
|
if (!this.isNarrow() && this.isHeaderNotVisible()) {
|
||||||
|
this.showButtons({ animate: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
window.removeEventListener('scroll', this.onScroll)
|
||||||
|
window.removeEventListener('resize', this.onResize)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResize = () => {
|
||||||
|
if (this.isNarrow()) {
|
||||||
|
this.hideButtons(true)
|
||||||
|
} else if (this.isHeaderNotVisible()) {
|
||||||
|
this.showButtons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isNarrow () {
|
||||||
|
return window.innerWidth < 1230
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll = () => {
|
||||||
|
if (this.isHeaderNotVisible() && !this.isNarrow()) {
|
||||||
|
this.showButtons()
|
||||||
|
} else {
|
||||||
|
this.hideButtons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isHeaderNotVisible () {
|
||||||
|
const rect = this.header.getBoundingClientRect()
|
||||||
|
return rect.bottom <= 0 || rect.top >= window.innerHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
showButtons ({ animate } = { animate: true }) {
|
||||||
|
if (animate) {
|
||||||
|
this.classList.add('transition-transform', 'duration-300')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.classList.remove('hidden', '-translate-y-10', 'opacity-0')
|
||||||
|
this.classList.add('translate-y-0', 'opacity-100')
|
||||||
|
}
|
||||||
|
|
||||||
|
hideButtons () {
|
||||||
|
this.classList.remove('translate-y-0', 'opacity-100')
|
||||||
|
this.classList.add('-translate-y-10', 'opacity-0')
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.classList.contains('-translate-y-10')) {
|
||||||
|
this.classList.add('hidden')
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ export default actionable(class extends HTMLElement {
|
|||||||
const elementIds = JSON.parse(this.dataset.elementIds)
|
const elementIds = JSON.parse(this.dataset.elementIds)
|
||||||
|
|
||||||
elementIds.forEach((elementId) => {
|
elementIds.forEach((elementId) => {
|
||||||
document.getElementById(elementId).classList.toggle('hidden', event.target.value !== elementId)
|
document.getElementById(elementId).classList.toggle('hidden', (event.target.dataset.toggleId || event.target.value) !== elementId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -3,11 +3,15 @@ import { createApp, reactive } from 'vue'
|
|||||||
import Form from './submission_form/form'
|
import Form from './submission_form/form'
|
||||||
import DownloadButton from './elements/download_button'
|
import DownloadButton from './elements/download_button'
|
||||||
import ToggleSubmit from './elements/toggle_submit'
|
import ToggleSubmit from './elements/toggle_submit'
|
||||||
|
import FetchForm from './elements/fetch_form'
|
||||||
|
import ScrollButtons from './elements/scroll_buttons'
|
||||||
|
|
||||||
const safeRegisterElement = (name, element, options = {}) => !window.customElements.get(name) && window.customElements.define(name, element, options)
|
const safeRegisterElement = (name, element, options = {}) => !window.customElements.get(name) && window.customElements.define(name, element, options)
|
||||||
|
|
||||||
safeRegisterElement('download-button', DownloadButton)
|
safeRegisterElement('download-button', DownloadButton)
|
||||||
safeRegisterElement('toggle-submit', ToggleSubmit)
|
safeRegisterElement('toggle-submit', ToggleSubmit)
|
||||||
|
safeRegisterElement('fetch-form', FetchForm)
|
||||||
|
safeRegisterElement('scroll-buttons', ScrollButtons)
|
||||||
safeRegisterElement('submission-form', class extends HTMLElement {
|
safeRegisterElement('submission-form', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
this.appElem = document.createElement('div')
|
this.appElem = document.createElement('div')
|
||||||
@@ -15,6 +19,7 @@ safeRegisterElement('submission-form', class extends HTMLElement {
|
|||||||
this.app = createApp(Form, {
|
this.app = createApp(Form, {
|
||||||
submitter: JSON.parse(this.dataset.submitter),
|
submitter: JSON.parse(this.dataset.submitter),
|
||||||
inviteSubmitters: JSON.parse(this.dataset.inviteSubmitters),
|
inviteSubmitters: JSON.parse(this.dataset.inviteSubmitters),
|
||||||
|
optionalInviteSubmitters: JSON.parse(this.dataset.optionalInviteSubmitters),
|
||||||
schema: JSON.parse(this.dataset.schema),
|
schema: JSON.parse(this.dataset.schema),
|
||||||
canSendEmail: this.dataset.canSendEmail === 'true',
|
canSendEmail: this.dataset.canSendEmail === 'true',
|
||||||
previousSignatureValue: this.dataset.previousSignatureValue,
|
previousSignatureValue: this.dataset.previousSignatureValue,
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="field-area flex absolute lg:text-base -outline-offset-1"
|
class="flex absolute lg:text-base -outline-offset-1 field-area"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:style="computedStyle"
|
:style="computedStyle"
|
||||||
:class="{ '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, 'outline-red-500 outline-dashed outline-2 z-10 field-area-active': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||||
@@ -55,10 +55,12 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'signature' && signature"
|
v-else-if="field.type === 'signature' && signature"
|
||||||
class="flex flex-col justify-between h-full overflow-hidden"
|
class="flex justify-between h-full gap-1 overflow-hidden"
|
||||||
|
:class="isNarrow ? 'flex-row' : 'flex-col'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex-grow flex overflow-hidden"
|
class="flex overflow-hidden"
|
||||||
|
:class="isNarrow ? 'w-1/2' : 'flex-grow'"
|
||||||
style="min-height: 50%"
|
style="min-height: 50%"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -68,7 +70,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="withSignatureId"
|
v-if="withSignatureId"
|
||||||
class="w-full mt-1 text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem]"
|
class="text-[1vw] lg:text-[0.55rem] lg:leading-[0.65rem]"
|
||||||
|
:class="isNarrow ? 'w-1/2' : 'w-full'"
|
||||||
>
|
>
|
||||||
<div class="truncate uppercase">
|
<div class="truncate uppercase">
|
||||||
ID: {{ signature.uuid }}
|
ID: {{ signature.uuid }}
|
||||||
@@ -168,7 +171,7 @@
|
|||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'cells'"
|
v-else-if="field.type === 'cells'"
|
||||||
class="w-full flex items-center"
|
class="w-full flex items-center"
|
||||||
:class="{ 'justify-end': field.preferences?.align === 'right' }"
|
:class="{ 'justify-end': field.preferences?.align === 'right', ...fontClasses }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(char, index) in modelValue"
|
v-for="(char, index) in modelValue"
|
||||||
@@ -183,21 +186,30 @@
|
|||||||
v-else
|
v-else
|
||||||
ref="textContainer"
|
ref="textContainer"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="flex items-center px-0.5 w-full"
|
class="flex px-0.5 w-full"
|
||||||
:class="alignClasses[field.preferences?.align]"
|
:class="{ ...alignClasses, ...fontClasses }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="field && field.name && withFieldPlaceholder && !modelValue && modelValue !== 0"
|
v-if="field && field.name && withFieldPlaceholder && !modelValue && modelValue !== 0"
|
||||||
class="whitespace-pre-wrap text-gray-400"
|
class="whitespace-pre-wrap text-gray-400"
|
||||||
:class="{ 'w-full': field.preferences?.align }"
|
:class="{ 'w-full': field.preferences?.align }"
|
||||||
>{{ field.name }}</span>
|
>{{ field.name }}</span>
|
||||||
<span v-else-if="Array.isArray(modelValue)">
|
<span
|
||||||
|
v-else-if="Array.isArray(modelValue)"
|
||||||
|
:class="{ 'w-full': field.preferences?.align }"
|
||||||
|
>
|
||||||
{{ modelValue.join(', ') }}
|
{{ modelValue.join(', ') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="field.type === 'date'">
|
<span
|
||||||
|
v-else-if="field.type === 'date'"
|
||||||
|
:class="{ 'w-full': field.preferences?.align }"
|
||||||
|
>
|
||||||
{{ formattedDate }}
|
{{ formattedDate }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="field.type === 'number'">
|
<span
|
||||||
|
v-else-if="field.type === 'number'"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
{{ formatNumber(modelValue, field.preferences?.format) }}
|
{{ formatNumber(modelValue, field.preferences?.format) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
@@ -317,10 +329,29 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
alignClasses () {
|
alignClasses () {
|
||||||
|
if (!this.field.preferences) {
|
||||||
|
return { 'items-center': true }
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
center: 'text-center',
|
'text-center': this.field.preferences.align === 'center',
|
||||||
left: 'text-left',
|
'text-left': this.field.preferences.align === 'left',
|
||||||
right: 'text-right'
|
'text-right': this.field.preferences.align === 'right',
|
||||||
|
'items-center': !this.field.preferences.valign || this.field.preferences.valign === 'center',
|
||||||
|
'items-start': this.field.preferences.valign === 'top',
|
||||||
|
'items-end': this.field.preferences.valign === 'bottom'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fontClasses () {
|
||||||
|
if (!this.field.preferences) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'font-mono': this.field.preferences.font === 'Courier',
|
||||||
|
'font-serif': this.field.preferences.font === 'Times',
|
||||||
|
'font-bold': ['bold_italic', 'bold'].includes(this.field.preferences.font_type),
|
||||||
|
italic: ['bold_italic', 'italic'].includes(this.field.preferences.font_type)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
option () {
|
option () {
|
||||||
@@ -416,6 +447,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
},
|
||||||
|
isNarrow () {
|
||||||
|
return this.area.h > 0 && (this.area.w / this.area.h) > 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -447,8 +481,18 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
formatNumber (number, format) {
|
formatNumber (number, format) {
|
||||||
|
if (!number && number !== 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
if (format === 'comma') {
|
if (format === 'comma') {
|
||||||
return new Intl.NumberFormat('en-US').format(number)
|
return new Intl.NumberFormat('en-US').format(number)
|
||||||
|
} else if (format === 'usd') {
|
||||||
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||||
|
} else if (format === 'gbp') {
|
||||||
|
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||||
|
} else if (format === 'eur') {
|
||||||
|
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||||
} else if (format === 'dot') {
|
} else if (format === 'dot') {
|
||||||
return new Intl.NumberFormat('de-DE').format(number)
|
return new Intl.NumberFormat('de-DE').format(number)
|
||||||
} else if (format === 'space') {
|
} else if (format === 'space') {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
:with-label="withLabel && !withFieldPlaceholder && step.length < 2"
|
:with-label="withLabel && !withFieldPlaceholder && step.length < 2"
|
||||||
:is-value-set="step.some((f) => f.uuid in values)"
|
:is-value-set="step.some((f) => f.uuid in values)"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
@click="$emit('focus-step', stepIndex)"
|
@click="[$emit('focus-step', stepIndex), maybeScrollOnClick(field, area)]"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
@@ -109,6 +109,14 @@ export default {
|
|||||||
areaRefs: []
|
areaRefs: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
isMobileContainer () {
|
||||||
|
const root = this.$root.$el.parentNode.getRootNode()
|
||||||
|
const container = root.body || root.querySelector('div')
|
||||||
|
|
||||||
|
return container.style.overflow === 'hidden'
|
||||||
|
}
|
||||||
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
this.areaRefs = []
|
this.areaRefs = []
|
||||||
},
|
},
|
||||||
@@ -121,14 +129,16 @@ export default {
|
|||||||
this.scrollIntoArea(field.areas[0])
|
this.scrollIntoArea(field.areas[0])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
maybeScrollOnClick (field, area) {
|
||||||
|
if (['text', 'number', 'cells'].includes(field.type) && this.isMobileContainer) {
|
||||||
|
this.scrollIntoArea(area)
|
||||||
|
}
|
||||||
|
},
|
||||||
scrollIntoArea (area) {
|
scrollIntoArea (area) {
|
||||||
const areaRef = this.areaRefs.find((a) => a.area === area)
|
const areaRef = this.areaRefs.find((a) => a.area === area)
|
||||||
|
|
||||||
if (areaRef) {
|
if (areaRef) {
|
||||||
const root = this.$root.$el.parentNode.getRootNode()
|
if (this.isMobileContainer) {
|
||||||
const container = root.body || root.querySelector('div')
|
|
||||||
|
|
||||||
if (container.style.overflow === 'hidden') {
|
|
||||||
this.scrollInContainer(areaRef.$el)
|
this.scrollInContainer(areaRef.$el)
|
||||||
} else {
|
} else {
|
||||||
const targetRect = areaRef.$refs.scrollToElem.getBoundingClientRect()
|
const targetRect = areaRef.$refs.scrollToElem.getBoundingClientRect()
|
||||||
@@ -157,13 +167,15 @@ export default {
|
|||||||
const formContainer = root.getElementById('form_container')
|
const formContainer = root.getElementById('form_container')
|
||||||
const container = root.body || root.querySelector('div')
|
const container = root.body || root.querySelector('div')
|
||||||
|
|
||||||
const padding = 64
|
const isAndroid = /android/i.test(navigator.userAgent)
|
||||||
|
const padding = isAndroid ? 128 : 64
|
||||||
|
const scrollboxTop = isAndroid ? scrollbox.getBoundingClientRect().top : 0
|
||||||
const boxRect = scrollbox.children[0].getBoundingClientRect()
|
const boxRect = scrollbox.children[0].getBoundingClientRect()
|
||||||
const targetRect = target.getBoundingClientRect()
|
const targetRect = target.getBoundingClientRect()
|
||||||
|
|
||||||
const targetTopRelativeToBox = targetRect.top - boxRect.top
|
const targetTopRelativeToBox = targetRect.top - boxRect.top
|
||||||
|
|
||||||
scrollbox.scrollTo({ top: targetTopRelativeToBox - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding, behavior: 'smooth' })
|
scrollbox.scrollTo({ top: targetTopRelativeToBox + scrollboxTop - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding, behavior: 'smooth' })
|
||||||
},
|
},
|
||||||
setAreaRef (el) {
|
setAreaRef (el) {
|
||||||
if (el) {
|
if (el) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
v-if="val"
|
v-if="val"
|
||||||
class="flex items-center space-x-1.5 w-full"
|
class="flex items-center space-x-1.5 w-full attachment-file-name"
|
||||||
:href="attachmentsIndex[val].url"
|
:href="attachmentsIndex[val].url"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
@@ -26,7 +26,10 @@
|
|||||||
{{ attachmentsIndex[val].filename }}
|
{{ attachmentsIndex[val].filename }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<button @click.prevent="removeAttachment(val)">
|
<button
|
||||||
|
class="remove-attachment-button"
|
||||||
|
@click.prevent="removeAttachment(val)"
|
||||||
|
>
|
||||||
<IconTrashX
|
<IconTrashX
|
||||||
:width="18"
|
:width="18"
|
||||||
:heigh="19"
|
:heigh="19"
|
||||||
@@ -44,12 +47,12 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description && !modelValue.length"
|
v-if="field.description && !modelValue.length"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
<FileDropzone
|
<FileDropzone
|
||||||
:message="`${t('upload')} ${field.name || t('files')}${field.required ? '' : ` (${t('optional')})`}`"
|
:message="`${t('upload')} ${(field.title || field.name) || t('files')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:dry-run="dryRun"
|
:dry-run="dryRun"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="form_completed"
|
id="form_completed"
|
||||||
class="mx-auto max-w-md flex flex-col"
|
class="mx-auto max-w-md flex flex-col completed-form"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
<div class="font-medium text-2xl flex items-center space-x-1.5 mx-auto">
|
<div class="font-medium text-2xl flex items-center space-x-1.5 mx-auto">
|
||||||
@@ -10,13 +10,13 @@
|
|||||||
:width="30"
|
:width="30"
|
||||||
:height="30"
|
:height="30"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span class="completed-form-message-title">
|
||||||
{{ completedMessage.title || (hasSignatureFields ? (hasMultipleDocuments ? t('documents_have_been_signed') : t('document_has_been_signed')) : t('form_has_been_completed')) }}
|
{{ completedMessage.title || (hasSignatureFields ? (hasMultipleDocuments ? t('documents_have_been_signed') : t('document_has_been_signed')) : t('form_has_been_completed')) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="completedMessage.body"
|
v-if="completedMessage.body"
|
||||||
class="mt-2"
|
class="mt-2 completed-form-message-body"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
:string="completedMessage.body"
|
:string="completedMessage.body"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
v-if="completedButton.url"
|
v-if="completedButton.url"
|
||||||
:href="sanitizeHref(completedButton.url)"
|
:href="sanitizeHref(completedButton.url)"
|
||||||
rel="noopener noreferrer nofollow"
|
rel="noopener noreferrer nofollow"
|
||||||
class="white-button flex items-center w-full"
|
class="white-button flex items-center w-full completed-form-completed-button"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{{ completedButton.title || 'Back to Website' }}
|
{{ completedButton.title || 'Back to Website' }}
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
v-if="canSendEmail && !isDemo && withSendCopyButton"
|
v-if="canSendEmail && !isDemo && withSendCopyButton"
|
||||||
class="white-button !h-auto flex items-center space-x-1 w-full"
|
class="white-button !h-auto flex items-center space-x-1 w-full completed-form-send-copy-button"
|
||||||
:disabled="isSendingCopy"
|
:disabled="isSendingCopy"
|
||||||
@click.prevent="sendCopyToEmail"
|
@click.prevent="sendCopyToEmail"
|
||||||
>
|
>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!isWebView && withDownloadButton"
|
v-if="!isWebView && withDownloadButton"
|
||||||
class="base-button flex items-center space-x-1 w-full"
|
class="base-button flex items-center space-x-1 w-full completed-form-download-button"
|
||||||
:disabled="isDownloading"
|
:disabled="isDownloading"
|
||||||
@click.prevent="download"
|
@click.prevent="download"
|
||||||
>
|
>
|
||||||
@@ -193,9 +193,9 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.decline_button) {
|
document.querySelectorAll('#decline_button').forEach((button) => {
|
||||||
window.decline_button.setAttribute('disabled', 'true')
|
button.setAttribute('disabled', 'true')
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sendCopyToEmail () {
|
sendCopyToEmail () {
|
||||||
@@ -215,7 +215,8 @@ export default {
|
|||||||
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then(async (response) => {
|
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const urls = await response.json()
|
const urls = await response.json()
|
||||||
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||||
|
const isSafariIos = isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||||
|
|
||||||
if (isSafariIos && urls.length > 1) {
|
if (isSafariIos && urls.length > 1) {
|
||||||
this.downloadSafariIos(urls)
|
this.downloadSafariIos(urls)
|
||||||
|
|||||||
@@ -1,26 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div dir="auto">
|
<div dir="auto">
|
||||||
<div
|
<div
|
||||||
class="flex justify-between items-center w-full"
|
class="flex justify-between items-end w-full mb-3.5 sm:mb-4"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
v-if="showFieldNames"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
class="label text-2xl"
|
class="label text-xl sm:text-2xl py-0 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
:string="field.title"
|
:string="field.title"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ field.name && showFieldNames ? field.name : t('date') }}
|
{{ field.name || t('date') }}
|
||||||
<template v-if="!field.required">
|
</template>
|
||||||
|
<template v-if="!field.required">
|
||||||
|
<span
|
||||||
|
class="ml-1"
|
||||||
|
:class="{ 'hidden sm:inline': (field.title || field.name || t('date') ).length > 10 }"
|
||||||
|
>
|
||||||
({{ t('optional') }})
|
({{ t('optional') }})
|
||||||
</template>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline btn-sm !normal-case font-normal"
|
class="btn btn-outline btn-sm !normal-case font-normal set-current-date-button"
|
||||||
@click.prevent="[setCurrentDate(), $emit('focus')]"
|
@click.prevent="[setCurrentDate(), $emit('focus')]"
|
||||||
>
|
>
|
||||||
<IconCalendarCheck :width="16" />
|
<IconCalendarCheck :width="16" />
|
||||||
@@ -29,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
class="w-full relative bg-base-300 hover:bg-base-200 rounded-md border border-base-content border-dashed"
|
class="w-full relative bg-base-300 hover:bg-base-200 rounded-md border border-base-content border-dashed file-dropzone"
|
||||||
:class="{ 'opacity-50': isLoading }"
|
:class="{ 'opacity-50': isLoading }"
|
||||||
>
|
>
|
||||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
||||||
@@ -128,7 +128,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (file.type === 'image/bmp') {
|
if (file.type === 'image/bmp' || file.type === 'image/vnd.microsoft.icon') {
|
||||||
file = await this.convertBmpToPng(file)
|
file = await this.convertBmpToPng(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
:with-label="!isAnonymousChecboxes && showFieldNames"
|
:with-label="!isAnonymousChecboxes && showFieldNames"
|
||||||
:current-step="currentStepFields"
|
:current-step="currentStepFields"
|
||||||
:scroll-padding="scrollPadding"
|
:scroll-padding="scrollPadding"
|
||||||
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
@focus-step="[saveStep(), currentField.type !== 'checkbox' ? isFormVisible = true : '', goToStep($event, false, true)]"
|
||||||
/>
|
/>
|
||||||
<FieldAreas
|
<FieldAreas
|
||||||
:steps="readonlyConditionalFields.map((e) => [e])"
|
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||||
:values="readonlyConditionalFields.reduce((acc, f) => { acc[f.uuid] = (values[f.uuid] || f.default_value); return acc }, {})"
|
:values="readonlyConditionalFieldValues"
|
||||||
:submitter="submitter"
|
:submitter="submitter"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:submittable="false"
|
:submittable="false"
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
<FormulaFieldAreas
|
<FormulaFieldAreas
|
||||||
v-if="formulaFields.length"
|
v-if="formulaFields.length"
|
||||||
:fields="formulaFields"
|
:fields="formulaFields"
|
||||||
|
:readonly-values="readonlyConditionalFieldValues"
|
||||||
:values="values"
|
:values="values"
|
||||||
/>
|
/>
|
||||||
<Teleport
|
<Teleport
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="!isFormVisible"
|
v-if="!isFormVisible"
|
||||||
id="expand_form_button"
|
id="expand_form_button"
|
||||||
class="btn btn-neutral flex text-white absolute bottom-0 w-full mb-3"
|
class="btn btn-neutral flex text-white absolute bottom-0 w-full mb-3 expand-form-button"
|
||||||
style="width: 96%; margin-left: 2%"
|
style="width: 96%; margin-left: 2%"
|
||||||
@click.prevent="[isFormVisible = true, scrollIntoField(currentField)]"
|
@click.prevent="[isFormVisible = true, scrollIntoField(currentField)]"
|
||||||
>
|
>
|
||||||
@@ -92,14 +93,15 @@
|
|||||||
<div
|
<div
|
||||||
v-show="isFormVisible"
|
v-show="isFormVisible"
|
||||||
id="form_container"
|
id="form_container"
|
||||||
class="shadow-md bg-base-100 absolute bottom-0 w-full border-base-200 border p-4 rounded"
|
class="shadow-md bg-base-100 absolute bottom-0 w-full border-base-200 border p-4 rounded form-container overflow-hidden"
|
||||||
:class="{ 'md:bottom-4': isBreakpointMd }"
|
:class="{ 'md:bottom-4': isBreakpointMd }"
|
||||||
:style="{ backgroundColor: backgroundColor }"
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="!isCompleted"
|
v-if="!isCompleted"
|
||||||
id="minimize_form_button"
|
id="minimize_form_button"
|
||||||
class="absolute right-0 mr-2 mt-2 top-0 hidden md:block"
|
class="absolute right-0 top-0 minimize-form-button"
|
||||||
|
:class="currentField?.description?.length > 100 ? 'mr-1 mt-1 md:mr-2 md:mt-2': 'mr-2 mt-2 hidden md:block'"
|
||||||
:title="t('minimize')"
|
:title="t('minimize')"
|
||||||
@click.prevent="minimizeForm"
|
@click.prevent="minimizeForm"
|
||||||
>
|
>
|
||||||
@@ -117,7 +119,7 @@
|
|||||||
ref="form"
|
ref="form"
|
||||||
:action="submitPath"
|
:action="submitPath"
|
||||||
method="post"
|
method="post"
|
||||||
class="mx-auto"
|
class="mx-auto steps-form"
|
||||||
:style="{ maxWidth: isBreakpointMd ? '582px' : '' }"
|
:style="{ maxWidth: isBreakpointMd ? '582px' : '' }"
|
||||||
@submit.prevent="submitStep"
|
@submit.prevent="submitStep"
|
||||||
>
|
>
|
||||||
@@ -163,7 +165,7 @@
|
|||||||
v-if="showFieldNames && (currentField.name || currentField.title)"
|
v-if="showFieldNames && (currentField.name || currentField.title)"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !currentField.description }"
|
:class="{ 'mb-2': !currentField.description }"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -172,7 +174,11 @@
|
|||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ currentField.name }}
|
{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
</template>
|
||||||
|
<template v-if="!currentField.required">
|
||||||
|
<span :class="{ 'hidden sm:inline': (currentField.title || currentField.name).length > 20 }">
|
||||||
|
({{ t('optional') }})
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
@@ -182,7 +188,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="currentField.description"
|
v-if="currentField.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="currentField.description" />
|
<MarkdownContent :string="currentField.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -220,7 +226,7 @@
|
|||||||
v-if="showFieldNames && (currentField.name || currentField.title)"
|
v-if="showFieldNames && (currentField.name || currentField.title)"
|
||||||
:for="currentField.uuid"
|
:for="currentField.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !currentField.description }"
|
:class="{ 'mb-2': !currentField.description }"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -229,13 +235,17 @@
|
|||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ currentField.name }}
|
{{ currentField.name }}
|
||||||
<template v-if="!currentField.required">({{ t('optional') }})</template>
|
</template>
|
||||||
|
<template v-if="!currentField.required">
|
||||||
|
<span :class="{ 'hidden sm:inline': (currentField.title || currentField.name).length > 20 }">
|
||||||
|
({{ t('optional') }})
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-if="currentField.description"
|
v-if="currentField.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="currentField.description" />
|
<MarkdownContent :string="currentField.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -260,7 +270,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
:for="option.uuid"
|
:for="option.uuid"
|
||||||
class="flex items-center space-x-3"
|
class="flex items-center space-x-3 radio-label"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
:id="option.uuid"
|
:id="option.uuid"
|
||||||
@@ -294,7 +304,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="currentField.description"
|
v-if="currentField.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="currentField.description" />
|
<MarkdownContent :string="currentField.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -328,7 +338,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
class="flex items-center space-x-3"
|
class="flex items-center space-x-3 checkbox-label"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
@@ -465,13 +475,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="(currentField.type !== 'payment' && currentField.type !== 'verification') || submittedValues[currentField.uuid]"
|
v-if="(currentField.type !== 'payment' && currentField.type !== 'verification') || submittedValues[currentField.uuid]"
|
||||||
:class="currentField.type === 'signature' ? 'mt-2' : 'mt-6 md:mt-8'"
|
:class="currentField.type === 'signature' ? 'mt-2' : 'mt-4 md:mt-6'"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
id="submit_form_button"
|
id="submit_form_button"
|
||||||
ref="submitButton"
|
ref="submitButton"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="base-button w-full flex justify-center"
|
class="base-button w-full flex justify-center submit-form-button"
|
||||||
:disabled="isButtonDisabled"
|
:disabled="isButtonDisabled"
|
||||||
>
|
>
|
||||||
<span class="flex">
|
<span class="flex">
|
||||||
@@ -498,6 +508,7 @@
|
|||||||
<InviteForm
|
<InviteForm
|
||||||
v-else-if="isInvite"
|
v-else-if="isInvite"
|
||||||
:submitters="inviteSubmitters"
|
:submitters="inviteSubmitters"
|
||||||
|
:optional-submitters="optionalInviteSubmitters"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
:authenticity-token="authenticityToken"
|
:authenticity-token="authenticityToken"
|
||||||
:url="baseUrl + submitPath + '/invite'"
|
:url="baseUrl + submitPath + '/invite'"
|
||||||
@@ -520,15 +531,15 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="stepFields.length < 80"
|
v-if="stepFields.length < 80"
|
||||||
class="flex justify-center"
|
class="flex justify-center mt-3 sm:mt-4 mb-0 sm:mb-1"
|
||||||
>
|
>
|
||||||
<div class="flex items-center mt-4 mb-1 flex-wrap">
|
<div class="flex items-center flex-wrap steps-progress">
|
||||||
<a
|
<a
|
||||||
v-for="(step, index) in stepFields"
|
v-for="(step, index) in stepFields"
|
||||||
:key="step[0].uuid"
|
:key="step[0].uuid"
|
||||||
href="#"
|
href="#"
|
||||||
class="inline border border-base-300 h-3 w-3 rounded-full mx-1 mt-1"
|
class="inline border border-base-300 h-3 w-3 rounded-full mx-1 mt-1"
|
||||||
:class="{ 'bg-base-300': index === currentStep, 'bg-base-content': (index < currentStep && stepFields[index].every((f) => !f.required || ![null, undefined, ''].includes(values[f.uuid]))) || isCompleted, 'bg-white': index > currentStep }"
|
:class="{ 'bg-base-300 steps-progress-current': index === currentStep, 'bg-base-content': (index < currentStep && stepFields[index].every((f) => !f.required || ![null, undefined, ''].includes(values[f.uuid]))) || isCompleted, 'bg-white': index > currentStep }"
|
||||||
@click.prevent="isCompleted ? '' : [saveStep(), goToStep(index, true)]"
|
@click.prevent="isCompleted ? '' : [saveStep(), goToStep(index, true)]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -627,6 +638,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
optionalInviteSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
withSignatureId: {
|
withSignatureId: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -637,6 +653,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: '-80px'
|
default: '-80px'
|
||||||
},
|
},
|
||||||
|
orderAsOnPage: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
requireSigningReason: {
|
requireSigningReason: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -844,7 +865,16 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isMobile () {
|
isMobile () {
|
||||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||||
|
|
||||||
|
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||||
|
},
|
||||||
|
readonlyConditionalFieldValues () {
|
||||||
|
return this.readonlyConditionalFields.reduce((acc, f) => {
|
||||||
|
acc[f.uuid] = (this.values[f.uuid] || f.default_value)
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
},
|
},
|
||||||
attachmentConditionsIndex () {
|
attachmentConditionsIndex () {
|
||||||
return this.schema.reduce((acc, item) => {
|
return this.schema.reduce((acc, item) => {
|
||||||
@@ -956,6 +986,38 @@ export default {
|
|||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
if (this.orderAsOnPage) {
|
||||||
|
const fieldAreasIndex = {}
|
||||||
|
const attachmentUuids = Object.keys(this.attachmentConditionsIndex)
|
||||||
|
|
||||||
|
const sortArea = (aArea, bArea) => {
|
||||||
|
if (aArea.attachment_uuid === bArea.attachment_uuid) {
|
||||||
|
if (aArea.page === bArea.page) {
|
||||||
|
if (Math.abs(aArea.y - bArea.y) < 0.01) {
|
||||||
|
if (aArea.x === bArea.x) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return aArea.x - bArea.x
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return aArea.y - bArea.y
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return aArea.page - bArea.page
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return attachmentUuids.indexOf(aArea.attachment_uuid) - attachmentUuids.indexOf(bArea.attachment_uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortedFields.sort((aField, bField) => {
|
||||||
|
const aArea = (fieldAreasIndex[aField.uuid] ||= [...(aField.areas || [])].sort(sortArea)[0])
|
||||||
|
const bArea = (fieldAreasIndex[bField.uuid] ||= [...(bField.areas || [])].sort(sortArea)[0])
|
||||||
|
|
||||||
|
return sortArea(aArea, bArea)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (verificationFields.length) {
|
if (verificationFields.length) {
|
||||||
sortedFields.push(verificationFields.pop())
|
sortedFields.push(verificationFields.pop())
|
||||||
}
|
}
|
||||||
@@ -975,7 +1037,7 @@ export default {
|
|||||||
}, [])
|
}, [])
|
||||||
},
|
},
|
||||||
formulaFields () {
|
formulaFields () {
|
||||||
return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment')
|
return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment' && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
|
||||||
},
|
},
|
||||||
attachmentsIndex () {
|
attachmentsIndex () {
|
||||||
return this.attachments.reduce((acc, a) => {
|
return this.attachments.reduce((acc, a) => {
|
||||||
@@ -1060,7 +1122,9 @@ export default {
|
|||||||
this.minimizeForm()
|
this.minimizeForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
const isMobile = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit|android/i.test(navigator.userAgent)
|
||||||
|
|
||||||
|
if (isMobile || /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()
|
||||||
const scrollbox = root.getElementById('scrollbox')
|
const scrollbox = root.getElementById('scrollbox')
|
||||||
@@ -1069,7 +1133,7 @@ export default {
|
|||||||
parent.style.overflow = 'hidden'
|
parent.style.overflow = 'hidden'
|
||||||
|
|
||||||
scrollbox.classList.add('h-full', 'overflow-y-auto')
|
scrollbox.classList.add('h-full', 'overflow-y-auto')
|
||||||
scrollbox.parentNode.classList.add('h-screen', 'overflow-y-auto')
|
scrollbox.parentNode.classList.add('h-screen', 'h-[100dvh]', 'overflow-y-auto')
|
||||||
scrollbox.parentNode.style.maxHeight = '-webkit-fill-available'
|
scrollbox.parentNode.style.maxHeight = '-webkit-fill-available'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1127,22 +1191,24 @@ export default {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultValue = !field || isEmpty(field.default_value) ? null : field.default_value
|
||||||
|
|
||||||
if (['empty', 'unchecked'].includes(condition.action)) {
|
if (['empty', 'unchecked'].includes(condition.action)) {
|
||||||
return isEmpty(this.values[condition.field_uuid])
|
return isEmpty(this.values[condition.field_uuid] ?? defaultValue)
|
||||||
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
||||||
return !isEmpty(this.values[condition.field_uuid])
|
return !isEmpty(this.values[condition.field_uuid] ?? defaultValue)
|
||||||
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
||||||
if (field.options) {
|
if (field.options) {
|
||||||
const option = field.options.find((o) => o.uuid === condition.value)
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
const values = [this.values[condition.field_uuid]].flat()
|
const values = [this.values[condition.field_uuid] ?? defaultValue].flat()
|
||||||
|
|
||||||
return values.includes(this.optionValue(option, field.options.indexOf(option)))
|
return values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
} else {
|
} else {
|
||||||
return [this.values[condition.field_uuid]].flat().includes(condition.value)
|
return [this.values[condition.field_uuid] ?? defaultValue].flat().includes(condition.value)
|
||||||
}
|
}
|
||||||
} else if (['not_equal', 'does_not_contain'].includes(condition.action) && field) {
|
} else if (['not_equal', 'does_not_contain'].includes(condition.action) && field) {
|
||||||
const option = field.options.find((o) => o.uuid === condition.value)
|
const option = field.options.find((o) => o.uuid === condition.value)
|
||||||
const values = [this.values[condition.field_uuid]].flat()
|
const values = [this.values[condition.field_uuid] ?? defaultValue].flat()
|
||||||
|
|
||||||
return !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
return !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||||
} else {
|
} else {
|
||||||
@@ -1235,7 +1301,9 @@ export default {
|
|||||||
|
|
||||||
if (!this.isCompleted) {
|
if (!this.isCompleted) {
|
||||||
if (scrollToArea) {
|
if (scrollToArea) {
|
||||||
this.scrollIntoField(this.currentField)
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => this.scrollIntoField(this.currentField), 1)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.enableScrollIntoField = false
|
this.enableScrollIntoField = false
|
||||||
@@ -1252,7 +1320,7 @@ export default {
|
|||||||
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
||||||
const currentFieldType = this.currentField.type
|
const currentFieldType = this.currentField.type
|
||||||
|
|
||||||
if (!formData && !this.$refs.form.checkValidity()) {
|
if (!formData && !this.$refs.form.checkValidity() && currentFieldUuids.every((fieldUuid) => isEmpty(this.submittedValues[fieldUuid]) || !isEmpty(this.values[fieldUuid]))) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1320,7 +1388,7 @@ export default {
|
|||||||
const formData = new FormData(this.$refs.form)
|
const formData = new FormData(this.$refs.form)
|
||||||
const isLastStep = (submitStep === this.stepFields.length - 1) || forceComplete
|
const isLastStep = (submitStep === this.stepFields.length - 1) || forceComplete
|
||||||
|
|
||||||
if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length) {
|
if (isLastStep && !emptyRequiredField && !this.inviteSubmitters.length && !this.optionalInviteSubmitters.length) {
|
||||||
formData.append('completed', 'true')
|
formData.append('completed', 'true')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1336,7 +1404,21 @@ export default {
|
|||||||
if (response.status === 422 || response.status === 500) {
|
if (response.status === 422 || response.status === 500) {
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
if (data.error) {
|
if (data.field_uuid) {
|
||||||
|
const field = this.fieldsUuidIndex[data.field_uuid]
|
||||||
|
|
||||||
|
if (field) {
|
||||||
|
const step = this.stepFields.findIndex((fields) => fields.includes(field))
|
||||||
|
|
||||||
|
if (step !== -1) {
|
||||||
|
this.goToStep(step, this.autoscrollFields)
|
||||||
|
|
||||||
|
this.showFillAllRequiredFields = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(new Error('Required field: ' + data.field_uuid))
|
||||||
|
} else if (data.error) {
|
||||||
const i18nKey = data.error.replace(/\s+/g, '_').toLowerCase()
|
const i18nKey = data.error.replace(/\s+/g, '_').toLowerCase()
|
||||||
|
|
||||||
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
||||||
@@ -1359,7 +1441,7 @@ export default {
|
|||||||
if (emptyRequiredField === nextStep) {
|
if (emptyRequiredField === nextStep) {
|
||||||
this.showFillAllRequiredFields = true
|
this.showFillAllRequiredFields = true
|
||||||
}
|
}
|
||||||
} else if (this.inviteSubmitters.length) {
|
} else if (this.inviteSubmitters.length || this.optionalInviteSubmitters.length) {
|
||||||
this.isInvite = true
|
this.isInvite = true
|
||||||
} else {
|
} else {
|
||||||
this.performComplete(response)
|
this.performComplete(response)
|
||||||
@@ -1371,11 +1453,7 @@ export default {
|
|||||||
this.isSubmittingComplete = false
|
this.isSubmittingComplete = false
|
||||||
})
|
})
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (error?.message === 'Image too small') {
|
console.log(error)
|
||||||
alert(this.t('signature_is_too_small_please_redraw'))
|
|
||||||
} else {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isSubmitting = false
|
this.isSubmitting = false
|
||||||
this.isSubmittingComplete = false
|
this.isSubmittingComplete = false
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
readonlyValues: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
values: {
|
values: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -87,7 +92,7 @@ export default {
|
|||||||
},
|
},
|
||||||
calculateFormula (field) {
|
calculateFormula (field) {
|
||||||
const transformedFormula = field.preferences.formula.replace(/{{(.*?)}}/g, (match, uuid) => {
|
const transformedFormula = field.preferences.formula.replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||||
return this.values[uuid] || 0.0
|
return this.readonlyValues[uuid] || this.values[uuid] || 0.0
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.math.evaluate(transformedFormula.toLowerCase())
|
return this.math.evaluate(transformedFormula.toLowerCase())
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ const en = {
|
|||||||
reupload: 'Reupload',
|
reupload: 'Reupload',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
files: 'Files',
|
files: 'Files',
|
||||||
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.'
|
signature_is_too_small_or_simple_please_redraw: 'Signature is too small or simple. Please redraw.',
|
||||||
|
wait_countdown_seconds: 'Wait {countdown} seconds'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
@@ -147,6 +148,7 @@ const es = {
|
|||||||
sign_now: 'Firmar ahora',
|
sign_now: 'Firmar ahora',
|
||||||
type_here_: 'Escribe aquí...',
|
type_here_: 'Escribe aquí...',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
|
option: 'Opción',
|
||||||
appears_on: 'Aparece en',
|
appears_on: 'Aparece en',
|
||||||
page: 'Página',
|
page: 'Página',
|
||||||
select_your_option: 'Selecciona tu opción',
|
select_your_option: 'Selecciona tu opción',
|
||||||
@@ -190,7 +192,8 @@ const es = {
|
|||||||
reupload: 'Volver a subir',
|
reupload: 'Volver a subir',
|
||||||
upload: 'Subir',
|
upload: 'Subir',
|
||||||
files: 'Archivos',
|
files: 'Archivos',
|
||||||
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.'
|
signature_is_too_small_or_simple_please_redraw: 'La firma es demasiado pequeña o simple. Por favor, vuelve a dibujarla.',
|
||||||
|
wait_countdown_seconds: 'Espera {countdown} segundos'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
@@ -243,6 +246,7 @@ const it = {
|
|||||||
sign_now: 'Firma ora',
|
sign_now: 'Firma ora',
|
||||||
type_here_: 'Digita qui...',
|
type_here_: 'Digita qui...',
|
||||||
optional: 'opzionale',
|
optional: 'opzionale',
|
||||||
|
option: 'Opzione',
|
||||||
appears_on: 'Compare su',
|
appears_on: 'Compare su',
|
||||||
page: 'Pagina',
|
page: 'Pagina',
|
||||||
take_photo: 'Scattare una foto',
|
take_photo: 'Scattare una foto',
|
||||||
@@ -263,7 +267,7 @@ const it = {
|
|||||||
document_has_been_signed: 'Il documento è stato firmato!',
|
document_has_been_signed: 'Il documento è stato firmato!',
|
||||||
documents_have_been_signed: 'I documenti sono stati firmati!',
|
documents_have_been_signed: 'I documenti sono stati firmati!',
|
||||||
create_a_free_account: 'Crea un Account Gratuito',
|
create_a_free_account: 'Crea un Account Gratuito',
|
||||||
powered_by: 'Desarrollado por',
|
powered_by: 'Fornito da',
|
||||||
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare.',
|
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare.',
|
||||||
open_source_documents_software: 'software di documenti open source',
|
open_source_documents_software: 'software di documenti open source',
|
||||||
verified_phone_number: 'Verifica numero di telefono',
|
verified_phone_number: 'Verifica numero di telefono',
|
||||||
@@ -287,7 +291,8 @@ const it = {
|
|||||||
reupload: 'Ricarica',
|
reupload: 'Ricarica',
|
||||||
upload: 'Carica',
|
upload: 'Carica',
|
||||||
files: 'File',
|
files: 'File',
|
||||||
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.'
|
signature_is_too_small_or_simple_please_redraw: 'La firma è troppo piccola o semplice. Ridisegnala, per favore.',
|
||||||
|
wait_countdown_seconds: 'Attendi {countdown} secondi'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
@@ -340,6 +345,7 @@ const de = {
|
|||||||
sign_now: 'Jetzt unterschreiben',
|
sign_now: 'Jetzt unterschreiben',
|
||||||
type_here_: 'Hier eingeben...',
|
type_here_: 'Hier eingeben...',
|
||||||
optional: 'optional',
|
optional: 'optional',
|
||||||
|
option: 'Option',
|
||||||
appears_on: 'Erscheint auf',
|
appears_on: 'Erscheint auf',
|
||||||
page: 'Seite',
|
page: 'Seite',
|
||||||
take_photo: 'Foto aufnehmen',
|
take_photo: 'Foto aufnehmen',
|
||||||
@@ -384,13 +390,14 @@ const de = {
|
|||||||
reupload: 'Erneut hochladen',
|
reupload: 'Erneut hochladen',
|
||||||
upload: 'Hochladen',
|
upload: 'Hochladen',
|
||||||
files: 'Dateien',
|
files: 'Dateien',
|
||||||
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.'
|
signature_is_too_small_or_simple_please_redraw: 'Die Unterschrift ist zu klein oder zu einfach. Bitte erneut zeichnen.',
|
||||||
|
wait_countdown_seconds: 'Warte {countdown} Sekunden'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
complete_all_required_fields_to_proceed_with_identity_verification: "Veuillez remplir tous les champs obligatoires pour continuer la vérification de l'identité.",
|
complete_all_required_fields_to_proceed_with_identity_verification: "Veuillez remplir tous les champs obligatoires pour continuer la vérification de l'identité.",
|
||||||
verif_id: "Vérification de l'ID",
|
verify_id: "Vérification de l'ID",
|
||||||
verif_identite: "Vérification de l'identité",
|
identity_verification: "Vérification de l'identité",
|
||||||
complete: 'Terminer',
|
complete: 'Terminer',
|
||||||
fill_all_required_fields_to_complete: 'Veuillez remplir tous les champs obligatoires pour compléter',
|
fill_all_required_fields_to_complete: 'Veuillez remplir tous les champs obligatoires pour compléter',
|
||||||
sign_and_complete: 'Signer et Terminer',
|
sign_and_complete: 'Signer et Terminer',
|
||||||
@@ -437,6 +444,7 @@ const fr = {
|
|||||||
sign_now: 'Signer maintenant',
|
sign_now: 'Signer maintenant',
|
||||||
type_here_: 'Tapez ici...',
|
type_here_: 'Tapez ici...',
|
||||||
optional: 'facultatif',
|
optional: 'facultatif',
|
||||||
|
option: 'Option',
|
||||||
appears_on: 'Apparaît sur',
|
appears_on: 'Apparaît sur',
|
||||||
page: 'Page',
|
page: 'Page',
|
||||||
take_photo: 'Prendre une photo',
|
take_photo: 'Prendre une photo',
|
||||||
@@ -481,7 +489,8 @@ const fr = {
|
|||||||
reupload: 'Recharger',
|
reupload: 'Recharger',
|
||||||
upload: 'Télécharger',
|
upload: 'Télécharger',
|
||||||
files: 'Fichiers',
|
files: 'Fichiers',
|
||||||
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.'
|
signature_is_too_small_or_simple_please_redraw: 'La signature est trop petite ou trop simple. Veuillez la redessiner.',
|
||||||
|
wait_countdown_seconds: 'Attendez {countdown} secondes'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pl = {
|
const pl = {
|
||||||
@@ -534,6 +543,7 @@ const pl = {
|
|||||||
sign_now: 'Podpisz teraz',
|
sign_now: 'Podpisz teraz',
|
||||||
type_here_: 'Wpisz tutaj...',
|
type_here_: 'Wpisz tutaj...',
|
||||||
optional: 'opcjonalny',
|
optional: 'opcjonalny',
|
||||||
|
option: 'Opcja',
|
||||||
appears_on: 'Pojawia się na',
|
appears_on: 'Pojawia się na',
|
||||||
page: 'Strona',
|
page: 'Strona',
|
||||||
select_your_option: 'Wybierz swoją opcję',
|
select_your_option: 'Wybierz swoją opcję',
|
||||||
@@ -578,7 +588,8 @@ const pl = {
|
|||||||
reupload: 'Ponowne przesłanie',
|
reupload: 'Ponowne przesłanie',
|
||||||
upload: 'Przesyłanie',
|
upload: 'Przesyłanie',
|
||||||
files: 'Pliki',
|
files: 'Pliki',
|
||||||
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.'
|
signature_is_too_small_or_simple_please_redraw: 'Podpis jest zbyt mały lub zbyt prosty. Proszę narysować go ponownie.',
|
||||||
|
wait_countdown_seconds: 'Poczekaj {countdown} sekund'
|
||||||
}
|
}
|
||||||
|
|
||||||
const uk = {
|
const uk = {
|
||||||
@@ -631,6 +642,7 @@ const uk = {
|
|||||||
sign_now: 'Підписати зараз',
|
sign_now: 'Підписати зараз',
|
||||||
type_here_: 'Введіть тут',
|
type_here_: 'Введіть тут',
|
||||||
optional: 'необов’язково',
|
optional: 'необов’язково',
|
||||||
|
option: 'Опція',
|
||||||
appears_on: "З'являється на",
|
appears_on: "З'являється на",
|
||||||
page: 'Сторінка',
|
page: 'Сторінка',
|
||||||
take_photo: 'Зробити фото',
|
take_photo: 'Зробити фото',
|
||||||
@@ -653,7 +665,7 @@ const uk = {
|
|||||||
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
create_a_free_account: 'Створити безкоштовний обліковий запис',
|
||||||
powered_by: 'Працює на базі',
|
powered_by: 'Працює на базі',
|
||||||
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити.',
|
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити.',
|
||||||
open_source_documents_software: 'відкритий програмний засіб для документів',
|
open_source_documents_software: 'відкрите програмне забезпечення для документів',
|
||||||
verified_phone_number: 'Підтвердіть номер телефону',
|
verified_phone_number: 'Підтвердіть номер телефону',
|
||||||
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
use_international_format: 'Використовуйте міжнародний формат: +1xxx',
|
||||||
six_digits_code: '6-значний код',
|
six_digits_code: '6-значний код',
|
||||||
@@ -675,7 +687,8 @@ const uk = {
|
|||||||
reupload: 'Перезавантажити',
|
reupload: 'Перезавантажити',
|
||||||
upload: 'Завантажити',
|
upload: 'Завантажити',
|
||||||
files: 'Файли',
|
files: 'Файли',
|
||||||
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.'
|
signature_is_too_small_or_simple_please_redraw: 'Підпис занадто маленький або надто простий. Будь ласка, перемалюйте.',
|
||||||
|
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
|
||||||
}
|
}
|
||||||
|
|
||||||
const cs = {
|
const cs = {
|
||||||
@@ -728,6 +741,7 @@ const cs = {
|
|||||||
sign_now: 'Podepsat nyní',
|
sign_now: 'Podepsat nyní',
|
||||||
type_here_: 'Zadejte zde',
|
type_here_: 'Zadejte zde',
|
||||||
optional: 'volitelné',
|
optional: 'volitelné',
|
||||||
|
option: 'Možnost',
|
||||||
appears_on: 'Zobrazuje se na',
|
appears_on: 'Zobrazuje se na',
|
||||||
page: 'Stránka',
|
page: 'Stránka',
|
||||||
select_your_option: 'Vyberte svou volbu',
|
select_your_option: 'Vyberte svou volbu',
|
||||||
@@ -772,7 +786,8 @@ const cs = {
|
|||||||
reupload: 'Znovu nahrát',
|
reupload: 'Znovu nahrát',
|
||||||
upload: 'Nahrát',
|
upload: 'Nahrát',
|
||||||
files: 'Soubory',
|
files: 'Soubory',
|
||||||
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.'
|
signature_is_too_small_or_simple_please_redraw: 'Podpis je příliš malý nebo jednoduchý. Nakreslete jej prosím znovu.',
|
||||||
|
wait_countdown_seconds: 'Počkejte {countdown} sekund'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
@@ -825,6 +840,7 @@ const pt = {
|
|||||||
sign_now: 'Assinar agora',
|
sign_now: 'Assinar agora',
|
||||||
type_here_: 'Digite aqui',
|
type_here_: 'Digite aqui',
|
||||||
optional: 'opcional',
|
optional: 'opcional',
|
||||||
|
option: 'Opção',
|
||||||
appears_on: 'Aparece em',
|
appears_on: 'Aparece em',
|
||||||
page: 'Página',
|
page: 'Página',
|
||||||
take_photo: 'Tirar foto',
|
take_photo: 'Tirar foto',
|
||||||
@@ -869,7 +885,8 @@ const pt = {
|
|||||||
reupload: 'Reenviar',
|
reupload: 'Reenviar',
|
||||||
upload: 'Carregar',
|
upload: 'Carregar',
|
||||||
files: 'Arquivos',
|
files: 'Arquivos',
|
||||||
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.'
|
signature_is_too_small_or_simple_please_redraw: 'A assinatura é muito pequena ou simples. Por favor, redesenhe.',
|
||||||
|
wait_countdown_seconds: 'Aguarde {countdown} segundos'
|
||||||
}
|
}
|
||||||
|
|
||||||
const he = {
|
const he = {
|
||||||
@@ -967,7 +984,8 @@ const he = {
|
|||||||
reupload: 'העלה שוב',
|
reupload: 'העלה שוב',
|
||||||
upload: 'העלאה',
|
upload: 'העלאה',
|
||||||
files: 'קבצים',
|
files: 'קבצים',
|
||||||
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.'
|
signature_is_too_small_or_simple_please_redraw: 'החתימה קטנה או פשוטה מדי. אנא חתום מחדש.',
|
||||||
|
wait_countdown_seconds: 'המתן {countdown} שניות'
|
||||||
}
|
}
|
||||||
|
|
||||||
const nl = {
|
const nl = {
|
||||||
@@ -1065,7 +1083,8 @@ const nl = {
|
|||||||
reupload: 'Opnieuw uploaden',
|
reupload: 'Opnieuw uploaden',
|
||||||
upload: 'Uploaden',
|
upload: 'Uploaden',
|
||||||
files: 'Bestanden',
|
files: 'Bestanden',
|
||||||
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.'
|
signature_is_too_small_or_simple_please_redraw: 'De handtekening is te klein of te eenvoudig. Teken opnieuw.',
|
||||||
|
wait_countdown_seconds: 'Wacht {countdown} seconden'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ar = {
|
const ar = {
|
||||||
@@ -1087,6 +1106,7 @@ const ar = {
|
|||||||
select_a_reason: 'اختر سببًا',
|
select_a_reason: 'اختر سببًا',
|
||||||
value_is_invalid: 'القيمة غير صالحة',
|
value_is_invalid: 'القيمة غير صالحة',
|
||||||
verification_code_is_invalid: 'رمز التحقق غير صالح',
|
verification_code_is_invalid: 'رمز التحقق غير صالح',
|
||||||
|
already_paid: 'تم الدفع بالفعل',
|
||||||
drawn_signature_on_a_touchscreen_device: 'توقيع مرسوم على جهاز بشاشة تعمل باللمس',
|
drawn_signature_on_a_touchscreen_device: 'توقيع مرسوم على جهاز بشاشة تعمل باللمس',
|
||||||
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'امسح رمز الاستجابة السريعة باستخدام تطبيق الكاميرا لفتح النموذج على الهاتف المحمول ورسم توقيعك',
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'امسح رمز الاستجابة السريعة باستخدام تطبيق الكاميرا لفتح النموذج على الهاتف المحمول ورسم توقيعك',
|
||||||
by_clicking_you_agree_to_the: 'بالنقر فوق "{button}"، أنت توافق على',
|
by_clicking_you_agree_to_the: 'بالنقر فوق "{button}"، أنت توافق على',
|
||||||
@@ -1162,7 +1182,8 @@ const ar = {
|
|||||||
reupload: 'إعادة التحميل',
|
reupload: 'إعادة التحميل',
|
||||||
upload: 'تحميل',
|
upload: 'تحميل',
|
||||||
files: 'الملفات',
|
files: 'الملفات',
|
||||||
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.'
|
signature_is_too_small_or_simple_please_redraw: 'التوقيع صغير جدًا أو بسيط جدًا. يرجى إعادة رسمه.',
|
||||||
|
wait_countdown_seconds: 'انتظر {countdown} ثانية'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ko = {
|
const ko = {
|
||||||
@@ -1187,6 +1208,8 @@ const ko = {
|
|||||||
by_clicking_you_agree_to_the: '"{button}"를 클릭함으로써, 다음에 동의하게 됩니다',
|
by_clicking_you_agree_to_the: '"{button}"를 클릭함으로써, 다음에 동의하게 됩니다',
|
||||||
electronic_signature_disclosure: '전자 서명 공개',
|
electronic_signature_disclosure: '전자 서명 공개',
|
||||||
esignature_disclosure: '전자 서명 공개',
|
esignature_disclosure: '전자 서명 공개',
|
||||||
|
value_is_invalid: '값이 올바르지 않습니다',
|
||||||
|
verification_code_is_invalid: '인증 코드가 올바르지 않습니다',
|
||||||
already_paid: '이미 지불됨',
|
already_paid: '이미 지불됨',
|
||||||
text: '텍스트',
|
text: '텍스트',
|
||||||
signature: '서명',
|
signature: '서명',
|
||||||
@@ -1258,9 +1281,109 @@ const ko = {
|
|||||||
reupload: '다시 업로드',
|
reupload: '다시 업로드',
|
||||||
upload: '업로드',
|
upload: '업로드',
|
||||||
files: '파일',
|
files: '파일',
|
||||||
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.'
|
signature_is_too_small_or_simple_please_redraw: '서명이 너무 작거나 단순합니다. 다시 그려주세요.',
|
||||||
|
wait_countdown_seconds: '{countdown}초 기다리세요'
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko }
|
const ja = {
|
||||||
|
complete_all_required_fields_to_proceed_with_identity_verification: '本人確認を進めるには、すべての必須項目を入力してください。',
|
||||||
|
verify_id: '本人確認',
|
||||||
|
identity_verification: '本人確認',
|
||||||
|
complete: '完了',
|
||||||
|
fill_all_required_fields_to_complete: '完了するにはすべての必須項目を入力してください',
|
||||||
|
sign_and_complete: '署名して完了',
|
||||||
|
text: 'テキスト',
|
||||||
|
by_clicking_you_agree_to_the: '"{button}" をクリックすることで、次に同意したことになります:',
|
||||||
|
electronic_signature_disclosure: '電子署名に関する開示',
|
||||||
|
esignature_disclosure: '電子署名開示',
|
||||||
|
signature: '署名',
|
||||||
|
initials: 'イニシャル',
|
||||||
|
drawn_signature_on_a_touchscreen_device: 'タッチスクリーンデバイスで描かれた署名',
|
||||||
|
approved: '承認済み',
|
||||||
|
reviewed: '確認済み',
|
||||||
|
other: 'その他',
|
||||||
|
authored_by_me: '自分が作成',
|
||||||
|
invite: '招待',
|
||||||
|
email: 'メール',
|
||||||
|
approved_by: '承認者',
|
||||||
|
reviewed_by: '確認者',
|
||||||
|
authored_by: '作成者',
|
||||||
|
select_a_reason: '理由を選択',
|
||||||
|
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'QRコードをカメラアプリで読み取り、モバイルでフォームを開いて署名を描いてください',
|
||||||
|
date: '日付',
|
||||||
|
number: '数値',
|
||||||
|
value_is_invalid: '無効な値です',
|
||||||
|
verification_code_is_invalid: '認証コードが無効です',
|
||||||
|
already_paid: 'すでに支払い済み',
|
||||||
|
image: '画像',
|
||||||
|
pay: '支払う',
|
||||||
|
take_photo: '写真を撮る',
|
||||||
|
number_phone_is_invalid: '{number} の電話番号は無効です',
|
||||||
|
file: 'ファイル',
|
||||||
|
digitally_signed_by: '電子署名者:',
|
||||||
|
reason: '理由',
|
||||||
|
select: '選択',
|
||||||
|
checkbox: 'チェックボックス',
|
||||||
|
multiple: '複数選択',
|
||||||
|
radio: 'ラジオボタン',
|
||||||
|
cells: 'セル',
|
||||||
|
stamp: 'スタンプ',
|
||||||
|
minimize: '最小化',
|
||||||
|
payment: '支払い',
|
||||||
|
phone: '電話',
|
||||||
|
start_now: '今すぐ開始',
|
||||||
|
continue: '続行',
|
||||||
|
sign_now: '今すぐ署名',
|
||||||
|
type_here_: 'ここに入力...',
|
||||||
|
optional: '任意',
|
||||||
|
option: 'オプション',
|
||||||
|
appears_on: '表示ページ',
|
||||||
|
page: 'ページ',
|
||||||
|
select_your_option: 'オプションを選択',
|
||||||
|
complete_hightlighted_checkboxes_and_click: 'ハイライトされたチェックボックスを完了し、クリックしてください',
|
||||||
|
submit: '送信',
|
||||||
|
next: '次へ',
|
||||||
|
click_to_upload: 'クリックしてアップロード',
|
||||||
|
or_drag_and_drop_files: 'またはファイルをドラッグ&ドロップ',
|
||||||
|
send_copy_via_email: 'メールでコピーを送信',
|
||||||
|
download: 'ダウンロード',
|
||||||
|
clear: 'クリア',
|
||||||
|
redraw: '描き直す',
|
||||||
|
draw_initials: 'イニシャルを描く',
|
||||||
|
type_signature_here: 'ここに署名を入力',
|
||||||
|
type_initial_here: 'ここにイニシャルを入力',
|
||||||
|
form_has_been_completed: 'フォームが完了しました!',
|
||||||
|
document_has_been_signed: 'ドキュメントが署名されました!',
|
||||||
|
documents_have_been_signed: 'ドキュメントがすべて署名されました!',
|
||||||
|
create_a_free_account: '無料アカウントを作成',
|
||||||
|
powered_by: '提供元:',
|
||||||
|
please_check_the_box_to_continue: '続行するにはボックスにチェックを入れてください。',
|
||||||
|
open_source_documents_software: 'オープンソースのドキュメントソフトウェア',
|
||||||
|
verified_phone_number: '電話番号を認証',
|
||||||
|
use_international_format: '国際形式を使用してください:+1xxx',
|
||||||
|
six_digits_code: '6桁のコード',
|
||||||
|
change_phone_number: '電話番号を変更',
|
||||||
|
sending: '送信中...',
|
||||||
|
resend_code: 'コードを再送信',
|
||||||
|
verification_code_has_been_resent: '認証コードがSMSで再送信されました',
|
||||||
|
please_fill_all_required_fields: 'すべての必須項目を入力してください',
|
||||||
|
set_today: '今日の日付を設定',
|
||||||
|
toggle_multiline_text: '複数行テキスト切り替え',
|
||||||
|
draw_signature: '署名を描く',
|
||||||
|
type_initial: 'イニシャルを入力',
|
||||||
|
draw: '描く',
|
||||||
|
type: '入力',
|
||||||
|
type_text: 'テキストを入力',
|
||||||
|
email_has_been_sent: 'メールが送信されました',
|
||||||
|
processing: '処理中',
|
||||||
|
pay_with_strip: 'Stripeで支払う',
|
||||||
|
reupload: '再アップロード',
|
||||||
|
upload: 'アップロード',
|
||||||
|
files: 'ファイル',
|
||||||
|
signature_is_too_small_or_simple_please_redraw: '署名が小さすぎるか単純すぎます。もう一度描いてください。',
|
||||||
|
wait_countdown_seconds: '{countdown} 秒お待ちください'
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko, ja }
|
||||||
|
|
||||||
export default i18n
|
export default i18n
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="modelValue">
|
<div v-if="modelValue">
|
||||||
<div class="flex justify-between items-center w-full mb-2">
|
<div class="flex justify-between items-end w-full mb-3.5 md:mb-4">
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
v-if="showFieldNames"
|
||||||
>{{ showFieldNames && field.name ? field.name : t('image') }}</label>
|
:for="field.uuid"
|
||||||
|
class="label text-xl sm:text-2xl py-0 field-name-label"
|
||||||
|
>
|
||||||
|
<MarkdownContent
|
||||||
|
v-if="field.title"
|
||||||
|
:string="field.title"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
{{ field.name || t('image') }}
|
||||||
|
</template>
|
||||||
|
</label>
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline btn-sm"
|
class="btn btn-outline btn-sm reupload-button"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
@@ -15,7 +25,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
:src="attachmentsIndex[modelValue].url"
|
:src="attachmentsIndex[modelValue].url"
|
||||||
class="h-52 border border-base-300 rounded mx-auto"
|
class="h-52 border border-base-300 rounded mx-auto uploaded-image-preview"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
@@ -30,12 +40,12 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
<FileDropzone
|
<FileDropzone
|
||||||
:message="`${t('upload')} ${field.name || t('image')}${field.required ? '' : ` (${t('optional')})`}`"
|
:message="`${t('upload')} ${(field.title || field.name) || t('image')}${field.required ? '' : ` (${t('optional')})`}`"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
:dry-run="dryRun"
|
:dry-run="dryRun"
|
||||||
:accept="'image/*'"
|
:accept="'image/*'"
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div dir="auto">
|
<div dir="auto">
|
||||||
<div
|
<div
|
||||||
class="flex justify-between items-center w-full"
|
class="flex justify-between items-end w-full mb-3.5 md:mb-4"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
v-if="showFieldNames"
|
||||||
|
class="label text-xl sm:text-2xl py-0 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
:string="field.title"
|
:string="field.title"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ showFieldNames && field.name ? field.name : t('initials') }}
|
{{ field.name || t('initials') }}
|
||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<div class="space-x-2 flex">
|
<div class="space-x-2 flex flex-none">
|
||||||
<span
|
<span
|
||||||
v-if="isDrawInitials"
|
v-if="isDrawInitials"
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
<a
|
<a
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline font-medium btn-sm"
|
class="btn btn-outline font-medium btn-sm type-text-button"
|
||||||
@click.prevent="toggleTextInput"
|
@click.prevent="toggleTextInput"
|
||||||
>
|
>
|
||||||
<IconTextSize :width="16" />
|
<IconTextSize :width="16" />
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
<a
|
<a
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline font-medium btn-sm"
|
class="btn btn-outline font-medium btn-sm type-text-button"
|
||||||
@click.prevent="toggleTextInput"
|
@click.prevent="toggleTextInput"
|
||||||
>
|
>
|
||||||
<IconSignature :width="16" />
|
<IconSignature :width="16" />
|
||||||
@@ -54,9 +55,7 @@
|
|||||||
class="tooltip"
|
class="tooltip"
|
||||||
:data-tip="t('click_to_upload')"
|
:data-tip="t('click_to_upload')"
|
||||||
>
|
>
|
||||||
<label
|
<label class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button">
|
||||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap"
|
|
||||||
>
|
|
||||||
<IconUpload :width="16" />
|
<IconUpload :width="16" />
|
||||||
<input
|
<input
|
||||||
:key="uploadImageInputKey"
|
:key="uploadImageInputKey"
|
||||||
@@ -73,7 +72,7 @@
|
|||||||
<a
|
<a
|
||||||
v-if="modelValue || computedPreviousValue"
|
v-if="modelValue || computedPreviousValue"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn font-medium btn-outline btn-sm"
|
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
@@ -82,7 +81,7 @@
|
|||||||
<a
|
<a
|
||||||
v-else
|
v-else
|
||||||
href="#"
|
href="#"
|
||||||
class="btn font-medium btn-outline btn-sm"
|
class="btn font-medium btn-outline btn-sm clear-canvas-button"
|
||||||
@click.prevent="clear"
|
@click.prevent="clear"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
@@ -104,7 +103,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -119,11 +118,17 @@
|
|||||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||||
>
|
>
|
||||||
<canvas
|
<div class="relative">
|
||||||
v-show="!modelValue && !computedPreviousValue"
|
<div
|
||||||
ref="canvas"
|
v-if="!isDrawInitials"
|
||||||
class="bg-white border border-base-300 rounded-2xl w-full"
|
class="absolute top-0 right-0 left-0 bottom-0"
|
||||||
/>
|
/>
|
||||||
|
<canvas
|
||||||
|
v-show="!modelValue && !computedPreviousValue"
|
||||||
|
ref="canvas"
|
||||||
|
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
||||||
id="initials_text_input"
|
id="initials_text_input"
|
||||||
@@ -334,8 +339,8 @@ export default {
|
|||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
cropCanvasAndExportToPNG(this.$refs.canvas).then(async (blob) => {
|
cropCanvasAndExportToPNG(this.$refs.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
||||||
const file = new File([blob], 'initials.png', { type: 'image/png' })
|
const file = new File([blob], 'initials.png', { type: 'image/png' })
|
||||||
|
|
||||||
if (this.dryRun) {
|
if (this.dryRun) {
|
||||||
@@ -368,6 +373,14 @@ export default {
|
|||||||
return resolve(attachment)
|
return resolve(attachment)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.field.required === true) {
|
||||||
|
alert(this.t('signature_is_too_small_or_simple_please_redraw'))
|
||||||
|
|
||||||
|
return reject(error)
|
||||||
|
} else {
|
||||||
|
return resolve({})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
:value="authenticityToken"
|
:value="authenticityToken"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(submitter, index) in submitters"
|
v-for="(submitter, index) in [...submitters, ...optionalSubmitters]"
|
||||||
:key="submitter.uuid"
|
:key="submitter.uuid"
|
||||||
:class="{ 'mt-4': index !== 0 }"
|
:class="{ 'mt-4': index !== 0 }"
|
||||||
>
|
>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl"
|
class="label text-2xl"
|
||||||
>
|
>
|
||||||
{{ t('invite') }} {{ submitter.name }}
|
{{ t('invite') }} {{ submitter.name }} <template v-if="!submitters.includes(submitter)">({{ t('optional') }})</template>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
:id="submitter.uuid"
|
:id="submitter.uuid"
|
||||||
@@ -34,13 +34,13 @@
|
|||||||
class="base-input !text-2xl w-full"
|
class="base-input !text-2xl w-full"
|
||||||
:placeholder="t('email')"
|
:placeholder="t('email')"
|
||||||
type="email"
|
type="email"
|
||||||
required
|
:required="submitters.includes(submitter)"
|
||||||
autofocus="true"
|
autofocus="true"
|
||||||
name="submission[submitters][][email]"
|
name="submission[submitters][][email]"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mt-6 md:mt-8"
|
class="mt-4 md:mt-6"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
class="mr-1 animate-spin"
|
class="mr-1 animate-spin"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ t('submit') }}
|
{{ t('complete') }}
|
||||||
</span><span
|
</span><span
|
||||||
v-if="isSubmitting"
|
v-if="isSubmitting"
|
||||||
class="w-6 flex justify-start mr-1"
|
class="w-6 flex justify-start mr-1"
|
||||||
@@ -78,6 +78,11 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
optionalSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -3,18 +3,24 @@
|
|||||||
v-if="showFieldNames && (field.name || field.title)"
|
v-if="showFieldNames && (field.name || field.title)"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
><MarkdownContent
|
>
|
||||||
v-if="field.title"
|
<MarkdownContent
|
||||||
:string="field.title"
|
v-if="field.title"
|
||||||
/>
|
:string="field.title"
|
||||||
|
/>
|
||||||
<template v-else>{{ field.name }}</template>
|
<template v-else>{{ field.name }}</template>
|
||||||
|
<template v-if="!field.required">
|
||||||
|
<span :class="{ 'hidden sm:inline': (field.title || field.name).length > 20 }">
|
||||||
|
({{ t('optional') }})
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -44,7 +50,7 @@
|
|||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
:for="option.uuid"
|
:for="option.uuid"
|
||||||
class="flex items-center space-x-3"
|
class="flex items-center space-x-3 checkbox-label"
|
||||||
@click="scrollIntoField(field)"
|
@click="scrollIntoField(field)"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -3,15 +3,18 @@
|
|||||||
v-if="showFieldNames && (field.name || field.title)"
|
v-if="showFieldNames && (field.name || field.title)"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
><MarkdownContent
|
>
|
||||||
v-if="field.title"
|
<MarkdownContent
|
||||||
:string="field.title"
|
v-if="field.title"
|
||||||
/>
|
:string="field.title"
|
||||||
|
/>
|
||||||
<template v-else>{{ field.name }}</template>
|
<template v-else>{{ field.name }}</template>
|
||||||
<template v-if="!field.required">
|
<template v-if="!field.required">
|
||||||
({{ t('optional') }})
|
<span :class="{ 'hidden sm:inline': (field.title || field.name).length > 20 }">
|
||||||
|
({{ t('optional') }})
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
@@ -21,7 +24,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
v-if="!modelValue && !sessionId"
|
v-if="!modelValue && !sessionId"
|
||||||
class="label text-2xl mb-2"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="sessionId"
|
v-if="sessionId"
|
||||||
disabled
|
disabled
|
||||||
class="base-button w-full"
|
class="base-button w-full modal-save-button"
|
||||||
>
|
>
|
||||||
<IconLoader
|
<IconLoader
|
||||||
width="22"
|
width="22"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
|
v-if="showFieldNames"
|
||||||
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
:for="isCodeSent ? 'one_time_code' : field.uuid"
|
||||||
class="label text-2xl"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
@@ -10,16 +11,13 @@
|
|||||||
:string="field.title"
|
:string="field.title"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ showFieldNames && field.name ? field.name : t('verified_phone_number') }}
|
{{ field.name || t('verified_phone_number') }}
|
||||||
<template v-if="!field.required">
|
|
||||||
({{ t('optional') }})
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -47,14 +45,21 @@
|
|||||||
<a
|
<a
|
||||||
v-if="!defaultValue"
|
v-if="!defaultValue"
|
||||||
href="#"
|
href="#"
|
||||||
class="link"
|
class="link change-phone-number-link"
|
||||||
@click.prevent="isCodeSent = false"
|
@click.prevent="isCodeSent = false"
|
||||||
>
|
>
|
||||||
{{ t('change_phone_number') }}
|
{{ t('change_phone_number') }}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<span
|
||||||
href="#"
|
v-if="resendCodeCountdown > 0"
|
||||||
class="link"
|
class="link"
|
||||||
|
>
|
||||||
|
{{ t('wait_countdown_seconds').replace('{countdown}', resendCodeCountdown) }}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
href="#"
|
||||||
|
class="link resend-code-link"
|
||||||
@click.prevent="resendCode"
|
@click.prevent="resendCode"
|
||||||
>
|
>
|
||||||
{{ isResendLoading ? t('sending') : t('resend_code') }}
|
{{ isResendLoading ? t('sending') : t('resend_code') }}
|
||||||
@@ -63,13 +68,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="!isCodeSent"
|
v-show="!isCodeSent"
|
||||||
class="flex w-full rounded-full outline-neutral-content outline-2 outline-offset-2 focus-within:outline"
|
class="flex w-full rounded-full outline-neutral-content outline-2 outline-offset-2 focus-within:outline phone-number-input-container"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="country_code"
|
id="country_code"
|
||||||
class="relative inline-block"
|
class="relative inline-block"
|
||||||
>
|
>
|
||||||
<div class="btn bg-base-200 border border-neutral-300 text-2xl whitespace-nowrap font-normal rounded-l-full">
|
<div class="btn bg-base-200 border border-neutral-300 text-2xl whitespace-nowrap font-normal rounded-l-full country-code-select-label">
|
||||||
{{ selectedCountry.flag }} +{{ selectedCountry.dial }}
|
{{ selectedCountry.flag }} +{{ selectedCountry.dial }}
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
@@ -173,6 +178,8 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isCodeSent: false,
|
isCodeSent: false,
|
||||||
|
codeSentAt: null,
|
||||||
|
resendCodeCountdown: 0,
|
||||||
isResendLoading: false,
|
isResendLoading: false,
|
||||||
phoneValue: this.modelValue || this.defaultValue || '',
|
phoneValue: this.modelValue || this.defaultValue || '',
|
||||||
selectedCountry: {}
|
selectedCountry: {}
|
||||||
@@ -220,6 +227,11 @@ export default {
|
|||||||
this.selectedCountry = this.countries.find((country) => country.tz.includes(tz)) || this.countries[0]
|
this.selectedCountry = this.countries.find((country) => country.tz.includes(tz)) || this.countries[0]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
emitSubmit: throttle(function (e) {
|
emitSubmit: throttle(function (e) {
|
||||||
this.$emit('submit')
|
this.$emit('submit')
|
||||||
@@ -246,13 +258,28 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
resendCode () {
|
resendCode () {
|
||||||
this.isResendLoading = true
|
if (this.codeSentAt && Date.now() - this.codeSentAt < 15000) {
|
||||||
|
this.startResendCodeCountdown()
|
||||||
|
} else {
|
||||||
|
this.isResendLoading = true
|
||||||
|
|
||||||
this.sendVerificationCode().finally(() => {
|
this.sendVerificationCode().then(() => {
|
||||||
alert(this.t('verification_code_has_been_resent'))
|
alert(this.t('verification_code_has_been_resent'))
|
||||||
|
}).finally(() => {
|
||||||
|
this.isResendLoading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startResendCodeCountdown () {
|
||||||
|
this.resendCodeCountdown = 15 - parseInt((Date.now() - this.codeSentAt) / 1000)
|
||||||
|
|
||||||
this.isResendLoading = false
|
this.interval = setInterval(() => {
|
||||||
})
|
this.resendCodeCountdown--
|
||||||
|
|
||||||
|
if (this.resendCodeCountdown <= 0) {
|
||||||
|
clearInterval(this.interval)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
},
|
},
|
||||||
sendVerificationCode () {
|
sendVerificationCode () {
|
||||||
return fetch(this.baseUrl + '/api/send_phone_verification_code', {
|
return fetch(this.baseUrl + '/api/send_phone_verification_code', {
|
||||||
@@ -264,12 +291,20 @@ export default {
|
|||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
}).then(async (resp) => {
|
}).then(async (resp) => {
|
||||||
if (resp.status === 422) {
|
if ([422, 429].includes(resp.status)) {
|
||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
|
|
||||||
alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue))
|
if (resp.status === 422) {
|
||||||
|
alert(data.error || this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue))
|
||||||
|
} else if (resp.status === 429) {
|
||||||
|
alert(data.error)
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error))
|
||||||
|
} else if (resp.ok) {
|
||||||
|
this.codeSentAt = Date.now()
|
||||||
|
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,30 +4,31 @@
|
|||||||
class="relative"
|
class="relative"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex justify-between items-center w-full"
|
class="flex justify-between items-end w-full mb-3.5 md:mb-4"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="label text-2xl"
|
v-if="showFieldNames"
|
||||||
|
class="label text-xl sm:text-2xl py-0 field-name-label"
|
||||||
>
|
>
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
:string="field.title"
|
:string="field.title"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ showFieldNames && field.name ? field.name : t('signature') }}
|
{{ field.name || t('signature') }}
|
||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<div class="space-x-2 flex">
|
<div class="space-x-2 flex flex-none">
|
||||||
<span
|
<span
|
||||||
v-if="isTextSignature && field.preferences?.format !== 'typed'"
|
v-if="isTextSignature && format !== 'typed' && format !== 'upload'"
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
:data-tip="t('draw_signature')"
|
:data-tip="t('draw_signature')"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline btn-sm font-medium"
|
class="btn btn-outline btn-sm font-medium type-text-button"
|
||||||
@click.prevent="[toggleTextInput(), hideQr()]"
|
@click.prevent="[toggleTextInput(), hideQr()]"
|
||||||
>
|
>
|
||||||
<IconSignature :width="16" />
|
<IconSignature :width="16" />
|
||||||
@@ -37,14 +38,15 @@
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="withTypedSignature && field.preferences?.format !== 'typed' && field.preferences?.format !== 'drawn'"
|
v-else-if="withTypedSignature && format !== 'typed' && format !== 'drawn' && format !== 'upload'"
|
||||||
class="tooltip ml-2"
|
class="tooltip ml-2"
|
||||||
|
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
||||||
:data-tip="t('type_text')"
|
:data-tip="t('type_text')"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
id="type_text_button"
|
id="type_text_button"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap"
|
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap type-text-button"
|
||||||
@click.prevent="[toggleTextInput(), hideQr()]"
|
@click.prevent="[toggleTextInput(), hideQr()]"
|
||||||
>
|
>
|
||||||
<IconTextSize :width="16" />
|
<IconTextSize :width="16" />
|
||||||
@@ -54,13 +56,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="field.preferences?.format !== 'typed' && field.preferences?.format !== 'drawn'"
|
v-if="format !== 'typed' && format !== 'drawn' && format !== 'upload' && format !== 'drawn_or_typed'"
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
|
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
||||||
:data-tip="t('take_photo')"
|
:data-tip="t('take_photo')"
|
||||||
>
|
>
|
||||||
<label
|
<label class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap upload-image-button">
|
||||||
class="btn btn-outline btn-sm font-medium inline-flex flex-nowrap"
|
|
||||||
>
|
|
||||||
<IconCamera :width="16" />
|
<IconCamera :width="16" />
|
||||||
<input
|
<input
|
||||||
:key="uploadImageInputKey"
|
:key="uploadImageInputKey"
|
||||||
@@ -77,14 +78,14 @@
|
|||||||
<a
|
<a
|
||||||
v-if="modelValue || computedPreviousValue"
|
v-if="modelValue || computedPreviousValue"
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-outline btn-sm font-medium"
|
class="btn btn-outline btn-sm font-medium reupload-button"
|
||||||
@click.prevent="remove"
|
@click.prevent="remove"
|
||||||
>
|
>
|
||||||
<IconReload :width="16" />
|
<IconReload :width="16" />
|
||||||
{{ t('redraw') }}
|
{{ t(format === 'upload' ? 'reupload' : 'redraw') }}
|
||||||
</a>
|
</a>
|
||||||
<span
|
<span
|
||||||
v-if="withQrButton && !modelValue && !computedPreviousValue && field.preferences?.format !== 'typed'"
|
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed' && format !== 'upload'"
|
||||||
class=" tooltip"
|
class=" tooltip"
|
||||||
:data-tip="t('drawn_signature_on_a_touchscreen_device')"
|
:data-tip="t('drawn_signature_on_a_touchscreen_device')"
|
||||||
>
|
>
|
||||||
@@ -116,7 +117,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +132,18 @@
|
|||||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<FileDropzone
|
||||||
|
v-if="format === 'upload' && !modelValue"
|
||||||
|
:message="`${t('upload')} ${field.name || t('signature')}`"
|
||||||
|
:submitter-slug="submitterSlug"
|
||||||
|
:dry-run="dryRun"
|
||||||
|
:accept="'image/*'"
|
||||||
|
@upload="[$emit('attached', $event[0]), $emit('update:model-value', $event[0].uuid)]"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="relative"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!modelValue && !computedPreviousValue && !isShowQr && !isTextSignature && isSignatureStarted"
|
v-if="!modelValue && !computedPreviousValue && !isShowQr && !isTextSignature && isSignatureStarted"
|
||||||
class="absolute top-0.5 right-0.5"
|
class="absolute top-0.5 right-0.5"
|
||||||
@@ -153,7 +165,7 @@
|
|||||||
v-show="!modelValue && !computedPreviousValue"
|
v-show="!modelValue && !computedPreviousValue"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
style="padding: 1px; 0"
|
style="padding: 1px; 0"
|
||||||
class="bg-white border border-base-300 rounded-2xl w-full"
|
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="isShowQr"
|
v-if="isShowQr"
|
||||||
@@ -280,8 +292,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonalMinimize2, IconQrcode, IconX } from '@tabler/icons-vue'
|
import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonalMinimize2, IconQrcode, IconX } from '@tabler/icons-vue'
|
||||||
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
||||||
|
import { isValidSignatureCanvas } from './validate_signature'
|
||||||
import SignaturePad from 'signature_pad'
|
import SignaturePad from 'signature_pad'
|
||||||
import AppearsOn from './appears_on'
|
import AppearsOn from './appears_on'
|
||||||
|
import FileDropzone from './dropzone'
|
||||||
import MarkdownContent from './markdown_content'
|
import MarkdownContent from './markdown_content'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
|
|
||||||
@@ -294,6 +308,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
AppearsOn,
|
AppearsOn,
|
||||||
IconReload,
|
IconReload,
|
||||||
|
FileDropzone,
|
||||||
IconCamera,
|
IconCamera,
|
||||||
IconQrcode,
|
IconQrcode,
|
||||||
MarkdownContent,
|
MarkdownContent,
|
||||||
@@ -388,6 +403,9 @@ export default {
|
|||||||
submitterSlug () {
|
submitterSlug () {
|
||||||
return this.submitter.slug
|
return this.submitter.slug
|
||||||
},
|
},
|
||||||
|
format () {
|
||||||
|
return this.field.preferences?.format
|
||||||
|
},
|
||||||
defaultReasons () {
|
defaultReasons () {
|
||||||
return {
|
return {
|
||||||
[this.t('approved_by')]: this.t('approved'),
|
[this.t('approved_by')]: this.t('approved'),
|
||||||
@@ -581,6 +599,7 @@ export default {
|
|||||||
},
|
},
|
||||||
drawImage (event) {
|
drawImage (event) {
|
||||||
this.remove()
|
this.remove()
|
||||||
|
this.clear()
|
||||||
this.isSignatureStarted = true
|
this.isSignatureStarted = true
|
||||||
|
|
||||||
this.drawOnCanvas(event.target.files[0], this.$refs.canvas)
|
this.drawOnCanvas(event.target.files[0], this.$refs.canvas)
|
||||||
@@ -663,6 +682,16 @@ export default {
|
|||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isSignatureStarted && this.pad.toData().length > 0 && !isValidSignatureCanvas(this.pad.toData())) {
|
||||||
|
if (this.field.required === true || this.pad.toData().length > 0) {
|
||||||
|
alert(this.t('signature_is_too_small_or_simple_please_redraw'))
|
||||||
|
|
||||||
|
return Promise.reject(new Error('Image too small or simple'))
|
||||||
|
} else {
|
||||||
|
Promise.resolve({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
cropCanvasAndExportToPNG(this.$refs.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
cropCanvasAndExportToPNG(this.$refs.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
||||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||||
@@ -701,10 +730,12 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error.message === 'Image too small' && this.field.required === false) {
|
if (this.field.required === true) {
|
||||||
return resolve({})
|
alert(this.t('signature_is_too_small_or_simple_please_redraw'))
|
||||||
} else {
|
|
||||||
return reject(error)
|
return reject(error)
|
||||||
|
} else {
|
||||||
|
return resolve({})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,15 +3,18 @@
|
|||||||
v-if="showFieldNames && (field.name || field.title)"
|
v-if="showFieldNames && (field.name || field.title)"
|
||||||
:for="field.uuid"
|
:for="field.uuid"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="label text-2xl"
|
class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label"
|
||||||
:class="{ 'mb-2': !field.description }"
|
:class="{ 'mb-2': !field.description }"
|
||||||
><MarkdownContent
|
>
|
||||||
v-if="field.title"
|
<MarkdownContent
|
||||||
:string="field.title"
|
v-if="field.title"
|
||||||
/>
|
:string="field.title"
|
||||||
|
/>
|
||||||
<template v-else>{{ field.name }}</template>
|
<template v-else>{{ field.name }}</template>
|
||||||
<template v-if="!field.required">
|
<template v-if="!field.required">
|
||||||
({{ t('optional') }})
|
<span :class="{ 'hidden sm:inline': (field.title || field.name).length > 20 }">
|
||||||
|
({{ t('optional') }})
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
@@ -21,7 +24,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-3 px-1"
|
class="mb-3 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
@@ -64,7 +67,7 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-ghost btn-circle btn-sm"
|
class="btn btn-ghost btn-circle btn-sm toggle-multiline-text-button"
|
||||||
@click.prevent="toggleTextArea"
|
@click.prevent="toggleTextArea"
|
||||||
>
|
>
|
||||||
<IconAlignBoxLeftTop />
|
<IconAlignBoxLeftTop />
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
function isValidSignatureCanvas (data) {
|
||||||
|
if (data.length === 0) return false
|
||||||
|
|
||||||
|
const strokes = data.filter(stroke => Array.isArray(stroke.points) && stroke.points.length > 2)
|
||||||
|
|
||||||
|
if (strokes.length === 0) return false
|
||||||
|
|
||||||
|
let skippedStraightLine = 0
|
||||||
|
|
||||||
|
const validStrokes = strokes.filter(stroke => {
|
||||||
|
const points = stroke.points
|
||||||
|
const first = points[0]
|
||||||
|
const last = points[points.length - 1]
|
||||||
|
const A = last.y - first.y
|
||||||
|
const B = first.x - last.x
|
||||||
|
const C = last.x * first.y - first.x * last.y
|
||||||
|
const lineLength = Math.sqrt(A * A + B * B)
|
||||||
|
|
||||||
|
const totalDeviation = points.reduce((sum, p) => {
|
||||||
|
const distanceToLine = Math.abs(A * p.x + B * p.y + C) / lineLength
|
||||||
|
return sum + distanceToLine
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
const avgDeviation = totalDeviation / points.length
|
||||||
|
|
||||||
|
if (avgDeviation < 3 && skippedStraightLine < 2) {
|
||||||
|
skippedStraightLine++
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return validStrokes.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isValidSignatureCanvas }
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<label class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label">
|
||||||
class="label text-2xl mb-2"
|
|
||||||
>
|
|
||||||
<MarkdownContent
|
<MarkdownContent
|
||||||
v-if="field.title"
|
v-if="field.title"
|
||||||
:string="field.title"
|
:string="field.title"
|
||||||
@@ -11,13 +9,13 @@
|
|||||||
<div
|
<div
|
||||||
v-if="field.description"
|
v-if="field.description"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
class="mb-4 px-1"
|
class="mb-4 px-1 field-description-text"
|
||||||
>
|
>
|
||||||
<MarkdownContent :string="field.description" />
|
<MarkdownContent :string="field.description" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="emptyValueRequiredStep && emptyValueRequiredStep[0] !== field"
|
v-if="emptyValueRequiredStep && emptyValueRequiredStep[0] !== field"
|
||||||
class="px-1"
|
class="px-1 field-description-text"
|
||||||
>
|
>
|
||||||
{{ t('complete_all_required_fields_to_proceed_with_identity_verification') }}
|
{{ t('complete_all_required_fields_to_proceed_with_identity_verification') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -31,6 +29,15 @@
|
|||||||
class="animate-spin h-10"
|
class="animate-spin h-10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="redirectUrl">
|
||||||
|
<a
|
||||||
|
:href="redirectUrl"
|
||||||
|
target="_blank"
|
||||||
|
class="white-button w-full"
|
||||||
|
>
|
||||||
|
{{ t('verify_id') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="widgetContainer"
|
ref="widgetContainer"
|
||||||
/>
|
/>
|
||||||
@@ -87,6 +94,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
isCreatingCheckout: false,
|
isCreatingCheckout: false,
|
||||||
isMathLoaded: false,
|
isMathLoaded: false,
|
||||||
|
redirectUrl: '',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
eidEasyData: {}
|
eidEasyData: {}
|
||||||
}
|
}
|
||||||
@@ -125,12 +133,16 @@ export default {
|
|||||||
async mounted () {
|
async mounted () {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
|
|
||||||
Promise.all([
|
if (new URLSearchParams(window.location.search).get('submit') === 'true') {
|
||||||
import('@eid-easy/eideasy-widget'),
|
this.$emit('submit')
|
||||||
this.start()
|
} else {
|
||||||
]).finally(() => {
|
Promise.all([
|
||||||
this.isLoading = false
|
import('@eid-easy/eideasy-widget'),
|
||||||
})
|
this.start()
|
||||||
|
]).finally(() => {
|
||||||
|
this.isLoading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
start () {
|
start () {
|
||||||
@@ -143,14 +155,26 @@ export default {
|
|||||||
}).then(async (resp) => {
|
}).then(async (resp) => {
|
||||||
this.eidEasyData = await resp.json()
|
this.eidEasyData = await resp.json()
|
||||||
|
|
||||||
const eidEasyWidget = document.createElement('eideasy-widget')
|
if (this.eidEasyData.available_methods[0] === 'itsme-qes-signature' &&
|
||||||
|
this.eidEasyData.available_methods.length === 1) {
|
||||||
|
const redirectUrl = new URL('https://id.eideasy.com/sign_contract_external')
|
||||||
|
|
||||||
for (const key in this.widgetSettings) {
|
redirectUrl.searchParams.append('client_id', this.eidEasyData.client_id)
|
||||||
eidEasyWidget[key] = this.widgetSettings[key]
|
redirectUrl.searchParams.append('doc_id', this.eidEasyData.doc_id)
|
||||||
|
redirectUrl.searchParams.append('country', this.countryCode)
|
||||||
|
redirectUrl.searchParams.append('lang', this.locale)
|
||||||
|
|
||||||
|
this.redirectUrl = redirectUrl.toString()
|
||||||
|
} else {
|
||||||
|
const eidEasyWidget = document.createElement('eideasy-widget')
|
||||||
|
|
||||||
|
for (const key in this.widgetSettings) {
|
||||||
|
eidEasyWidget[key] = this.widgetSettings[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.widgetContainer.innerHTML = ''
|
||||||
|
this.$refs.widgetContainer.appendChild(eidEasyWidget)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$refs.widgetContainer.innerHTML = ''
|
|
||||||
this.$refs.widgetContainer.appendChild(eidEasyWidget)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async submit () {
|
async submit () {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="absolute overflow-visible group"
|
class="absolute overflow-visible group field-area-container"
|
||||||
:style="positionStyle"
|
:style="positionStyle"
|
||||||
|
:class="{ 'z-[1]': isMoved || isDragged }"
|
||||||
@pointerdown.stop
|
@pointerdown.stop
|
||||||
@mousedown="startMouseMove"
|
@mousedown="startMouseMove"
|
||||||
@touchstart="startTouchDrag"
|
@touchstart="startTouchDrag"
|
||||||
@@ -9,7 +10,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="isSelected || isDraw"
|
v-if="isSelected || isDraw"
|
||||||
class="top-0 bottom-0 right-0 left-0 absolute border border-1.5 pointer-events-none"
|
class="top-0 bottom-0 right-0 left-0 absolute border border-1.5 pointer-events-none"
|
||||||
:class="field.type === 'heading' ? '' : borderColors[submitterIndex]"
|
:class="field.type === 'heading' ? '' : borderColors[submitterIndex % borderColors.length]"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="field.type === 'cells' && (isSelected || isDraw)"
|
v-if="field.type === 'cells' && (isSelected || isDraw)"
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
v-for="(cellW, index) in cells"
|
v-for="(cellW, index) in cells"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="absolute top-0 bottom-0 border-r"
|
class="absolute top-0 bottom-0 border-r"
|
||||||
:class="field.type === 'heading' ? '' : borderColors[submitterIndex]"
|
:class="field.type === 'heading' ? '' : borderColors[submitterIndex % borderColors.length]"
|
||||||
:style="{ left: (cellW / area.w * 100) + '%' }"
|
:style="{ left: (cellW / area.w * 100) + '%' }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field?.type && (isSelected || isNameFocus)"
|
v-if="field?.type && (isSelected || isNameFocus)"
|
||||||
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap flex z-10"
|
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap flex z-10 field-area-controls"
|
||||||
style="top: -25px; height: 25px"
|
style="top: -25px; height: 25px"
|
||||||
@mousedown.stop
|
@mousedown.stop
|
||||||
@pointerdown.stop
|
@pointerdown.stop
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
<FieldSubmitter
|
<FieldSubmitter
|
||||||
v-if="field.type != 'heading'"
|
v-if="field.type != 'heading'"
|
||||||
v-model="field.submitter_uuid"
|
v-model="field.submitter_uuid"
|
||||||
class="border-r"
|
class="border-r roles-dropdown"
|
||||||
:compact="true"
|
:compact="true"
|
||||||
:editable="editable && (!defaultField || defaultField.role !== submitter?.name)"
|
:editable="editable && (!defaultField || defaultField.role !== submitter?.name)"
|
||||||
:allow-add-new="!defaultSubmitters.length"
|
:allow-add-new="!defaultSubmitters.length"
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
@keydown.enter.prevent="onNameEnter"
|
@keydown.enter.prevent="onNameEnter"
|
||||||
@focus="onNameFocus"
|
@focus="onNameFocus"
|
||||||
@blur="onNameBlur"
|
@blur="onNameBlur"
|
||||||
>{{ optionIndexText }} {{ (defaultField ? (field.title || field.name) : field.name) || defaultName }}</span>
|
>{{ optionIndexText }} {{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || defaultName }}</span>
|
||||||
<div
|
<div
|
||||||
v-if="isSettingsFocus || (isValueInput && field.type !== 'heading') || (isNameFocus && !['checkbox', 'phone'].includes(field.type))"
|
v-if="isSettingsFocus || (isValueInput && field.type !== 'heading') || (isNameFocus && !['checkbox', 'phone'].includes(field.type))"
|
||||||
class="flex items-center ml-1.5"
|
class="flex items-center ml-1.5"
|
||||||
@@ -107,7 +108,7 @@
|
|||||||
>{{ t('editable') }}</label>
|
>{{ t('editable') }}</label>
|
||||||
<span
|
<span
|
||||||
v-if="field.type !== 'payment' && !isValueInput"
|
v-if="field.type !== 'payment' && !isValueInput"
|
||||||
class="dropdown dropdown-end"
|
class="dropdown dropdown-end field-area-settings-dropdown"
|
||||||
@mouseenter="renderDropdown = true"
|
@mouseenter="renderDropdown = true"
|
||||||
@touchstart="renderDropdown = true"
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
@@ -140,6 +141,7 @@
|
|||||||
:with-required="false"
|
:with-required="false"
|
||||||
:with-areas="false"
|
:with-areas="false"
|
||||||
@click-formula="isShowFormulaModal = true"
|
@click-formula="isShowFormulaModal = true"
|
||||||
|
@click-font="isShowFontModal = true"
|
||||||
@click-description="isShowDescriptionModal = true"
|
@click-description="isShowDescriptionModal = true"
|
||||||
@click-condition="isShowConditionsModal = true"
|
@click-condition="isShowConditionsModal = true"
|
||||||
@scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]"
|
@scroll-to="[selectedAreaRef.value = $event, $emit('scroll-to', $event)]"
|
||||||
@@ -158,15 +160,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="touchValueTarget"
|
ref="touchValueTarget"
|
||||||
class="flex items-center h-full w-full"
|
class="flex h-full w-full field-area"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex], 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 items-center']"
|
||||||
@click="focusValueInput"
|
@click="focusValueInput"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="field"
|
v-if="field"
|
||||||
class="flex justify-center items-center space-x-1"
|
class="flex justify-center items-center space-x-1"
|
||||||
:class="{ 'w-full': ['cells', 'checkbox'].includes(field.type), 'h-full': !isValueInput }"
|
:class="{ 'w-full': ['cells', 'checkbox'].includes(field.type), 'h-full': !isValueInput && !isDefaultValuePresent }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas && field.type !== 'checkbox')"
|
v-if="isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas && field.type !== 'checkbox')"
|
||||||
@@ -211,7 +213,7 @@
|
|||||||
:contenteditable="isValueInput"
|
:contenteditable="isValueInput"
|
||||||
class="whitespace-pre-wrap outline-none empty:before:content-[attr(placeholder)] before:text-gray-400"
|
class="whitespace-pre-wrap outline-none empty:before:content-[attr(placeholder)] before:text-gray-400"
|
||||||
:class="{ 'cursor-text': isValueInput }"
|
:class="{ 'cursor-text': isValueInput }"
|
||||||
:placeholder="withFieldPlaceholder && !isValueInput ? field.name || defaultName : t('type_value')"
|
:placeholder="withFieldPlaceholder && !isValueInput ? defaultField?.title || field.title || field.name || defaultName : t('type_value')"
|
||||||
@blur="onDefaultValueBlur"
|
@blur="onDefaultValueBlur"
|
||||||
@paste.prevent="onPaste"
|
@paste.prevent="onPaste"
|
||||||
@keydown.enter="onDefaultValueEnter"
|
@keydown.enter="onDefaultValueEnter"
|
||||||
@@ -252,6 +254,17 @@
|
|||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowFontModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<FontModal
|
||||||
|
:field="field"
|
||||||
|
:editable="editable && !defaultField"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
@close="isShowFontModal = false"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
<Teleport
|
<Teleport
|
||||||
v-if="isShowConditionsModal"
|
v-if="isShowConditionsModal"
|
||||||
:to="modalContainerEl"
|
:to="modalContainerEl"
|
||||||
@@ -282,6 +295,7 @@ import FieldType from './field_type'
|
|||||||
import Field from './field'
|
import Field from './field'
|
||||||
import FieldSettings from './field_settings'
|
import FieldSettings from './field_settings'
|
||||||
import FormulaModal from './formula_modal'
|
import FormulaModal from './formula_modal'
|
||||||
|
import FontModal from './font_modal'
|
||||||
import ConditionsModal from './conditions_modal'
|
import ConditionsModal from './conditions_modal'
|
||||||
import DescriptionModal from './description_modal'
|
import DescriptionModal from './description_modal'
|
||||||
import { IconX, IconCheck, IconDotsVertical } from '@tabler/icons-vue'
|
import { IconX, IconCheck, IconDotsVertical } from '@tabler/icons-vue'
|
||||||
@@ -294,6 +308,7 @@ export default {
|
|||||||
IconCheck,
|
IconCheck,
|
||||||
FieldSettings,
|
FieldSettings,
|
||||||
FormulaModal,
|
FormulaModal,
|
||||||
|
FontModal,
|
||||||
IconDotsVertical,
|
IconDotsVertical,
|
||||||
DescriptionModal,
|
DescriptionModal,
|
||||||
ConditionsModal,
|
ConditionsModal,
|
||||||
@@ -316,6 +331,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
maxPage: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
defaultField: {
|
defaultField: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -346,6 +366,7 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isShowFormulaModal: false,
|
isShowFormulaModal: false,
|
||||||
|
isShowFontModal: false,
|
||||||
isShowConditionsModal: false,
|
isShowConditionsModal: false,
|
||||||
isContenteditable: false,
|
isContenteditable: false,
|
||||||
isSettingsFocus: false,
|
isSettingsFocus: false,
|
||||||
@@ -355,6 +376,7 @@ export default {
|
|||||||
isMoved: false,
|
isMoved: false,
|
||||||
renderDropdown: false,
|
renderDropdown: false,
|
||||||
isNameFocus: false,
|
isNameFocus: false,
|
||||||
|
isHeadingSelected: false,
|
||||||
textOverflowChars: 0,
|
textOverflowChars: 0,
|
||||||
dragFrom: { x: 0, y: 0 }
|
dragFrom: { x: 0, y: 0 }
|
||||||
}
|
}
|
||||||
@@ -371,7 +393,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
isValueInput () {
|
isValueInput () {
|
||||||
return (this.field.type === 'heading' && this.isSelected) || this.isContenteditable || (this.inputMode && ['text', 'number', 'date'].includes(this.field.type))
|
return (this.field.type === 'heading' && this.isHeadingSelected) || this.isContenteditable || (this.inputMode && ['text', 'number', 'date'].includes(this.field.type))
|
||||||
},
|
},
|
||||||
modalContainerEl () {
|
modalContainerEl () {
|
||||||
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||||
@@ -379,11 +401,22 @@ export default {
|
|||||||
defaultName () {
|
defaultName () {
|
||||||
return this.buildDefaultName(this.field, this.template.fields)
|
return this.buildDefaultName(this.field, this.template.fields)
|
||||||
},
|
},
|
||||||
alignClasses () {
|
fontClasses () {
|
||||||
|
if (!this.field.preferences) {
|
||||||
|
return { 'items-center': true }
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
center: 'justify-center',
|
'items-center': !this.field.preferences.valign || this.field.preferences.valign === 'center',
|
||||||
left: 'justify-start',
|
'items-start': this.field.preferences.valign === 'top',
|
||||||
right: 'justify-end'
|
'items-end': this.field.preferences.valign === 'bottom',
|
||||||
|
'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 () {
|
optionIndexText () {
|
||||||
@@ -414,16 +447,6 @@ export default {
|
|||||||
},
|
},
|
||||||
borderColors () {
|
borderColors () {
|
||||||
return [
|
return [
|
||||||
'border-red-500/80',
|
|
||||||
'border-sky-500/80',
|
|
||||||
'border-emerald-500/80',
|
|
||||||
'border-yellow-300/80',
|
|
||||||
'border-purple-600/80',
|
|
||||||
'border-pink-500/80',
|
|
||||||
'border-cyan-500/80',
|
|
||||||
'border-orange-500/80',
|
|
||||||
'border-lime-500/80',
|
|
||||||
'border-indigo-500/80',
|
|
||||||
'border-red-500/80',
|
'border-red-500/80',
|
||||||
'border-sky-500/80',
|
'border-sky-500/80',
|
||||||
'border-emerald-500/80',
|
'border-emerald-500/80',
|
||||||
@@ -438,16 +461,6 @@ export default {
|
|||||||
},
|
},
|
||||||
bgColors () {
|
bgColors () {
|
||||||
return [
|
return [
|
||||||
'bg-red-100',
|
|
||||||
'bg-sky-100',
|
|
||||||
'bg-emerald-100',
|
|
||||||
'bg-yellow-100',
|
|
||||||
'bg-purple-100',
|
|
||||||
'bg-pink-100',
|
|
||||||
'bg-cyan-100',
|
|
||||||
'bg-orange-100',
|
|
||||||
'bg-lime-100',
|
|
||||||
'bg-indigo-100',
|
|
||||||
'bg-red-100',
|
'bg-red-100',
|
||||||
'bg-sky-100',
|
'bg-sky-100',
|
||||||
'bg-emerald-100',
|
'bg-emerald-100',
|
||||||
@@ -493,13 +506,17 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
buildDefaultName: Field.methods.buildDefaultName,
|
buildDefaultName: Field.methods.buildDefaultName,
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
this.$el.getRootNode().activeElement.blur()
|
||||||
},
|
},
|
||||||
maybeToggleDefaultValue () {
|
maybeToggleDefaultValue () {
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (['text', 'number'].includes(this.field.type)) {
|
if (['text', 'number'].includes(this.field.type)) {
|
||||||
this.isContenteditable = true
|
this.isContenteditable = true
|
||||||
|
|
||||||
this.$nextTick(() => this.focusValueInput())
|
this.focusValueInput()
|
||||||
} else if (this.field.type === 'checkbox') {
|
} else if (this.field.type === 'checkbox') {
|
||||||
this.field.readonly = !this.field.readonly
|
this.field.readonly = !this.field.readonly
|
||||||
this.field.default_value === true ? delete this.field.default_value : this.field.default_value = true
|
this.field.default_value === true ? delete this.field.default_value : this.field.default_value = true
|
||||||
@@ -521,20 +538,28 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusValueInput (e) {
|
focusValueInput (e) {
|
||||||
if (this.$refs.defaultValue !== document.activeElement) {
|
this.$nextTick(() => {
|
||||||
this.$refs.defaultValue.focus()
|
if (this.$refs.defaultValue && this.$refs.defaultValue !== document.activeElement) {
|
||||||
|
this.$refs.defaultValue.focus()
|
||||||
|
|
||||||
if (this.$refs.defaultValue.innerText.length && this.$refs.defaultValue !== e?.target) {
|
if (this.$refs.defaultValue.innerText.length && this.$refs.defaultValue !== e?.target) {
|
||||||
window.getSelection().collapse(
|
window.getSelection().collapse(
|
||||||
this.$refs.defaultValue.firstChild,
|
this.$refs.defaultValue.firstChild,
|
||||||
this.$refs.defaultValue.innerText.length
|
this.$refs.defaultValue.innerText.length
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
formatNumber (number, format) {
|
formatNumber (number, format) {
|
||||||
if (format === 'comma') {
|
if (format === 'comma') {
|
||||||
return new Intl.NumberFormat('en-US').format(number)
|
return new Intl.NumberFormat('en-US').format(number)
|
||||||
|
} else if (format === 'usd') {
|
||||||
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||||
|
} else if (format === 'gbp') {
|
||||||
|
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||||
|
} else if (format === 'eur') {
|
||||||
|
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||||
} else if (format === 'dot') {
|
} else if (format === 'dot') {
|
||||||
return new Intl.NumberFormat('de-DE').format(number)
|
return new Intl.NumberFormat('de-DE').format(number)
|
||||||
} else if (format === 'space') {
|
} else if (format === 'space') {
|
||||||
@@ -640,6 +665,7 @@ export default {
|
|||||||
const text = this.$refs.defaultValue.innerText.trim()
|
const text = this.$refs.defaultValue.innerText.trim()
|
||||||
|
|
||||||
this.isContenteditable = false
|
this.isContenteditable = false
|
||||||
|
this.isHeadingSelected = false
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
if (this.field.type === 'number') {
|
if (this.field.type === 'number') {
|
||||||
@@ -720,12 +746,18 @@ export default {
|
|||||||
const rect = page.getBoundingClientRect()
|
const rect = page.getBoundingClientRect()
|
||||||
|
|
||||||
this.area.x = Math.min(Math.max((this.dragFrom.x + e.touches[0].clientX - rect.left) / rect.width, 0), 1 - this.area.w)
|
this.area.x = Math.min(Math.max((this.dragFrom.x + e.touches[0].clientX - rect.left) / rect.width, 0), 1 - this.area.w)
|
||||||
this.area.y = Math.min(Math.max((this.dragFrom.y + e.touches[0].clientY - rect.top) / rect.height, 0), 1 - this.area.h)
|
this.area.y = (this.dragFrom.y + e.touches[0].clientY - rect.top) / rect.height
|
||||||
|
|
||||||
|
if ((this.area.page === 0 && this.area.y < 0) || (this.area.page === this.maxPage && this.area.y > 1 - this.area.h)) {
|
||||||
|
this.area.y = Math.min(Math.max(this.area.y, 0), 1 - this.area.h)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
stopTouchDrag () {
|
stopTouchDrag () {
|
||||||
this.$el.getRootNode().removeEventListener('touchmove', this.touchDrag)
|
this.$el.getRootNode().removeEventListener('touchmove', this.touchDrag)
|
||||||
this.$el.getRootNode().removeEventListener('touchend', this.stopTouchDrag)
|
this.$el.getRootNode().removeEventListener('touchend', this.stopTouchDrag)
|
||||||
|
|
||||||
|
this.maybeChangeAreaPage(this.area)
|
||||||
|
|
||||||
if (this.isDragged) {
|
if (this.isDragged) {
|
||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
@@ -751,10 +783,6 @@ export default {
|
|||||||
|
|
||||||
this.selectedAreaRef.value = this.area
|
this.selectedAreaRef.value = this.area
|
||||||
|
|
||||||
if (this.field.type === 'heading') {
|
|
||||||
this.$nextTick(() => this.focusValueInput())
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dragFrom = { x: rect.left - e.clientX, y: rect.top - e.clientY }
|
this.dragFrom = { x: rect.left - e.clientX, y: rect.top - e.clientY }
|
||||||
|
|
||||||
this.$el.getRootNode().addEventListener('mousemove', this.mouseMove)
|
this.$el.getRootNode().addEventListener('mousemove', this.mouseMove)
|
||||||
@@ -773,21 +801,42 @@ export default {
|
|||||||
const rect = page.getBoundingClientRect()
|
const rect = page.getBoundingClientRect()
|
||||||
|
|
||||||
this.area.x = Math.min(Math.max((this.dragFrom.x + e.clientX - rect.left) / rect.width, 0), 1 - this.area.w)
|
this.area.x = Math.min(Math.max((this.dragFrom.x + e.clientX - rect.left) / rect.width, 0), 1 - this.area.w)
|
||||||
this.area.y = Math.min(Math.max((this.dragFrom.y + e.clientY - rect.top) / rect.height, 0), 1 - this.area.h)
|
this.area.y = (this.dragFrom.y + e.clientY - rect.top) / rect.height
|
||||||
|
|
||||||
|
if ((this.area.page === 0 && this.area.y < 0) || (this.area.page === this.maxPage && this.area.y > 1 - this.area.h)) {
|
||||||
|
this.area.y = Math.min(Math.max(this.area.y, 0), 1 - this.area.h)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
stopMouseMove (e) {
|
stopMouseMove (e) {
|
||||||
this.$el.getRootNode().removeEventListener('mousemove', this.mouseMove)
|
this.$el.getRootNode().removeEventListener('mousemove', this.mouseMove)
|
||||||
this.$el.getRootNode().removeEventListener('mouseup', this.stopMouseMove)
|
this.$el.getRootNode().removeEventListener('mouseup', this.stopMouseMove)
|
||||||
|
|
||||||
|
this.maybeChangeAreaPage(this.area)
|
||||||
|
|
||||||
if (this.isMoved) {
|
if (this.isMoved) {
|
||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.field.type === 'heading') {
|
||||||
|
this.isHeadingSelected = !this.isMoved
|
||||||
|
|
||||||
|
this.focusValueInput()
|
||||||
|
}
|
||||||
|
|
||||||
this.isDragged = false
|
this.isDragged = false
|
||||||
this.isMoved = false
|
this.isMoved = false
|
||||||
|
|
||||||
this.$emit('stop-drag')
|
this.$emit('stop-drag')
|
||||||
},
|
},
|
||||||
|
maybeChangeAreaPage (area) {
|
||||||
|
if (area.y < -(area.h / 2)) {
|
||||||
|
area.page -= 1
|
||||||
|
area.y = 1 + area.y + (16.0 / this.$parent.$refs.mask.previousSibling.offsetHeight)
|
||||||
|
} else if (area.y > 1 - (area.h / 2)) {
|
||||||
|
area.page += 1
|
||||||
|
area.y = area.y - 1 - (16.0 / this.$parent.$refs.mask.previousSibling.offsetHeight)
|
||||||
|
}
|
||||||
|
},
|
||||||
stopDrag () {
|
stopDrag () {
|
||||||
this.$el.getRootNode().removeEventListener('mousemove', this.drag)
|
this.$el.getRootNode().removeEventListener('mousemove', this.drag)
|
||||||
this.$el.getRootNode().removeEventListener('mouseup', this.stopDrag)
|
this.$el.getRootNode().removeEventListener('mouseup', this.stopDrag)
|
||||||
|
|||||||
@@ -1,9 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
ref="dragContainer"
|
||||||
style="max-width: 1600px"
|
style="max-width: 1600px"
|
||||||
class="mx-auto pl-3 h-full"
|
class="mx-auto pl-3 h-full"
|
||||||
:class="isMobile ? 'pl-4' : 'md:pl-4'"
|
:class="isMobile ? 'pl-4' : 'md:pl-4'"
|
||||||
|
@dragover="onDragover"
|
||||||
|
@drop="isDragFile = false"
|
||||||
>
|
>
|
||||||
|
<HoverDropzone
|
||||||
|
v-if="sortedDocuments.length && withUploadButton && editable"
|
||||||
|
:is-dragging="isDragFile"
|
||||||
|
:template-id="template.id"
|
||||||
|
:accept-file-types="acceptFileTypes"
|
||||||
|
:with-replace-and-clone="withReplaceAndCloneUpload"
|
||||||
|
@add="[updateFromUpload($event), isDragFile = false]"
|
||||||
|
@replace="[onDocumentsReplace($event), isDragFile = false]"
|
||||||
|
@replace-and-clone="onDocumentsReplaceAndTemplateClone($event)"
|
||||||
|
@error="[onUploadFailed($event), isDragFile = false]"
|
||||||
|
/>
|
||||||
|
<DragPlaceholder
|
||||||
|
ref="dragPlaceholder"
|
||||||
|
:field="fieldsDragFieldRef.value || toRaw(dragField)"
|
||||||
|
:is-field="template.fields.includes(fieldsDragFieldRef.value)"
|
||||||
|
:is-default="defaultFields.includes(toRaw(dragField))"
|
||||||
|
:is-required="defaultRequiredFields.includes(toRaw(dragField))"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="pendingFieldAttachmentUuids.length && editable"
|
v-if="pendingFieldAttachmentUuids.length && editable"
|
||||||
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
|
class="top-1.5 sticky h-0 z-20 max-w-2xl mx-auto"
|
||||||
@@ -32,7 +53,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="$slots.buttons || withTitle"
|
v-if="$slots.buttons || withTitle"
|
||||||
id="title_container"
|
id="title_container"
|
||||||
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10"
|
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10 title-container"
|
||||||
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
@@ -47,7 +68,7 @@
|
|||||||
v-if="withTitle"
|
v-if="withTitle"
|
||||||
:model-value="template.name"
|
:model-value="template.name"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
class="text-xl md:text-3xl font-semibold focus:text-clip"
|
class="text-xl md:text-3xl font-semibold focus:text-clip template-name"
|
||||||
:icon-stroke-width="2.3"
|
:icon-stroke-width="2.3"
|
||||||
@update:model-value="updateName"
|
@update:model-value="updateName"
|
||||||
/>
|
/>
|
||||||
@@ -60,6 +81,7 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<a
|
<a
|
||||||
v-if="withSignYourselfButton"
|
v-if="withSignYourselfButton"
|
||||||
|
id="sign_yourself_button"
|
||||||
:href="template.submitters.length > 1 ? `/templates/${template.id}/submissions/new?selfsign=true` : `/d/${template.slug}`"
|
:href="template.submitters.length > 1 ? `/templates/${template.id}/submissions/new?selfsign=true` : `/d/${template.slug}`"
|
||||||
class="btn btn-primary btn-ghost text-base hidden md:flex"
|
class="btn btn-primary btn-ghost text-base hidden md:flex"
|
||||||
:target="template.submitters.length > 1 ? '' : '_blank'"
|
:target="template.submitters.length > 1 ? '' : '_blank'"
|
||||||
@@ -76,6 +98,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-if="withSendButton"
|
v-if="withSendButton"
|
||||||
|
id="send_button"
|
||||||
:href="`/templates/${template.id}/submissions/new?with_link=true`"
|
:href="`/templates/${template.id}/submissions/new?with_link=true`"
|
||||||
data-turbo-frame="modal"
|
data-turbo-frame="modal"
|
||||||
class="white-button md:!px-6"
|
class="white-button md:!px-6"
|
||||||
@@ -91,6 +114,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<span
|
<span
|
||||||
v-if="editable"
|
v-if="editable"
|
||||||
|
id="save_button_container"
|
||||||
class="flex"
|
class="flex"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -163,7 +187,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="main_container"
|
id="main_container"
|
||||||
class="flex"
|
class="flex main-container"
|
||||||
:class="$slots.buttons || withTitle ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')"
|
:class="$slots.buttons || withTitle ? (isMobile ? 'max-h-[calc(100%_-_60px)]' : 'md:max-h-[calc(100%_-_60px)]') : (isMobile ? 'max-h-[100%]' : 'md:max-h-[100%]')"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -179,6 +203,7 @@
|
|||||||
:with-arrows="template.schema.length > 1"
|
:with-arrows="template.schema.length > 1"
|
||||||
:item="item"
|
:item="item"
|
||||||
:document="sortedDocuments[index]"
|
:document="sortedDocuments[index]"
|
||||||
|
:data-document-uuid="item.attachment_uuid"
|
||||||
:accept-file-types="acceptFileTypes"
|
:accept-file-types="acceptFileTypes"
|
||||||
:with-replace-button="withUploadButton"
|
:with-replace-button="withUploadButton"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
@@ -187,6 +212,7 @@
|
|||||||
@remove="onDocumentRemove"
|
@remove="onDocumentRemove"
|
||||||
@replace="onDocumentReplace"
|
@replace="onDocumentReplace"
|
||||||
@up="moveDocument(item, -1)"
|
@up="moveDocument(item, -1)"
|
||||||
|
@reorder="reorderFields"
|
||||||
@down="moveDocument(item, 1)"
|
@down="moveDocument(item, 1)"
|
||||||
@change="save"
|
@change="save"
|
||||||
/>
|
/>
|
||||||
@@ -203,7 +229,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="sortedDocuments.length && editable && withAddPageButton"
|
v-if="sortedDocuments.length && editable && withAddPageButton"
|
||||||
id="add_blank_page_button"
|
id="add_blank_page_button"
|
||||||
class="btn btn-outline w-full"
|
class="btn btn-outline w-full add-blank-page-button"
|
||||||
@click.prevent="addBlankPage"
|
@click.prevent="addBlankPage"
|
||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
@@ -220,8 +246,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="pages_container"
|
id="pages_container"
|
||||||
class="w-full overflow-y-hidden overflow-x-hidden mt-0.5 pt-0.5"
|
class="w-full overflow-x-hidden mt-0.5 pt-0.5"
|
||||||
:class="isMobile ? 'overflow-y-auto' : 'md:overflow-y-auto'"
|
:class="isMobile ? 'overflow-y-auto' : 'overflow-y-hidden md:overflow-y-auto'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="documents"
|
ref="documents"
|
||||||
@@ -237,7 +263,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="withAddPageButton"
|
v-if="withAddPageButton"
|
||||||
id="add_blank_page_button"
|
id="add_blank_page_button"
|
||||||
class="btn btn-outline w-full mt-4"
|
class="btn btn-outline w-full mt-4 add-blank-page-button"
|
||||||
@click.prevent="addBlankPage"
|
@click.prevent="addBlankPage"
|
||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
@@ -264,8 +290,10 @@
|
|||||||
:is-drag="!!dragField"
|
:is-drag="!!dragField"
|
||||||
:input-mode="inputMode"
|
:input-mode="inputMode"
|
||||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||||
:allow-draw="!onlyDefinedFields"
|
:allow-draw="!onlyDefinedFields || drawField"
|
||||||
|
:data-document-uuid="document.uuid"
|
||||||
:default-submitters="defaultSubmitters"
|
:default-submitters="defaultSubmitters"
|
||||||
|
:drag-field-placeholder="fieldsDragFieldRef.value || dragField"
|
||||||
:with-field-placeholder="withFieldPlaceholder"
|
:with-field-placeholder="withFieldPlaceholder"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
:draw-field-type="drawFieldType"
|
:draw-field-type="drawFieldType"
|
||||||
@@ -304,7 +332,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="withAddPageButton"
|
v-if="withAddPageButton"
|
||||||
id="add_blank_page_button"
|
id="add_blank_page_button"
|
||||||
class="btn btn-outline w-full mt-4"
|
class="btn btn-outline w-full mt-4 add-blank-page-button"
|
||||||
@click.prevent="addBlankPage"
|
@click.prevent="addBlankPage"
|
||||||
>
|
>
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
@@ -324,7 +352,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="withFieldsList && !isMobile"
|
v-if="withFieldsList && !isMobile"
|
||||||
id="fields_list_container"
|
id="fields_list_container"
|
||||||
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block"
|
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block fields-list-container"
|
||||||
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -332,13 +360,13 @@
|
|||||||
class="sticky inset-0 h-full z-20"
|
class="sticky inset-0 h-full z-20"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4">
|
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4 draw-field-container">
|
||||||
<p>
|
<p>
|
||||||
{{ t('draw_field_on_the_document') }}
|
{{ t('draw_field_on_the_document') }}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="base-button"
|
class="base-button cancel-draw-button"
|
||||||
@click="clearDrawField"
|
@click="clearDrawField"
|
||||||
>
|
>
|
||||||
{{ t('cancel') }}
|
{{ t('cancel') }}
|
||||||
@@ -363,6 +391,7 @@
|
|||||||
:with-help="withHelp"
|
:with-help="withHelp"
|
||||||
:default-submitters="defaultSubmitters"
|
:default-submitters="defaultSubmitters"
|
||||||
:draw-field-type="drawFieldType"
|
:draw-field-type="drawFieldType"
|
||||||
|
:with-fields-search="withFieldsSearch"
|
||||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||||
:template="template"
|
:template="template"
|
||||||
:default-required-fields="defaultRequiredFields"
|
:default-required-fields="defaultRequiredFields"
|
||||||
@@ -370,12 +399,14 @@
|
|||||||
:with-sticky-submitters="withStickySubmitters"
|
:with-sticky-submitters="withStickySubmitters"
|
||||||
:only-defined-fields="onlyDefinedFields"
|
:only-defined-fields="onlyDefinedFields"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
|
:show-tour-start-form="showTourStartForm"
|
||||||
@add-field="addField"
|
@add-field="addField"
|
||||||
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
||||||
@set-draw-type="[drawFieldType = $event, showDrawField = true]"
|
@set-draw-type="[drawFieldType = $event, showDrawField = true]"
|
||||||
@set-drag="dragField = $event"
|
@set-drag="dragField = $event"
|
||||||
|
@set-drag-placeholder="$refs.dragPlaceholder.dragPlaceholder = $event"
|
||||||
@change-submitter="selectedSubmitter = $event"
|
@change-submitter="selectedSubmitter = $event"
|
||||||
@drag-end="dragField = null"
|
@drag-end="[dragField = null, $refs.dragPlaceholder.dragPlaceholder = null]"
|
||||||
@scroll-to-area="scrollToArea"
|
@scroll-to-area="scrollToArea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -404,13 +435,18 @@
|
|||||||
@select="startFieldDraw($event)"
|
@select="startFieldDraw($event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="docuseal_modal_container" />
|
<div
|
||||||
|
id="docuseal_modal_container"
|
||||||
|
class="modal-container"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Upload from './upload'
|
import Upload from './upload'
|
||||||
import Dropzone from './dropzone'
|
import Dropzone from './dropzone'
|
||||||
|
import HoverDropzone from './hover_dropzone'
|
||||||
|
import DragPlaceholder from './drag_placeholder'
|
||||||
import Fields from './fields'
|
import Fields from './fields'
|
||||||
import MobileDrawField from './mobile_draw_field'
|
import MobileDrawField from './mobile_draw_field'
|
||||||
import Document from './document'
|
import Document from './document'
|
||||||
@@ -422,13 +458,14 @@ import MobileFields from './mobile_fields'
|
|||||||
import FieldSubmitter from './field_submitter'
|
import FieldSubmitter from './field_submitter'
|
||||||
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
|
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, toRaw } from 'vue'
|
||||||
import * as i18n from './i18n'
|
import * as i18n from './i18n'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplateBuilder',
|
name: 'TemplateBuilder',
|
||||||
components: {
|
components: {
|
||||||
Upload,
|
Upload,
|
||||||
|
DragPlaceholder,
|
||||||
Document,
|
Document,
|
||||||
Fields,
|
Fields,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
@@ -438,6 +475,7 @@ export default {
|
|||||||
MobileFields,
|
MobileFields,
|
||||||
Logo,
|
Logo,
|
||||||
Dropzone,
|
Dropzone,
|
||||||
|
HoverDropzone,
|
||||||
DocumentPreview,
|
DocumentPreview,
|
||||||
DocumentControls,
|
DocumentControls,
|
||||||
IconInnerShadowTop,
|
IconInnerShadowTop,
|
||||||
@@ -453,6 +491,7 @@ export default {
|
|||||||
template: this.template,
|
template: this.template,
|
||||||
save: this.save,
|
save: this.save,
|
||||||
t: this.t,
|
t: this.t,
|
||||||
|
assignDropAreaSize: this.assignDropAreaSize,
|
||||||
currencies: this.currencies,
|
currencies: this.currencies,
|
||||||
locale: this.locale,
|
locale: this.locale,
|
||||||
baseFetch: this.baseFetch,
|
baseFetch: this.baseFetch,
|
||||||
@@ -620,6 +659,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
withFieldsSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
withFieldsList: {
|
withFieldsList: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -635,6 +679,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
withReplaceAndCloneUpload: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
withPhone: {
|
withPhone: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -674,6 +723,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
default: () => ({ headers: {} })
|
default: () => ({ headers: {} })
|
||||||
|
},
|
||||||
|
showTourStartForm: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
@@ -689,7 +743,8 @@ export default {
|
|||||||
copiedArea: null,
|
copiedArea: null,
|
||||||
drawFieldType: null,
|
drawFieldType: null,
|
||||||
drawOption: null,
|
drawOption: null,
|
||||||
dragField: null
|
dragField: null,
|
||||||
|
isDragFile: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -700,7 +755,9 @@ export default {
|
|||||||
return this.locale.split('-')[0].toLowerCase()
|
return this.locale.split('-')[0].toLowerCase()
|
||||||
},
|
},
|
||||||
isMobile () {
|
isMobile () {
|
||||||
return /android|iphone|ipad/i.test(navigator.userAgent)
|
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||||
|
|
||||||
|
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||||
},
|
},
|
||||||
defaultDateFormat () {
|
defaultDateFormat () {
|
||||||
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
||||||
@@ -769,13 +826,17 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const defineSubmittersUuids = this.defineSubmitters.map((name) => {
|
||||||
|
return this.template.submitters.find(e => e.name === name)?.uuid
|
||||||
|
})
|
||||||
|
|
||||||
this.defineSubmitters.forEach((name, index) => {
|
this.defineSubmitters.forEach((name, index) => {
|
||||||
const submitter = (this.template.submitters[index] ||= {})
|
const submitter = (this.template.submitters[index] ||= {})
|
||||||
|
|
||||||
submitter.name = name || this.submitterDefaultNames[index]
|
submitter.name = name || this.submitterDefaultNames[index]
|
||||||
|
|
||||||
if (existingSubmittersUuids.filter(Boolean).length) {
|
if (defineSubmittersUuids.filter(Boolean).length || existingSubmittersUuids.filter(Boolean).length) {
|
||||||
submitter.uuid = existingSubmittersUuids[index] || submitter.uuid || v4()
|
submitter.uuid = defineSubmittersUuids[index] || existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||||
} else {
|
} else {
|
||||||
submitter.uuid ||= v4()
|
submitter.uuid ||= v4()
|
||||||
}
|
}
|
||||||
@@ -795,6 +856,7 @@ export default {
|
|||||||
window.addEventListener('keydown', this.onKeyDown)
|
window.addEventListener('keydown', this.onKeyDown)
|
||||||
|
|
||||||
window.addEventListener('resize', this.onWindowResize)
|
window.addEventListener('resize', this.onWindowResize)
|
||||||
|
window.addEventListener('dragleave', this.onWindowDragLeave)
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (document.location.search?.includes('stripe_connect_success')) {
|
if (document.location.search?.includes('stripe_connect_success')) {
|
||||||
@@ -813,11 +875,113 @@ export default {
|
|||||||
window.removeEventListener('keydown', this.onKeyDown)
|
window.removeEventListener('keydown', this.onKeyDown)
|
||||||
|
|
||||||
window.removeEventListener('resize', this.onWindowResize)
|
window.removeEventListener('resize', this.onWindowResize)
|
||||||
|
window.removeEventListener('dragleave', this.onWindowDragLeave)
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
this.documentRefs = []
|
this.documentRefs = []
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
toRaw,
|
||||||
|
onDragover (e) {
|
||||||
|
if (this.$refs.dragPlaceholder?.dragPlaceholder) {
|
||||||
|
this.$refs.dragPlaceholder.isMask = e.target.id === 'mask'
|
||||||
|
|
||||||
|
const ref = this.$refs.dragPlaceholder.dragPlaceholder
|
||||||
|
|
||||||
|
ref.x = e.clientX - ref.offsetX
|
||||||
|
ref.y = e.clientY - ref.offsetY
|
||||||
|
} else if (e.dataTransfer?.types?.includes('Files')) {
|
||||||
|
this.isDragFile = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onWindowDragLeave (event) {
|
||||||
|
if (event.clientX <= 0 || event.clientY <= 0 || event.clientX >= window.innerWidth || event.clientY >= window.innerHeight) {
|
||||||
|
this.isDragFile = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reorderFields (item) {
|
||||||
|
const itemFields = []
|
||||||
|
const fields = []
|
||||||
|
const fieldAreasIndex = {}
|
||||||
|
|
||||||
|
const attachmentUuids = this.template.schema.map((e) => e.attachment_uuid)
|
||||||
|
|
||||||
|
this.template.fields.forEach((f) => {
|
||||||
|
if (f.areas?.length) {
|
||||||
|
const firstArea = f.areas.reduce((min, a) => {
|
||||||
|
return attachmentUuids.indexOf(a.attachment_uuid) < attachmentUuids.indexOf(min.attachment_uuid) ? a : min
|
||||||
|
}, f.areas[0])
|
||||||
|
|
||||||
|
if (firstArea.attachment_uuid === item.attachment_uuid) {
|
||||||
|
itemFields.push(f)
|
||||||
|
} else {
|
||||||
|
fields.push(f)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fields.push(f)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortArea = (aArea, bArea) => {
|
||||||
|
if (aArea.attachment_uuid === bArea.attachment_uuid) {
|
||||||
|
if (aArea.page === bArea.page) {
|
||||||
|
if (Math.abs(aArea.y - bArea.y) < 0.01) {
|
||||||
|
if (aArea.x === bArea.x) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return aArea.x - bArea.x
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return aArea.y - bArea.y
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return aArea.page - bArea.page
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return attachmentUuids.indexOf(aArea.attachment_uuid) - attachmentUuids.indexOf(bArea.attachment_uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemFields.sort((aField, bField) => {
|
||||||
|
const aArea = (fieldAreasIndex[aField.uuid] ||= [...(aField.areas || [])].sort(sortArea)[0])
|
||||||
|
const bArea = (fieldAreasIndex[bField.uuid] ||= [...(bField.areas || [])].sort(sortArea)[0])
|
||||||
|
|
||||||
|
return sortArea(aArea, bArea)
|
||||||
|
})
|
||||||
|
|
||||||
|
const insertBeforeAttachmentUuids = attachmentUuids.slice(this.template.schema.indexOf(item) + 1)
|
||||||
|
|
||||||
|
let sortedFields = []
|
||||||
|
|
||||||
|
if (insertBeforeAttachmentUuids.length) {
|
||||||
|
const insertAfterField = fields.find((f) => {
|
||||||
|
if (f.areas?.length) {
|
||||||
|
return f.areas.find((a) => insertBeforeAttachmentUuids.includes(a.attachment_uuid))
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (insertAfterField) {
|
||||||
|
fields.splice(fields.indexOf(insertAfterField), 0, ...itemFields)
|
||||||
|
|
||||||
|
sortedFields = fields
|
||||||
|
} else {
|
||||||
|
sortedFields = fields.concat(itemFields)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (fields.length && itemFields.length && this.template.fields.indexOf(fields[0]) > this.template.fields.indexOf(itemFields[0])) {
|
||||||
|
sortedFields = itemFields.concat(fields)
|
||||||
|
} else {
|
||||||
|
sortedFields = fields.concat(itemFields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.template.fields.length === sortedFields.length) {
|
||||||
|
this.template.fields = sortedFields
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
document.activeElement.blur()
|
||||||
},
|
},
|
||||||
@@ -1115,7 +1279,7 @@ export default {
|
|||||||
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
|
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDraw (area) {
|
onDraw ({ area, isTooSmall }) {
|
||||||
if (this.drawField) {
|
if (this.drawField) {
|
||||||
if (this.drawOption) {
|
if (this.drawOption) {
|
||||||
const areaWithoutOption = this.drawField.areas?.find((a) => !a.option_uuid)
|
const areaWithoutOption = this.drawField.areas?.find((a) => !a.option_uuid)
|
||||||
@@ -1210,7 +1374,7 @@ export default {
|
|||||||
area.y -= area.h / 2
|
area.y -= area.h / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
if (area.w) {
|
if (area.w && (type !== 'checkbox' || this.drawFieldType || !isTooSmall)) {
|
||||||
this.addField(type, area)
|
this.addField(type, area)
|
||||||
|
|
||||||
this.selectedAreaRef.value = area
|
this.selectedAreaRef.value = area
|
||||||
@@ -1218,6 +1382,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDropfield (area) {
|
onDropfield (area) {
|
||||||
|
if (this.$refs.dragPlaceholder) {
|
||||||
|
this.$refs.dragPlaceholder.dragPlaceholder = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const field = this.fieldsDragFieldRef.value || {
|
const field = this.fieldsDragFieldRef.value || {
|
||||||
name: '',
|
name: '',
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
@@ -1226,9 +1398,17 @@ export default {
|
|||||||
...this.dragField
|
...this.dragField
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!field.type) {
|
||||||
|
field.type = 'text'
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.fieldsDragFieldRef.value) {
|
if (!this.fieldsDragFieldRef.value) {
|
||||||
if (['select', 'multiple', 'radio'].includes(field.type)) {
|
if (['select', 'multiple', 'radio'].includes(field.type)) {
|
||||||
field.options = [{ value: '', uuid: v4() }]
|
if (this.dragField?.options?.length) {
|
||||||
|
field.options = this.dragField.options.map(option => ({ value: option, uuid: v4() }))
|
||||||
|
} else {
|
||||||
|
field.options = [{ value: '', uuid: v4() }]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['stamp', 'heading'].includes(field.type)) {
|
if (['stamp', 'heading'].includes(field.type)) {
|
||||||
@@ -1236,9 +1416,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'date') {
|
if (field.type === 'date') {
|
||||||
field.preferences = {
|
field.preferences ||= {}
|
||||||
format: this.defaultDateFormat
|
field.preferences.format ||= this.defaultDateFormat
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1249,71 +1428,18 @@ export default {
|
|||||||
attachment_uuid: area.attachment_uuid
|
attachment_uuid: area.attachment_uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
const previousField = [...this.template.fields].reverse().find((f) => f.type === field.type)
|
this.assignDropAreaSize(fieldArea, field, area)
|
||||||
|
|
||||||
let baseArea
|
|
||||||
|
|
||||||
if (this.selectedField?.type === field.type) {
|
|
||||||
baseArea = this.selectedAreaRef.value
|
|
||||||
} else if (previousField?.areas?.length) {
|
|
||||||
baseArea = previousField.areas[previousField.areas.length - 1]
|
|
||||||
} else {
|
|
||||||
if (['checkbox'].includes(field.type)) {
|
|
||||||
baseArea = {
|
|
||||||
w: area.maskW / 30 / area.maskW,
|
|
||||||
h: area.maskW / 30 / area.maskW * (area.maskW / area.maskH)
|
|
||||||
}
|
|
||||||
} else if (field.type === 'image') {
|
|
||||||
baseArea = {
|
|
||||||
w: area.maskW / 5 / area.maskW,
|
|
||||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH)
|
|
||||||
}
|
|
||||||
} else if (field.type === 'signature' || field.type === 'stamp' || field.type === 'verification') {
|
|
||||||
baseArea = {
|
|
||||||
w: area.maskW / 5 / area.maskW,
|
|
||||||
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
|
|
||||||
}
|
|
||||||
} else if (field.type === 'initials') {
|
|
||||||
baseArea = {
|
|
||||||
w: area.maskW / 10 / area.maskW,
|
|
||||||
h: area.maskW / 35 / area.maskW
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
baseArea = {
|
|
||||||
w: area.maskW / 5 / area.maskW,
|
|
||||||
h: area.maskW / 35 / area.maskW
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldArea.w = baseArea.w
|
|
||||||
fieldArea.h = baseArea.h
|
|
||||||
fieldArea.y = fieldArea.y - baseArea.h / 2
|
|
||||||
|
|
||||||
if (field.type === 'cells') {
|
|
||||||
fieldArea.cell_w = baseArea.cell_w || (baseArea.w / 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.width) {
|
if (field.width) {
|
||||||
fieldArea.w = field.width / area.maskW
|
|
||||||
delete field.width
|
delete field.width
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.height) {
|
if (field.height) {
|
||||||
fieldArea.h = field.height / area.maskH
|
|
||||||
delete field.height
|
delete field.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
field.areas ||= []
|
||||||
|
|
||||||
field.areas.push(fieldArea)
|
field.areas.push(fieldArea)
|
||||||
|
|
||||||
this.selectedAreaRef.value = fieldArea
|
this.selectedAreaRef.value = fieldArea
|
||||||
@@ -1331,10 +1457,78 @@ export default {
|
|||||||
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||||
const areaRef = documentRef.pageRefs[area.page].areaRefs.find((ref) => ref.area === this.selectedAreaRef.value)
|
const areaRef = documentRef.pageRefs[area.page].areaRefs.find((ref) => ref.area === this.selectedAreaRef.value)
|
||||||
|
|
||||||
|
areaRef.isHeadingSelected = true
|
||||||
|
|
||||||
areaRef.focusValueInput()
|
areaRef.focusValueInput()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
assignDropAreaSize (fieldArea, field, area) {
|
||||||
|
const fieldType = field.type || 'text'
|
||||||
|
|
||||||
|
const previousField = [...this.template.fields].reverse().find((f) => f.type === fieldType)
|
||||||
|
|
||||||
|
let baseArea
|
||||||
|
|
||||||
|
if (this.selectedField?.type === fieldType) {
|
||||||
|
baseArea = this.selectedAreaRef.value
|
||||||
|
} else if (previousField?.areas?.length) {
|
||||||
|
baseArea = previousField.areas[previousField.areas.length - 1]
|
||||||
|
} else {
|
||||||
|
if (['checkbox'].includes(fieldType)) {
|
||||||
|
baseArea = {
|
||||||
|
w: area.maskW / 30 / area.maskW,
|
||||||
|
h: area.maskW / 30 / area.maskW * (area.maskW / area.maskH)
|
||||||
|
}
|
||||||
|
} else if (fieldType === 'image') {
|
||||||
|
baseArea = {
|
||||||
|
w: area.maskW / 5 / area.maskW,
|
||||||
|
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH)
|
||||||
|
}
|
||||||
|
} else if (fieldType === 'signature' || fieldType === 'stamp' || fieldType === 'verification') {
|
||||||
|
baseArea = {
|
||||||
|
w: area.maskW / 5 / area.maskW,
|
||||||
|
h: (area.maskW / 5 / area.maskW) * (area.maskW / area.maskH) / 2
|
||||||
|
}
|
||||||
|
} else if (fieldType === 'initials') {
|
||||||
|
baseArea = {
|
||||||
|
w: area.maskW / 10 / area.maskW,
|
||||||
|
h: area.maskW / 35 / area.maskW
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
baseArea = {
|
||||||
|
w: area.maskW / 5 / area.maskW,
|
||||||
|
h: area.maskW / 35 / area.maskW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldArea.w = baseArea.w
|
||||||
|
fieldArea.h = baseArea.h
|
||||||
|
|
||||||
|
if (fieldType === 'cells') {
|
||||||
|
fieldArea.cell_w = baseArea.cell_w || (baseArea.w / 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.areas?.length) {
|
||||||
|
const lastArea = field.areas[field.areas.length - 1]
|
||||||
|
|
||||||
|
if (lastArea) {
|
||||||
|
fieldArea.w = lastArea.w
|
||||||
|
fieldArea.h = lastArea.h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.width) {
|
||||||
|
fieldArea.w = field.width / area.maskW
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.height) {
|
||||||
|
fieldArea.h = field.height / area.maskH
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldArea.y = fieldArea.y - fieldArea.h / 2
|
||||||
|
},
|
||||||
addBlankPage () {
|
addBlankPage () {
|
||||||
this.isLoadingBlankPage = true
|
this.isLoadingBlankPage = true
|
||||||
|
|
||||||
@@ -1364,6 +1558,9 @@ export default {
|
|||||||
})
|
})
|
||||||
}, 'image/png')
|
}, 'image/png')
|
||||||
},
|
},
|
||||||
|
onUploadFailed (error) {
|
||||||
|
if (error) alert(error)
|
||||||
|
},
|
||||||
updateFromUpload (data) {
|
updateFromUpload (data) {
|
||||||
this.template.schema.push(...data.schema)
|
this.template.schema.push(...data.schema)
|
||||||
this.template.documents.push(...data.documents)
|
this.template.documents.push(...data.documents)
|
||||||
@@ -1418,29 +1615,29 @@ export default {
|
|||||||
onDocumentRemove (item) {
|
onDocumentRemove (item) {
|
||||||
if (window.confirm(this.t('are_you_sure_'))) {
|
if (window.confirm(this.t('are_you_sure_'))) {
|
||||||
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||||
}
|
|
||||||
|
|
||||||
const removedFieldUuids = []
|
const removedFieldUuids = []
|
||||||
|
|
||||||
this.template.fields.forEach((field) => {
|
this.template.fields.forEach((field) => {
|
||||||
[...(field.areas || [])].forEach((area) => {
|
[...(field.areas || [])].forEach((area) => {
|
||||||
if (area.attachment_uuid === item.attachment_uuid) {
|
if (area.attachment_uuid === item.attachment_uuid) {
|
||||||
field.areas.splice(field.areas.indexOf(area), 1)
|
field.areas.splice(field.areas.indexOf(area), 1)
|
||||||
|
|
||||||
removedFieldUuids.push(field.uuid)
|
removedFieldUuids.push(field.uuid)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
this.template.fields =
|
this.template.fields =
|
||||||
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
|
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onDocumentReplace (data) {
|
onDocumentReplace (data) {
|
||||||
const { replaceSchemaItem, schema, documents } = data
|
const { replaceSchemaItem, schema, documents } = data
|
||||||
|
|
||||||
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, schema[0])
|
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, { ...replaceSchemaItem, ...schema[0] })
|
||||||
this.template.documents.push(...documents)
|
this.template.documents.push(...documents)
|
||||||
|
|
||||||
if (data.fields) {
|
if (data.fields) {
|
||||||
@@ -1484,6 +1681,29 @@ export default {
|
|||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
|
onDocumentsReplace (data) {
|
||||||
|
data.schema.forEach((schemaItem, index) => {
|
||||||
|
const existingSchemaItem = this.template.schema[index]
|
||||||
|
|
||||||
|
if (this.template.schema[index]) {
|
||||||
|
this.onDocumentReplace({
|
||||||
|
replaceSchemaItem: existingSchemaItem,
|
||||||
|
schema: [schemaItem],
|
||||||
|
documents: [data.documents.find((doc) => doc.uuid === schemaItem.attachment_uuid)]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.updateFromUpload({
|
||||||
|
schema: [schemaItem],
|
||||||
|
documents: [data.documents.find((doc) => doc.uuid === schemaItem.attachment_uuid)],
|
||||||
|
fields: data.fields,
|
||||||
|
submitters: data.submitters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDocumentsReplaceAndTemplateClone (template) {
|
||||||
|
window.Turbo.visit(`/templates/${template.id}/edit`)
|
||||||
|
},
|
||||||
moveDocument (item, direction) {
|
moveDocument (item, direction) {
|
||||||
const currentIndex = this.template.schema.indexOf(item)
|
const currentIndex = this.template.schema.indexOf(item)
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
/>
|
/>
|
||||||
<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 class="modal-title">
|
||||||
{{ t('condition') }} - {{ item.name || buildDefaultName(item, template.fields) }}
|
{{ t('condition') }} - {{ item.name || buildDefaultName(item, template.fields) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-xl"
|
class="text-xl modal-close-button"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
>×</a>
|
>×</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,9 +50,9 @@
|
|||||||
v-if="conditions.length > 1"
|
v-if="conditions.length > 1"
|
||||||
class="flex justify-between mx-1"
|
class="flex justify-between mx-1"
|
||||||
>
|
>
|
||||||
<span class="text-sm">
|
<label class="text-sm">
|
||||||
{{ t('condition') }} {{ cindex + 1 }}
|
{{ t('condition') }} {{ cindex + 1 }}
|
||||||
</span>
|
</label>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="link text-sm"
|
class="link text-sm"
|
||||||
@@ -131,9 +131,7 @@
|
|||||||
class="inline float-right link text-right mb-3 px-2"
|
class="inline float-right link text-right mb-3 px-2"
|
||||||
@click.prevent="conditions.push({})"
|
@click.prevent="conditions.push({})"
|
||||||
> + {{ t('add_condition') }}</a>
|
> + {{ t('add_condition') }}</a>
|
||||||
<button
|
<button class="base-button w-full mt-2 modal-save-button">
|
||||||
class="base-button w-full mt-2"
|
|
||||||
>
|
|
||||||
{{ t('save') }}
|
{{ t('save') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -177,7 +175,7 @@ export default {
|
|||||||
fields () {
|
fields () {
|
||||||
if (this.item.submitter_uuid) {
|
if (this.item.submitter_uuid) {
|
||||||
return this.template.fields.reduce((acc, f) => {
|
return this.template.fields.reduce((acc, f) => {
|
||||||
if (f !== this.item && f.submitter_uuid === this.item.submitter_uuid) {
|
if (f !== this.item && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) {
|
||||||
acc.push(f)
|
acc.push(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<Contenteditable
|
<Contenteditable
|
||||||
class="w-full block mr-6"
|
class="w-full block mr-6 document-preview-name"
|
||||||
:model-value="item.name"
|
:model-value="item.name"
|
||||||
:icon-width="16"
|
:icon-width="16"
|
||||||
@update:model-value="onUpdateName"
|
@update:model-value="onUpdateName"
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="withArrows"
|
v-if="withArrows"
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors document-control-button"
|
||||||
style="width: 24px; height: 24px"
|
style="width: 24px; height: 24px"
|
||||||
@click.stop="$emit('up', item)"
|
@click.stop="$emit('up', item)"
|
||||||
>
|
>
|
||||||
@@ -23,14 +23,14 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="withArrows"
|
v-if="withArrows"
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors document-control-button"
|
||||||
style="width: 24px; height: 24px"
|
style="width: 24px; height: 24px"
|
||||||
@click.stop="$emit('down', item)"
|
@click.stop="$emit('down', item)"
|
||||||
>
|
>
|
||||||
↓
|
↓
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors document-control-button"
|
||||||
style="width: 24px; height: 24px"
|
style="width: 24px; height: 24px"
|
||||||
@click.stop="$emit('remove', item)"
|
@click.stop="$emit('remove', item)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
/>
|
/>
|
||||||
<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 class="modal-title">
|
||||||
{{ field.name || buildDefaultName(field, template.fields) }}
|
{{ field.name || buildDefaultName(field, template.fields) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-xl"
|
class="text-xl modal-close-button"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
>×</a>
|
>×</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,9 +55,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button class="base-button w-full mt-4 modal-save-button">
|
||||||
class="base-button w-full mt-4"
|
|
||||||
>
|
|
||||||
{{ t('save') }}
|
{{ t('save') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -7,20 +7,23 @@
|
|||||||
:input-mode="inputMode"
|
:input-mode="inputMode"
|
||||||
:number="index"
|
:number="index"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
|
:data-page="index"
|
||||||
:areas="areasIndex[index]"
|
:areas="areasIndex[index]"
|
||||||
:allow-draw="allowDraw"
|
:allow-draw="allowDraw"
|
||||||
:is-drag="isDrag"
|
:is-drag="isDrag"
|
||||||
:with-field-placeholder="withFieldPlaceholder"
|
:with-field-placeholder="withFieldPlaceholder"
|
||||||
:default-fields="defaultFields"
|
:default-fields="defaultFields"
|
||||||
|
:drag-field-placeholder="dragFieldPlaceholder"
|
||||||
:default-submitters="defaultSubmitters"
|
:default-submitters="defaultSubmitters"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
:draw-field-type="drawFieldType"
|
:draw-field-type="drawFieldType"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
|
:total-pages="sortedPreviewImages.length"
|
||||||
:image="image"
|
:image="image"
|
||||||
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
|
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
|
||||||
@remove-area="$emit('remove-area', $event)"
|
@remove-area="$emit('remove-area', $event)"
|
||||||
@scroll-to="scrollToArea"
|
@scroll-to="scrollToArea"
|
||||||
@draw="$emit('draw', {...$event, attachment_uuid: document.uuid })"
|
@draw="$emit('draw', { area: {...$event.area, attachment_uuid: document.uuid }, isTooSmall: $event.isTooSmall })"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -37,6 +40,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
dragFieldPlaceholder: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
inputMode: {
|
inputMode: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<Field
|
||||||
|
v-if="dragPlaceholder && isField && !isMask && field"
|
||||||
|
ref="dragPlaceholder"
|
||||||
|
:style="dragPlaceholderStyle"
|
||||||
|
:field="field"
|
||||||
|
:with-options="false"
|
||||||
|
class="fixed z-20 pointer-events-none"
|
||||||
|
:editable="false"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else-if="dragPlaceholder && (isDefault || isRequired) && !isMask && field"
|
||||||
|
ref="dragPlaceholder"
|
||||||
|
:style="[dragPlaceholderStyle, { backgroundColor: backgroundColor }]"
|
||||||
|
class="fixed z-20 border border-base-300 rounded group default-field fields-list-item pointer-events-none"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between relative cursor-grab">
|
||||||
|
<div class="flex items-center p-1 space-x-1">
|
||||||
|
<IconDrag />
|
||||||
|
<component
|
||||||
|
:is="fieldIcons[field.type || 'text']"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
:width="20"
|
||||||
|
/>
|
||||||
|
<span class="block pl-0.5">
|
||||||
|
{{ field.title || field.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-if="isRequired"
|
||||||
|
:data-tip="t('required')"
|
||||||
|
class="text-red-400 text-3xl pr-1.5 tooltip tooltip-left h-8"
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-else-if="dragPlaceholder && !isMask && field"
|
||||||
|
ref="dragPlaceholder"
|
||||||
|
class="fixed field-type-button z-20 flex items-center justify-center border border-dashed w-full rounded border-base-content/20 opacity-90 pointer-events-none"
|
||||||
|
:style="[dragPlaceholderStyle, { backgroundColor }]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-console cursor-grab h-full absolute left-0 bg-base-200/50"
|
||||||
|
>
|
||||||
|
<IconDrag class="my-auto" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center flex-col px-2 py-2">
|
||||||
|
<component :is="fieldIcons[field.type || 'text']" />
|
||||||
|
<span class="text-xs mt-1">
|
||||||
|
{{ fieldNames[field.type || 'text'] }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Field from './field'
|
||||||
|
import IconDrag from './icon_drag'
|
||||||
|
import FieldType from './field_type'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DragPlaceholder',
|
||||||
|
components: {
|
||||||
|
Field,
|
||||||
|
IconDrag
|
||||||
|
},
|
||||||
|
inject: ['t', 'backgroundColor'],
|
||||||
|
props: {
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
isDefault: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isRequired: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isField: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isMask: false,
|
||||||
|
dragPlaceholder: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dragPlaceholderStyle () {
|
||||||
|
if (this.dragPlaceholder) {
|
||||||
|
return {
|
||||||
|
left: this.dragPlaceholder.x + 'px',
|
||||||
|
top: this.dragPlaceholder.y + 'px',
|
||||||
|
width: this.dragPlaceholder.w + 'px',
|
||||||
|
height: this.dragPlaceholder.h + 'px'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
|
fieldIcons: FieldType.computed.fieldIcons
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -2,34 +2,42 @@
|
|||||||
<div
|
<div
|
||||||
class="flex h-60 w-full"
|
class="flex h-60 w-full"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
|
@dragenter="isDragEntering = true"
|
||||||
|
@dragleave="isDragEntering = false"
|
||||||
@drop.prevent="onDropFiles"
|
@drop.prevent="onDropFiles"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
id="document_dropzone"
|
id="document_dropzone"
|
||||||
class="w-full relative hover:bg-base-200/30 rounded-md border border-2 border-base-content/10 border-dashed"
|
class="w-full relative rounded-md border-2 border-base-content/10 border-dashed"
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
:class="{ 'opacity-50': isLoading || isProcessing }"
|
:class="{ 'opacity-50': isLoading, 'hover:bg-base-200/50': withHoverClass && !isDragEntering, 'bg-base-200/50 border-base-content/30': isDragEntering }"
|
||||||
>
|
>
|
||||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
|
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center pointer-events-none">
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<IconInnerShadowTop
|
<IconInnerShadowTop
|
||||||
v-if="isLoading || isProcessing"
|
v-if="isLoading"
|
||||||
class="animate-spin"
|
class="animate-spin"
|
||||||
:width="40"
|
:width="40"
|
||||||
:height="40"
|
:height="40"
|
||||||
/>
|
/>
|
||||||
<IconCloudUpload
|
<component
|
||||||
|
:is="icon"
|
||||||
v-else
|
v-else
|
||||||
|
class="stroke-[1.5px]"
|
||||||
:width="40"
|
:width="40"
|
||||||
:height="40"
|
:height="40"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="message"
|
v-if="message"
|
||||||
class="font-medium text-lg mb-1"
|
class="text-lg mb-1"
|
||||||
|
:class="{ 'mt-1': !withDescription, 'font-medium': withDescription }"
|
||||||
>
|
>
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div
|
||||||
|
v-if="withDescription"
|
||||||
|
class="text-sm"
|
||||||
|
>
|
||||||
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
|
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,13 +62,16 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Upload from './upload'
|
import Upload from './upload'
|
||||||
import { IconCloudUpload, IconInnerShadowTop } from '@tabler/icons-vue'
|
import { IconCloudUpload, IconFilePlus, IconFileSymlink, IconFiles, IconInnerShadowTop } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FileDropzone',
|
name: 'FileDropzone',
|
||||||
components: {
|
components: {
|
||||||
|
IconFilePlus,
|
||||||
IconCloudUpload,
|
IconCloudUpload,
|
||||||
IconInnerShadowTop
|
IconInnerShadowTop,
|
||||||
|
IconFileSymlink,
|
||||||
|
IconFiles
|
||||||
},
|
},
|
||||||
inject: ['baseFetch', 't'],
|
inject: ['baseFetch', 't'],
|
||||||
props: {
|
props: {
|
||||||
@@ -68,35 +79,70 @@ export default {
|
|||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
withHoverClass: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'IconCloudUpload'
|
||||||
|
},
|
||||||
|
cloneTemplateOnUpload: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
withDescription: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
acceptFileTypes: {
|
acceptFileTypes: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: 'image/*, application/pdf'
|
default: 'image/*, application/pdf'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['success'],
|
emits: ['success', 'error', 'loading'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isProcessing: false
|
isDragEntering: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
inputId () {
|
inputId () {
|
||||||
return 'el' + Math.random().toString(32).split('.')[1]
|
return 'el' + Math.random().toString(32).split('.')[1]
|
||||||
},
|
},
|
||||||
|
uploadUrl () {
|
||||||
|
if (this.cloneTemplateOnUpload) {
|
||||||
|
return `/templates/${this.templateId}/clone_and_replace`
|
||||||
|
} else {
|
||||||
|
return `/templates/${this.templateId}/documents`
|
||||||
|
}
|
||||||
|
},
|
||||||
message () {
|
message () {
|
||||||
if (this.isLoading) {
|
if (this.isLoading) {
|
||||||
return this.t('uploading')
|
return this.t('uploading')
|
||||||
} else if (this.isProcessing) {
|
|
||||||
return this.t('processing_')
|
|
||||||
} else if (this.acceptFileTypes === 'image/*, application/pdf') {
|
} else if (this.acceptFileTypes === 'image/*, application/pdf') {
|
||||||
return this.t('add_pdf_documents_or_images')
|
return this.title || this.t('add_pdf_documents_or_images')
|
||||||
} else {
|
} else {
|
||||||
return this.t('add_documents_or_images')
|
return this.title || this.t('add_documents_or_images')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
isLoading (value) {
|
||||||
|
this.$emit('loading', value)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
upload: Upload.methods.upload,
|
upload: Upload.methods.upload,
|
||||||
onDropFiles (e) {
|
onDropFiles (e) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class="list-field group mb-2"
|
class="list-field group mb-2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="border border-base-300 rounded rounded-tr-none relative group"
|
class="border border-base-300 rounded relative group fields-list-item"
|
||||||
:style="{ backgroundColor: backgroundColor }"
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between relative group/contenteditable-container">
|
<div class="flex items-center justify-between relative group/contenteditable-container">
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
/>
|
/>
|
||||||
<Contenteditable
|
<Contenteditable
|
||||||
ref="name"
|
ref="name"
|
||||||
:model-value="(defaultField ? (field.title || field.name) : field.name) || defaultName"
|
:model-value="(defaultField ? (defaultField.title || field.title || field.name) : field.name) || defaultName"
|
||||||
:editable="editable && !defaultField && field.type != 'heading'"
|
:editable="editable && !defaultField && field.type != 'heading'"
|
||||||
:icon-inline="true"
|
:icon-inline="true"
|
||||||
:icon-width="18"
|
:icon-width="18"
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
v-else-if="field.type !== 'heading'"
|
v-else-if="field.type !== 'heading'"
|
||||||
class="dropdown dropdown-end"
|
class="dropdown dropdown-end field-settings-dropdown"
|
||||||
@mouseenter="renderDropdown = true"
|
@mouseenter="renderDropdown = true"
|
||||||
@touchstart="renderDropdown = true"
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
@@ -127,6 +127,7 @@
|
|||||||
:editable="editable"
|
:editable="editable"
|
||||||
:background-color="dropdownBgColor"
|
:background-color="dropdownBgColor"
|
||||||
@click-formula="isShowFormulaModal = true"
|
@click-formula="isShowFormulaModal = true"
|
||||||
|
@click-font="isShowFontModal = true"
|
||||||
@click-description="isShowDescriptionModal = true"
|
@click-description="isShowDescriptionModal = true"
|
||||||
@click-condition="isShowConditionsModal = true"
|
@click-condition="isShowConditionsModal = true"
|
||||||
@set-draw="$emit('set-draw', $event)"
|
@set-draw="$emit('set-draw', $event)"
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="relative text-transparent group-hover:text-base-content pr-1"
|
class="relative text-transparent group-hover:text-base-content pr-1 field-remove-button"
|
||||||
:title="t('remove')"
|
:title="t('remove')"
|
||||||
@click="$emit('remove', field)"
|
@click="$emit('remove', field)"
|
||||||
>
|
>
|
||||||
@@ -148,7 +149,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field.options"
|
v-if="field.options && withOptions"
|
||||||
ref="options"
|
ref="options"
|
||||||
class="border-t border-base-300 mx-2 pt-2 space-y-1.5"
|
class="border-t border-base-300 mx-2 pt-2 space-y-1.5"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@@ -192,14 +193,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"
|
:readonly="!editable || defaultField"
|
||||||
required
|
required
|
||||||
dir="auto"
|
dir="auto"
|
||||||
@focus="maybeFocusOnOptionArea(option)"
|
@focus="maybeFocusOnOptionArea(option)"
|
||||||
@blur="save"
|
@blur="save"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="editable"
|
v-if="editable && !defaultField"
|
||||||
class="text-sm w-3.5"
|
class="text-sm w-3.5"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@click="removeOption(option)"
|
@click="removeOption(option)"
|
||||||
@@ -208,11 +209,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="field.options && !editable"
|
v-if="field.options && (!editable || defaultField)"
|
||||||
class="pb-1"
|
class="pb-1"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-else-if="field.options && editable"
|
v-else-if="field.options && editable && !defaultField"
|
||||||
class="text-center text-sm w-full pb-1"
|
class="text-center text-sm w-full pb-1"
|
||||||
@click="addOption"
|
@click="addOption"
|
||||||
>
|
>
|
||||||
@@ -231,6 +232,17 @@
|
|||||||
@close="isShowFormulaModal = false"
|
@close="isShowFormulaModal = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
<Teleport
|
||||||
|
v-if="isShowFontModal"
|
||||||
|
:to="modalContainerEl"
|
||||||
|
>
|
||||||
|
<FontModal
|
||||||
|
:field="field"
|
||||||
|
:editable="editable && !defaultField"
|
||||||
|
:build-default-name="buildDefaultName"
|
||||||
|
@close="isShowFontModal = false"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
<Teleport
|
<Teleport
|
||||||
v-if="isShowConditionsModal"
|
v-if="isShowConditionsModal"
|
||||||
:to="modalContainerEl"
|
:to="modalContainerEl"
|
||||||
@@ -261,6 +273,7 @@ import FieldType from './field_type'
|
|||||||
import PaymentSettings from './payment_settings'
|
import PaymentSettings from './payment_settings'
|
||||||
import FieldSettings from './field_settings'
|
import FieldSettings from './field_settings'
|
||||||
import FormulaModal from './formula_modal'
|
import FormulaModal from './formula_modal'
|
||||||
|
import FontModal from './font_modal'
|
||||||
import ConditionsModal from './conditions_modal'
|
import ConditionsModal from './conditions_modal'
|
||||||
import DescriptionModal from './description_modal'
|
import DescriptionModal from './description_modal'
|
||||||
import { IconRouteAltLeft, IconMathFunction, IconNewSection, IconTrashX, IconSettings } from '@tabler/icons-vue'
|
import { IconRouteAltLeft, IconMathFunction, IconNewSection, IconTrashX, IconSettings } from '@tabler/icons-vue'
|
||||||
@@ -275,6 +288,7 @@ export default {
|
|||||||
PaymentSettings,
|
PaymentSettings,
|
||||||
IconNewSection,
|
IconNewSection,
|
||||||
FormulaModal,
|
FormulaModal,
|
||||||
|
FontModal,
|
||||||
DescriptionModal,
|
DescriptionModal,
|
||||||
ConditionsModal,
|
ConditionsModal,
|
||||||
IconRouteAltLeft,
|
IconRouteAltLeft,
|
||||||
@@ -288,6 +302,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
withOptions: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
defaultField: {
|
defaultField: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -305,6 +324,7 @@ export default {
|
|||||||
isNameFocus: false,
|
isNameFocus: false,
|
||||||
showPaymentModal: false,
|
showPaymentModal: false,
|
||||||
isShowFormulaModal: false,
|
isShowFormulaModal: false,
|
||||||
|
isShowFontModal: false,
|
||||||
isShowConditionsModal: false,
|
isShowConditionsModal: false,
|
||||||
isShowDescriptionModal: false,
|
isShowDescriptionModal: false,
|
||||||
renderDropdown: false
|
renderDropdown: false
|
||||||
@@ -392,7 +412,7 @@ export default {
|
|||||||
return this.sortedAreas[0] && this.$emit('scroll-to', this.sortedAreas[0])
|
return this.sortedAreas[0] && this.$emit('scroll-to', this.sortedAreas[0])
|
||||||
},
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
this.$el.getRootNode().activeElement.blur()
|
||||||
},
|
},
|
||||||
addOption () {
|
addOption () {
|
||||||
this.field.options.push({ value: '', uuid: v4() })
|
this.field.options.push({ value: '', uuid: v4() })
|
||||||
|
|||||||
@@ -27,7 +27,34 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="['number', 'cells'].includes(field.type)"
|
v-if="field.type === 'verification'"
|
||||||
|
class="py-1.5 px-1 relative"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
:placeholder="t('method')"
|
||||||
|
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0 bg-transparent"
|
||||||
|
@change="[field.preferences ||= {}, field.preferences.method = $event.target.value, save()]"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="method in ['QeS', 'AeS']"
|
||||||
|
:key="method"
|
||||||
|
:value="method.toLowerCase()"
|
||||||
|
:selected="method.toLowerCase() === field.preferences?.method || (method === 'QeS' && !field.preferences?.method)"
|
||||||
|
>
|
||||||
|
{{ method }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<label
|
||||||
|
:style="{ backgroundColor }"
|
||||||
|
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||||
|
style="font-size: 8px"
|
||||||
|
>
|
||||||
|
{{ t('method') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="['cells'].includes(field.type)"
|
||||||
class="py-1.5 px-1 relative"
|
class="py-1.5 px-1 relative"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
@@ -212,16 +239,12 @@
|
|||||||
{{ t('any') }}
|
{{ t('any') }}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
value="drawn"
|
v-for="type in ['drawn', 'typed', 'drawn_or_typed', 'upload']"
|
||||||
:selected="field.preferences?.format === 'drawn'"
|
:key="type"
|
||||||
|
:value="type"
|
||||||
|
:selected="field.preferences?.format === type"
|
||||||
>
|
>
|
||||||
{{ t('drawn') }}
|
{{ t(type) }}
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="typed"
|
|
||||||
:selected="field.preferences?.format === 'typed'"
|
|
||||||
>
|
|
||||||
{{ t('typed') }}
|
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<label
|
<label
|
||||||
@@ -233,7 +256,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<li
|
<li
|
||||||
v-if="withRequired && field.type != 'phone' && field.type != 'stamp'"
|
v-if="withRequired && field.type !== 'phone' && field.type !== 'stamp' && field.type !== 'verification'"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<label class="cursor-pointer py-1.5">
|
<label class="cursor-pointer py-1.5">
|
||||||
@@ -307,6 +330,19 @@
|
|||||||
v-if="field.type != 'stamp'"
|
v-if="field.type != 'stamp'"
|
||||||
class="pb-0.5 mt-0.5"
|
class="pb-0.5 mt-0.5"
|
||||||
>
|
>
|
||||||
|
<li v-if="['text', 'number', 'date'].includes(field.type)">
|
||||||
|
<label
|
||||||
|
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||||
|
@click="$emit('click-font')"
|
||||||
|
>
|
||||||
|
<IconTypography
|
||||||
|
width="18"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">
|
||||||
|
{{ t('font') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="field.type != 'stamp'"
|
v-if="field.type != 'stamp'"
|
||||||
>
|
>
|
||||||
@@ -404,7 +440,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconRouteAltLeft, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy } from '@tabler/icons-vue'
|
import { IconRouteAltLeft, IconTypography, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldSettings',
|
name: 'FieldSettings',
|
||||||
@@ -415,6 +451,7 @@ export default {
|
|||||||
IconRouteAltLeft,
|
IconRouteAltLeft,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconNewSection,
|
IconNewSection,
|
||||||
|
IconTypography,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['template', 'save', 't'],
|
inject: ['template', 'save', 't'],
|
||||||
@@ -449,7 +486,7 @@ export default {
|
|||||||
default: true
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'remove-area'],
|
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'click-font', 'remove-area'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
}
|
}
|
||||||
@@ -465,6 +502,9 @@ export default {
|
|||||||
numberFormats () {
|
numberFormats () {
|
||||||
return [
|
return [
|
||||||
'none',
|
'none',
|
||||||
|
'usd',
|
||||||
|
'eur',
|
||||||
|
'gbp',
|
||||||
'comma',
|
'comma',
|
||||||
'dot',
|
'dot',
|
||||||
'space'
|
'space'
|
||||||
@@ -545,6 +585,12 @@ export default {
|
|||||||
formatNumber (number, format) {
|
formatNumber (number, format) {
|
||||||
if (format === 'comma') {
|
if (format === 'comma') {
|
||||||
return new Intl.NumberFormat('en-US').format(number)
|
return new Intl.NumberFormat('en-US').format(number)
|
||||||
|
} else if (format === 'usd') {
|
||||||
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||||
|
} else if (format === 'gbp') {
|
||||||
|
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||||
|
} else if (format === 'eur') {
|
||||||
|
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number)
|
||||||
} else if (format === 'dot') {
|
} else if (format === 'dot') {
|
||||||
return new Intl.NumberFormat('de-DE').format(number)
|
return new Intl.NumberFormat('de-DE').format(number)
|
||||||
} else if (format === 'space') {
|
} else if (format === 'space') {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
@touchstart="renderDropdown = true"
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
<div class="flex space-x-2 items-end">
|
<div class="flex space-x-2 items-end">
|
||||||
<div class="group/contenteditable-container bg-base-100 rounded-md p-2 border border-base-300 w-full flex justify-between items-end">
|
<div class="group/contenteditable-container bg-base-100 rounded-md p-2 border border-base-300 w-full flex justify-between items-end roles-dropdown-label-mobile">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span
|
<span
|
||||||
class="w-3 h-3 flex-shrink-0 rounded-full"
|
class="w-3 h-3 flex-shrink-0 rounded-full"
|
||||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||||
/>
|
/>
|
||||||
<Contenteditable
|
<Contenteditable
|
||||||
v-model="selectedSubmitter.name"
|
v-model="selectedSubmitter.name"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown dropdown-top dropdown-end">
|
<div class="dropdown dropdown-top dropdown-end roles-dropdown-mobile">
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="bg-base-100 cursor-pointer rounded-md p-2 border border-base-300 w-full flex justify-center"
|
class="bg-base-100 cursor-pointer rounded-md p-2 border border-base-300 w-full flex justify-center"
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
<span class="py-1 flex items-center">
|
<span class="py-1 flex items-center">
|
||||||
<span
|
<span
|
||||||
class="rounded-full w-3 h-3 ml-1 mr-3"
|
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||||
:class="colors[index]"
|
:class="colors[index % colors.length]"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ submitter.name }}
|
{{ submitter.name }}
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="mx-1 w-3 h-3 rounded-full"
|
class="mx-1 w-3 h-3 rounded-full"
|
||||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span
|
<span
|
||||||
class="w-3 h-3 rounded-full"
|
class="w-3 h-3 rounded-full"
|
||||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||||
/>
|
/>
|
||||||
<Contenteditable
|
<Contenteditable
|
||||||
v-model="selectedSubmitter.name"
|
v-model="selectedSubmitter.name"
|
||||||
@@ -143,6 +143,7 @@
|
|||||||
<li
|
<li
|
||||||
v-for="(submitter, index) in submitters"
|
v-for="(submitter, index) in submitters"
|
||||||
:key="submitter.uuid"
|
:key="submitter.uuid"
|
||||||
|
class="w-full"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
@@ -153,7 +154,7 @@
|
|||||||
<span class="py-1 flex items-center">
|
<span class="py-1 flex items-center">
|
||||||
<span
|
<span
|
||||||
class="rounded-full w-3 h-3 ml-1 mr-3"
|
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||||
:class="colors[index]"
|
:class="colors[index % colors.length]"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ submitter.name }}
|
{{ submitter.name }}
|
||||||
@@ -191,7 +192,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="submitters.length < names.length && editable && allowAddNew">
|
<li
|
||||||
|
v-if="submitters.length < names.length && editable && allowAddNew"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="flex px-2"
|
class="flex px-2"
|
||||||
@@ -215,6 +219,14 @@ import { IconUserPlus, IconTrashX, IconPlus, IconChevronUp, IconChevronDown } fr
|
|||||||
import Contenteditable from './contenteditable'
|
import Contenteditable from './contenteditable'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
|
|
||||||
|
function getOrdinalSuffix (num) {
|
||||||
|
if (num % 10 === 1 && num % 100 !== 11) return 'st'
|
||||||
|
if (num % 10 === 2 && num % 100 !== 12) return 'nd'
|
||||||
|
if (num % 10 === 3 && num % 100 !== 13) return 'rd'
|
||||||
|
|
||||||
|
return 'th'
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldSubmitter',
|
name: 'FieldSubmitter',
|
||||||
components: {
|
components: {
|
||||||
@@ -275,16 +287,6 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
colors () {
|
colors () {
|
||||||
return [
|
return [
|
||||||
'bg-red-500',
|
|
||||||
'bg-sky-500',
|
|
||||||
'bg-emerald-500',
|
|
||||||
'bg-yellow-300',
|
|
||||||
'bg-purple-600',
|
|
||||||
'bg-pink-500',
|
|
||||||
'bg-cyan-500',
|
|
||||||
'bg-orange-500',
|
|
||||||
'bg-lime-500',
|
|
||||||
'bg-indigo-500',
|
|
||||||
'bg-red-500',
|
'bg-red-500',
|
||||||
'bg-sky-500',
|
'bg-sky-500',
|
||||||
'bg-emerald-500',
|
'bg-emerald-500',
|
||||||
@@ -298,6 +300,14 @@ export default {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
names () {
|
names () {
|
||||||
|
const generatedNames = []
|
||||||
|
|
||||||
|
for (let i = 21; i < 101; i++) {
|
||||||
|
const suffix = getOrdinalSuffix(i)
|
||||||
|
|
||||||
|
generatedNames.push(`${i}${suffix} ${this.t('party')}`)
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
this.t('first_party'),
|
this.t('first_party'),
|
||||||
this.t('second_party'),
|
this.t('second_party'),
|
||||||
@@ -318,7 +328,8 @@ export default {
|
|||||||
this.t('seventeenth_party'),
|
this.t('seventeenth_party'),
|
||||||
this.t('eighteenth_party'),
|
this.t('eighteenth_party'),
|
||||||
this.t('nineteenth_party'),
|
this.t('nineteenth_party'),
|
||||||
this.t('twentieth_party')
|
this.t('twentieth_party'),
|
||||||
|
...generatedNames
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
lastPartyIndex () {
|
lastPartyIndex () {
|
||||||
@@ -372,7 +383,7 @@ export default {
|
|||||||
this.$emit('new-submitter', newSubmitter)
|
this.$emit('new-submitter', newSubmitter)
|
||||||
},
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
this.$el.getRootNode().activeElement.blur()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
class="dropdown"
|
class="dropdown field-types-dropdown"
|
||||||
@mouseenter="renderDropdown = true"
|
@mouseenter="renderDropdown = true"
|
||||||
@touchstart="renderDropdown = true"
|
@touchstart="renderDropdown = true"
|
||||||
>
|
>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
v-for="(icon, type) in fieldIconsSorted"
|
v-for="(icon, type) in fieldIconsSorted"
|
||||||
:key="type"
|
:key="type"
|
||||||
>
|
>
|
||||||
<li v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')">
|
<li v-if="fieldTypes.includes(type) || ((withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification'))">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-sm py-1 px-2"
|
class="text-sm py-1 px-2"
|
||||||
@@ -170,7 +170,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
this.$el.getRootNode().activeElement.blur()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<div :class="withStickySubmitters ? 'sticky top-0 z-[1]' : ''">
|
<div :class="withStickySubmitters ? 'sticky top-0 z-[1]' : ''">
|
||||||
<FieldSubmitter
|
<FieldSubmitter
|
||||||
:model-value="selectedSubmitter.uuid"
|
:model-value="selectedSubmitter.uuid"
|
||||||
class="roles-dropdown w-full rounded-lg"
|
class="roles-dropdown w-full rounded-lg roles-dropdown"
|
||||||
:style="withStickySubmitters ? { backgroundColor } : {}"
|
:style="withStickySubmitters ? { backgroundColor } : {}"
|
||||||
:submitters="submitters"
|
:submitters="submitters"
|
||||||
:menu-style="{ backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
:menu-style="{ overflow: 'auto', display: 'flex', flexDirection: 'row', maxHeight: 'calc(100vh - 120px)', backgroundColor: ['', null, 'transparent'].includes(backgroundColor) ? 'white' : backgroundColor }"
|
||||||
:editable="editable && !defaultSubmitters.length"
|
:editable="editable && !defaultSubmitters.length"
|
||||||
@new-submitter="save"
|
@new-submitter="save"
|
||||||
@remove="removeSubmitter"
|
@remove="removeSubmitter"
|
||||||
@@ -25,11 +25,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 && (!fieldsDragFieldRef.value || fieldsDragFieldRef.value !== field)"
|
:editable="editable"
|
||||||
:default-field="defaultFields.find((f) => f.name === field.name)"
|
:default-field="defaultFieldsIndex[field.name]"
|
||||||
:draggable="editable"
|
:draggable="editable"
|
||||||
@dragstart="fieldsDragFieldRef.value = field"
|
@dragstart="[fieldsDragFieldRef.value = field, removeDragOverlay($event), setDragPlaceholder($event)]"
|
||||||
@dragend="fieldsDragFieldRef.value = null"
|
@dragend="[fieldsDragFieldRef.value = null, $emit('set-drag-placeholder', 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)"
|
||||||
@@ -74,8 +74,8 @@
|
|||||||
<div
|
<div
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="default-field border border-base-300 rounded rounded-tr-none relative group mb-2"
|
class="border border-base-300 rounded relative group mb-2 default-field fields-list-item"
|
||||||
@dragstart="onDragstart({ type: 'text', ...field })"
|
@dragstart="onDragstart($event, field)"
|
||||||
@dragend="$emit('drag-end')"
|
@dragend="$emit('drag-end')"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between relative cursor-grab">
|
<div class="flex items-center justify-between relative cursor-grab">
|
||||||
@@ -104,19 +104,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="editable && !onlyDefinedFields"
|
v-if="editable && !onlyDefinedFields"
|
||||||
class="grid grid-cols-3 gap-1 pb-2"
|
id="field-types-grid"
|
||||||
|
class="grid grid-cols-3 gap-1 pb-2 fields-grid"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-for="(icon, type) in fieldIconsSorted"
|
v-for="(icon, type) in fieldIconsSorted"
|
||||||
:key="type"
|
:key="type"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="(fieldTypes.length === 0 || fieldTypes.includes(type)) && (withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification')"
|
v-if="fieldTypes.includes(type) || ((withPhone || type != 'phone') && (withPayment || type != 'payment') && (withVerification || type != 'verification'))"
|
||||||
|
:id="`${type}_type_field_button`"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative"
|
class="field-type-button group flex items-center justify-center border border-dashed w-full rounded relative fields-grid-item"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
:class="drawFieldType === type ? 'border-base-content/40' : 'border-base-300 hover:border-base-content/20'"
|
||||||
@dragstart="onDragstart({ type: type })"
|
@dragstart="onDragstart($event, { type: type })"
|
||||||
@dragend="$emit('drag-end')"
|
@dragend="$emit('drag-end')"
|
||||||
@click="['file', 'payment', 'verification'].includes(type) ? $emit('add-field', type) : $emit('set-draw-type', type)"
|
@click="['file', 'payment', 'verification'].includes(type) ? $emit('add-field', type) : $emit('set-draw-type', type)"
|
||||||
>
|
>
|
||||||
@@ -142,7 +144,7 @@
|
|||||||
<a
|
<a
|
||||||
href="https://www.docuseal.com/pricing"
|
href="https://www.docuseal.com/pricing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative fields-grid-item"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="w-0 absolute left-0">
|
<div class="w-0 absolute left-0">
|
||||||
@@ -166,9 +168,9 @@
|
|||||||
:data-tip="t('obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more')"
|
:data-tip="t('obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more')"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.com/contact"
|
href="https://www.docuseal.com/qualified-electronic-signature"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative fields-grid-item"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="w-0 absolute left-0">
|
<div class="w-0 absolute left-0">
|
||||||
@@ -189,7 +191,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="fields.length < 4 && editable && withHelp"
|
v-if="fields.length < 4 && editable && withHelp && !showTourStartForm"
|
||||||
class="text-xs p-2 border border-base-200 rounded"
|
class="text-xs p-2 border border-base-200 rounded"
|
||||||
>
|
>
|
||||||
<ul class="list-disc list-outside ml-3">
|
<ul class="list-disc list-outside ml-3">
|
||||||
@@ -204,6 +206,23 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="fields.length < 4 && editable && withHelp && showTourStartForm"
|
||||||
|
class="rounded py-2 px-4 w-full border border-dashed border-base-300"
|
||||||
|
>
|
||||||
|
<div class="text-center text-sm">
|
||||||
|
{{ t('start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document') }}
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<label
|
||||||
|
for="start_tour_button"
|
||||||
|
class="btn btn-sm btn-warning w-40 mt-2"
|
||||||
|
@click="startTour"
|
||||||
|
>
|
||||||
|
{{ t('start_tour') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -229,6 +248,11 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
withFieldsSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
template: {
|
template: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
@@ -285,9 +309,14 @@ export default {
|
|||||||
selectedSubmitter: {
|
selectedSubmitter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
showTourStartForm: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['add-field', 'set-draw', 'set-draw-type', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
|
emits: ['add-field', 'set-draw', 'set-draw-type', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter', 'set-drag-placeholder'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
defaultFieldsSearch: ''
|
defaultFieldsSearch: ''
|
||||||
@@ -297,7 +326,18 @@ export default {
|
|||||||
fieldNames: FieldType.computed.fieldNames,
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldIcons: FieldType.computed.fieldIcons,
|
fieldIcons: FieldType.computed.fieldIcons,
|
||||||
isShowFieldSearch () {
|
isShowFieldSearch () {
|
||||||
return this.submitterDefaultFields.length > 15
|
if (this.withFieldsSearch === false) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return this.submitterDefaultFields.length > 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultFieldsIndex () {
|
||||||
|
return this.defaultFields.reduce((acc, field) => {
|
||||||
|
acc[field.name] = field
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
},
|
},
|
||||||
fieldIconsSorted () {
|
fieldIconsSorted () {
|
||||||
if (this.fieldTypes.length) {
|
if (this.fieldTypes.length) {
|
||||||
@@ -320,29 +360,63 @@ export default {
|
|||||||
},
|
},
|
||||||
filteredSubmitterDefaultFields () {
|
filteredSubmitterDefaultFields () {
|
||||||
if (this.defaultFieldsSearch) {
|
if (this.defaultFieldsSearch) {
|
||||||
return this.submitterDefaultFields.filter((f) => f.name.toLowerCase().includes(this.defaultFieldsSearch.toLowerCase()))
|
return this.submitterDefaultFields.filter((f) => (f.title || f.name).toLowerCase().includes(this.defaultFieldsSearch.toLowerCase()))
|
||||||
} else {
|
} else {
|
||||||
return this.submitterDefaultFields
|
return this.submitterDefaultFields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onDragstart (field) {
|
onDragstart (event, field) {
|
||||||
|
this.removeDragOverlay(event)
|
||||||
|
|
||||||
|
this.setDragPlaceholder(event)
|
||||||
|
|
||||||
this.$emit('set-drag', field)
|
this.$emit('set-drag', field)
|
||||||
},
|
},
|
||||||
|
setDragPlaceholder (event) {
|
||||||
|
this.$emit('set-drag-placeholder', {
|
||||||
|
offsetX: event.offsetX,
|
||||||
|
offsetY: event.offsetY,
|
||||||
|
x: event.clientX - event.offsetX,
|
||||||
|
y: event.clientY - event.offsetY,
|
||||||
|
w: event.currentTarget.clientWidth + 2,
|
||||||
|
h: event.currentTarget.clientHeight + 2
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeDragOverlay (event) {
|
||||||
|
const root = this.$el.getRootNode()
|
||||||
|
const hiddenEl = document.createElement('div')
|
||||||
|
|
||||||
|
hiddenEl.style.width = '1px'
|
||||||
|
hiddenEl.style.height = '1px'
|
||||||
|
hiddenEl.style.opacity = '0'
|
||||||
|
hiddenEl.style.position = 'fixed'
|
||||||
|
|
||||||
|
root.querySelector('#docuseal_modal_container').appendChild(hiddenEl)
|
||||||
|
|
||||||
|
event.dataTransfer.setDragImage(hiddenEl, 0, 0)
|
||||||
|
|
||||||
|
setTimeout(() => { hiddenEl.remove() }, 1000)
|
||||||
|
},
|
||||||
|
startTour () {
|
||||||
|
document.querySelector('app-tour').start()
|
||||||
|
},
|
||||||
onFieldDragover (e) {
|
onFieldDragover (e) {
|
||||||
const targetField = e.target.closest('[data-uuid]')
|
if (this.fieldsDragFieldRef.value) {
|
||||||
const dragField = this.$refs.fields.querySelector(`[data-uuid="${this.fieldsDragFieldRef.value.uuid}"]`)
|
const targetField = e.target.closest('[data-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)
|
||||||
const currentIndex = fields.indexOf(dragField)
|
const currentIndex = fields.indexOf(dragField)
|
||||||
const targetIndex = fields.indexOf(targetField)
|
const targetIndex = fields.indexOf(targetField)
|
||||||
|
|
||||||
if (currentIndex < targetIndex) {
|
if (currentIndex < targetIndex) {
|
||||||
targetField.after(dragField)
|
targetField.after(dragField)
|
||||||
} else {
|
} else {
|
||||||
targetField.before(dragField)
|
targetField.before(dragField)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,326 @@
|
|||||||
|
<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 class="modal-title">
|
||||||
|
{{ t('font') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-xl modal-close-button"
|
||||||
|
@click.prevent="$emit('close')"
|
||||||
|
>×</a>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center space-x-1.5">
|
||||||
|
<span>
|
||||||
|
<div class="dropdown modal-field-font-dropdown">
|
||||||
|
<label
|
||||||
|
tabindex="0"
|
||||||
|
class="base-input flex items-center justify-between"
|
||||||
|
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; line-height: 12px; 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 bottom-0 pointer-events-none text-sm h-5"
|
||||||
|
style="right: 13px; top: 6px"
|
||||||
|
>
|
||||||
|
pt
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="flex">
|
||||||
|
<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 class="flex">
|
||||||
|
<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 class="flex">
|
||||||
|
<div class="dropdown modal-field-font-dropdown">
|
||||||
|
<label
|
||||||
|
tabindex="0"
|
||||||
|
class="cursor-pointer flex bg-white border input-bordered rounded-md h-8 items-center justify-center px-1"
|
||||||
|
style="-webkit-appearance: none; -moz-appearance: none;"
|
||||||
|
>
|
||||||
|
<component :is="valigns.find((v) => v.value === (preferences.valign || 'center'))?.icon" />
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
class="dropdown-content p-0 mt-1 block z-10 menu shadow bg-white border border-base-300 rounded-md"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(valign, index) in valigns"
|
||||||
|
:key="index"
|
||||||
|
:value="valign.value"
|
||||||
|
:class="{ 'bg-base-300': preferences.valign == valign.value }"
|
||||||
|
class="hover:bg-base-300 px-2 py-1.5 cursor-pointer"
|
||||||
|
@click="[valign.value ? preferences.valign = valign.value : delete preferences.valign, closeDropdown()]"
|
||||||
|
>
|
||||||
|
<component :is="valign.icon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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 border border-base-content/20 rounded-xl bg-white px-4 h-16 modal-field-font-preview"
|
||||||
|
:style="{
|
||||||
|
color: preferences.color || 'black',
|
||||||
|
fontSize: (preferences.font_size || 12) + 'pt',
|
||||||
|
}"
|
||||||
|
:class="textClasses"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
contenteditable="true"
|
||||||
|
class="outline-none whitespace-nowrap truncate"
|
||||||
|
>
|
||||||
|
{{ field.default_value || field.name || buildDefaultName(field, template.fields) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<button
|
||||||
|
class="base-button w-full modal-save-button"
|
||||||
|
@click.prevent="saveAndClose"
|
||||||
|
>
|
||||||
|
{{ t('save') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IconChevronDown, IconBold, IconItalic, IconAlignLeft, IconAlignRight, IconAlignCenter, IconAlignBoxCenterTop, IconAlignBoxCenterBottom, IconAlignBoxCenterMiddle } 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' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
valigns () {
|
||||||
|
return [
|
||||||
|
{ icon: IconAlignBoxCenterTop, value: 'top' },
|
||||||
|
{ icon: IconAlignBoxCenterMiddle, value: 'center' },
|
||||||
|
{ icon: IconAlignBoxCenterBottom, value: 'bottom' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
'items-center': !this.preferences.valign || this.preferences.valign === 'center',
|
||||||
|
'items-start': this.preferences.valign === 'top',
|
||||||
|
'items-end': this.preferences.valign === 'bottom',
|
||||||
|
'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', 'valign', '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>
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
/>
|
/>
|
||||||
<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 class="modal-title">
|
||||||
{{ t('formula') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
{{ t('formula') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="text-xl"
|
class="text-xl modal-close-button"
|
||||||
@click.prevent="$emit('close')"
|
@click.prevent="$emit('close')"
|
||||||
>×</a>
|
>×</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="base-button w-full"
|
class="base-button w-full modal-save-button"
|
||||||
@click.prevent="validateSaveAndClose"
|
@click.prevent="validateSaveAndClose"
|
||||||
>
|
>
|
||||||
{{ t('save') }}
|
{{ t('save') }}
|
||||||
@@ -154,7 +154,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
fields () {
|
fields () {
|
||||||
return this.template.fields.reduce((acc, f) => {
|
return this.template.fields.reduce((acc, f) => {
|
||||||
if (f !== this.field && f.submitter_uuid === this.field.submitter_uuid && ['number'].includes(f.type) && !f.preferences?.formula) {
|
if (f !== this.field && ['number'].includes(f.type) && (!f.preferences?.formula || f.submitter_uuid !== this.field.submitter_uuid)) {
|
||||||
acc.push(f)
|
acc.push(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="isDragging || isLoading"
|
||||||
|
class="modal modal-open"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-2 p-4 items-center bg-base-100 h-full max-h-[85vh] max-w-6xl rounded-2xl w-full">
|
||||||
|
<Dropzone
|
||||||
|
class="flex-1 h-full"
|
||||||
|
icon="IconFilePlus"
|
||||||
|
:template-id="templateId"
|
||||||
|
:accept-file-types="acceptFileTypes"
|
||||||
|
:with-hover-class="false"
|
||||||
|
:with-description="false"
|
||||||
|
:title="t('add_a_new_document')"
|
||||||
|
type="add_files"
|
||||||
|
@loading="isLoading = $event"
|
||||||
|
@success="$emit('add', $event)"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
<div class="flex-1 flex gap-2 w-full">
|
||||||
|
<Dropzone
|
||||||
|
class="flex-1 h-full"
|
||||||
|
icon="IconFileSymlink"
|
||||||
|
:template-id="templateId"
|
||||||
|
:accept-file-types="acceptFileTypes"
|
||||||
|
:with-hover-class="false"
|
||||||
|
:with-description="false"
|
||||||
|
:title="t('replace_existing_document')"
|
||||||
|
@loading="isLoading = $event"
|
||||||
|
@success="$emit('replace', $event)"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
<Dropzone
|
||||||
|
v-if="withReplaceAndClone"
|
||||||
|
class="flex-1 h-full"
|
||||||
|
icon="IconFiles"
|
||||||
|
:template-id="templateId"
|
||||||
|
:accept-file-types="acceptFileTypes"
|
||||||
|
:with-hover-class="false"
|
||||||
|
:with-description="false"
|
||||||
|
:clone-template-on-upload="true"
|
||||||
|
:title="t('clone_and_replace_documents')"
|
||||||
|
@loading="isLoading = $event"
|
||||||
|
@success="$emit('replace-and-clone', $event)"
|
||||||
|
@error="$emit('error', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Dropzone from './dropzone'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HoverDropzone',
|
||||||
|
components: {
|
||||||
|
Dropzone
|
||||||
|
},
|
||||||
|
inject: ['t'],
|
||||||
|
props: {
|
||||||
|
isDragging: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
templateId: {
|
||||||
|
type: [Number, String],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
withReplaceAndClone: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
acceptFileTypes: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'image/*, application/pdf'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['add', 'replace', 'replace-and-clone', 'error'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isLoading: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
const en = {
|
const en = {
|
||||||
|
font: 'Font',
|
||||||
|
party: 'Party',
|
||||||
|
method: 'Method',
|
||||||
|
reorder_fields: 'Reorder fields',
|
||||||
verify_id: 'Verify ID',
|
verify_id: 'Verify ID',
|
||||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtain qualified electronic signature (QeS) with the trusted provider. Click to learn more.',
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtain qualified electronic signature (QeS) with the trusted provider. Click to learn more.',
|
||||||
editable: 'Editable',
|
editable: 'Editable',
|
||||||
@@ -48,6 +52,8 @@ const en = {
|
|||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
any: 'Any',
|
any: 'Any',
|
||||||
drawn: 'Drawn',
|
drawn: 'Drawn',
|
||||||
|
drawn_or_typed: 'Drawn or Typed',
|
||||||
|
upload: 'Upload',
|
||||||
formula: 'Formula',
|
formula: 'Formula',
|
||||||
typed: 'Typed',
|
typed: 'Typed',
|
||||||
draw_field_on_the_document: 'Draw a field on the document',
|
draw_field_on_the_document: 'Draw a field on the document',
|
||||||
@@ -57,6 +63,9 @@ const en = {
|
|||||||
processing_: 'Processing...',
|
processing_: 'Processing...',
|
||||||
add_pdf_documents_or_images: 'Add PDF documents or images',
|
add_pdf_documents_or_images: 'Add PDF documents or images',
|
||||||
add_documents_or_images: 'Add documents or images',
|
add_documents_or_images: 'Add documents or images',
|
||||||
|
add_a_new_document: 'Add a new document',
|
||||||
|
replace_existing_document: 'Replace existing document',
|
||||||
|
clone_and_replace_documents: 'Clone and replace documents',
|
||||||
required: 'Required',
|
required: 'Required',
|
||||||
default_value: 'Default value',
|
default_value: 'Default value',
|
||||||
format: 'Format',
|
format: 'Format',
|
||||||
@@ -152,10 +161,16 @@ const en = {
|
|||||||
some_fields_are_missing_in_the_formula: 'Some fields are missing in the formula.',
|
some_fields_are_missing_in_the_formula: 'Some fields are missing in the formula.',
|
||||||
learn_more: 'Learn more',
|
learn_more: 'Learn more',
|
||||||
and: 'and',
|
and: 'and',
|
||||||
or: 'or'
|
or: 'or',
|
||||||
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Start a quick tour to learn how to create an send your first document',
|
||||||
|
start_tour: 'Start Tour'
|
||||||
}
|
}
|
||||||
|
|
||||||
const es = {
|
const es = {
|
||||||
|
fuente: 'Fuente',
|
||||||
|
party: 'Parte',
|
||||||
|
method: 'Método',
|
||||||
|
reorder_fields: 'Reordenar campos',
|
||||||
verify_id: 'Verificar ID',
|
verify_id: 'Verificar ID',
|
||||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenga una firma electrónica cualificada (QeS) con el proveedor de confianza. Haga clic para obtener más información.',
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenga una firma electrónica cualificada (QeS) con el proveedor de confianza. Haga clic para obtener más información.',
|
||||||
recurrent: 'Recurrente',
|
recurrent: 'Recurrente',
|
||||||
@@ -213,6 +228,9 @@ const es = {
|
|||||||
processing_: 'Procesando...',
|
processing_: 'Procesando...',
|
||||||
add_pdf_documents_or_images: 'Agregar documentos PDF o imágenes',
|
add_pdf_documents_or_images: 'Agregar documentos PDF o imágenes',
|
||||||
add_documents_or_images: 'Agregar documentos o imágenes',
|
add_documents_or_images: 'Agregar documentos o imágenes',
|
||||||
|
add_a_new_document: 'Agregar un nuevo documento',
|
||||||
|
replace_existing_document: 'Reemplazar documento existente',
|
||||||
|
clone_and_replace_documents: 'Clonar y reemplazar documentos',
|
||||||
required: 'Requerido',
|
required: 'Requerido',
|
||||||
default_value: 'Valor predeterminado',
|
default_value: 'Valor predeterminado',
|
||||||
format: 'Formato',
|
format: 'Formato',
|
||||||
@@ -289,6 +307,8 @@ const es = {
|
|||||||
add_document: 'Subir',
|
add_document: 'Subir',
|
||||||
any: 'Cualquier',
|
any: 'Cualquier',
|
||||||
drawn: 'Dibujado',
|
drawn: 'Dibujado',
|
||||||
|
drawn_or_typed: 'Dibujado o Escrito',
|
||||||
|
upload: 'Subir',
|
||||||
typed: 'Escrito',
|
typed: 'Escrito',
|
||||||
none: 'Ninguno',
|
none: 'Ninguno',
|
||||||
ssn: 'SSN',
|
ssn: 'SSN',
|
||||||
@@ -309,10 +329,16 @@ const es = {
|
|||||||
some_fields_are_missing_in_the_formula: 'Faltan algunos campos en la fórmula.',
|
some_fields_are_missing_in_the_formula: 'Faltan algunos campos en la fórmula.',
|
||||||
learn_more: 'Aprende más',
|
learn_more: 'Aprende más',
|
||||||
and: 'y',
|
and: 'y',
|
||||||
or: 'o'
|
or: 'o',
|
||||||
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Inicia una guía rápida para aprender a crear y enviar tu primer documento.',
|
||||||
|
start_tour: 'Iniciar guía'
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = {
|
const it = {
|
||||||
|
font: 'Carattere',
|
||||||
|
party: 'Parte',
|
||||||
|
method: 'Metodo',
|
||||||
|
reorder_fields: 'Riordina i campi',
|
||||||
verify_id: 'Verifica ID',
|
verify_id: 'Verifica ID',
|
||||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Ottieni una firma elettronica qualificata (QeS) con il fornitore di fiducia. Clicca per saperne di più.',
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Ottieni una firma elettronica qualificata (QeS) con il fornitore di fiducia. Clicca per saperne di più.',
|
||||||
ricorrente: 'Ricorrente',
|
ricorrente: 'Ricorrente',
|
||||||
@@ -362,6 +388,8 @@ const it = {
|
|||||||
cancel: 'Annulla',
|
cancel: 'Annulla',
|
||||||
any: 'Qualsiasi',
|
any: 'Qualsiasi',
|
||||||
drawn: 'Disegnato',
|
drawn: 'Disegnato',
|
||||||
|
drawn_or_typed: 'Disegnato o Digitato',
|
||||||
|
upload: 'Caricare',
|
||||||
formula: 'Formula',
|
formula: 'Formula',
|
||||||
typed: 'Digitato',
|
typed: 'Digitato',
|
||||||
draw_field_on_the_document: 'Disegnare un campo sul documento',
|
draw_field_on_the_document: 'Disegnare un campo sul documento',
|
||||||
@@ -371,6 +399,9 @@ const it = {
|
|||||||
processing_: 'Elaborazione...',
|
processing_: 'Elaborazione...',
|
||||||
add_pdf_documents_or_images: 'Aggiungi documenti PDF o immagini',
|
add_pdf_documents_or_images: 'Aggiungi documenti PDF o immagini',
|
||||||
add_documents_or_images: 'Aggiungi documenti o immagini',
|
add_documents_or_images: 'Aggiungi documenti o immagini',
|
||||||
|
add_a_new_document: 'Aggiungi un nuovo documento',
|
||||||
|
replace_existing_document: 'Sostituisci documento esistente',
|
||||||
|
clone_and_replace_documents: 'Clona e sostituisci documenti',
|
||||||
required: 'Obbligatorio',
|
required: 'Obbligatorio',
|
||||||
default_value: 'Valore predefinito',
|
default_value: 'Valore predefinito',
|
||||||
format: 'Formato',
|
format: 'Formato',
|
||||||
@@ -466,10 +497,16 @@ const it = {
|
|||||||
some_fields_are_missing_in_the_formula: 'Alcuni campi mancano nella formula.',
|
some_fields_are_missing_in_the_formula: 'Alcuni campi mancano nella formula.',
|
||||||
learn_more: 'Scopri di più',
|
learn_more: 'Scopri di più',
|
||||||
and: 'e',
|
and: 'e',
|
||||||
or: 'o'
|
or: 'o',
|
||||||
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Inizia un tour rapido per imparare a creare e inviare il tuo primo documento.',
|
||||||
|
start_tour: 'Inizia il tour'
|
||||||
}
|
}
|
||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
|
fonte: 'Fonte',
|
||||||
|
party: 'Parte',
|
||||||
|
method: 'Método',
|
||||||
|
reorder_fields: 'Reorganizar campos',
|
||||||
verify_id: 'Verificar ID',
|
verify_id: 'Verificar ID',
|
||||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenha a assinatura eletrônica qualificada (QeS) com o provedor confiável. Clique para saber mais.',
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenha a assinatura eletrônica qualificada (QeS) com o provedor confiável. Clique para saber mais.',
|
||||||
recurrent: 'Recurrente',
|
recurrent: 'Recurrente',
|
||||||
@@ -527,6 +564,9 @@ const pt = {
|
|||||||
processing_: 'Processando...',
|
processing_: 'Processando...',
|
||||||
add_pdf_documents_or_images: 'Adicionar documentos PDF ou imagens',
|
add_pdf_documents_or_images: 'Adicionar documentos PDF ou imagens',
|
||||||
add_documents_or_images: 'Adicionar documentos ou imagens',
|
add_documents_or_images: 'Adicionar documentos ou imagens',
|
||||||
|
add_a_new_document: 'Ajouter un nouveau document',
|
||||||
|
replace_existing_document: 'Substituir documento existente',
|
||||||
|
clone_and_replace_documents: 'Clonar e substituir documentos',
|
||||||
required: 'Obrigatório',
|
required: 'Obrigatório',
|
||||||
default_value: 'Valor padrão',
|
default_value: 'Valor padrão',
|
||||||
format: 'Formato',
|
format: 'Formato',
|
||||||
@@ -603,6 +643,8 @@ const pt = {
|
|||||||
add_document: 'Enviar',
|
add_document: 'Enviar',
|
||||||
any: 'Qualquer',
|
any: 'Qualquer',
|
||||||
drawn: 'Desenhado',
|
drawn: 'Desenhado',
|
||||||
|
drawn_or_typed: 'Desenhado ou Digitado',
|
||||||
|
upload: 'Carregar',
|
||||||
typed: 'Digitado',
|
typed: 'Digitado',
|
||||||
none: 'Nenhum',
|
none: 'Nenhum',
|
||||||
ssn: 'SSN',
|
ssn: 'SSN',
|
||||||
@@ -623,10 +665,16 @@ const pt = {
|
|||||||
some_fields_are_missing_in_the_formula: 'Faltam alguns campos na fórmula.',
|
some_fields_are_missing_in_the_formula: 'Faltam alguns campos na fórmula.',
|
||||||
learn_more: 'Saiba mais',
|
learn_more: 'Saiba mais',
|
||||||
and: 'e',
|
and: 'e',
|
||||||
or: 'ou'
|
or: 'ou',
|
||||||
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Comece um tour rápido para aprender a criar e enviar seu primeiro documento.',
|
||||||
|
start_tour: 'Iniciar tour'
|
||||||
}
|
}
|
||||||
|
|
||||||
const fr = {
|
const fr = {
|
||||||
|
font: 'Police',
|
||||||
|
party: 'Partie',
|
||||||
|
method: 'Méthode',
|
||||||
|
reorder_fields: 'Réorganiser les champs',
|
||||||
verify_id: "Vérifier l'ID",
|
verify_id: "Vérifier l'ID",
|
||||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenez une signature électronique qualifiée (QeS) avec le fournisseur de confiance. Cliquez pour en savoir plus.',
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Obtenez une signature électronique qualifiée (QeS) avec le fournisseur de confiance. Cliquez pour en savoir plus.',
|
||||||
recurrent: 'Récurrent',
|
recurrent: 'Récurrent',
|
||||||
@@ -683,6 +731,9 @@ const fr = {
|
|||||||
processing_: 'Traitement en cours...',
|
processing_: 'Traitement en cours...',
|
||||||
add_pdf_documents_or_images: 'Ajoutez des documents PDF ou des images',
|
add_pdf_documents_or_images: 'Ajoutez des documents PDF ou des images',
|
||||||
add_documents_or_images: 'Ajoutez des documents ou des images',
|
add_documents_or_images: 'Ajoutez des documents ou des images',
|
||||||
|
add_a_new_document: 'Adicionar um novo documento',
|
||||||
|
replace_existing_document: 'Remplacer le document existant',
|
||||||
|
clone_and_replace_documents: 'Cloner et remplacer les documents',
|
||||||
required: 'Requis',
|
required: 'Requis',
|
||||||
default_value: 'Valeur par défaut',
|
default_value: 'Valeur par défaut',
|
||||||
format: 'Format',
|
format: 'Format',
|
||||||
@@ -760,6 +811,8 @@ const fr = {
|
|||||||
add_document: 'Télécharger',
|
add_document: 'Télécharger',
|
||||||
any: 'Tout',
|
any: 'Tout',
|
||||||
drawn: 'Dessiné',
|
drawn: 'Dessiné',
|
||||||
|
drawn_or_typed: 'Dessiné ou Tapé',
|
||||||
|
upload: 'Téléverser',
|
||||||
typed: 'Tapé',
|
typed: 'Tapé',
|
||||||
none: 'Aucun',
|
none: 'Aucun',
|
||||||
ssn: 'SSN',
|
ssn: 'SSN',
|
||||||
@@ -780,10 +833,16 @@ const fr = {
|
|||||||
some_fields_are_missing_in_the_formula: 'Certains champs manquent dans la formule.',
|
some_fields_are_missing_in_the_formula: 'Certains champs manquent dans la formule.',
|
||||||
learn_more: 'En savoir plus',
|
learn_more: 'En savoir plus',
|
||||||
and: 'et',
|
and: 'et',
|
||||||
or: 'ou'
|
or: 'ou',
|
||||||
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Lancez une visite rapide pour apprendre à créer et envoyer votre premier document.',
|
||||||
|
start_tour: 'Démarrer'
|
||||||
}
|
}
|
||||||
|
|
||||||
const de = {
|
const de = {
|
||||||
|
font: 'Schriftart',
|
||||||
|
party: 'Partei',
|
||||||
|
method: 'Verfahren',
|
||||||
|
reorder_fields: 'Felder neu anordnen',
|
||||||
verify_id: 'ID überprüfen',
|
verify_id: 'ID überprüfen',
|
||||||
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Erhalten Sie eine qualifizierte elektronische Signatur (QeS) beim vertrauenswürdigen Anbieter. Klicken Sie hier, um mehr zu erfahren.',
|
obtain_qualified_electronic_signature_with_the_trusted_provider_click_to_learn_more: 'Erhalten Sie eine qualifizierte elektronische Signatur (QeS) beim vertrauenswürdigen Anbieter. Klicken Sie hier, um mehr zu erfahren.',
|
||||||
wiederkehrend: 'Wiederkehrend',
|
wiederkehrend: 'Wiederkehrend',
|
||||||
@@ -840,6 +899,9 @@ const de = {
|
|||||||
processing_: 'Verarbeitung...',
|
processing_: 'Verarbeitung...',
|
||||||
add_pdf_documents_or_images: 'PDF-Dokumente oder Bilder hinzufügen',
|
add_pdf_documents_or_images: 'PDF-Dokumente oder Bilder hinzufügen',
|
||||||
add_documents_or_images: 'Dokumente oder Bilder hinzufügen',
|
add_documents_or_images: 'Dokumente oder Bilder hinzufügen',
|
||||||
|
add_a_new_document: 'Neues Dokument hinzufügen',
|
||||||
|
replace_existing_document: 'Vorhandenes Dokument ersetzen',
|
||||||
|
clone_and_replace_documents: 'Dokumente klonen und ersetzen',
|
||||||
required: 'Erforderlich',
|
required: 'Erforderlich',
|
||||||
default_value: 'Standardwert',
|
default_value: 'Standardwert',
|
||||||
format: 'Format',
|
format: 'Format',
|
||||||
@@ -917,6 +979,8 @@ const de = {
|
|||||||
add_document: 'Hochladen',
|
add_document: 'Hochladen',
|
||||||
any: 'Jede',
|
any: 'Jede',
|
||||||
drawn: 'Gezeichnet',
|
drawn: 'Gezeichnet',
|
||||||
|
drawn_or_typed: 'Gezeichnet oder getippt',
|
||||||
|
upload: 'Upload',
|
||||||
typed: 'Getippt',
|
typed: 'Getippt',
|
||||||
none: 'Keine',
|
none: 'Keine',
|
||||||
ssn: 'SSN',
|
ssn: 'SSN',
|
||||||
@@ -937,7 +1001,9 @@ const de = {
|
|||||||
some_fields_are_missing_in_the_formula: 'Einige Felder fehlen in der Formel.',
|
some_fields_are_missing_in_the_formula: 'Einige Felder fehlen in der Formel.',
|
||||||
learn_more: 'Erfahren Sie mehr',
|
learn_more: 'Erfahren Sie mehr',
|
||||||
and: 'und',
|
and: 'und',
|
||||||
or: 'oder'
|
or: 'oder',
|
||||||
|
start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: 'Starte eine kurze Tour, um zu lernen, wie du dein erstes Dokument erstellst und versendest.',
|
||||||
|
start_tour: 'Starten'
|
||||||
}
|
}
|
||||||
|
|
||||||
export { en, es, it, pt, fr, de }
|
export { en, es, it, pt, fr, de }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute text-center w-full bottom-0 pr-3 mb-4">
|
<div class="absolute text-center w-full bottom-0 pr-3 mb-4">
|
||||||
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20">
|
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20 draw-field-container-mobile">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<component
|
<component
|
||||||
:is="fieldIcons[drawField.type]"
|
:is="fieldIcons[drawField.type]"
|
||||||
|
|||||||
Loaded 100 of 353 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user