mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 06:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
bb3b38e76f | ||
|
|
bc4c21f804 | ||
|
|
2fb5de4fa9 | ||
|
|
2e58b2e37b | ||
|
|
780f5512f4 | ||
|
|
2cf4908d53 | ||
|
|
4a9b898713 | ||
|
|
bb552d13d4 | ||
|
|
15d06f4fb0 | ||
|
|
35a2aba887 | ||
|
|
48a82b1d49 | ||
|
|
b8453e14c4 | ||
|
|
29fbdfbe2f | ||
|
|
5340af0bc6 | ||
|
|
1881b6c50f | ||
|
|
b6a2aae970 | ||
|
|
b317094192 | ||
|
|
ab1f4cf297 | ||
|
|
d3033c072a | ||
|
|
f380200e03 | ||
|
|
a4928268ea | ||
|
|
dd18c51384 | ||
|
|
c2014a1549 | ||
|
|
46edc7dc37 | ||
|
|
d62d83ef8e | ||
|
|
79350c8fba | ||
|
|
6702dd52b6 | ||
|
|
c8b74a2c43 | ||
|
|
1746920eb5 | ||
|
|
bacbc4309c | ||
|
|
4429d742b3 | ||
|
|
38b5eccb65 | ||
|
|
d8126c4a47 | ||
|
|
b1c31672ee | ||
|
|
975f3a95fe | ||
|
|
e3bed5ae67 | ||
|
|
4737b103c0 |
No files matched your search
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3.4
|
||||
ruby-version: 3.4.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3.4
|
||||
ruby-version: 3.4.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3.4
|
||||
ruby-version: 3.4.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ RSpec/MultipleMemoizedHelpers:
|
||||
Max: 9
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 4
|
||||
Max: 5
|
||||
|
||||
Rails/I18nLocaleTexts:
|
||||
Enabled: false
|
||||
|
||||
+5
-4
@@ -1,4 +1,4 @@
|
||||
FROM ruby:3.3.4-alpine as fonts
|
||||
FROM ruby:3.4.1-alpine as fonts
|
||||
|
||||
WORKDIR /fonts
|
||||
|
||||
@@ -6,7 +6,7 @@ RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-
|
||||
|
||||
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.1-alpine as webpack
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV NODE_ENV=production
|
||||
@@ -32,7 +32,7 @@ COPY ./app/views ./app/views
|
||||
|
||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||
|
||||
FROM ruby:3.3.4-alpine as app
|
||||
FROM ruby:3.4.1-alpine as app
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV BUNDLE_WITHOUT="development:test"
|
||||
@@ -62,12 +62,13 @@ RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-
|
||||
COPY ./bin ./bin
|
||||
COPY ./app ./app
|
||||
COPY ./config ./config
|
||||
COPY ./db ./db
|
||||
COPY ./db/migrate ./db/migrate
|
||||
COPY ./log ./log
|
||||
COPY ./lib ./lib
|
||||
COPY ./public ./public
|
||||
COPY ./tmp ./tmp
|
||||
COPY LICENSE README.md Rakefile config.ru .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=fonts /fonts/FreeSans.ttf /usr/share/fonts/freefont
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '3.3.4'
|
||||
ruby '3.4.1'
|
||||
|
||||
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
||||
gem 'aws-sdk-s3', require: false
|
||||
|
||||
+161
-155
@@ -1,67 +1,67 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
actioncable (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activestorage (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
actionmailbox (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activestorage (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
actionview (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
actionmailer (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
actionview (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (7.2.1.1)
|
||||
actionview (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
actionpack (8.0.1)
|
||||
actionview (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4, < 3.2)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activestorage (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
actiontext (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activestorage (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
actionview (8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
activejob (8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
activerecord (7.2.1.1)
|
||||
activemodel (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
activemodel (8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
activerecord (8.0.1)
|
||||
activemodel (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
activestorage (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
marcel (~> 1.0)
|
||||
activesupport (7.2.1.1)
|
||||
activesupport (8.0.1)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
@@ -71,31 +71,32 @@ GEM
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
uri (>= 0.13.1)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
annotate (2.6.5)
|
||||
activerecord (>= 2.3.0)
|
||||
rake (>= 0.8.7)
|
||||
arabic-letter-connector (0.1.1)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.985.0)
|
||||
aws-sdk-core (3.209.1)
|
||||
aws-partitions (1.1027.0)
|
||||
aws-sdk-core (3.214.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.94.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sdk-kms (1.96.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.167.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sdk-s3 (1.176.1)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-secretsmanager (1.108.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sdk-secretsmanager (1.110.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.10.0)
|
||||
aws-sigv4 (1.10.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
azure-storage-blob (2.0.3)
|
||||
azure-storage-common (~> 2.0)
|
||||
@@ -107,6 +108,7 @@ GEM
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
base64 (0.2.0)
|
||||
bcrypt (3.1.20)
|
||||
benchmark (0.4.0)
|
||||
better_html (2.1.1)
|
||||
actionview (>= 6.0)
|
||||
activesupport (>= 6.0)
|
||||
@@ -119,7 +121,7 @@ GEM
|
||||
bootsnap (1.18.4)
|
||||
msgpack (~> 1.2)
|
||||
builder (3.3.0)
|
||||
bullet (7.2.0)
|
||||
bullet (8.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
camertron-eprun (1.1.1)
|
||||
@@ -145,14 +147,14 @@ GEM
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
css_parser (1.19.0)
|
||||
css_parser (1.21.0)
|
||||
addressable
|
||||
csv (3.3.0)
|
||||
csv (3.3.2)
|
||||
cuprite (0.15.1)
|
||||
capybara (~> 3.0)
|
||||
ferrum (~> 0.15.0)
|
||||
date (3.3.4)
|
||||
debug (1.9.2)
|
||||
date (3.4.1)
|
||||
debug (1.10.0)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
declarative (0.0.20)
|
||||
@@ -162,32 +164,32 @@ GEM
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-two-factor (6.0.0)
|
||||
activesupport (~> 7.0)
|
||||
devise-two-factor (6.1.0)
|
||||
activesupport (>= 7.0, < 8.1)
|
||||
devise (~> 4.0)
|
||||
railties (~> 7.0)
|
||||
railties (>= 7.0, < 8.1)
|
||||
rotp (~> 6.0)
|
||||
diff-lcs (1.5.1)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
docile (1.4.1)
|
||||
dotenv (3.1.4)
|
||||
dotenv (3.1.7)
|
||||
drb (2.2.1)
|
||||
email_typo (0.2.3)
|
||||
erb_lint (0.6.0)
|
||||
erb_lint (0.7.0)
|
||||
activesupport
|
||||
better_html (>= 2.0.1)
|
||||
parser (>= 2.7.1.4)
|
||||
rainbow
|
||||
rubocop (>= 1)
|
||||
smart_properties
|
||||
erubi (1.13.0)
|
||||
erubi (1.13.1)
|
||||
factory_bot (6.5.0)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.4.3)
|
||||
factory_bot (~> 6.4)
|
||||
factory_bot_rails (6.4.4)
|
||||
factory_bot (~> 6.5)
|
||||
railties (>= 5.0.0)
|
||||
faker (3.4.2)
|
||||
faker (3.5.1)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
@@ -207,8 +209,8 @@ GEM
|
||||
faraday-follow_redirects (0.3.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-multipart (1.1.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
@@ -235,9 +237,9 @@ GEM
|
||||
mutex_m
|
||||
representable (~> 3.0)
|
||||
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-storage_v1 (0.46.0)
|
||||
google-apis-storage_v1 (0.49.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-core (1.7.1)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
@@ -245,7 +247,7 @@ GEM
|
||||
google-cloud-env (2.2.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-storage (1.52.0)
|
||||
google-cloud-storage (1.54.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-core (~> 0.13)
|
||||
@@ -254,15 +256,17 @@ GEM
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (~> 1.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.11.1)
|
||||
google-logging-utils (0.1.0)
|
||||
googleauth (1.12.2)
|
||||
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)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
hashdiff (1.1.1)
|
||||
hexapdf (0.47.0)
|
||||
hashdiff (1.1.2)
|
||||
hexapdf (1.0.3)
|
||||
cmdparse (~> 3.0, >= 3.0.3)
|
||||
geom2d (~> 0.4, >= 0.4.1)
|
||||
openssl (>= 2.2.1)
|
||||
@@ -273,12 +277,12 @@ GEM
|
||||
image_processing (1.13.0)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
io-console (0.7.2)
|
||||
irb (1.14.1)
|
||||
io-console (0.8.0)
|
||||
irb (1.14.3)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.2)
|
||||
json (2.9.1)
|
||||
jwt (2.9.3)
|
||||
base64
|
||||
language_server-protocol (3.17.0.3)
|
||||
@@ -292,13 +296,13 @@ GEM
|
||||
letter_opener (~> 1.9)
|
||||
railties (>= 6.1)
|
||||
rexml
|
||||
logger (1.6.1)
|
||||
logger (1.6.4)
|
||||
lograge (0.14.0)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.22.0)
|
||||
loofah (2.23.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
@@ -311,16 +315,16 @@ GEM
|
||||
method_source (1.1.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.7)
|
||||
minitest (5.25.1)
|
||||
msgpack (1.7.3)
|
||||
mini_portile2 (2.8.8)
|
||||
minitest (5.25.4)
|
||||
msgpack (1.7.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.2.0)
|
||||
mutex_m (0.3.0)
|
||||
mysql2 (0.5.6)
|
||||
net-http-persistent (4.0.4)
|
||||
net-http-persistent (4.0.5)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.4.17)
|
||||
net-imap (0.5.3)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -329,28 +333,28 @@ GEM
|
||||
timeout
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
nio4r (2.7.3)
|
||||
nokogiri (1.16.7)
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.17.2)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.7-aarch64-linux)
|
||||
nokogiri (1.17.2-aarch64-linux)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.7-x86_64-linux)
|
||||
nokogiri (1.17.2-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.6)
|
||||
oj (3.16.8)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
openssl (3.2.0)
|
||||
openssl (3.3.0)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.1.4)
|
||||
ostruct (0.6.0)
|
||||
ostruct (0.6.1)
|
||||
package_json (0.1.0)
|
||||
pagy (9.1.0)
|
||||
pagy (9.3.3)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.5.0)
|
||||
parser (3.3.6.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pg (1.5.8)
|
||||
pg (1.5.9)
|
||||
premailer (1.27.0)
|
||||
addressable
|
||||
css_parser (>= 1.19.0)
|
||||
@@ -361,15 +365,16 @@ GEM
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
pretender (0.5.0)
|
||||
actionpack (>= 6.1)
|
||||
pry (0.14.2)
|
||||
pry (0.15.0)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-rails (0.3.11)
|
||||
pry (>= 0.13.0)
|
||||
psych (5.1.2)
|
||||
psych (5.2.2)
|
||||
date
|
||||
stringio
|
||||
public_suffix (6.0.1)
|
||||
puma (6.4.3)
|
||||
puma (6.5.0)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.8)
|
||||
@@ -379,40 +384,39 @@ GEM
|
||||
rack (>= 3.0.0)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rackup (2.1.0)
|
||||
rackup (2.2.1)
|
||||
rack (>= 3)
|
||||
webrick (~> 1.8)
|
||||
rails (7.2.1.1)
|
||||
actioncable (= 7.2.1.1)
|
||||
actionmailbox (= 7.2.1.1)
|
||||
actionmailer (= 7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
actiontext (= 7.2.1.1)
|
||||
actionview (= 7.2.1.1)
|
||||
activejob (= 7.2.1.1)
|
||||
activemodel (= 7.2.1.1)
|
||||
activerecord (= 7.2.1.1)
|
||||
activestorage (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
rails (8.0.1)
|
||||
actioncable (= 8.0.1)
|
||||
actionmailbox (= 8.0.1)
|
||||
actionmailer (= 8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
actiontext (= 8.0.1)
|
||||
actionview (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activemodel (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activestorage (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.2.1.1)
|
||||
railties (= 8.0.1)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
rails-html-sanitizer (1.6.2)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
rails-i18n (7.0.9)
|
||||
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 (8.0.1)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
railties (>= 8.0.0, < 9)
|
||||
rails_autolink (1.1.8)
|
||||
actionview (> 3.1)
|
||||
activesupport (> 3.1)
|
||||
railties (> 3.1)
|
||||
railties (7.2.1.1)
|
||||
actionpack (= 7.2.1.1)
|
||||
activesupport (= 7.2.1.1)
|
||||
railties (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
@@ -420,12 +424,12 @@ GEM
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.2.1)
|
||||
rdoc (6.7.0)
|
||||
rdoc (6.10.0)
|
||||
psych (>= 4.0.0)
|
||||
redis-client (0.22.2)
|
||||
redis-client (0.23.0)
|
||||
connection_pool
|
||||
regexp_parser (2.9.2)
|
||||
reline (0.5.10)
|
||||
regexp_parser (2.9.3)
|
||||
reline (0.6.0)
|
||||
io-console (~> 0.5)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
@@ -437,13 +441,13 @@ GEM
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.9)
|
||||
rexml (3.4.0)
|
||||
rotp (6.3.0)
|
||||
rqrcode (2.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec-core (3.13.1)
|
||||
rspec-core (3.13.2)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
@@ -451,7 +455,7 @@ GEM
|
||||
rspec-mocks (3.13.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (7.0.1)
|
||||
rspec-rails (7.1.0)
|
||||
actionpack (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
@@ -459,48 +463,47 @@ GEM
|
||||
rspec-expectations (~> 3.13)
|
||||
rspec-mocks (~> 3.13)
|
||||
rspec-support (~> 3.13)
|
||||
rspec-support (3.13.1)
|
||||
rubocop (1.66.1)
|
||||
rspec-support (3.13.2)
|
||||
rubocop (1.69.2)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.4, < 3.0)
|
||||
rubocop-ast (>= 1.32.2, < 2.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.36.2, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.32.3)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.37.0)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-performance (1.22.1)
|
||||
rubocop-performance (1.23.0)
|
||||
rubocop (>= 1.48.1, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rails (2.26.2)
|
||||
rubocop-rails (2.27.0)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.52.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (3.1.0)
|
||||
rubocop-rspec (3.3.0)
|
||||
rubocop (~> 1.61)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-vips (2.2.2)
|
||||
ffi (~> 1.12)
|
||||
logger
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyXL (3.4.27)
|
||||
rubyXL (3.4.33)
|
||||
nokogiri (>= 1.10.8)
|
||||
rubyzip (>= 1.3.0)
|
||||
rubyzip (2.3.2)
|
||||
securerandom (0.3.1)
|
||||
semantic_range (3.0.0)
|
||||
securerandom (0.4.1)
|
||||
semantic_range (3.1.0)
|
||||
shakapacker (8.0.2)
|
||||
activesupport (>= 5.2)
|
||||
package_json
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
sidekiq (7.3.2)
|
||||
concurrent-ruby (< 2)
|
||||
sidekiq (7.3.7)
|
||||
connection_pool (>= 2.3.0)
|
||||
logger
|
||||
rack (>= 2.2.4)
|
||||
@@ -517,17 +520,17 @@ GEM
|
||||
simplecov-html (0.13.1)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
smart_properties (1.17.0)
|
||||
sqlite3 (2.1.0)
|
||||
sqlite3 (2.4.1)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
sqlite3 (2.1.0-aarch64-linux-gnu)
|
||||
sqlite3 (2.1.0-x86_64-linux-gnu)
|
||||
stringio (3.1.1)
|
||||
strip_attributes (1.13.0)
|
||||
activemodel (>= 3.0, < 8.0)
|
||||
sqlite3 (2.4.1-aarch64-linux-gnu)
|
||||
sqlite3 (2.4.1-x86_64-linux-gnu)
|
||||
stringio (3.1.2)
|
||||
strip_attributes (1.14.1)
|
||||
activemodel (>= 3.0, < 9.0)
|
||||
thor (1.3.2)
|
||||
timeout (0.4.1)
|
||||
timeout (0.4.3)
|
||||
trailblazer-option (0.1.2)
|
||||
turbo-rails (2.0.10)
|
||||
turbo-rails (2.0.11)
|
||||
actionpack (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
twitter_cldr (6.12.1)
|
||||
@@ -539,9 +542,12 @@ GEM
|
||||
tzinfo-data (1.2024.2)
|
||||
tzinfo (>= 1.0.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)
|
||||
useragent (0.16.10)
|
||||
uri (1.0.2)
|
||||
useragent (0.16.11)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.1)
|
||||
@@ -553,13 +559,13 @@ GEM
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.8.2)
|
||||
webrick (1.9.1)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.7.0)
|
||||
zeitwerk (2.7.1)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
@@ -628,7 +634,7 @@ DEPENDENCIES
|
||||
webmock
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.3.4p94
|
||||
ruby 3.4.1p0
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.3
|
||||
@@ -42,6 +42,7 @@ module Api
|
||||
|
||||
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
|
||||
(current_user && attachment.record.account.id == current_user.account_id) ||
|
||||
(current_user && !Docuseal.multitenant? && current_user.role == 'superadmin') ||
|
||||
!attachment.record.account.account_configs
|
||||
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ module Api
|
||||
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
|
||||
end
|
||||
|
||||
submissions = Submissions::Filter.call(submissions, current_user, params)
|
||||
|
||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||
template: :folder,
|
||||
combined_document_attachment: :blob,
|
||||
@@ -78,7 +80,9 @@ module Api
|
||||
|
||||
submissions.each do |submission|
|
||||
submission.submitters.each do |submitter|
|
||||
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id) if submitter.completed_at?
|
||||
next unless submitter.completed_at?
|
||||
|
||||
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, 'send_invitation_email' => false)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -91,7 +95,7 @@ module Api
|
||||
end
|
||||
|
||||
def destroy
|
||||
if params[:permanently] == 'true'
|
||||
if params[:permanently].in?(['true', true])
|
||||
@submission.destroy!
|
||||
else
|
||||
@submission.update!(archived_at: Time.current)
|
||||
@@ -114,7 +118,19 @@ module Api
|
||||
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
|
||||
end
|
||||
@@ -176,7 +192,7 @@ module Api
|
||||
key = params.key?(:submission) ? :submission : :submissions
|
||||
|
||||
params.permit(
|
||||
key => [permitted_attrs]
|
||||
{ key => [permitted_attrs] }, { key => permitted_attrs }
|
||||
).fetch(key, [])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,9 @@ module Api
|
||||
cloned_template.source = :api
|
||||
cloned_template.save!
|
||||
|
||||
schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
||||
schema_documents = Templates::CloneAttachments.call(template: cloned_template,
|
||||
original_template: @template,
|
||||
documents: params[:documents])
|
||||
|
||||
WebhookUrls.for_account_id(cloned_template.account_id, 'template.created').each do |webhook_url|
|
||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id,
|
||||
|
||||
@@ -74,7 +74,7 @@ module Api
|
||||
end
|
||||
|
||||
def destroy
|
||||
if params[:permanently] == 'true'
|
||||
if params[:permanently].in?(['true', true])
|
||||
@template.destroy!
|
||||
else
|
||||
@template.update!(archived_at: Time.current)
|
||||
@@ -100,7 +100,7 @@ module Api
|
||||
:name,
|
||||
: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,
|
||||
:required, :readonly, :default_value,
|
||||
:title, :description,
|
||||
|
||||
@@ -6,7 +6,7 @@ class ConsoleRedirectController < ApplicationController
|
||||
|
||||
def index
|
||||
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
|
||||
|
||||
params[:redir] = "#{Docuseal::CONSOLE_URL}/manage" if request.path == '/manage'
|
||||
|
||||
@@ -9,7 +9,7 @@ class EmbedScriptsController < ActionController::Metal
|
||||
<h2>Upgrade to Pro</h2>
|
||||
<p>Unlock embedded components by upgrading to Pro</p>
|
||||
<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
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,9 @@ class SubmissionsArchivedController < ApplicationController
|
||||
@submissions = @submissions.where.not(archived_at: nil)
|
||||
.or(@submissions.where.not(templates: { archived_at: nil }))
|
||||
.preload(: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::Filter.call(@submissions, current_user, params)
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
def destroy
|
||||
notice =
|
||||
if params[:permanently].present?
|
||||
if params[:permanently].in?(['true', true])
|
||||
@submission.destroy!
|
||||
|
||||
I18n.t('submission_has_been_removed')
|
||||
|
||||
@@ -10,12 +10,11 @@ class SubmissionsDashboardController < ApplicationController
|
||||
.where(templates: { archived_at: nil })
|
||||
.preload(: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::Filter.call(@submissions, current_user, params)
|
||||
|
||||
@submissions = @submissions.pending if params[:status] == 'pending'
|
||||
@submissions = @submissions.completed if params[:status] == 'completed'
|
||||
|
||||
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
else
|
||||
|
||||
@@ -4,6 +4,7 @@ class SubmissionsFiltersController < ApplicationController
|
||||
ALLOWED_NAMES = %w[
|
||||
author
|
||||
completed_at
|
||||
status
|
||||
created_at
|
||||
].freeze
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ class SubmitFormInviteController < ApplicationController
|
||||
|
||||
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
|
||||
invite_submitters.each do |item|
|
||||
(invite_submitters + optional_invite_submitters).each do |item|
|
||||
attrs = submitters_attributes.find { |e| e[:uuid] == item['uuid'] }
|
||||
|
||||
next unless attrs
|
||||
next if attrs[:email].blank?
|
||||
|
||||
submitter.submission.submitters.create!(**attrs, account_id: submitter.account_id)
|
||||
|
||||
@@ -46,9 +48,9 @@ class SubmitFormInviteController < ApplicationController
|
||||
!submitter.submission.template.archived_at?
|
||||
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|
|
||||
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
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ class SubmittersAutocompleteController < ApplicationController
|
||||
def index
|
||||
submitters = search_submitters(@submitters)
|
||||
|
||||
values = submitters.limit(LIMIT).group(SELECT_COLUMNS.join(', ')).pluck(SELECT_COLUMNS.join(', '))
|
||||
arel_columns = SELECT_COLUMNS.map { |col| Submitter.arel_table[col] }
|
||||
values = submitters.limit(LIMIT).group(arel_columns).pluck(arel_columns)
|
||||
|
||||
attrs = values.map { |row| SELECT_COLUMNS.zip(row).to_h }
|
||||
attrs = attrs.uniq { |e| e[params[:field]] } if params[:field].present?
|
||||
|
||||
@@ -4,7 +4,8 @@ class TemplateFoldersController < ApplicationController
|
||||
load_and_authorize_resource :template_folder
|
||||
|
||||
def show
|
||||
@templates = @template_folder.templates.active.preload(:author).order(id: :desc)
|
||||
@templates = @template_folder.templates.active.accessible_by(current_ability)
|
||||
.preload(:author, :template_accesses).order(id: :desc)
|
||||
@templates = Templates.search(@templates, params[:q])
|
||||
|
||||
@pagy, @templates = pagy(@templates, limit: 12)
|
||||
|
||||
@@ -4,7 +4,7 @@ class TemplatesArchivedController < ApplicationController
|
||||
load_and_authorize_resource :template, parent: false
|
||||
|
||||
def index
|
||||
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder).order(id: :desc)
|
||||
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
|
||||
@templates = Templates.search(@templates, params[:q])
|
||||
|
||||
@pagy, @templates = pagy(@templates, limit: 12)
|
||||
|
||||
@@ -9,12 +9,11 @@ class TemplatesController < ApplicationController
|
||||
submissions = @template.submissions.accessible_by(current_ability)
|
||||
submissions = submissions.active if @template.archived_at.blank?
|
||||
submissions = Submissions.search(submissions, params[:q], search_values: true)
|
||||
submissions = Submissions::Filter.call(submissions, current_user, params)
|
||||
submissions = Submissions::Filter.call(submissions, current_user, params.except(:status))
|
||||
|
||||
@base_submissions = submissions
|
||||
|
||||
submissions = submissions.pending if params[:status] == 'pending'
|
||||
submissions = submissions.completed if params[:status] == 'completed'
|
||||
submissions = Submissions::Filter.filter_by_status(submissions, params)
|
||||
|
||||
submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
|
||||
submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
|
||||
@@ -22,6 +21,8 @@ class TemplatesController < ApplicationController
|
||||
submissions.order(id: :desc)
|
||||
end
|
||||
|
||||
submissions = submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
|
||||
|
||||
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events))
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to root_path
|
||||
@@ -91,7 +92,7 @@ class TemplatesController < ApplicationController
|
||||
|
||||
def destroy
|
||||
notice =
|
||||
if params[:permanently].present?
|
||||
if params[:permanently].in?(['true', true])
|
||||
@template.destroy!
|
||||
|
||||
I18n.t('template_has_been_removed')
|
||||
@@ -109,8 +110,8 @@ class TemplatesController < ApplicationController
|
||||
def template_params
|
||||
params.require(:template).permit(
|
||||
:name,
|
||||
{ schema: [%i[attachment_uuid name]],
|
||||
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid email]],
|
||||
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
|
||||
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email]],
|
||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||
:required, :readonly, :default_value,
|
||||
:title, :description,
|
||||
|
||||
@@ -9,7 +9,9 @@ class TemplatesDashboardController < ApplicationController
|
||||
FOLDERS_PER_PAGE = 18
|
||||
|
||||
def index
|
||||
@template_folders = filter_template_folders(@template_folders)
|
||||
@template_folders = @template_folders.where(id: @templates.active.select(:folder_id)).order(id: :desc)
|
||||
|
||||
@template_folders = TemplateFolders.search(@template_folders, params[:q])
|
||||
|
||||
@pagy, @template_folders = pagy(
|
||||
@template_folders,
|
||||
@@ -36,16 +38,8 @@ class TemplatesDashboardController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def filter_template_folders(template_folders)
|
||||
rel = template_folders.joins(:active_templates)
|
||||
.order(id: :desc)
|
||||
.distinct
|
||||
|
||||
TemplateFolders.search(rel, params[:q])
|
||||
end
|
||||
|
||||
def filter_templates(templates)
|
||||
rel = templates.active.preload(:author).order(id: :desc)
|
||||
rel = templates.active.preload(:author, :template_accesses).order(id: :desc)
|
||||
|
||||
if params[:q].blank?
|
||||
if Docuseal.multitenant? && !current_account.testing?
|
||||
|
||||
@@ -22,7 +22,7 @@ class TemplatesPreferencesController < ApplicationController
|
||||
preferences: %i[bcc_completed request_email_subject request_email_body
|
||||
documents_copy_email_subject documents_copy_email_body
|
||||
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_redirect_url
|
||||
submitters_order
|
||||
|
||||
@@ -17,38 +17,47 @@ class TemplatesRecipientsController < ApplicationController
|
||||
private
|
||||
|
||||
def submitters_params
|
||||
params.require(:template).permit(
|
||||
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email option]]
|
||||
).fetch(:submitters, {}).values.filter_map do |s|
|
||||
permit_params = { submitters: [%i[name uuid is_requester optional_invite_by_uuid
|
||||
invite_by_uuid linked_to_uuid email option]] }
|
||||
|
||||
params.require(:template).permit(permit_params).fetch(:submitters, {}).values.filter_map do |s|
|
||||
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
|
||||
else
|
||||
s.delete(:is_requester)
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
normalize_option_value(s)
|
||||
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
|
||||
@@ -24,16 +24,14 @@ class UsersController < ApplicationController
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
existing_user = User.accessible_by(current_ability).find_by(email: @user.email)
|
||||
if User.accessible_by(current_ability).exists?(email: @user.email)
|
||||
@user.errors.add(:email, I18n.t('already_exists'))
|
||||
|
||||
if existing_user
|
||||
existing_user.archived_at = nil
|
||||
existing_user.assign_attributes(user_params)
|
||||
existing_user.account = current_account
|
||||
|
||||
@user = existing_user
|
||||
return render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
|
||||
end
|
||||
|
||||
@user.role = User::ADMIN_ROLE unless role_valid?(@user.role)
|
||||
|
||||
if @user.save
|
||||
UserMailer.invitation_email(@user).deliver_later!
|
||||
|
||||
@@ -81,14 +79,7 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
def build_user
|
||||
@user = current_account.users.find_by(email: user_params[:email])&.tap do |user|
|
||||
user.assign_attributes(user_params)
|
||||
user.archived_at = nil
|
||||
end
|
||||
|
||||
@user ||= current_account.users.new(user_params)
|
||||
|
||||
@user
|
||||
@user = current_account.users.new(user_params)
|
||||
end
|
||||
|
||||
def user_params
|
||||
|
||||
@@ -31,6 +31,7 @@ import LinkedInput from './elements/linked_input'
|
||||
import CheckboxGroup from './elements/checkbox_group'
|
||||
import MaskedInput from './elements/masked_input'
|
||||
import SetDateButton from './elements/set_date_button'
|
||||
import IndeterminateCheckbox from './elements/indeterminate_checkbox'
|
||||
|
||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||
|
||||
@@ -99,6 +100,7 @@ safeRegisterElement('linked-input', LinkedInput)
|
||||
safeRegisterElement('checkbox-group', CheckboxGroup)
|
||||
safeRegisterElement('masked-input', MaskedInput)
|
||||
safeRegisterElement('set-date-button', SetDateButton)
|
||||
safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox)
|
||||
|
||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
|
||||
@@ -20,7 +20,8 @@ export default targetable(class extends HTMLElement {
|
||||
fetch(this.dataset.src).then(async (response) => {
|
||||
if (response.ok) {
|
||||
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) {
|
||||
this.downloadSafariIos(urls)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ safeRegisterElement('submission-form', class extends HTMLElement {
|
||||
this.app = createApp(Form, {
|
||||
submitter: JSON.parse(this.dataset.submitter),
|
||||
inviteSubmitters: JSON.parse(this.dataset.inviteSubmitters),
|
||||
optionalInviteSubmitters: JSON.parse(this.dataset.optionalInviteSubmitters),
|
||||
schema: JSON.parse(this.dataset.schema),
|
||||
canSendEmail: this.dataset.canSendEmail === 'true',
|
||||
previousSignatureValue: this.dataset.previousSignatureValue,
|
||||
goToLast: this.dataset.goToLast === 'true',
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
ID: {{ signature.uuid }}
|
||||
</div>
|
||||
<div>
|
||||
{{ t('reason') }}: {{ values[field.preferences?.reason_field_uuid] || t('digitally_signed_by') }} {{ submitter.name }}
|
||||
<span v-if="values[field.preferences?.reason_field_uuid]">{{ t('reason') }}: </span>{{ values[field.preferences?.reason_field_uuid] || t('digitally_signed_by') }} {{ submitter.name }}
|
||||
<template v-if="submitter.email">
|
||||
<{{ submitter.email }}>
|
||||
</template>
|
||||
@@ -168,6 +168,7 @@
|
||||
<div
|
||||
v-else-if="field.type === 'cells'"
|
||||
class="w-full flex items-center"
|
||||
:class="{ 'justify-end': field.preferences?.align === 'right' }"
|
||||
>
|
||||
<div
|
||||
v-for="(char, index) in modelValue"
|
||||
@@ -196,7 +197,10 @@
|
||||
<span v-else-if="field.type === 'date'">
|
||||
{{ formattedDate }}
|
||||
</span>
|
||||
<span v-else-if="field.type === 'number'">
|
||||
<span
|
||||
v-else-if="field.type === 'number'"
|
||||
class="w-full"
|
||||
>
|
||||
{{ formatNumber(modelValue, field.preferences?.format) }}
|
||||
</span>
|
||||
<span
|
||||
|
||||
@@ -215,7 +215,8 @@ export default {
|
||||
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
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) {
|
||||
this.downloadSafariIos(urls)
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
/>
|
||||
<FieldAreas
|
||||
:steps="readonlyConditionalFields.map((e) => [e])"
|
||||
:values="readonlyConditionalFields.reduce((acc, f) => { acc[f.uuid] = f.default_value; return acc }, {})"
|
||||
:values="readonlyConditionalFieldValues"
|
||||
:submitter="submitter"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submittable="false"
|
||||
/>
|
||||
<FormulaFieldAreas
|
||||
v-if="formulaFields.length"
|
||||
:fields="formulaFields"
|
||||
:readonly-values="readonlyConditionalFieldValues"
|
||||
:values="values"
|
||||
/>
|
||||
<Teleport
|
||||
@@ -497,6 +499,7 @@
|
||||
<InviteForm
|
||||
v-else-if="isInvite"
|
||||
:submitters="inviteSubmitters"
|
||||
:optional-submitters="optionalInviteSubmitters"
|
||||
:submitter-slug="submitterSlug"
|
||||
:authenticity-token="authenticityToken"
|
||||
:url="baseUrl + submitPath + '/invite'"
|
||||
@@ -626,6 +629,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
optionalInviteSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
withSignatureId: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -636,6 +644,11 @@ export default {
|
||||
required: false,
|
||||
default: '-80px'
|
||||
},
|
||||
orderAsOnPage: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
requireSigningReason: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -651,6 +664,11 @@ export default {
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
schema: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
attachments: {
|
||||
type: Array,
|
||||
required: false,
|
||||
@@ -838,7 +856,31 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
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 () {
|
||||
return this.schema.reduce((acc, item) => {
|
||||
if (item.conditions?.length) {
|
||||
if (item.conditions.every((c) => this.fieldsUuidIndex[c.field_uuid])) {
|
||||
acc[item.attachment_uuid] = this.checkFieldConditions(item)
|
||||
} else {
|
||||
acc[item.attachment_uuid] = true
|
||||
}
|
||||
} else {
|
||||
acc[item.attachment_uuid] = true
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
emptyValueRequiredStep () {
|
||||
return this.stepFields.find((fields, index) => {
|
||||
@@ -920,7 +962,7 @@ export default {
|
||||
return this.currentStepFields[0]
|
||||
},
|
||||
readonlyConditionalFields () {
|
||||
return this.fields.filter((f) => f.readonly && f.conditions?.length && this.checkFieldConditions(f))
|
||||
return this.fields.filter((f) => f.readonly && f.conditions?.length && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
|
||||
},
|
||||
stepFields () {
|
||||
const verificationFields = []
|
||||
@@ -935,6 +977,38 @@ export default {
|
||||
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) {
|
||||
sortedFields.push(verificationFields.pop())
|
||||
}
|
||||
@@ -942,7 +1016,7 @@ export default {
|
||||
return sortedFields.reduce((acc, f) => {
|
||||
const prevStep = acc[acc.length - 1]
|
||||
|
||||
if (this.checkFieldConditions(f)) {
|
||||
if (this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) {
|
||||
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox' && !f.description) {
|
||||
prevStep.push(f)
|
||||
} else {
|
||||
@@ -975,6 +1049,23 @@ export default {
|
||||
if (isEmpty(value) && this.currentStep > 0) {
|
||||
this.currentStep -= 1
|
||||
}
|
||||
},
|
||||
attachmentConditionsIndex: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (value) {
|
||||
this.$nextTick(() => {
|
||||
const root = this.$root.$el.parentNode.getRootNode()
|
||||
|
||||
for (const key in value) {
|
||||
const doc = root.querySelector(`[id="document-${key}"`)
|
||||
|
||||
if (doc) {
|
||||
doc.classList.toggle('hidden', !value[key])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
@@ -1022,7 +1113,9 @@ export default {
|
||||
this.minimizeForm()
|
||||
}
|
||||
|
||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
|
||||
if (isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
this.$nextTick(() => {
|
||||
const root = this.$root.$el.parentNode.getRootNode()
|
||||
const scrollbox = root.getElementById('scrollbox')
|
||||
@@ -1056,6 +1149,15 @@ export default {
|
||||
onOrientationChange (event) {
|
||||
this.orientation = event.target.type
|
||||
},
|
||||
checkFieldDocumentsConditions (field) {
|
||||
if (field.areas?.length) {
|
||||
return field.areas.some((area) => {
|
||||
return this.attachmentConditionsIndex[area.attachment_uuid]
|
||||
})
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
checkFieldConditions (field) {
|
||||
if (field.conditions?.length) {
|
||||
const result = field.conditions.reduce((acc, cond) => {
|
||||
@@ -1273,7 +1375,7 @@ export default {
|
||||
const formData = new FormData(this.$refs.form)
|
||||
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')
|
||||
}
|
||||
|
||||
@@ -1312,7 +1414,7 @@ export default {
|
||||
if (emptyRequiredField === nextStep) {
|
||||
this.showFillAllRequiredFields = true
|
||||
}
|
||||
} else if (this.inviteSubmitters.length) {
|
||||
} else if (this.inviteSubmitters.length || this.optionalInviteSubmitters.length) {
|
||||
this.isInvite = true
|
||||
} else {
|
||||
this.performComplete(response)
|
||||
|
||||
@@ -38,6 +38,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
readonlyValues: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
values: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -87,7 +92,7 @@ export default {
|
||||
},
|
||||
calculateFormula (field) {
|
||||
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())
|
||||
|
||||
@@ -93,7 +93,8 @@ const en = {
|
||||
reupload: 'Reupload',
|
||||
upload: 'Upload',
|
||||
files: 'Files',
|
||||
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.'
|
||||
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.',
|
||||
wait_countdown_seconds: 'Wait {countdown} seconds'
|
||||
}
|
||||
|
||||
const es = {
|
||||
@@ -190,7 +191,8 @@ const es = {
|
||||
reupload: 'Volver a subir',
|
||||
upload: 'Subir',
|
||||
files: 'Archivos',
|
||||
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.'
|
||||
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.',
|
||||
wait_countdown_seconds: 'Espera {countdown} segundos'
|
||||
}
|
||||
|
||||
const it = {
|
||||
@@ -287,7 +289,8 @@ const it = {
|
||||
reupload: 'Ricarica',
|
||||
upload: 'Carica',
|
||||
files: 'File',
|
||||
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.'
|
||||
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.',
|
||||
wait_countdown_seconds: 'Attendi {countdown} secondi'
|
||||
}
|
||||
|
||||
const de = {
|
||||
@@ -384,7 +387,8 @@ const de = {
|
||||
reupload: 'Erneut hochladen',
|
||||
upload: 'Hochladen',
|
||||
files: 'Dateien',
|
||||
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.'
|
||||
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.',
|
||||
wait_countdown_seconds: 'Warte {countdown} Sekunden'
|
||||
}
|
||||
|
||||
const fr = {
|
||||
@@ -481,7 +485,8 @@ const fr = {
|
||||
reupload: 'Recharger',
|
||||
upload: 'Télécharger',
|
||||
files: 'Fichiers',
|
||||
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.'
|
||||
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.',
|
||||
wait_countdown_seconds: 'Attendez {countdown} secondes'
|
||||
}
|
||||
|
||||
const pl = {
|
||||
@@ -675,7 +680,8 @@ const uk = {
|
||||
reupload: 'Перезавантажити',
|
||||
upload: 'Завантажити',
|
||||
files: 'Файли',
|
||||
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.'
|
||||
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.',
|
||||
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
|
||||
}
|
||||
|
||||
const cs = {
|
||||
@@ -772,7 +778,8 @@ const cs = {
|
||||
reupload: 'Znovu nahrát',
|
||||
upload: 'Nahrát',
|
||||
files: 'Soubory',
|
||||
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.'
|
||||
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.',
|
||||
wait_countdown_seconds: 'Počkejte {countdown} sekund'
|
||||
}
|
||||
|
||||
const pt = {
|
||||
@@ -780,7 +787,7 @@ const pt = {
|
||||
verify_id: 'Verificar ID',
|
||||
identity_verification: 'Verificação de identidade',
|
||||
complete: 'Completar',
|
||||
preencher_todos_os_campos_obrigatórios_para_concluir: 'Preencher todos os campos obrigatórios para concluir',
|
||||
fill_all_required_fields_to_complete: 'Preencher todos os campos obrigatórios para concluir',
|
||||
sign_and_complete: 'Assinar e Completar',
|
||||
invite: 'Convidar',
|
||||
email: 'E-mail',
|
||||
@@ -869,7 +876,8 @@ const pt = {
|
||||
reupload: 'Reenviar',
|
||||
upload: 'Carregar',
|
||||
files: 'Arquivos',
|
||||
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.'
|
||||
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.',
|
||||
wait_countdown_seconds: 'Aguarde {countdown} segundos'
|
||||
}
|
||||
|
||||
const he = {
|
||||
@@ -967,7 +975,8 @@ const he = {
|
||||
reupload: 'העלה שוב',
|
||||
upload: 'העלאה',
|
||||
files: 'קבצים',
|
||||
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.'
|
||||
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.',
|
||||
wait_countdown_seconds: 'המתן {countdown} שניות'
|
||||
}
|
||||
|
||||
const nl = {
|
||||
@@ -975,7 +984,7 @@ const nl = {
|
||||
verify_id: 'Verifiëren ID',
|
||||
identity_verification: 'Identiteitsverificatie',
|
||||
complete: 'Voltooien',
|
||||
vul_alle_verplichte_velden_in_om_te_voltooien: 'Vul alle verplichte velden in om te voltooien',
|
||||
fill_all_required_fields_to_complete: 'Vul alle verplichte velden in om te voltooien',
|
||||
sign_and_complete: 'Ondertekenen en voltooien',
|
||||
invite: 'Uitnodigen',
|
||||
email: 'E-mail',
|
||||
@@ -1065,7 +1074,8 @@ const nl = {
|
||||
reupload: 'Opnieuw uploaden',
|
||||
upload: 'Uploaden',
|
||||
files: 'Bestanden',
|
||||
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.'
|
||||
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.',
|
||||
wait_countdown_seconds: 'Wacht {countdown} seconden'
|
||||
}
|
||||
|
||||
const ar = {
|
||||
@@ -1162,7 +1172,8 @@ const ar = {
|
||||
reupload: 'إعادة التحميل',
|
||||
upload: 'تحميل',
|
||||
files: 'الملفات',
|
||||
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.'
|
||||
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.',
|
||||
wait_countdown_seconds: 'انتظر {countdown} ثانية'
|
||||
}
|
||||
|
||||
const ko = {
|
||||
@@ -1258,7 +1269,8 @@ const ko = {
|
||||
reupload: '다시 업로드',
|
||||
upload: '업로드',
|
||||
files: '파일',
|
||||
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.'
|
||||
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.',
|
||||
wait_countdown_seconds: '{countdown}초 기다리세요'
|
||||
}
|
||||
|
||||
const i18n = { en, es, it, de, fr, pl, uk, cs, pt, he, nl, ar, ko }
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
:value="authenticityToken"
|
||||
>
|
||||
<div
|
||||
v-for="(submitter, index) in submitters"
|
||||
v-for="(submitter, index) in [...submitters, ...optionalSubmitters]"
|
||||
:key="submitter.uuid"
|
||||
:class="{ 'mt-4': index !== 0 }"
|
||||
>
|
||||
@@ -26,7 +26,7 @@
|
||||
dir="auto"
|
||||
class="label text-2xl"
|
||||
>
|
||||
{{ t('invite') }} {{ submitter.name }}
|
||||
{{ t('invite') }} {{ submitter.name }} <template v-if="!submitters.includes(submitter)">({{ t('optional') }})</template>
|
||||
</label>
|
||||
<input
|
||||
:id="submitter.uuid"
|
||||
@@ -34,7 +34,7 @@
|
||||
class="base-input !text-2xl w-full"
|
||||
:placeholder="t('email')"
|
||||
type="email"
|
||||
required
|
||||
:required="submitters.includes(submitter)"
|
||||
autofocus="true"
|
||||
name="submission[submitters][][email]"
|
||||
>
|
||||
@@ -53,7 +53,7 @@
|
||||
class="mr-1 animate-spin"
|
||||
/>
|
||||
<span>
|
||||
{{ t('submit') }}
|
||||
{{ t('complete') }}
|
||||
</span><span
|
||||
v-if="isSubmitting"
|
||||
class="w-6 flex justify-start mr-1"
|
||||
@@ -78,6 +78,11 @@ export default {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
optionalSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -52,7 +52,14 @@
|
||||
>
|
||||
{{ t('change_phone_number') }}
|
||||
</a>
|
||||
<span
|
||||
v-if="resendCodeCountdown > 0"
|
||||
class="link"
|
||||
>
|
||||
{{ t('wait_countdown_seconds').replace('{countdown}', resendCodeCountdown) }}
|
||||
</span>
|
||||
<a
|
||||
v-else
|
||||
href="#"
|
||||
class="link"
|
||||
@click.prevent="resendCode"
|
||||
@@ -173,6 +180,8 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
isCodeSent: false,
|
||||
codeSentAt: null,
|
||||
resendCodeCountdown: 0,
|
||||
isResendLoading: false,
|
||||
phoneValue: this.modelValue || this.defaultValue || '',
|
||||
selectedCountry: {}
|
||||
@@ -180,8 +189,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
countries () {
|
||||
return phoneData.map(([name, dial, flag, tz]) => {
|
||||
return { name, dial, flag, tz }
|
||||
return phoneData.map(([iso, name, dial, flag, tz]) => {
|
||||
return { iso, name, dial, flag, tz }
|
||||
})
|
||||
},
|
||||
countriesDialIndex () {
|
||||
@@ -220,6 +229,11 @@ export default {
|
||||
this.selectedCountry = this.countries.find((country) => country.tz.includes(tz)) || this.countries[0]
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emitSubmit: throttle(function (e) {
|
||||
this.$emit('submit')
|
||||
@@ -246,13 +260,28 @@ export default {
|
||||
}
|
||||
},
|
||||
resendCode () {
|
||||
this.isResendLoading = true
|
||||
if (this.codeSentAt && Date.now() - this.codeSentAt < 15000) {
|
||||
this.startResendCodeCountdown()
|
||||
} else {
|
||||
this.isResendLoading = true
|
||||
|
||||
this.sendVerificationCode().finally(() => {
|
||||
alert(this.t('verification_code_has_been_resent'))
|
||||
this.sendVerificationCode().then(() => {
|
||||
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 () {
|
||||
return fetch(this.baseUrl + '/api/send_phone_verification_code', {
|
||||
@@ -264,12 +293,20 @@ export default {
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}).then(async (resp) => {
|
||||
if (resp.status === 422) {
|
||||
if ([422, 429].includes(resp.status)) {
|
||||
const data = await resp.json()
|
||||
|
||||
alert(this.t('number_phone_is_invalid').replace('{number}', this.fullInternationalPhoneValue))
|
||||
if (resp.status === 422) {
|
||||
alert(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))
|
||||
} else if (resp.ok) {
|
||||
this.codeSentAt = Date.now()
|
||||
|
||||
return resp
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</label>
|
||||
<div class="space-x-2 flex">
|
||||
<span
|
||||
v-if="isTextSignature && field.preferences?.format !== 'typed'"
|
||||
v-if="isTextSignature && format !== 'typed' && format !== 'upload'"
|
||||
class="tooltip"
|
||||
:data-tip="t('draw_signature')"
|
||||
>
|
||||
@@ -37,7 +37,7 @@
|
||||
</a>
|
||||
</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"
|
||||
:data-tip="t('type_text')"
|
||||
>
|
||||
@@ -54,7 +54,7 @@
|
||||
</a>
|
||||
</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"
|
||||
:data-tip="t('take_photo')"
|
||||
>
|
||||
@@ -81,10 +81,10 @@
|
||||
@click.prevent="remove"
|
||||
>
|
||||
<IconReload :width="16" />
|
||||
{{ t('redraw') }}
|
||||
{{ t(format === 'upload' ? 'reupload' : 'redraw') }}
|
||||
</a>
|
||||
<span
|
||||
v-if="withQrButton && !modelValue && !computedPreviousValue && field.preferences?.format !== 'typed'"
|
||||
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed' && format !== 'upload'"
|
||||
class=" tooltip"
|
||||
:data-tip="t('drawn_signature_on_a_touchscreen_device')"
|
||||
>
|
||||
@@ -131,7 +131,18 @@
|
||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||
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
|
||||
v-if="!modelValue && !computedPreviousValue && !isShowQr && !isTextSignature && isSignatureStarted"
|
||||
class="absolute top-0.5 right-0.5"
|
||||
@@ -282,6 +293,7 @@ import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonal
|
||||
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
||||
import SignaturePad from 'signature_pad'
|
||||
import AppearsOn from './appears_on'
|
||||
import FileDropzone from './dropzone'
|
||||
import MarkdownContent from './markdown_content'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
@@ -294,6 +306,7 @@ export default {
|
||||
components: {
|
||||
AppearsOn,
|
||||
IconReload,
|
||||
FileDropzone,
|
||||
IconCamera,
|
||||
IconQrcode,
|
||||
MarkdownContent,
|
||||
@@ -388,6 +401,9 @@ export default {
|
||||
submitterSlug () {
|
||||
return this.submitter.slug
|
||||
},
|
||||
format () {
|
||||
return this.field.preferences?.format
|
||||
},
|
||||
defaultReasons () {
|
||||
return {
|
||||
[this.t('approved_by')]: this.t('approved'),
|
||||
|
||||
@@ -95,7 +95,7 @@ export default {
|
||||
countryCode () {
|
||||
const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
const browserTz = browserTimeZone.split('/')[1]
|
||||
const country = phoneData.find(([a, b, c, tz]) => tz.includes(browserTz))
|
||||
const country = phoneData.find(([a, b, c, e, tz]) => tz.includes(browserTz))
|
||||
|
||||
return country[0]
|
||||
},
|
||||
@@ -107,7 +107,7 @@ export default {
|
||||
clientId: this.eidEasyData.client_id,
|
||||
docId: this.eidEasyData.doc_id,
|
||||
language: this.locale,
|
||||
countryCode: this.browserCountry,
|
||||
countryCode: this.countryCode,
|
||||
enabledMethods: {
|
||||
signature: this.eidEasyData.available_methods
|
||||
},
|
||||
@@ -125,12 +125,16 @@ export default {
|
||||
async mounted () {
|
||||
this.isLoading = true
|
||||
|
||||
Promise.all([
|
||||
import('@eid-easy/eideasy-widget'),
|
||||
this.start()
|
||||
]).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
if (new URLSearchParams(window.location.search).get('submit') === 'true') {
|
||||
this.$emit('submit')
|
||||
} else {
|
||||
Promise.all([
|
||||
import('@eid-easy/eideasy-widget'),
|
||||
this.start()
|
||||
]).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
start () {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div
|
||||
class="absolute overflow-visible group"
|
||||
:style="positionStyle"
|
||||
:class="{ 'z-[1]': isMoved || isDragged }"
|
||||
@pointerdown.stop
|
||||
@mousedown="startMouseMove"
|
||||
@touchstart="startTouchDrag"
|
||||
@@ -9,7 +10,7 @@
|
||||
<div
|
||||
v-if="isSelected || isDraw"
|
||||
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
|
||||
v-if="field.type === 'cells' && (isSelected || isDraw)"
|
||||
@@ -19,7 +20,7 @@
|
||||
v-for="(cellW, index) in cells"
|
||||
:key="index"
|
||||
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) + '%' }"
|
||||
>
|
||||
<span
|
||||
@@ -69,7 +70,7 @@
|
||||
@keydown.enter.prevent="onNameEnter"
|
||||
@focus="onNameFocus"
|
||||
@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
|
||||
v-if="isSettingsFocus || (isValueInput && field.type !== 'heading') || (isNameFocus && !['checkbox', 'phone'].includes(field.type))"
|
||||
class="flex items-center ml-1.5"
|
||||
@@ -160,7 +161,7 @@
|
||||
ref="touchValueTarget"
|
||||
class="flex items-center h-full w-full"
|
||||
dir="auto"
|
||||
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex], 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) ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
|
||||
@click="focusValueInput"
|
||||
>
|
||||
<span
|
||||
@@ -211,7 +212,7 @@
|
||||
:contenteditable="isValueInput"
|
||||
class="whitespace-pre-wrap outline-none empty:before:content-[attr(placeholder)] before:text-gray-400"
|
||||
: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"
|
||||
@paste.prevent="onPaste"
|
||||
@keydown.enter="onDefaultValueEnter"
|
||||
@@ -237,7 +238,7 @@
|
||||
/>
|
||||
<span
|
||||
v-if="field?.type && editable"
|
||||
class="h-4 w-4 md:h-2.5 md:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
||||
class="h-4 w-4 lg:h-2.5 lg:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
||||
@mousedown.stop="startResize"
|
||||
@touchstart="startTouchResize"
|
||||
/>
|
||||
@@ -257,7 +258,7 @@
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:field="field"
|
||||
:item="field"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
@@ -316,6 +317,11 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
maxPage: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
defaultField: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -414,16 +420,6 @@ export default {
|
||||
},
|
||||
borderColors () {
|
||||
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-sky-500/80',
|
||||
'border-emerald-500/80',
|
||||
@@ -438,16 +434,6 @@ export default {
|
||||
},
|
||||
bgColors () {
|
||||
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-sky-100',
|
||||
'bg-emerald-100',
|
||||
@@ -720,12 +706,18 @@ export default {
|
||||
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.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 () {
|
||||
this.$el.getRootNode().removeEventListener('touchmove', this.touchDrag)
|
||||
this.$el.getRootNode().removeEventListener('touchend', this.stopTouchDrag)
|
||||
|
||||
this.maybeChangeAreaPage(this.area)
|
||||
|
||||
if (this.isDragged) {
|
||||
this.save()
|
||||
}
|
||||
@@ -773,12 +765,18 @@ export default {
|
||||
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.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) {
|
||||
this.$el.getRootNode().removeEventListener('mousemove', this.mouseMove)
|
||||
this.$el.getRootNode().removeEventListener('mouseup', this.stopMouseMove)
|
||||
|
||||
this.maybeChangeAreaPage(this.area)
|
||||
|
||||
if (this.isMoved) {
|
||||
this.save()
|
||||
}
|
||||
@@ -788,6 +786,15 @@ export default {
|
||||
|
||||
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 () {
|
||||
this.$el.getRootNode().removeEventListener('mousemove', this.drag)
|
||||
this.$el.getRootNode().removeEventListener('mouseup', this.stopDrag)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div
|
||||
style="max-width: 1600px"
|
||||
class="mx-auto pl-3 md:pl-4 h-full"
|
||||
class="mx-auto pl-3 h-full"
|
||||
:class="isMobile ? 'pl-4' : 'md:pl-4'"
|
||||
>
|
||||
<div
|
||||
v-if="pendingFieldAttachmentUuids.length && editable"
|
||||
@@ -163,7 +164,7 @@
|
||||
<div
|
||||
id="main_container"
|
||||
class="flex"
|
||||
:class="$slots.buttons || withTitle ? 'md:max-h-[calc(100%_-_60px)]' : '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
|
||||
v-if="withDocumentsList"
|
||||
@@ -178,6 +179,7 @@
|
||||
:with-arrows="template.schema.length > 1"
|
||||
:item="item"
|
||||
:document="sortedDocuments[index]"
|
||||
:data-document-uuid="item.attachment_uuid"
|
||||
:accept-file-types="acceptFileTypes"
|
||||
:with-replace-button="withUploadButton"
|
||||
:editable="editable"
|
||||
@@ -186,6 +188,7 @@
|
||||
@remove="onDocumentRemove"
|
||||
@replace="onDocumentReplace"
|
||||
@up="moveDocument(item, -1)"
|
||||
@reorder="reorderFields"
|
||||
@down="moveDocument(item, 1)"
|
||||
@change="save"
|
||||
/>
|
||||
@@ -219,7 +222,8 @@
|
||||
</div>
|
||||
<div
|
||||
id="pages_container"
|
||||
class="w-full overflow-y-hidden md:overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5"
|
||||
class="w-full overflow-y-hidden overflow-x-hidden mt-0.5 pt-0.5"
|
||||
:class="isMobile ? 'overflow-y-auto' : 'md:overflow-y-auto'"
|
||||
>
|
||||
<div
|
||||
ref="documents"
|
||||
@@ -263,6 +267,7 @@
|
||||
:input-mode="inputMode"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:allow-draw="!onlyDefinedFields"
|
||||
:data-document-uuid="document.uuid"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:with-field-placeholder="withFieldPlaceholder"
|
||||
:draw-field="drawField"
|
||||
@@ -320,7 +325,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="withFieldsList"
|
||||
v-if="withFieldsList && !isMobile"
|
||||
id="fields_list_container"
|
||||
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block"
|
||||
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
||||
@@ -362,6 +367,7 @@
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field-type="drawFieldType"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:template="template"
|
||||
:default-required-fields="defaultRequiredFields"
|
||||
:field-types="fieldTypes"
|
||||
:with-sticky-submitters="withStickySubmitters"
|
||||
@@ -378,14 +384,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sticky bottom-0">
|
||||
<div class="sticky bottom-0 z-10">
|
||||
<MobileDrawField
|
||||
v-if="drawField && isBreakpointLg"
|
||||
v-if="drawField && (isBreakpointLg || isMobile)"
|
||||
:draw-field="drawField"
|
||||
:fields="template.fields"
|
||||
:submitters="template.submitters"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
class="md:hidden"
|
||||
:class="{ 'md:hidden': !isMobile }"
|
||||
:editable="editable"
|
||||
@cancel="[drawField = null, drawOption = null]"
|
||||
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
||||
@@ -396,6 +402,7 @@
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:default-required-fields="defaultRequiredFields"
|
||||
:field-types="fieldTypes"
|
||||
:class="{ 'md:hidden': !isMobile }"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
@select="startFieldDraw($event)"
|
||||
/>
|
||||
@@ -695,6 +702,11 @@ export default {
|
||||
language () {
|
||||
return this.locale.split('-')[0].toLowerCase()
|
||||
},
|
||||
isMobile () {
|
||||
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
|
||||
|
||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||
},
|
||||
defaultDateFormat () {
|
||||
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
||||
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
|
||||
@@ -762,13 +774,17 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
const defineSubmittersUuids = this.defineSubmitters.map((name) => {
|
||||
return this.template.submitters.find(e => e.name === name)?.uuid
|
||||
})
|
||||
|
||||
this.defineSubmitters.forEach((name, index) => {
|
||||
const submitter = (this.template.submitters[index] ||= {})
|
||||
|
||||
submitter.name = name || this.submitterDefaultNames[index]
|
||||
|
||||
if (existingSubmittersUuids.filter(Boolean).length) {
|
||||
submitter.uuid = existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||
if (defineSubmittersUuids.filter(Boolean).length || existingSubmittersUuids.filter(Boolean).length) {
|
||||
submitter.uuid = defineSubmittersUuids[index] || existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||
} else {
|
||||
submitter.uuid ||= v4()
|
||||
}
|
||||
@@ -811,6 +827,89 @@ export default {
|
||||
this.documentRefs = []
|
||||
},
|
||||
methods: {
|
||||
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 () {
|
||||
document.activeElement.blur()
|
||||
},
|
||||
@@ -1023,6 +1122,22 @@ export default {
|
||||
|
||||
if (!field.areas.length) {
|
||||
this.template.fields.splice(this.template.fields.indexOf(field), 1)
|
||||
|
||||
this.template.fields.forEach((f) => {
|
||||
(f.conditions || []).forEach((c) => {
|
||||
if (c.field_uuid === field.uuid) {
|
||||
f.conditions.splice(f.conditions.indexOf(c), 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.template.schema.forEach((item) => {
|
||||
(item.conditions || []).forEach((c) => {
|
||||
if (c.field_uuid === field.uuid) {
|
||||
item.conditions.splice(item.conditions.indexOf(c), 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.save()
|
||||
@@ -1281,6 +1396,16 @@ export default {
|
||||
fieldArea.h = lastArea.h
|
||||
}
|
||||
|
||||
if (field.width) {
|
||||
fieldArea.w = field.width / area.maskW
|
||||
delete field.width
|
||||
}
|
||||
|
||||
if (field.height) {
|
||||
fieldArea.h = field.height / area.maskH
|
||||
delete field.height
|
||||
}
|
||||
|
||||
field.areas.push(fieldArea)
|
||||
|
||||
this.selectedAreaRef.value = fieldArea
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
||||
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
||||
<span>
|
||||
{{ t('condition') }} - {{ field.name || buildDefaultName(field, template.fields) }}
|
||||
{{ t('condition') }} - {{ item.name || buildDefaultName(item, template.fields) }}
|
||||
</span>
|
||||
<a
|
||||
href="#"
|
||||
@@ -138,12 +138,12 @@
|
||||
</button>
|
||||
</form>
|
||||
<div
|
||||
v-if="field.conditions?.[0]?.field_uuid"
|
||||
v-if="item.conditions?.[0]?.field_uuid"
|
||||
class="text-center w-full mt-4"
|
||||
>
|
||||
<button
|
||||
class="link"
|
||||
@click="[conditions = [], delete field.conditions, validateSaveAndClose()]"
|
||||
@click="[conditions = [], delete item.conditions, validateSaveAndClose()]"
|
||||
>
|
||||
{{ t('remove_condition') }}
|
||||
</button>
|
||||
@@ -158,7 +158,7 @@ export default {
|
||||
name: 'ConditionModal',
|
||||
inject: ['t', 'save', 'template', 'withConditions'],
|
||||
props: {
|
||||
field: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
@@ -170,22 +170,26 @@ export default {
|
||||
emits: ['close'],
|
||||
data () {
|
||||
return {
|
||||
conditions: this.field.conditions?.[0] ? JSON.parse(JSON.stringify(this.field.conditions)) : [{}]
|
||||
conditions: this.item.conditions?.[0] ? JSON.parse(JSON.stringify(this.item.conditions)) : [{}]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fields () {
|
||||
return this.template.fields.reduce((acc, f) => {
|
||||
if (f !== this.field && f.submitter_uuid === this.field.submitter_uuid) {
|
||||
acc.push(f)
|
||||
}
|
||||
if (this.item.submitter_uuid) {
|
||||
return this.template.fields.reduce((acc, f) => {
|
||||
if (f !== this.item && f.submitter_uuid === this.item.submitter_uuid) {
|
||||
acc.push(f)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
return acc
|
||||
}, [])
|
||||
} else {
|
||||
return this.template.fields
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.field.conditions ||= []
|
||||
this.item.conditions ||= []
|
||||
},
|
||||
methods: {
|
||||
conditionField (condition) {
|
||||
@@ -219,9 +223,9 @@ export default {
|
||||
}
|
||||
|
||||
if (this.conditions.find((f) => f.field_uuid)) {
|
||||
this.field.conditions = this.conditions
|
||||
this.item.conditions = this.conditions
|
||||
} else {
|
||||
delete this.field.conditions
|
||||
delete this.item.conditions
|
||||
}
|
||||
|
||||
this.save()
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:input-mode="inputMode"
|
||||
:number="index"
|
||||
:editable="editable"
|
||||
:data-page="index"
|
||||
:areas="areasIndex[index]"
|
||||
:allow-draw="allowDraw"
|
||||
:is-drag="isDrag"
|
||||
@@ -16,6 +17,7 @@
|
||||
:draw-field="drawField"
|
||||
:draw-field-type="drawFieldType"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
:total-pages="sortedPreviewImages.length"
|
||||
:image="image"
|
||||
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
|
||||
@remove-area="$emit('remove-area', $event)"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
/>
|
||||
<Contenteditable
|
||||
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'"
|
||||
:icon-inline="true"
|
||||
:icon-width="18"
|
||||
@@ -236,7 +236,7 @@
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:field="field"
|
||||
:item="field"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
@@ -365,7 +365,7 @@ export default {
|
||||
} else {
|
||||
const typeIndex = fields.filter((f) => f.type === field.type).indexOf(field)
|
||||
|
||||
if (this.field.type === 'heading') {
|
||||
if (field.type === 'heading') {
|
||||
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
|
||||
} else {
|
||||
return `${this.fieldLabels[field.type]} ${typeIndex + 1}`
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="['number'].includes(field.type)"
|
||||
v-if="['number', 'cells'].includes(field.type)"
|
||||
class="py-1.5 px-1 relative"
|
||||
@click.stop
|
||||
>
|
||||
@@ -36,7 +36,7 @@
|
||||
@change="[field.preferences ||= {}, field.preferences.align = $event.target.value, save()]"
|
||||
>
|
||||
<option
|
||||
v-for="value in ['left', 'right', 'center']"
|
||||
v-for="value in ['left', 'right', field.type === 'cells' ? null : 'center'].filter(Boolean)"
|
||||
:key="value"
|
||||
:selected="field.preferences?.align ? value === field.preferences.align : value === 'left'"
|
||||
:value="value"
|
||||
@@ -212,16 +212,12 @@
|
||||
{{ t('any') }}
|
||||
</option>
|
||||
<option
|
||||
value="drawn"
|
||||
:selected="field.preferences?.format === 'drawn'"
|
||||
v-for="type in ['drawn', 'typed', 'drawn_or_typed', 'upload']"
|
||||
:key="type"
|
||||
:value="type"
|
||||
:selected="field.preferences?.format === type"
|
||||
>
|
||||
{{ t('drawn') }}
|
||||
</option>
|
||||
<option
|
||||
value="typed"
|
||||
:selected="field.preferences?.format === 'typed'"
|
||||
>
|
||||
{{ t('typed') }}
|
||||
{{ t(type) }}
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
@@ -240,7 +236,7 @@
|
||||
<input
|
||||
v-model="field.required"
|
||||
type="checkbox"
|
||||
:disabled="!editable || defaultField"
|
||||
:disabled="!editable || (defaultField && [true, false].includes(defaultField.required))"
|
||||
class="toggle toggle-xs"
|
||||
@update:model-value="save"
|
||||
>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
class="w-3 h-3 flex-shrink-0 rounded-full"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||
/>
|
||||
<Contenteditable
|
||||
v-model="selectedSubmitter.name"
|
||||
@@ -53,7 +53,7 @@
|
||||
<span class="py-1 flex items-center">
|
||||
<span
|
||||
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||
:class="colors[index]"
|
||||
:class="colors[index % colors.length]"
|
||||
/>
|
||||
<span>
|
||||
{{ submitter.name }}
|
||||
@@ -101,7 +101,7 @@
|
||||
>
|
||||
<button
|
||||
class="mx-1 w-3 h-3 rounded-full"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="flex items-center space-x-2">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||
:class="colors[submitters.indexOf(selectedSubmitter) % colors.length]"
|
||||
/>
|
||||
<Contenteditable
|
||||
v-model="selectedSubmitter.name"
|
||||
@@ -153,7 +153,7 @@
|
||||
<span class="py-1 flex items-center">
|
||||
<span
|
||||
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||
:class="colors[index]"
|
||||
:class="colors[index % colors.length]"
|
||||
/>
|
||||
<span>
|
||||
{{ submitter.name }}
|
||||
@@ -275,16 +275,6 @@ export default {
|
||||
computed: {
|
||||
colors () {
|
||||
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-sky-500',
|
||||
'bg-emerald-500',
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
:field="field"
|
||||
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
||||
:editable="editable && (!fieldsDragFieldRef.value || fieldsDragFieldRef.value !== field)"
|
||||
:default-field="defaultFields.find((f) => f.name === field.name)"
|
||||
:default-field="defaultFieldsIndex[field.name]"
|
||||
:draggable="editable"
|
||||
@dragstart="fieldsDragFieldRef.value = field"
|
||||
@dragend="fieldsDragFieldRef.value = null"
|
||||
@@ -229,6 +229,10 @@ export default {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
template: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
withHelp: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -295,6 +299,13 @@ export default {
|
||||
isShowFieldSearch () {
|
||||
return this.submitterDefaultFields.length > 15
|
||||
},
|
||||
defaultFieldsIndex () {
|
||||
return this.defaultFields.reduce((acc, field) => {
|
||||
acc[field.name] = field
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
fieldIconsSorted () {
|
||||
if (this.fieldTypes.length) {
|
||||
return this.fieldTypes.reduce((acc, type) => {
|
||||
@@ -380,6 +391,14 @@ export default {
|
||||
})
|
||||
})
|
||||
|
||||
this.template.schema.forEach((item) => {
|
||||
(item.conditions || []).forEach((c) => {
|
||||
if (c.field_uuid === field.uuid) {
|
||||
item.conditions.splice(item.conditions.indexOf(c), 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.save()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const en = {
|
||||
reorder_fields: 'Reorder fields',
|
||||
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.',
|
||||
editable: 'Editable',
|
||||
@@ -48,6 +49,8 @@ const en = {
|
||||
cancel: 'Cancel',
|
||||
any: 'Any',
|
||||
drawn: 'Drawn',
|
||||
drawn_or_typed: 'Drawn or Typed',
|
||||
upload: 'Upload',
|
||||
formula: 'Formula',
|
||||
typed: 'Typed',
|
||||
draw_field_on_the_document: 'Draw a field on the document',
|
||||
@@ -156,6 +159,7 @@ const en = {
|
||||
}
|
||||
|
||||
const es = {
|
||||
reorder_fields: 'Reordenar campos',
|
||||
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.',
|
||||
recurrent: 'Recurrente',
|
||||
@@ -289,6 +293,8 @@ const es = {
|
||||
add_document: 'Subir',
|
||||
any: 'Cualquier',
|
||||
drawn: 'Dibujado',
|
||||
drawn_or_typed: 'Dibujado o Escrito',
|
||||
upload: 'Subir',
|
||||
typed: 'Escrito',
|
||||
none: 'Ninguno',
|
||||
ssn: 'SSN',
|
||||
@@ -313,6 +319,7 @@ const es = {
|
||||
}
|
||||
|
||||
const it = {
|
||||
reorder_fields: 'Riordina i campi',
|
||||
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ù.',
|
||||
ricorrente: 'Ricorrente',
|
||||
@@ -362,6 +369,8 @@ const it = {
|
||||
cancel: 'Annulla',
|
||||
any: 'Qualsiasi',
|
||||
drawn: 'Disegnato',
|
||||
drawn_or_typed: 'Disegnato o Digitato',
|
||||
upload: 'Caricare',
|
||||
formula: 'Formula',
|
||||
typed: 'Digitato',
|
||||
draw_field_on_the_document: 'Disegnare un campo sul documento',
|
||||
@@ -470,6 +479,7 @@ const it = {
|
||||
}
|
||||
|
||||
const pt = {
|
||||
reorder_fields: 'Reorganizar campos',
|
||||
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.',
|
||||
recurrent: 'Recurrente',
|
||||
@@ -603,6 +613,8 @@ const pt = {
|
||||
add_document: 'Enviar',
|
||||
any: 'Qualquer',
|
||||
drawn: 'Desenhado',
|
||||
drawn_or_typed: 'Desenhado ou Digitado',
|
||||
upload: 'Carregar',
|
||||
typed: 'Digitado',
|
||||
none: 'Nenhum',
|
||||
ssn: 'SSN',
|
||||
@@ -627,6 +639,7 @@ const pt = {
|
||||
}
|
||||
|
||||
const fr = {
|
||||
reorder_fields: 'Réorganiser les champs',
|
||||
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.',
|
||||
recurrent: 'Récurrent',
|
||||
@@ -760,6 +773,8 @@ const fr = {
|
||||
add_document: 'Télécharger',
|
||||
any: 'Tout',
|
||||
drawn: 'Dessiné',
|
||||
drawn_or_typed: 'Dessiné ou Tapé',
|
||||
upload: 'Téléverser',
|
||||
typed: 'Tapé',
|
||||
none: 'Aucun',
|
||||
ssn: 'SSN',
|
||||
@@ -784,6 +799,7 @@ const fr = {
|
||||
}
|
||||
|
||||
const de = {
|
||||
reorder_fields: 'Felder neu anordnen',
|
||||
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.',
|
||||
wiederkehrend: 'Wiederkehrend',
|
||||
@@ -917,6 +933,8 @@ const de = {
|
||||
add_document: 'Hochladen',
|
||||
any: 'Jede',
|
||||
drawn: 'Gezeichnet',
|
||||
drawn_or_typed: 'Gezeichnet oder getippt',
|
||||
upload: 'Upload',
|
||||
typed: 'Getippt',
|
||||
none: 'Keine',
|
||||
ssn: 'SSN',
|
||||
|
||||
@@ -346,7 +346,7 @@ export default {
|
||||
return this.$el.closest('form')
|
||||
},
|
||||
fieldTypes () {
|
||||
return ['text', 'cells', 'date', 'number', 'radio', 'select', 'checkbox']
|
||||
return ['text', 'cells', 'date', 'number', 'radio', 'select', 'checkbox', 'image']
|
||||
},
|
||||
defaultFields () {
|
||||
return [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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 md:hidden">
|
||||
<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">
|
||||
<div class="flex items-center space-x-2">
|
||||
<component
|
||||
:is="fieldIcons[drawField.type]"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span
|
||||
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10 md:hidden"
|
||||
class="dropdown dropdown-top dropdown-end absolute bottom-4 right-4 z-10"
|
||||
>
|
||||
<label
|
||||
class="btn btn-neutral text-white btn-circle btn-lg group"
|
||||
@@ -12,9 +12,10 @@
|
||||
height="28"
|
||||
/>
|
||||
<IconX
|
||||
class="hidden group-focus:inline"
|
||||
width="28"
|
||||
height="28"
|
||||
class="hidden group-focus:inline p-3"
|
||||
width="64"
|
||||
height="50"
|
||||
@click="closeDropdown"
|
||||
/>
|
||||
</label>
|
||||
<ul
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative cursor-crosshair select-none"
|
||||
class="relative select-none"
|
||||
:class="{ 'cursor-crosshair': allowDraw }"
|
||||
:style="drawField ? 'touch-action: none' : ''"
|
||||
>
|
||||
<img
|
||||
@@ -25,8 +26,9 @@
|
||||
:field="item.field"
|
||||
:editable="editable"
|
||||
:with-field-placeholder="withFieldPlaceholder"
|
||||
:default-field="defaultFields.find((f) => f.name === item.field.name)"
|
||||
:default-field="defaultFieldsIndex[item.field.name]"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:max-page="totalPages - 1"
|
||||
@start-resize="resizeDirection = $event"
|
||||
@stop-resize="resizeDirection = null"
|
||||
@remove="$emit('remove-area', item.area)"
|
||||
@@ -88,6 +90,10 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
totalPages: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
drawFieldType: {
|
||||
type: String,
|
||||
required: false,
|
||||
@@ -137,6 +143,13 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
defaultFieldsIndex () {
|
||||
return this.defaultFields.reduce((acc, field) => {
|
||||
acc[field.name] = field
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
defaultFieldType () {
|
||||
if (this.drawFieldType) {
|
||||
return this.drawFieldType
|
||||
@@ -149,7 +162,9 @@ export default {
|
||||
}
|
||||
},
|
||||
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)
|
||||
},
|
||||
resizeDirectionClasses () {
|
||||
return {
|
||||
|
||||
@@ -9,14 +9,30 @@
|
||||
loading="lazy"
|
||||
>
|
||||
<div
|
||||
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1"
|
||||
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1 hover:bg-black/10 transition-colors"
|
||||
@click="$emit('scroll-to', item)"
|
||||
>
|
||||
<div
|
||||
v-if="editable"
|
||||
class="flex justify-between w-full"
|
||||
>
|
||||
<div style="width: 26px" />
|
||||
<div
|
||||
style="width: 26px"
|
||||
class="flex flex-col justify-between group-hover:opacity-100"
|
||||
:class="{'opacity-0': !item.conditions?.length }"
|
||||
>
|
||||
<div>
|
||||
<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 p-0"
|
||||
@click.stop="isShowConditionsModal = true"
|
||||
>
|
||||
<IconRouteAltLeft
|
||||
:width="14"
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<ReplaceButton
|
||||
v-if="withReplaceButton"
|
||||
@@ -40,23 +56,39 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="withArrows"
|
||||
class="flex flex-col space-y-1"
|
||||
>
|
||||
<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"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('up', item)"
|
||||
<span
|
||||
:data-tip="t('reorder_fields')"
|
||||
class="tooltip tooltip-left before:text-xs"
|
||||
>
|
||||
↑
|
||||
</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"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
↓
|
||||
</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 p-0"
|
||||
@click.stop="$emit('reorder', item)"
|
||||
>
|
||||
<IconSortDescending2
|
||||
:width="18"
|
||||
:height="18"
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
<template v-if="withArrows">
|
||||
<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"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('up', item)"
|
||||
>
|
||||
↑
|
||||
</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"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,20 +104,38 @@
|
||||
@update:model-value="onUpdateName"
|
||||
/>
|
||||
</div>
|
||||
<Teleport
|
||||
v-if="isShowConditionsModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<ConditionsModal
|
||||
:item="item"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowConditionsModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Contenteditable from './contenteditable'
|
||||
import Upload from './upload'
|
||||
import { IconRouteAltLeft, IconSortDescending2 } from '@tabler/icons-vue'
|
||||
import ConditionsModal from './conditions_modal'
|
||||
import ReplaceButton from './replace'
|
||||
import Field from './field'
|
||||
import FieldType from './field_type'
|
||||
|
||||
export default {
|
||||
name: 'DocumentPreview',
|
||||
components: {
|
||||
Contenteditable,
|
||||
ReplaceButton
|
||||
IconRouteAltLeft,
|
||||
ConditionsModal,
|
||||
ReplaceButton,
|
||||
IconSortDescending2
|
||||
},
|
||||
inject: ['t'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@@ -120,14 +170,25 @@ export default {
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'],
|
||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace', 'reorder'],
|
||||
data () {
|
||||
return {
|
||||
isShowConditionsModal: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fieldNames: FieldType.computed.fieldNames,
|
||||
fieldLabels: FieldType.computed.fieldLabels,
|
||||
previewImage () {
|
||||
return [...this.document.preview_images].sort((a, b) => parseInt(a.filename) - parseInt(b.filename))[0]
|
||||
},
|
||||
modalContainerEl () {
|
||||
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
upload: Upload.methods.upload,
|
||||
buildDefaultName: Field.methods.buildDefaultName,
|
||||
onUpdateName (value) {
|
||||
this.item.name = value
|
||||
|
||||
|
||||
@@ -10,10 +10,6 @@ class ProcessSubmitterCompletionJob
|
||||
|
||||
is_all_completed = !submitter.submission.submitters.exists?(completed_at: nil)
|
||||
|
||||
if !is_all_completed && submitter.submission.submitters_order_preserved?
|
||||
enqueue_next_submitter_request_notification(submitter)
|
||||
end
|
||||
|
||||
Submissions::EnsureResultGenerated.call(submitter)
|
||||
|
||||
if is_all_completed && submitter.completed_at == submitter.submission.submitters.maximum(:completed_at)
|
||||
@@ -28,6 +24,10 @@ class ProcessSubmitterCompletionJob
|
||||
|
||||
create_completed_documents!(submitter)
|
||||
|
||||
if !is_all_completed && submitter.submission.submitters_order_preserved? && params['send_invitation_email'] != false
|
||||
enqueue_next_submitter_request_notification(submitter)
|
||||
end
|
||||
|
||||
enqueue_completed_webhooks(submitter, is_all_completed:)
|
||||
end
|
||||
|
||||
@@ -120,6 +120,8 @@ class ProcessSubmitterCompletionJob
|
||||
submitter.submission.template_submitters.find do |e|
|
||||
sub = submitter.submission.submitters.find { |s| s.uuid == e['uuid'] }
|
||||
|
||||
next unless sub
|
||||
|
||||
sub.completed_at.blank? && sub.sent_at.blank?
|
||||
end
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ class SendFormCompletedWebhookRequestJob
|
||||
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
MAX_ATTEMPTS = 20
|
||||
|
||||
def perform(params = {})
|
||||
submitter = Submitter.find(params['submitter_id'])
|
||||
|
||||
@@ -121,7 +121,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
@body ||= @email_config.value['body'] if @email_config
|
||||
|
||||
assign_message_metadata('submitter_documents_copy', @submitter)
|
||||
reply_to = build_submitter_reply_to(submitter)
|
||||
reply_to = build_submitter_reply_to(submitter, email_config: @email_config, documents_copy_email: true)
|
||||
|
||||
I18n.with_locale(@current_account.locale) do
|
||||
subject =
|
||||
@@ -140,8 +140,10 @@ class SubmitterMailer < ApplicationMailer
|
||||
|
||||
private
|
||||
|
||||
def build_submitter_reply_to(submitter)
|
||||
def build_submitter_reply_to(submitter, email_config: nil, documents_copy_email: nil)
|
||||
reply_to = submitter.preferences['reply_to'].presence
|
||||
reply_to ||= submitter.template.preferences['documents_copy_email_reply_to'].presence if documents_copy_email
|
||||
reply_to ||= email_config.value['reply_to'].presence if email_config
|
||||
|
||||
if reply_to.blank? && (submitter.submission.created_by_user || submitter.template.author)&.email != submitter.email
|
||||
reply_to = (submitter.submission.created_by_user || submitter.template.author)&.friendly_name&.sub(/\+\w+@/, '@')
|
||||
|
||||
@@ -57,6 +57,8 @@ class Submission < ApplicationRecord
|
||||
|
||||
has_many_attached :preview_documents
|
||||
|
||||
has_many :template_accesses, primary_key: :template_id, foreign_key: :template_id, dependent: nil, inverse_of: false
|
||||
|
||||
has_many :template_schema_documents,
|
||||
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
||||
through: :template, source: :documents_attachments
|
||||
@@ -67,6 +69,8 @@ class Submission < ApplicationRecord
|
||||
where.not(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
||||
}
|
||||
scope :declined, -> { joins(:submitters).where.not(submitters: { declined_at: nil }).group(:id) }
|
||||
scope :expired, -> { where(expire_at: ..Time.current) }
|
||||
|
||||
enum :source, {
|
||||
invite: 'invite',
|
||||
|
||||
@@ -54,6 +54,7 @@ class Submitter < ApplicationRecord
|
||||
has_many_attached :documents
|
||||
has_many_attached :attachments
|
||||
has_many_attached :preview_documents
|
||||
has_many :template_accesses, through: :template
|
||||
|
||||
has_many :document_generation_events, dependent: :destroy
|
||||
has_many :submission_events, dependent: :destroy
|
||||
|
||||
@@ -62,6 +62,7 @@ class Template < ApplicationRecord
|
||||
|
||||
has_many :submissions, dependent: :destroy
|
||||
has_many :template_sharings, dependent: :destroy
|
||||
has_many :template_accesses, dependent: :destroy
|
||||
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
scope :archived, -> { where.not(archived_at: nil) }
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: template_accesses
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# template_id :bigint not null
|
||||
# user_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_template_accesses_on_template_id_and_user_id (template_id,user_id) UNIQUE
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (template_id => templates.id)
|
||||
#
|
||||
class TemplateAccess < ApplicationRecord
|
||||
ADMIN_USER_ID = -1
|
||||
|
||||
belongs_to :template
|
||||
belongs_to :user, optional: true
|
||||
end
|
||||
@@ -57,6 +57,7 @@ class User < ApplicationRecord
|
||||
|
||||
belongs_to :account
|
||||
has_one :access_token, dependent: :destroy
|
||||
has_many :access_tokens, dependent: :destroy
|
||||
has_many :templates, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||
has_many :template_folders, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
|
||||
has_many :user_configs, dependent: :destroy
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<%= hidden_field_tag :redir, params[:redir] if params[:redir].present? %>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="form-control">
|
||||
<%= label_tag 'user[otp_attempt]', t('two_factor_code_from_authenticator_app'), class: 'label' %>
|
||||
<%= text_field_tag 'user[otp_attempt]', nil, autofocus: true, autocomplete: 'off', placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
||||
</div>
|
||||
@@ -3,9 +3,7 @@
|
||||
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||
<h1 class="text-4xl font-bold text-center mt-8"><%= t('sign_in') %></h1>
|
||||
<%= form_for(resource, as: resource_name, html: { class: 'space-y-6' }, data: { turbo: params[:redir].blank? }, url: session_path(resource_name)) do |f| %>
|
||||
<% if params[:redir].present? %>
|
||||
<%= hidden_field_tag :redir, params[:redir] %>
|
||||
<% end %>
|
||||
<%= render 'hidden_fields' %>
|
||||
<div class="space-y-2" dir="auto">
|
||||
<div class="form-control">
|
||||
<%= f.label :email, t(:email), class: 'label' %>
|
||||
|
||||
@@ -8,12 +8,7 @@
|
||||
<% if params[:redir].present? %>
|
||||
<%= hidden_field_tag :redir, params[:redir] %>
|
||||
<% end %>
|
||||
<div class="space-y-2">
|
||||
<div class="form-control">
|
||||
<%= f.label :otp_attempt, t('two_factor_code_from_authenticator_app'), class: 'label' %>
|
||||
<%= f.text_field :otp_attempt, autofocus: true, placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render 'otp_form', **local_assigns %>
|
||||
<div class="form-control">
|
||||
<%= f.button button_title(title: t('sign_in'), disabled_with: t('signing_in')), class: 'base-button' %>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="<%= "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" class="btn btn-neutral btn-sm text-white">
|
||||
<a href="<%= "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" class="btn btn-neutral btn-sm text-white">
|
||||
<%= t('unlock_with_docuseal_pro') %>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" 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="M20.997 12.25a9 9 0 1 0 -8.718 8.745" /><path d="M19 19m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" /><path d="M17 21l4 -4" /><path d="M12 7v5l2 2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 433 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" 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="M21 12a9 9 0 1 0 -9.972 8.948c.32 .034 .644 .052 .972 .052" /><path d="M12 7v5l2 2" /><path d="M18.42 15.61a2.1 2.1 0 0 1 2.97 2.97l-3.39 3.42h-3v-3l3.42 -3.39z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 456 B |
@@ -1,4 +1,4 @@
|
||||
<title>
|
||||
<%= content_for(:html_title) || 'DocuSeal | Open Source Document Signing' %>
|
||||
<%= content_for(:html_title) || (signed_in? ? 'DocuSeal' : 'DocuSeal | Open Source Document Signing') %>
|
||||
</title>
|
||||
<%= render 'shared/meta' %>
|
||||
@@ -7,7 +7,7 @@
|
||||
<p>
|
||||
<%= t('send_automatic_email_reminders_to_your_recipients') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="collapse-content">
|
||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'reply_to', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
@@ -24,6 +24,12 @@
|
||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
<% if can?(:manage, :reply_to) %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :reply_to, t('reply_to'), class: 'label' %>
|
||||
<%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-between pt-2.5 mx-1">
|
||||
<span>
|
||||
<%= t('attach_documents') %>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p>
|
||||
<%= t('display_your_company_name_and_logo_when_signing_documents') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<% 'stats stat stat-figure stat-title stat-value text-accent' %>
|
||||
<% 'stats stat stat-figure stat-title stat-value text-accent w-fit hover:bg-white' %>
|
||||
@@ -1,7 +1,7 @@
|
||||
<% if Docuseal.demo? || (request.path != '/' && !devise_controller?) %>
|
||||
<meta name="robots" content="noindex">
|
||||
<% end %>
|
||||
<% title = content_for(:html_title) || 'DocuSeal | Open Source Document Filling and Signing' %>
|
||||
<% title = content_for(:html_title) || (signed_in? ? 'DocuSeal' : 'DocuSeal | Open Source Document Signing') %>
|
||||
<% description = content_for(:html_description) || 'Open source, self-hosted tool to streamline document filling and signing. Create custom PDF forms to complete and sign with an easy to use online tool.' %>
|
||||
<meta name="description" content="<%= description %>">
|
||||
<meta property="og:title" content="<%= title %>">
|
||||
|
||||
@@ -49,6 +49,16 @@
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full py-1' } do |f| %>
|
||||
<label class="flex items-center pl-6 pr-4 py-2 border-y border-base-300 -ml-2 -mr-2" for="testing_toggle">
|
||||
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()', style: 'height: 0.885rem; width: 1.35rem; --handleoffset: 0.395rem; margin-left: -2px; margin-right: 8px' %>
|
||||
<span class="whitespace-nowrap">
|
||||
<%= t('test_mode') %>
|
||||
</span>
|
||||
</label>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<li>
|
||||
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false }, class: 'flex items-center' do %>
|
||||
<%= svg_icon('logout', class: 'w-5 h-5 flex-shrink-0 stroke-2 mr-2 inline') %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
|
||||
<%= t('upgrade') %>
|
||||
<% end %>
|
||||
<% if signed_in? && current_user != true_user %>
|
||||
|
||||
Whitespace-only changes.
@@ -64,7 +64,7 @@
|
||||
<% end %>
|
||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.linked_account_account) %>
|
||||
<li>
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||
<%= t('plans') %>
|
||||
<span class="badge badge-warning"><%= t('new') %></span>
|
||||
<% end %>
|
||||
@@ -72,7 +72,7 @@
|
||||
<% end %>
|
||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.testing?) %>
|
||||
<li>
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
||||
<% end %>
|
||||
</li>
|
||||
@@ -90,16 +90,16 @@
|
||||
<% end %>
|
||||
<%= render 'shared/settings_nav_extra2' %>
|
||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||
<li>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex w-full' } do |f| %>
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full' } do |f| %>
|
||||
<li>
|
||||
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
||||
<span class="mr-2 w-full">
|
||||
<%= t('test_environment') %>
|
||||
<%= t('test_mode') %>
|
||||
</span>
|
||||
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||
</label>
|
||||
<% end %>
|
||||
</li>
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="hidden md:flex alert py-1 text-sm font-medium gap-x-2 whitespace-nowrap">
|
||||
<a href="<%= testing_api_settings_path %>" data-turbo-frame="modal" class="link font-semibold flex">
|
||||
<%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %>
|
||||
<span><%= t('testing_environment') %></span>
|
||||
<span><%= t('test_mode') %></span>
|
||||
</a>
|
||||
<span>
|
||||
|
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
|
||||
<label class="flex items-center justify-between" for="testing_toggle">
|
||||
<span class="mr-2 text-lg">
|
||||
<%= t('test_environment') %>
|
||||
<%= t('test_mode') %>
|
||||
</span>
|
||||
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
|
||||
</label>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p class="text-gray-700">
|
||||
<%= t('unlock_with_docuseal_pro') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p class="text-gray-700">
|
||||
<%= t('unlock_with_docuseal_pro') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p class="text-gray-700">
|
||||
<%= t('unlock_with_docuseal_pro') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? } %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? } %>
|
||||
<dynamic-list class="space-y-4">
|
||||
<div class="space-y-4">
|
||||
<div class="card card-compact bg-base-300/40" data-targets="dynamic-list.items">
|
||||
@@ -20,7 +20,7 @@
|
||||
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
|
||||
<submitters-autocomplete data-field="name">
|
||||
<linked-input data-target-id="<%= "detailed_name_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
|
||||
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero?, value: (params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : '', dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
|
||||
<%= tag.input type: 'text', name: 'submission[1][submitters][][name]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('name'), required: index.zero?, value: item['email'].present? ? current_account.submitters.accessible_by(current_ability).where.not(name: nil).order(id: :desc).find_by(email: item['email'])&.name : ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.full_name : ''), dir: 'auto', id: "detailed_name_#{item['uuid']}" %>
|
||||
</linked-input>
|
||||
</submitters-autocomplete>
|
||||
<div class="grid <%= 'md:grid-cols-2 gap-1' if submitters.size == 1 %>">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? } %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? } %>
|
||||
<% if submitters.size == 1 %>
|
||||
<submitter-item class="form-control">
|
||||
<emails-textarea data-bulk-enabled="<%= Docuseal.demo? || !Docuseal.multitenant? || can?(:manage, :bulk_send) %>" data-limit="<%= Docuseal.multitenant? ? (can?(:manage, :bulk_send) ? 40 : 1) : nil %>">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? } %>
|
||||
<% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? } %>
|
||||
<dynamic-list class="space-y-4">
|
||||
<div class="space-y-4">
|
||||
<div class="card card-compact bg-base-300/40" data-targets="dynamic-list.items">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="mt-2 mb-1">
|
||||
<div class="tooltip w-full" data-tip="<%= t('unlock_with_docuseal_pro') %>">
|
||||
<%= link_to submitter.sent_at? ? t('re_send_sms') : t('send_sms'), Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
||||
<%= link_to submitter.sent_at? ? t('re_send_sms') : t('send_sms'), Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,7 +12,8 @@
|
||||
ID: <%= attachment.uuid %>
|
||||
</div>
|
||||
<div>
|
||||
<%= t('reason') %>: <%= submitter.values[field.dig('preferences', 'reason_field_uuid')].presence || t('digitally_signed_by') %> <%= submitter.name %>
|
||||
<% reason_value = submitter.values[field.dig('preferences', 'reason_field_uuid')].presence %>
|
||||
<% if reason_value %><%= t('reason') %>: <% end %><%= reason_value || t('digitally_signed_by') %> <%= submitter.name %>
|
||||
<% if submitter.email %>
|
||||
<<%= submitter.email %>>
|
||||
<% end %>
|
||||
@@ -49,7 +50,7 @@
|
||||
<% end %>
|
||||
<% elsif field['type'] == 'cells' && area['cell_w'].to_f > 0.0 %>
|
||||
<% cell_width = area['cell_w'] / area['w'] * 100 %>
|
||||
<div class="w-full flex items-center">
|
||||
<div class="w-full flex items-center <%= 'justify-end' if align == 'right' %>">
|
||||
<% (0..(area['w'] / area['cell_w']).ceil).each do |index| %>
|
||||
<% if value[index] %>
|
||||
<div class="text-center flex-none" style="width: <%= cell_width %>%;"><%= value[index] %></div>
|
||||
|
||||
@@ -65,7 +65,9 @@
|
||||
</div>
|
||||
<div class="flex md:max-h-[calc(100vh-60px)]">
|
||||
<div class="overflow-y-auto overflow-x-hidden hidden lg:block w-52 flex-none pr-3 mt-0.5 pt-0.5">
|
||||
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
|
||||
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
<% schema = Submissions.filtered_conditions_schema(@submission, values:) %>
|
||||
<% schema.each do |item| %>
|
||||
<% document = @submission.template_schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
||||
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
|
||||
<img src="<%= Docuseal::URL_CACHE.fetch([document.id, document.uuid, 0].join(':'), expires_in: 10.minutes) { document.preview_images.first.url } %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
|
||||
@@ -79,10 +81,9 @@
|
||||
<div class="pr-3.5 pl-0.5">
|
||||
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %>
|
||||
<% submitters_index = @submission.submitters.index_by(&:uuid) %>
|
||||
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
<% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
|
||||
<% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
|
||||
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
|
||||
<% schema.each do |item| %>
|
||||
<% document = @submission.template_schema_documents.find { |e| e.uuid == item['attachment_uuid'] } %>
|
||||
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
|
||||
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
|
||||
@@ -97,6 +98,7 @@
|
||||
<% end %>
|
||||
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
||||
<% value = values[field['uuid']] %>
|
||||
<% value ||= field['default_value'] if field['type'] == 'heading' %>
|
||||
<% next if value.blank? %>
|
||||
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submission.account.locale, timezone: @submission.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: %>
|
||||
<% end %>
|
||||
@@ -121,7 +123,7 @@
|
||||
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
|
||||
</span>
|
||||
</div>
|
||||
<% if signed_in? && can?(:update, submitter) && !submitter.completed_at? && !submitter.declined_at? && !@submission.archived_at? && !@submission.expired? && !submitter.start_form_submission_events.any? %>
|
||||
<% if signed_in? && can?(:update, @submission) && submitter && !submitter.completed_at? && !submitter.declined_at? && !@submission.archived_at? && !@submission.expired? && !submitter.start_form_submission_events.any? %>
|
||||
<span class="tooltip tooltip-left" data-tip="<%= t('edit') %>">
|
||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0 inline md:hidden md:group-hover:inline', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||
@@ -173,15 +175,15 @@
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && Accounts.can_send_emails?(current_account) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && can?(:update, @submission) && Accounts.can_send_emails?(current_account) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<div class="mt-2 mb-1">
|
||||
<%= button_to button_title(title: submitter.sent_at? ? t('re_send_email') : t('send_email'), disabled_with: t('sending')), submitter_send_email_index_path(submitter_slug: submitter.slug), class: 'btn btn-sm btn-primary w-full' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && can?(:update, @submission) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<%= render 'submissions/send_sms_button', submitter: %>
|
||||
<% end %>
|
||||
<% if signed_in? && submitter && !submitter.completed_at? && !@submission.archived_at? && can?(:create, submitter) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<% if signed_in? && submitter && !submitter.completed_at? && !@submission.archived_at? && can?(:create, @submission) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<div class="mt-2 mb-1">
|
||||
<a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_path(slug: submitter.slug) %>">
|
||||
<%= t('sign_in_person') %>
|
||||
@@ -195,6 +197,7 @@
|
||||
<% submitter_field_counters[field['type']] += 1 %>
|
||||
<% value = values[field['uuid']] %>
|
||||
<% next if value.blank? %>
|
||||
<% next if field['type'] == 'heading' %>
|
||||
<div class="pt-2.5 border-b border-base-300">
|
||||
<div class="text-xs font-medium uppercase mb-0.5" dir="auto">
|
||||
<%= field['name'].presence || "#{t("#{field['type']}_field")} #{submitter_field_counters[field['type']]}" %>
|
||||
|
||||
@@ -1,4 +1,26 @@
|
||||
<% query_params = params.permit(:q, :status).merge(filter_params) %>
|
||||
<% query_params = params.permit(:q).merge(filter_params) %>
|
||||
<% if icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit' }[params[:status]] %>
|
||||
<div class="flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700">
|
||||
<%= link_to submissions_filter_path('status', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
||||
<%= svg_icon(icon, class: 'w-5 h-5 shrink-0') %>
|
||||
<span class="font-normal truncate"><%= t(params[:status]) %></span>
|
||||
<% end %>
|
||||
<%= link_to url_for(params.to_unsafe_h.except(:status)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:author].present? %>
|
||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('author') %>">
|
||||
<%= link_to submissions_filter_path('author', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
||||
<%= svg_icon('user', class: 'w-5 h-5 shrink-0') %>
|
||||
<span class="font-normal truncate"><%= current_account.users.accessible_by(current_ability).where(account: current_account).find_by(email: params[:author])&.full_name || 'NA' %></span>
|
||||
<% end %>
|
||||
<%= link_to url_for(params.to_unsafe_h.except(:author)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if query_params[:completed_at_from].present? || query_params[:completed_at_to].present? %>
|
||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('completed_at') %>">
|
||||
<%= link_to submissions_filter_path('completed_at', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
||||
@@ -37,14 +59,3 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:author].present? %>
|
||||
<div class="tooltip tooltip-bottom flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700" data-tip="<%= t('author') %>">
|
||||
<%= link_to submissions_filter_path('author', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
||||
<%= svg_icon('user', class: 'w-5 h-5 shrink-0') %>
|
||||
<span class="font-normal truncate"><%= current_account.users.accessible_by(current_ability).where(account: current_account).find_by(email: params[:author])&.full_name || 'NA' %></span>
|
||||
<% end %>
|
||||
<%= link_to url_for(params.to_unsafe_h.except(:author)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -1,8 +1,8 @@
|
||||
<% query_params = params.permit(:q, :status).merge(filter_params) %>
|
||||
<% query_params = params.permit(:q).merge(filter_params) %>
|
||||
<div class="dropdown dropdown-end">
|
||||
<label tabindex="0" class="cursor-pointer flex h-10 px-3 py-1 space-x-1 text-lg items-center justify-between border text-center text-neutral rounded-xl border-neutral-300 hover:border-neutral-700">
|
||||
<%= svg_icon('filter', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||
<span class="<%= filter_params.present? ? 'md:hidden' : '' %>">Filter</span>
|
||||
<span class="<%= filter_params.then { |f| f[:status].in?(%w[pending completed]) ? f.except(:status) : f }.present? ? 'md:hidden' : '' %>">Filter</span>
|
||||
</label>
|
||||
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box min-w-[180px] text-right">
|
||||
<li class="flex">
|
||||
@@ -17,6 +17,12 @@
|
||||
<span><%= t('created_at') %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="flex">
|
||||
<%= link_to submissions_filter_path('status', query_params.merge(path: url_for)), data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('info_circle', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||
<span><%= t('status') %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="flex">
|
||||
<%= link_to submissions_filter_path('author', query_params.merge(path: url_for)), data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('user', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<%= render 'shared/turbo_modal', title: local_assigns[:title] do %>
|
||||
<%= form_for '', url: params[:path], method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
||||
<%= hidden_field_tag :status, params[:status] if params[:status].present? %>
|
||||
<%= hidden_field_tag :q, params[:q] if params[:q].present? %>
|
||||
<% local_assigns[:default_params].each do |key, value| %>
|
||||
<%= hidden_field_tag(key, value) if value.present? %>
|
||||
@@ -11,7 +10,7 @@
|
||||
</div>
|
||||
<% if params[:with_remove] %>
|
||||
<div class="text-center w-full mt-4">
|
||||
<%= link_to t('remove_filter'), "#{params[:path]}?#{params.to_unsafe_h.slice(:q, :status).merge(local_assigns[:default_params]).to_query}", class: 'link', data: { turbo_frame: :_top } %>
|
||||
<%= link_to t('remove_filter'), "#{params[:path]}?#{params.to_unsafe_h.slice(:q).merge(local_assigns[:default_params]).to_query}", class: 'link', data: { turbo_frame: :_top } %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<%= render 'filter_modal', title: t('status'), default_params: params.permit(*(Submissions::Filter::ALLOWED_PARAMS - %w[status])) do %>
|
||||
<div class="flex flex-col md:flex-row gap-2 mt-5">
|
||||
<div class="form-control w-full">
|
||||
<div id="status" class="radio-select grid grid-cols-2 gap-2 px-1">
|
||||
<% ['', 'pending', 'completed', 'partially_completed', 'declined', 'expired'].each do |status| %>
|
||||
<label class="radio-label cursor-pointer inline-flex items-center space-x-2">
|
||||
<%= radio_button_tag 'status', status, params[:status] == status || (status == '' && params[:status].blank?), class: 'base-radio' %>
|
||||
<span><%= t(status.presence || 'all') %></span>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -1,4 +1,5 @@
|
||||
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid created_at], methods: %i[url filename content_type]) %>
|
||||
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
|
||||
<% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template.preferences['completed_redirect_url'] %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template.preferences['completed_message'] || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
|
||||
<% optional_invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['optional_invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template.preferences['completed_redirect_url'] %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template.preferences['completed_message'] || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
|
||||
@@ -4,6 +4,7 @@
|
||||
<% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
<% submitters_index = @submitter.submission.submitters.index_by(&:uuid) %>
|
||||
<% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
|
||||
<% schema = Submissions.filtered_conditions_schema(@submitter.submission, values:, include_submitter_uuid: @submitter.uuid) %>
|
||||
<div style="max-height: -webkit-fill-available;">
|
||||
<div id="scrollbox">
|
||||
<div class="mx-auto block pb-72" style="max-width: 1000px">
|
||||
@@ -35,32 +36,34 @@
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% (@submitter.submission.template_schema || @submitter.submission.template.schema).each do |item| %>
|
||||
<% schema.each do |item| %>
|
||||
<% document = @submitter.submission.template_schema_documents.find { |a| a.uuid == item['attachment_uuid'] } %>
|
||||
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
|
||||
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
|
||||
<% lazyload_metadata = document.preview_images.last.metadata %>
|
||||
<% (document.metadata.dig('pdf', 'number_of_pages') || (document.preview_images.loaded? ? preview_images_index.size : document.preview_images.size)).times do |index| %>
|
||||
<% page = preview_images_index[index] || page_blob_struct.new(metadata: lazyload_metadata, url: preview_document_page_path(document.signed_uuid, "#{index}.jpg")) %>
|
||||
<div class="relative my-4 shadow-md">
|
||||
<img loading="lazy" src="<%= page.url %>" width="<%= page.metadata['width'] %>" height="<%= page.metadata['height'] %>">
|
||||
<div id="page-<%= [document.uuid, index].join('-') %>" class="top-0 bottom-0 left-0 right-0 absolute">
|
||||
<% document_annots_index[index]&.each do |annot| %>
|
||||
<%= render 'submissions/annotation', annot: %>
|
||||
<% end %>
|
||||
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
||||
<% value = values[field['uuid']].presence || (field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submitter.submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
||||
<% next if value.blank? %>
|
||||
<% next if !field['readonly'] && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<% next if value == '{{date}}' && field['submitter_uuid'] != @submitter.uuid %>
|
||||
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
|
||||
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %>
|
||||
<% end %>
|
||||
<div id="document-<%= document.uuid %>">
|
||||
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
|
||||
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
|
||||
<% lazyload_metadata = document.preview_images.last.metadata %>
|
||||
<% (document.metadata.dig('pdf', 'number_of_pages') || (document.preview_images.loaded? ? preview_images_index.size : document.preview_images.size)).times do |index| %>
|
||||
<% page = preview_images_index[index] || page_blob_struct.new(metadata: lazyload_metadata, url: preview_document_page_path(document.signed_uuid, "#{index}.jpg")) %>
|
||||
<div class="relative my-4 shadow-md">
|
||||
<img loading="lazy" src="<%= page.url %>" width="<%= page.metadata['width'] %>" height="<%= page.metadata['height'] %>">
|
||||
<div id="page-<%= [document.uuid, index].join('-') %>" class="top-0 bottom-0 left-0 right-0 absolute">
|
||||
<% document_annots_index[index]&.each do |annot| %>
|
||||
<%= render 'submissions/annotation', annot: %>
|
||||
<% end %>
|
||||
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
||||
<% value = values[field['uuid']].presence || (field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submitter.submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
||||
<% next if value.blank? %>
|
||||
<% next if !field['readonly'] && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<% next if value == '{{date}}' && field['submitter_uuid'] != @submitter.uuid %>
|
||||
<% next if field['conditions'].present? && values[field['uuid']].blank? && field['submitter_uuid'] != @submitter.uuid %>
|
||||
<% next if field['conditions'].present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
|
||||
<%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account, with_style: false %>
|
||||
<% if @form_configs[:policy_links].present? %>
|
||||
@@ -76,7 +79,7 @@
|
||||
<div class="fixed bottom-0 w-full h-0 z-20">
|
||||
<div class="mx-auto" style="max-width: 1000px">
|
||||
<div class="relative md:mx-32">
|
||||
<%= render 'submit_form/submission_form', attachments_index: @attachments_index, submitter: @submitter, signature_attachment: @signature_attachment, configs: @form_configs, dry_run: local_assigns[:dry_run], expand: local_assigns[:expand], scroll_padding: local_assigns.fetch(:scroll_padding, '-110px') %>
|
||||
<%= render 'submit_form/submission_form', attachments_index: @attachments_index, submitter: @submitter, signature_attachment: @signature_attachment, configs: @form_configs, dry_run: local_assigns[:dry_run], expand: local_assigns[:expand], scroll_padding: local_assigns.fetch(:scroll_padding, '-110px'), schema: %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<%= render 'custom_content', content: @body, submitter: @submitter %>
|
||||
<% else %>
|
||||
<p><%= t('hi_there') %>,</p>
|
||||
<p><%= I18n.t(:name_has_been_completed_by_submitters, name: @submitter.submission.template.name, submitters: @submitter.submission.submitters.order(:completed_at).map { |e| e.name || e.email || e.phone }.join(', ')) %></p>
|
||||
<p><%= I18n.t(:name_has_been_completed_by_submitters, name: @submitter.submission.template.name, submitters: @submitter.submission.submitters.order(:completed_at).map { |e| e.name || e.email || e.phone }.uniq.join(', ')) %></p>
|
||||
<p><%= link_to submission_url(@submitter.submission), submission_url(@submitter.submission) %></p>
|
||||
<% end %>
|
||||
Whitespace-only changes.
@@ -51,7 +51,7 @@
|
||||
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
|
||||
<%= submitter.name || submitter.email || submitter.phone %>
|
||||
</a>
|
||||
<% if can?(:update, submitter) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<% if can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||
@@ -144,7 +144,7 @@
|
||||
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
|
||||
<%= submitter.name || submitter.email || submitter.phone %>
|
||||
</a>
|
||||
<% if can?(:update, submitter) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<% if can?(:update, submission) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<div class="h-36 relative group">
|
||||
<a href="<%= template_path(template) %>" class="flex h-full flex-col justify-between rounded-2xl pt-6 px-7 w-full bg-base-200 peer">
|
||||
<div class="pb-4 text-xl font-semibold" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<% if template.template_accesses.present? %>
|
||||
<%= svg_icon('lock', class: 'w-6 h-6 inline -translate-y-[4px]') %>
|
||||
<% end %>
|
||||
<% template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %>
|
||||
</div>
|
||||
<div class="pb-6 pt-1 space-y-1">
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<div class="flex flex-col items-start md:flex-row space-y-2 md:space-y-0 md:space-x-2 md:justify-between md:items-start mb-6 md:mb-3">
|
||||
<div class="relative flex items-start justify-between w-full space-x-0">
|
||||
<div>
|
||||
<h1 class="text-3xl md:text-[2em] font-semibold" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<% template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %>
|
||||
<% if template.archived_at? %>
|
||||
<span class="ml-1 badge badge-outline badge-lg align-middle"><%= t('archived') %></span>
|
||||
<% end %>
|
||||
</h1>
|
||||
<div id="template_title" class="flex">
|
||||
<h1 class="text-3xl md:text-[2em] font-semibold" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<% template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %>
|
||||
<% if template.archived_at? %>
|
||||
<span class="ml-1 badge badge-outline badge-lg align-middle"><%= t('archived') %></span>
|
||||
<% end %>
|
||||
</h1>
|
||||
<%= render 'templates/access_icon', template: @template %>
|
||||
</div>
|
||||
<% if @template.account_id == current_account.id %>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p class="text-gray-700">
|
||||
<%= t('unlock_with_docuseal_pro') %>
|
||||
<br>
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
|
||||
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}" %>" data-turbo="false">
|
||||
<%= t('learn_more') %>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Loaded 100 of 145 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user