mirror of
https://github.com/docusealco/docuseal.git
synced 2026-08-07 23:32:07 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa38773be3 | |||
| 96530bd809 | |||
| 127f95a9d0 | |||
| cf31e12348 | |||
| 4adaa531b7 | |||
| c7659563b8 | |||
| 7b5494468f | |||
| d12c67fd54 | |||
| e20eacb5ee | |||
| 98055f0762 | |||
| a46c42c3a0 | |||
| c3d1503361 | |||
| 65b47d27cd | |||
| 968c07f8be | |||
| 91814ca105 | |||
| 9128f24270 | |||
| 264897bbd5 | |||
| 67d59ff1ba | |||
| 81eee56e1e | |||
| 92ea8af786 | |||
| df0616f77e | |||
| a337dff85c | |||
| 48e349c1d2 | |||
| df9fa0fec7 |
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.4.2
|
||||
ruby-version: 4.0.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.4.2
|
||||
ruby-version: 4.0.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.9.0
|
||||
node-version: 20.19.0
|
||||
- name: Cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.4.2
|
||||
ruby-version: 4.0.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -132,11 +132,11 @@ jobs:
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.4.2
|
||||
ruby-version: 4.0.1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20.9.0
|
||||
node-version: 20.19.0
|
||||
- name: Install Chrome
|
||||
uses: browser-actions/setup-chrome@latest
|
||||
with:
|
||||
|
||||
+8
-2
@@ -1,4 +1,4 @@
|
||||
require:
|
||||
plugins:
|
||||
- rubocop-performance
|
||||
- rubocop-rails
|
||||
- rubocop-rspec
|
||||
@@ -10,7 +10,7 @@ AllCops:
|
||||
- node_modules/**/*
|
||||
- bin/*
|
||||
- vendor/**/*
|
||||
TargetRubyVersion: '3.3'
|
||||
TargetRubyVersion: '4.0'
|
||||
SuggestExtensions: false
|
||||
|
||||
Metrics/BlockLength:
|
||||
@@ -100,3 +100,9 @@ Rails/ApplicationController:
|
||||
|
||||
Rails/Output:
|
||||
Enabled: false
|
||||
|
||||
Rails/StrongParametersExpect:
|
||||
Enabled: false
|
||||
|
||||
Rails/RedirectBackOrTo:
|
||||
Enabled: false
|
||||
|
||||
+5
-7
@@ -1,4 +1,4 @@
|
||||
FROM ruby:3.4.2-alpine AS download
|
||||
FROM ruby:4.0.1-alpine AS download
|
||||
|
||||
WORKDIR /fonts
|
||||
|
||||
@@ -9,14 +9,14 @@ RUN apk --no-cache add fontforge wget && \
|
||||
wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && \
|
||||
wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && \
|
||||
wget https://github.com/impallari/DancingScript/raw/master/OFL.txt && \
|
||||
wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.1.0/model_704_int8.onnx" && \
|
||||
wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.0.0/model_704_int8.onnx" && \
|
||||
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
|
||||
mkdir -p /pdfium-linux && \
|
||||
tar -xzf pdfium-linux.tgz -C /pdfium-linux
|
||||
|
||||
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
||||
|
||||
FROM ruby:3.4.2-alpine AS webpack
|
||||
FROM ruby:4.0.1-alpine AS webpack
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV NODE_ENV=production
|
||||
@@ -42,7 +42,7 @@ COPY ./app/views ./app/views
|
||||
|
||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||
|
||||
FROM ruby:3.4.2-alpine AS app
|
||||
FROM ruby:4.0.1-alpine AS app
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV BUNDLE_WITHOUT="development:test"
|
||||
@@ -51,7 +51,7 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev yaml-dev redis libheif vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev yaml-dev redis libheif vips-heif gcompat ttf-freefont onnxruntime && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||
|
||||
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
||||
|
||||
@@ -71,8 +71,6 @@ COPY --chown=docuseal:docuseal ./Gemfile ./Gemfile.lock ./
|
||||
|
||||
RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf && ln -sf /usr/lib/libonnxruntime.so.1 $(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first")
|
||||
|
||||
RUN echo 'https://dl-cdn.alpinelinux.org/alpine/edge/main' >> /etc/apk/repositories && echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache onnxruntime
|
||||
|
||||
COPY --chown=docuseal:docuseal ./bin ./bin
|
||||
COPY --chown=docuseal:docuseal ./app ./app
|
||||
COPY --chown=docuseal:docuseal ./config ./config
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '3.4.2'
|
||||
ruby '4.0.1'
|
||||
|
||||
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
|
||||
gem 'arabic-letter-connector', require: false
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'aws-sdk-secretsmanager', require: false
|
||||
gem 'azure-storage-blob', require: false
|
||||
gem 'azure-blob', require: false
|
||||
gem 'bootsnap', require: false
|
||||
gem 'cancancan'
|
||||
gem 'csv'
|
||||
gem 'csv-safe'
|
||||
gem 'csv', require: false
|
||||
gem 'csv-safe', require: false
|
||||
gem 'devise'
|
||||
gem 'devise-two-factor'
|
||||
gem 'dotenv', require: false
|
||||
@@ -21,12 +21,12 @@ gem 'faraday-follow_redirects'
|
||||
gem 'google-cloud-storage', require: false
|
||||
gem 'hexapdf'
|
||||
gem 'image_processing'
|
||||
gem 'jwt'
|
||||
gem 'jwt', require: false
|
||||
gem 'lograge'
|
||||
gem 'mysql2', require: false
|
||||
gem 'numo-narray'
|
||||
gem 'numo-narray-alt', require: false
|
||||
gem 'oj'
|
||||
gem 'onnxruntime'
|
||||
gem 'onnxruntime', require: false
|
||||
gem 'pagy'
|
||||
gem 'pg', require: false
|
||||
gem 'premailer-rails'
|
||||
@@ -38,9 +38,9 @@ gem 'rails_autolink'
|
||||
gem 'rails-i18n'
|
||||
gem 'rotp'
|
||||
gem 'rouge', require: false
|
||||
gem 'rqrcode'
|
||||
gem 'rqrcode', require: false
|
||||
gem 'ruby-vips'
|
||||
gem 'rubyXL'
|
||||
gem 'rubyXL', require: false
|
||||
gem 'shakapacker'
|
||||
gem 'sidekiq'
|
||||
gem 'sqlite3', require: false
|
||||
|
||||
+279
-284
@@ -1,29 +1,31 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
action_text-trix (2.1.16)
|
||||
railties
|
||||
actioncable (8.1.2)
|
||||
actionpack (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activestorage (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
actionmailbox (8.1.2)
|
||||
actionpack (= 8.1.2)
|
||||
activejob (= 8.1.2)
|
||||
activerecord (= 8.1.2)
|
||||
activestorage (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
actionview (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
actionmailer (8.1.2)
|
||||
actionpack (= 8.1.2)
|
||||
actionview (= 8.1.2)
|
||||
activejob (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.0.2.1)
|
||||
actionview (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
actionpack (8.1.2)
|
||||
actionview (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
@@ -31,55 +33,58 @@ GEM
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activestorage (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
actiontext (8.1.2)
|
||||
action_text-trix (~> 2.1.15)
|
||||
actionpack (= 8.1.2)
|
||||
activerecord (= 8.1.2)
|
||||
activestorage (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
actionview (8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
activejob (8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
activerecord (8.0.2.1)
|
||||
activemodel (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
activemodel (8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
activerecord (8.1.2)
|
||||
activemodel (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
activestorage (8.1.2)
|
||||
actionpack (= 8.1.2)
|
||||
activejob (= 8.1.2)
|
||||
activerecord (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.0.2.1)
|
||||
activesupport (8.1.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
json
|
||||
logger (>= 1.4.2)
|
||||
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)
|
||||
annotaterb (4.14.0)
|
||||
addressable (2.8.8)
|
||||
public_suffix (>= 2.0.2, < 8.0)
|
||||
annotaterb (4.20.0)
|
||||
activerecord (>= 6.0.0)
|
||||
activesupport (>= 6.0.0)
|
||||
arabic-letter-connector (0.1.1)
|
||||
ast (2.4.3)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1197.0)
|
||||
aws-sdk-core (3.240.0)
|
||||
aws-partitions (1.1209.0)
|
||||
aws-sdk-core (3.241.4)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@@ -87,44 +92,38 @@ GEM
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.118.0)
|
||||
aws-sdk-core (~> 3, >= 3.239.1)
|
||||
aws-sdk-kms (1.121.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.208.0)
|
||||
aws-sdk-core (~> 3, >= 3.234.0)
|
||||
aws-sdk-s3 (1.212.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-secretsmanager (1.110.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sdk-secretsmanager (1.128.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
azure-storage-blob (2.0.3)
|
||||
azure-storage-common (~> 2.0)
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
azure-storage-common (2.0.4)
|
||||
faraday (~> 1.0)
|
||||
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
|
||||
net-http-persistent (~> 4.0)
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
azure-blob (0.8.0)
|
||||
cgi
|
||||
rexml
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.20)
|
||||
benchmark (0.4.1)
|
||||
better_html (2.1.1)
|
||||
actionview (>= 6.0)
|
||||
activesupport (>= 6.0)
|
||||
bcrypt (3.1.21)
|
||||
better_html (2.2.0)
|
||||
actionview (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
ast (~> 2.0)
|
||||
erubi (~> 1.4)
|
||||
parser (>= 2.4)
|
||||
smart_properties
|
||||
bigdecimal (3.2.2)
|
||||
bigdecimal (4.0.1)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.18.4)
|
||||
bootsnap (1.21.1)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (7.0.0)
|
||||
brakeman (7.1.2)
|
||||
racc
|
||||
builder (3.3.0)
|
||||
bullet (8.0.0)
|
||||
bullet (8.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
camertron-eprun (1.1.1)
|
||||
@@ -138,28 +137,29 @@ GEM
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
cgi (0.5.1)
|
||||
childprocess (5.1.0)
|
||||
logger (~> 1.5)
|
||||
chunky_png (1.4.0)
|
||||
cldr-plurals-runtime-rb (1.1.0)
|
||||
cmdparse (3.0.7)
|
||||
coderay (1.1.3)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.3)
|
||||
crack (1.0.0)
|
||||
concurrent-ruby (1.3.6)
|
||||
connection_pool (3.0.2)
|
||||
crack (1.0.1)
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
css_parser (1.21.0)
|
||||
css_parser (1.21.1)
|
||||
addressable
|
||||
csv (3.3.2)
|
||||
csv (3.3.5)
|
||||
csv-safe (3.3.1)
|
||||
csv (~> 3.0)
|
||||
cuprite (0.15.1)
|
||||
cuprite (0.17)
|
||||
capybara (~> 3.0)
|
||||
ferrum (~> 0.15.0)
|
||||
date (3.4.1)
|
||||
debug (1.10.0)
|
||||
ferrum (~> 0.17.0)
|
||||
date (3.5.1)
|
||||
debug (1.11.1)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
declarative (0.0.20)
|
||||
@@ -169,20 +169,20 @@ GEM
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-two-factor (6.1.0)
|
||||
activesupport (>= 7.0, < 8.1)
|
||||
devise (~> 4.0)
|
||||
railties (>= 7.0, < 8.1)
|
||||
devise-two-factor (6.3.1)
|
||||
activesupport (>= 7.0, < 8.2)
|
||||
devise (>= 4.0, < 5.0)
|
||||
railties (>= 7.0, < 8.2)
|
||||
rotp (~> 6.0)
|
||||
diff-lcs (1.5.1)
|
||||
digest-crc (0.6.5)
|
||||
diff-lcs (1.6.2)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
docile (1.4.1)
|
||||
dotenv (3.1.7)
|
||||
dotenv (3.2.0)
|
||||
drb (2.2.3)
|
||||
email_typo (0.2.3)
|
||||
erb (5.0.2)
|
||||
erb_lint (0.7.0)
|
||||
erb (6.0.1)
|
||||
erb_lint (0.9.0)
|
||||
activesupport
|
||||
better_html (>= 2.0.1)
|
||||
parser (>= 2.7.1.4)
|
||||
@@ -190,117 +190,101 @@ GEM
|
||||
rubocop (>= 1)
|
||||
smart_properties
|
||||
erubi (1.13.1)
|
||||
factory_bot (6.5.0)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.4.4)
|
||||
factory_bot (6.5.6)
|
||||
activesupport (>= 6.1.0)
|
||||
factory_bot_rails (6.5.1)
|
||||
factory_bot (~> 6.5)
|
||||
railties (>= 5.0.0)
|
||||
faker (3.5.1)
|
||||
railties (>= 6.1.0)
|
||||
faker (3.6.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-follow_redirects (0.3.0)
|
||||
faraday (2.14.0)
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday-follow_redirects (0.5.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-httpclient (1.0.1)
|
||||
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)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
ferrum (0.15)
|
||||
faraday-net_http (3.4.2)
|
||||
net-http (~> 0.5)
|
||||
ferrum (0.17.1)
|
||||
addressable (~> 2.5)
|
||||
base64 (~> 0.2)
|
||||
concurrent-ruby (~> 1.1)
|
||||
webrick (~> 1.7)
|
||||
websocket-driver (~> 0.7)
|
||||
ffi (1.17.1)
|
||||
ffi (1.17.1-aarch64-linux-gnu)
|
||||
ffi (1.17.1-aarch64-linux-musl)
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
ffi (1.17.1-x86_64-linux-gnu)
|
||||
ffi (1.17.1-x86_64-linux-musl)
|
||||
foreman (0.88.1)
|
||||
ffi (1.17.3)
|
||||
ffi (1.17.3-aarch64-linux-gnu)
|
||||
ffi (1.17.3-aarch64-linux-musl)
|
||||
ffi (1.17.3-arm64-darwin)
|
||||
ffi (1.17.3-x86_64-linux-gnu)
|
||||
ffi (1.17.3-x86_64-linux-musl)
|
||||
foreman (0.90.0)
|
||||
thor (~> 1.4)
|
||||
geom2d (0.4.1)
|
||||
globalid (1.2.1)
|
||||
globalid (1.3.0)
|
||||
activesupport (>= 6.1)
|
||||
google-apis-core (0.15.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 1.9)
|
||||
httpclient (>= 2.8.3, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
mutex_m
|
||||
google-apis-core (1.0.2)
|
||||
addressable (~> 2.8, >= 2.8.7)
|
||||
faraday (~> 2.13)
|
||||
faraday-follow_redirects (~> 0.3)
|
||||
googleauth (~> 1.14)
|
||||
mini_mime (~> 1.1)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
google-apis-iamcredentials_v1 (0.22.0)
|
||||
retriable (~> 3.1)
|
||||
google-apis-iamcredentials_v1 (0.26.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-storage_v1 (0.49.0)
|
||||
google-apis-storage_v1 (0.59.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-core (1.7.1)
|
||||
google-cloud-core (1.8.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (2.2.1)
|
||||
google-cloud-env (2.3.1)
|
||||
base64 (~> 0.2)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-storage (1.54.0)
|
||||
google-cloud-errors (1.5.0)
|
||||
google-cloud-storage (1.58.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-core (~> 0.13)
|
||||
google-apis-core (>= 0.18, < 2)
|
||||
google-apis-iamcredentials_v1 (~> 0.18)
|
||||
google-apis-storage_v1 (~> 0.38)
|
||||
google-apis-storage_v1 (>= 0.42)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (~> 1.9)
|
||||
mini_mime (~> 1.0)
|
||||
google-logging-utils (0.1.0)
|
||||
googleauth (1.12.2)
|
||||
google-logging-utils (0.2.0)
|
||||
googleauth (1.16.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.2)
|
||||
google-logging-utils (~> 0.1)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
jwt (>= 1.4, < 4.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
hashdiff (1.1.2)
|
||||
hexapdf (1.4.0)
|
||||
hashdiff (1.2.1)
|
||||
hexapdf (1.5.0)
|
||||
cmdparse (~> 3.0, >= 3.0.3)
|
||||
geom2d (~> 0.4, >= 0.4.1)
|
||||
openssl (>= 2.2.1)
|
||||
strscan (>= 3.1.2)
|
||||
htmlentities (4.3.4)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.7)
|
||||
htmlentities (4.4.2)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_processing (1.13.0)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
image_processing (1.14.0)
|
||||
mini_magick (>= 4.9.5, < 6)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
io-console (0.8.1)
|
||||
irb (1.15.2)
|
||||
io-console (0.8.2)
|
||||
irb (1.16.0)
|
||||
pp (>= 0.6.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jmespath (1.6.2)
|
||||
json (2.15.0)
|
||||
jwt (2.9.3)
|
||||
json (2.18.0)
|
||||
jwt (3.1.2)
|
||||
base64
|
||||
language_server-protocol (3.17.0.5)
|
||||
launchy (3.0.1)
|
||||
launchy (3.1.1)
|
||||
addressable (~> 2.8)
|
||||
childprocess (~> 5.0)
|
||||
logger (~> 1.6)
|
||||
letter_opener (1.10.0)
|
||||
launchy (>= 2.2, < 4)
|
||||
letter_opener_web (3.0.0)
|
||||
@@ -315,29 +299,30 @@ GEM
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.24.1)
|
||||
loofah (2.25.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
mail (2.9.0)
|
||||
logger
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.4)
|
||||
matrix (0.4.2)
|
||||
marcel (1.1.0)
|
||||
matrix (0.4.3)
|
||||
method_source (1.1.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_magick (5.3.1)
|
||||
logger
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.9)
|
||||
minitest (5.25.5)
|
||||
msgpack (1.7.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
mysql2 (0.5.6)
|
||||
net-http-persistent (4.0.5)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.5.9)
|
||||
minitest (6.0.1)
|
||||
prism (~> 1.5)
|
||||
msgpack (1.8.0)
|
||||
multi_json (1.19.1)
|
||||
mysql2 (0.5.7)
|
||||
bigdecimal
|
||||
net-http (0.9.1)
|
||||
uri (>= 0.11.1)
|
||||
net-imap (0.6.2)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -346,22 +331,19 @@ GEM
|
||||
timeout
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.9)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
nio4r (2.7.5)
|
||||
nokogiri (1.19.0-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-aarch64-linux-gnu)
|
||||
nokogiri (1.19.0-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-aarch64-linux-musl)
|
||||
nokogiri (1.19.0-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-arm64-darwin)
|
||||
nokogiri (1.19.0-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-linux-gnu)
|
||||
nokogiri (1.19.0-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
numo-narray (0.9.2.1)
|
||||
oj (3.16.11)
|
||||
numo-narray-alt (0.9.13)
|
||||
oj (3.16.13)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
onnxruntime (0.10.1)
|
||||
@@ -372,18 +354,25 @@ GEM
|
||||
ffi
|
||||
onnxruntime (0.10.1-x86_64-linux)
|
||||
ffi
|
||||
openssl (3.3.0)
|
||||
openssl (4.0.0)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.1.4)
|
||||
ostruct (0.6.3)
|
||||
package_json (0.1.0)
|
||||
pagy (9.3.3)
|
||||
package_json (0.2.0)
|
||||
pagy (43.2.8)
|
||||
json
|
||||
yaml
|
||||
parallel (1.27.0)
|
||||
parser (3.3.9.0)
|
||||
parser (3.3.10.1)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pg (1.5.9)
|
||||
pp (0.6.2)
|
||||
pg (1.6.3)
|
||||
pg (1.6.3-aarch64-linux)
|
||||
pg (1.6.3-aarch64-linux-musl)
|
||||
pg (1.6.3-arm64-darwin)
|
||||
pg (1.6.3-x86_64-linux)
|
||||
pg (1.6.3-x86_64-linux-musl)
|
||||
pp (0.6.3)
|
||||
prettyprint
|
||||
premailer (1.27.0)
|
||||
addressable
|
||||
@@ -393,23 +382,24 @@ GEM
|
||||
actionmailer (>= 3)
|
||||
net-smtp
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
pretender (0.5.0)
|
||||
actionpack (>= 6.1)
|
||||
pretender (0.6.0)
|
||||
actionpack (>= 7.1)
|
||||
prettyprint (0.2.0)
|
||||
prism (1.5.1)
|
||||
pry (0.15.0)
|
||||
prism (1.8.0)
|
||||
pry (0.16.0)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
reline (>= 0.6.0)
|
||||
pry-rails (0.3.11)
|
||||
pry (>= 0.13.0)
|
||||
psych (5.2.6)
|
||||
psych (5.3.1)
|
||||
date
|
||||
stringio
|
||||
public_suffix (6.0.1)
|
||||
puma (6.5.0)
|
||||
public_suffix (7.0.2)
|
||||
puma (7.2.0)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (3.2.3)
|
||||
rack (3.2.4)
|
||||
rack-proxy (0.7.7)
|
||||
rack
|
||||
rack-session (2.1.1)
|
||||
@@ -417,22 +407,22 @@ GEM
|
||||
rack (>= 3.0.0)
|
||||
rack-test (2.2.0)
|
||||
rack (>= 1.3)
|
||||
rackup (2.2.1)
|
||||
rackup (2.3.1)
|
||||
rack (>= 3)
|
||||
rails (8.0.2.1)
|
||||
actioncable (= 8.0.2.1)
|
||||
actionmailbox (= 8.0.2.1)
|
||||
actionmailer (= 8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
actiontext (= 8.0.2.1)
|
||||
actionview (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activemodel (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activestorage (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
rails (8.1.2)
|
||||
actioncable (= 8.1.2)
|
||||
actionmailbox (= 8.1.2)
|
||||
actionmailer (= 8.1.2)
|
||||
actionpack (= 8.1.2)
|
||||
actiontext (= 8.1.2)
|
||||
actionview (= 8.1.2)
|
||||
activejob (= 8.1.2)
|
||||
activemodel (= 8.1.2)
|
||||
activerecord (= 8.1.2)
|
||||
activestorage (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.0.2.1)
|
||||
railties (= 8.1.2)
|
||||
rails-dom-testing (2.3.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
@@ -440,30 +430,32 @@ GEM
|
||||
rails-html-sanitizer (1.6.2)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||
rails-i18n (8.0.1)
|
||||
rails-i18n (8.1.0)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 8.0.0, < 9)
|
||||
rails_autolink (1.1.8)
|
||||
actionview (> 3.1)
|
||||
activesupport (> 3.1)
|
||||
railties (> 3.1)
|
||||
railties (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
railties (8.1.2)
|
||||
actionpack (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
tsort (>= 0.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.3.0)
|
||||
rdoc (6.14.2)
|
||||
rake (13.3.1)
|
||||
rdoc (7.1.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
redis-client (0.23.0)
|
||||
tsort
|
||||
redis-client (0.26.4)
|
||||
connection_pool
|
||||
regexp_parser (2.11.3)
|
||||
reline (0.6.2)
|
||||
reline (0.6.3)
|
||||
io-console (~> 0.5)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
@@ -471,35 +463,35 @@ GEM
|
||||
uber (< 0.2.0)
|
||||
request_store (1.7.0)
|
||||
rack (>= 1.4)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
responders (3.2.0)
|
||||
actionpack (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.4.4)
|
||||
rotp (6.3.0)
|
||||
rouge (4.5.2)
|
||||
rqrcode (2.2.0)
|
||||
rouge (4.7.0)
|
||||
rqrcode (3.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec-core (3.13.2)
|
||||
rqrcode_core (~> 2.0)
|
||||
rqrcode_core (2.1.0)
|
||||
rspec-core (3.13.6)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.3)
|
||||
rspec-expectations (3.13.5)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-mocks (3.13.2)
|
||||
rspec-mocks (3.13.7)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (7.1.0)
|
||||
actionpack (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
rspec-rails (8.0.2)
|
||||
actionpack (>= 7.2)
|
||||
activesupport (>= 7.2)
|
||||
railties (>= 7.2)
|
||||
rspec-core (~> 3.13)
|
||||
rspec-expectations (~> 3.13)
|
||||
rspec-mocks (~> 3.13)
|
||||
rspec-support (~> 3.13)
|
||||
rspec-support (3.13.2)
|
||||
rubocop (1.81.1)
|
||||
rspec-support (3.13.6)
|
||||
rubocop (1.82.1)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
@@ -507,86 +499,89 @@ GEM
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.47.1, < 2.0)
|
||||
rubocop-ast (>= 1.48.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.47.1)
|
||||
rubocop-ast (1.49.0)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.4)
|
||||
rubocop-performance (1.23.0)
|
||||
rubocop (>= 1.48.1, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rails (2.27.0)
|
||||
prism (~> 1.7)
|
||||
rubocop-performance (1.26.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.75.0, < 2.0)
|
||||
rubocop-ast (>= 1.47.1, < 2.0)
|
||||
rubocop-rails (2.34.3)
|
||||
activesupport (>= 4.2.0)
|
||||
lint_roller (~> 1.1)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.52.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (3.3.0)
|
||||
rubocop (~> 1.61)
|
||||
rubocop (>= 1.75.0, < 2.0)
|
||||
rubocop-ast (>= 1.44.0, < 2.0)
|
||||
rubocop-rspec (3.9.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.81)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-vips (2.2.2)
|
||||
ruby-vips (2.3.0)
|
||||
ffi (~> 1.12)
|
||||
logger
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyXL (3.4.33)
|
||||
rubyXL (3.4.35)
|
||||
nokogiri (>= 1.10.8)
|
||||
rubyzip (>= 1.3.0)
|
||||
rubyzip (2.3.2)
|
||||
rubyzip (>= 3.2.2)
|
||||
rubyzip (3.2.2)
|
||||
securerandom (0.4.1)
|
||||
semantic_range (3.1.0)
|
||||
shakapacker (8.0.2)
|
||||
shakapacker (9.5.0)
|
||||
activesupport (>= 5.2)
|
||||
package_json
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
sidekiq (7.3.7)
|
||||
connection_pool (>= 2.3.0)
|
||||
logger
|
||||
rack (>= 2.2.4)
|
||||
redis-client (>= 0.22.2)
|
||||
signet (0.19.0)
|
||||
sidekiq (8.1.0)
|
||||
connection_pool (>= 3.0.0)
|
||||
json (>= 2.16.0)
|
||||
logger (>= 1.7.0)
|
||||
rack (>= 3.2.0)
|
||||
redis-client (>= 0.26.0)
|
||||
signet (0.21.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
jwt (>= 1.5, < 4.0)
|
||||
multi_json (~> 1.10)
|
||||
simplecov (0.22.0)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.13.1)
|
||||
simplecov-html (0.13.2)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
smart_properties (1.17.0)
|
||||
sqlite3 (2.5.0)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
sqlite3 (2.5.0-aarch64-linux-gnu)
|
||||
sqlite3 (2.5.0-aarch64-linux-musl)
|
||||
sqlite3 (2.5.0-arm64-darwin)
|
||||
sqlite3 (2.5.0-x86_64-linux-gnu)
|
||||
sqlite3 (2.5.0-x86_64-linux-musl)
|
||||
stringio (3.1.7)
|
||||
strip_attributes (1.14.1)
|
||||
sqlite3 (2.9.0-aarch64-linux-gnu)
|
||||
sqlite3 (2.9.0-aarch64-linux-musl)
|
||||
sqlite3 (2.9.0-arm64-darwin)
|
||||
sqlite3 (2.9.0-x86_64-linux-gnu)
|
||||
sqlite3 (2.9.0-x86_64-linux-musl)
|
||||
stringio (3.2.0)
|
||||
strip_attributes (2.0.1)
|
||||
activemodel (>= 3.0, < 9.0)
|
||||
strscan (3.1.5)
|
||||
thor (1.4.0)
|
||||
timeout (0.4.3)
|
||||
strscan (3.1.7)
|
||||
thor (1.5.0)
|
||||
timeout (0.6.0)
|
||||
trailblazer-option (0.1.2)
|
||||
turbo-rails (2.0.11)
|
||||
actionpack (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
twitter_cldr (6.12.1)
|
||||
tsort (0.2.0)
|
||||
turbo-rails (2.0.21)
|
||||
actionpack (>= 7.1.0)
|
||||
railties (>= 7.1.0)
|
||||
twitter_cldr (6.14.0)
|
||||
base64
|
||||
camertron-eprun
|
||||
cldr-plurals-runtime-rb (~> 1.1)
|
||||
tzinfo
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2024.2)
|
||||
tzinfo-data (1.2025.3)
|
||||
tzinfo (>= 1.0.0)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (3.2.0)
|
||||
unicode-emoji (~> 4.1)
|
||||
unicode-emoji (4.1.0)
|
||||
uniform_notifier (1.16.0)
|
||||
unicode-emoji (4.2.0)
|
||||
uniform_notifier (1.18.0)
|
||||
uri (1.1.1)
|
||||
useragent (0.16.11)
|
||||
warden (1.2.9)
|
||||
@@ -596,24 +591,24 @@ GEM
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webmock (3.24.0)
|
||||
webmock (3.26.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.9.1)
|
||||
webrick (1.9.2)
|
||||
websocket-driver (0.8.0)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.7.3)
|
||||
yaml (0.4.0)
|
||||
zeitwerk (2.7.4)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
aarch64-linux-musl
|
||||
arm64-darwin
|
||||
ruby
|
||||
x86_64-linux
|
||||
x86_64-linux-musl
|
||||
|
||||
@@ -622,7 +617,7 @@ DEPENDENCIES
|
||||
arabic-letter-connector
|
||||
aws-sdk-s3
|
||||
aws-sdk-secretsmanager
|
||||
azure-storage-blob
|
||||
azure-blob
|
||||
better_html
|
||||
bootsnap
|
||||
brakeman
|
||||
@@ -650,7 +645,7 @@ DEPENDENCIES
|
||||
letter_opener_web
|
||||
lograge
|
||||
mysql2
|
||||
numo-narray
|
||||
numo-narray-alt
|
||||
oj
|
||||
onnxruntime
|
||||
pagy
|
||||
@@ -685,7 +680,7 @@ DEPENDENCIES
|
||||
webmock
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.4.2p28
|
||||
ruby 4.0.1
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.3
|
||||
4.0.3
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountCustomFieldsController < ApplicationController
|
||||
before_action :load_account_config, only: :create
|
||||
|
||||
def create
|
||||
authorize!(:create, Template)
|
||||
|
||||
@account_config.update!(account_config_params)
|
||||
|
||||
render json: @account_config.value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_account_config
|
||||
@account_config =
|
||||
AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY)
|
||||
end
|
||||
|
||||
def account_config_params
|
||||
params.permit(
|
||||
value: [[:uuid, :name, :type,
|
||||
:required, :readonly, :default_value,
|
||||
:title, :description,
|
||||
{ preferences: {},
|
||||
default_value: [],
|
||||
options: [%i[value uuid]],
|
||||
validation: %i[message pattern min max step],
|
||||
areas: [%i[x y w h cell_w option_uuid]] }]]
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@
|
||||
module Api
|
||||
class ApiBaseController < ActionController::API
|
||||
include ActiveStorage::SetCurrent
|
||||
include Pagy::Backend
|
||||
include Pagy::Method
|
||||
|
||||
DEFAULT_LIMIT = 10
|
||||
MAX_LIMIT = 100
|
||||
|
||||
@@ -4,7 +4,7 @@ class ApplicationController < ActionController::Base
|
||||
BROWSER_LOCALE_REGEXP = /\A\w{2}(?:-\w{2})?/
|
||||
|
||||
include ActiveStorage::SetCurrent
|
||||
include Pagy::Backend
|
||||
include Pagy::Method
|
||||
|
||||
check_authorization unless: :devise_controller?
|
||||
|
||||
@@ -23,7 +23,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
|
||||
|
||||
rescue_from Pagy::OverflowError do
|
||||
rescue_from Pagy::RangeError do
|
||||
redirect_to request.path
|
||||
end
|
||||
|
||||
@@ -60,7 +60,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
def pagy_auto(collection, **keyword_args)
|
||||
if current_ability.can?(:manage, :countless)
|
||||
pagy_countless(collection, **keyword_args)
|
||||
pagy(:countless, collection, **keyword_args)
|
||||
else
|
||||
pagy(collection, **keyword_args)
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ class TemplatesDetectFieldsController < ApplicationController
|
||||
documents = @template.schema_documents.preload(:blob)
|
||||
documents = documents.where(uuid: params[:attachment_uuid]) if params[:attachment_uuid].present?
|
||||
|
||||
page_number = params[:page].present? ? params[:page].to_i : nil
|
||||
page_number = params[:page].presence&.to_i
|
||||
|
||||
documents.each do |document|
|
||||
io = StringIO.new(document.download)
|
||||
|
||||
@@ -32,7 +32,7 @@ class TimestampServerController < ApplicationController
|
||||
uri = Addressable::URI.parse(url)
|
||||
|
||||
conn = Faraday.new(uri.origin) do |c|
|
||||
c.basic_auth(uri.user, uri.password) if uri.password.present?
|
||||
c.request :authorization, :basic, uri.user, uri.password if uri.password.present?
|
||||
end
|
||||
|
||||
response = conn.post(uri.path, req.to_der,
|
||||
|
||||
@@ -15,7 +15,7 @@ class WebhookSettingsController < ApplicationController
|
||||
|
||||
@webhook_events = @webhook_events.where(status: params[:status]) if %w[success error].include?(params[:status])
|
||||
|
||||
@pagy, @webhook_events = pagy_countless(@webhook_events.order(id: :desc))
|
||||
@pagy, @webhook_events = pagy(:countless, @webhook_events.order(id: :desc))
|
||||
|
||||
render :show
|
||||
end
|
||||
@@ -26,7 +26,7 @@ class WebhookSettingsController < ApplicationController
|
||||
|
||||
@webhook_events = @webhook_events.where(status: params[:status]) if %w[success error].include?(params[:status])
|
||||
|
||||
@pagy, @webhook_events = pagy_countless(@webhook_events.order(id: :desc))
|
||||
@pagy, @webhook_events = pagy(:countless, @webhook_events.order(id: :desc))
|
||||
end
|
||||
|
||||
def new; end
|
||||
|
||||
@@ -155,6 +155,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
|
||||
this.app = createApp(TemplateBuilder, {
|
||||
template: reactive(JSON.parse(this.dataset.template)),
|
||||
customFields: reactive(JSON.parse(this.dataset.customFields || '[]')),
|
||||
backgroundColor: '#faf7f5',
|
||||
locale: this.dataset.locale,
|
||||
withPhone: this.dataset.withPhone === 'true',
|
||||
@@ -164,6 +165,7 @@ safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
withFieldsDetection: this.dataset.withFieldsDetection === 'true',
|
||||
editable: this.dataset.editable !== 'false',
|
||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||
withCustomFields: true,
|
||||
withPayment: this.dataset.withPayment === 'true',
|
||||
isPaymentConnected: this.dataset.isPaymentConnected === 'true',
|
||||
withFormula: this.dataset.withFormula === 'true',
|
||||
|
||||
+51
-6
@@ -4,15 +4,26 @@ import { isValidSignatureCanvas } from './submission_form/validate_signature'
|
||||
|
||||
window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
const scale = 3
|
||||
|
||||
this.canvas.width = this.canvas.parentNode.clientWidth * scale
|
||||
this.canvas.height = this.canvas.parentNode.clientHeight * scale
|
||||
|
||||
this.canvas.getContext('2d').scale(scale, scale)
|
||||
this.setCanvasSize()
|
||||
|
||||
this.pad = new SignaturePad(this.canvas)
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (!this.canvas) return
|
||||
|
||||
const { width, height } = this.canvas
|
||||
|
||||
this.setCanvasSize()
|
||||
|
||||
if (this.canvas.width !== width || this.canvas.height !== height) {
|
||||
this.redrawCanvas(width, height)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.resizeObserver.observe(this.canvas.parentNode)
|
||||
|
||||
if (this.dataset.color) {
|
||||
this.pad.penColor = this.dataset.color
|
||||
}
|
||||
@@ -57,6 +68,40 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
this.updateSubmitButtonVisibility()
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
setCanvasSize () {
|
||||
const scale = 3
|
||||
|
||||
const width = this.canvas.parentNode.clientWidth
|
||||
const height = this.canvas.parentNode.clientHeight
|
||||
|
||||
if (this.canvas.width !== width * scale || this.canvas.height !== height * scale) {
|
||||
this.canvas.width = width * scale
|
||||
this.canvas.height = height * scale
|
||||
|
||||
this.canvas.getContext('2d').scale(scale, scale)
|
||||
}
|
||||
}
|
||||
|
||||
redrawCanvas (oldWidth, oldHeight) {
|
||||
if (this.pad && !this.pad.isEmpty()) {
|
||||
const sx = this.canvas.width / oldWidth
|
||||
const sy = this.canvas.height / oldHeight
|
||||
|
||||
const scaledData = this.pad.toData().map((stroke) => ({
|
||||
...stroke,
|
||||
points: stroke.points.map((p) => ({ ...p, x: p.x * sx, y: p.y * sy }))
|
||||
}))
|
||||
|
||||
this.pad.fromData(scaledData)
|
||||
}
|
||||
}
|
||||
|
||||
updateSubmitButtonVisibility () {
|
||||
if (this.pad.isEmpty()) {
|
||||
this.submitButton.style.display = 'none'
|
||||
|
||||
@@ -5,17 +5,28 @@ export default targetable(class extends HTMLElement {
|
||||
static [target.static] = ['canvas', 'input', 'clear', 'button']
|
||||
|
||||
async connectedCallback () {
|
||||
const scale = 3
|
||||
|
||||
this.canvas.width = this.canvas.parentNode.clientWidth * scale
|
||||
this.canvas.height = this.canvas.parentNode.clientHeight * scale
|
||||
|
||||
this.canvas.getContext('2d').scale(scale, scale)
|
||||
|
||||
const { default: SignaturePad } = await import('signature_pad')
|
||||
|
||||
this.setCanvasSize()
|
||||
|
||||
this.pad = new SignaturePad(this.canvas)
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (!this.canvas) return
|
||||
|
||||
const { width, height } = this.canvas
|
||||
|
||||
this.setCanvasSize()
|
||||
|
||||
if (this.canvas.width !== width || this.canvas.height !== height) {
|
||||
this.redrawCanvas(width, height)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.resizeObserver.observe(this.canvas.parentNode)
|
||||
|
||||
this.clear.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -47,4 +58,38 @@ export default targetable(class extends HTMLElement {
|
||||
|
||||
this.closest('form').requestSubmit()
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
setCanvasSize () {
|
||||
const scale = 3
|
||||
|
||||
const width = this.canvas.parentNode.clientWidth
|
||||
const height = this.canvas.parentNode.clientWidth / 2.5
|
||||
|
||||
if (this.canvas.width !== width * scale || this.canvas.height !== height * scale) {
|
||||
this.canvas.width = width * scale
|
||||
this.canvas.height = height * scale
|
||||
|
||||
this.canvas.getContext('2d').scale(scale, scale)
|
||||
}
|
||||
}
|
||||
|
||||
redrawCanvas (oldWidth, oldHeight) {
|
||||
if (this.pad && !this.pad.isEmpty()) {
|
||||
const sx = this.canvas.width / oldWidth
|
||||
const sy = this.canvas.height / oldHeight
|
||||
|
||||
const scaledData = this.pad.toData().map((stroke) => ({
|
||||
...stroke,
|
||||
points: stroke.points.map((p) => ({ ...p, x: p.x * sx, y: p.y * sy }))
|
||||
}))
|
||||
|
||||
this.pad.fromData(scaledData)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
id="expand_form_button"
|
||||
class="btn btn-neutral flex text-white absolute bottom-0 w-full mb-3 expand-form-button text-base"
|
||||
style="width: 96%; margin-left: 2%"
|
||||
@click.prevent="[isFormVisible = true, scrollIntoField(currentField)]"
|
||||
@click.prevent="[isFormVisible = true, $nextTick(() => scrollIntoField(currentField))]"
|
||||
>
|
||||
<template v-if="['initials', 'signature'].includes(currentField.type)">
|
||||
<IconWritingSign stroke-width="1.5" />
|
||||
@@ -592,6 +592,18 @@ import AppearsOn from './appears_on'
|
||||
import i18n from './i18n'
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url'
|
||||
|
||||
if (typeof URL.canParse !== 'function') {
|
||||
URL.canParse = function (url, base) {
|
||||
try {
|
||||
const parsed = new URL(url, base)
|
||||
|
||||
return !!parsed
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isEmpty = (obj) => {
|
||||
if (obj == null) return true
|
||||
|
||||
|
||||
@@ -175,9 +175,7 @@
|
||||
v-if="isShowQr"
|
||||
class="top-0 bottom-0 right-0 left-0 absolute bg-base-content/10 rounded-2xl"
|
||||
>
|
||||
<div
|
||||
class="absolute top-1.5 right-1.5 md:tooltip"
|
||||
>
|
||||
<div class="absolute top-1.5 right-1.5">
|
||||
<a
|
||||
href="#"
|
||||
class="btn btn-sm btn-circle btn-normal btn-outline"
|
||||
@@ -448,14 +446,7 @@ export default {
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.canvas) {
|
||||
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth * scale
|
||||
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth * scale / 3
|
||||
|
||||
this.$refs.canvas.getContext('2d').scale(scale, scale)
|
||||
}
|
||||
})
|
||||
this.$nextTick(() => this.setCanvasSize())
|
||||
|
||||
if (this.$refs.canvas) {
|
||||
this.pad = new SignaturePad(this.$refs.canvas)
|
||||
@@ -470,13 +461,10 @@ export default {
|
||||
this.$emit('start')
|
||||
})
|
||||
|
||||
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
|
||||
this.intersectionObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
this.$refs.canvas.width = this.$refs.canvas.parentNode.clientWidth * scale
|
||||
this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth * scale / 3
|
||||
|
||||
this.$refs.canvas.getContext('2d').scale(scale, scale)
|
||||
this.setCanvasSize()
|
||||
|
||||
this.intersectionObserver?.disconnect()
|
||||
}
|
||||
@@ -484,13 +472,62 @@ export default {
|
||||
})
|
||||
|
||||
this.intersectionObserver.observe(this.$refs.canvas)
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (!this.$refs.canvas) return
|
||||
|
||||
const { width, height } = this.$refs.canvas
|
||||
|
||||
this.setCanvasSize()
|
||||
|
||||
if (this.$refs.canvas.width !== width || this.$refs.canvas.height !== height) {
|
||||
this.redrawCanvas(width, height)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.resizeObserver.observe(this.$refs.canvas.parentNode)
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
this.intersectionObserver?.disconnect()
|
||||
this.resizeObserver?.disconnect()
|
||||
this.stopCheckSignature()
|
||||
},
|
||||
methods: {
|
||||
setCanvasSize () {
|
||||
const canvas = this.$refs.canvas
|
||||
|
||||
if (canvas) {
|
||||
const width = canvas.parentNode.clientWidth
|
||||
const height = width / 3
|
||||
|
||||
if (canvas.width !== width * scale || canvas.height !== height * scale) {
|
||||
canvas.width = width * scale
|
||||
canvas.height = height * scale
|
||||
|
||||
canvas.getContext('2d').scale(scale, scale)
|
||||
}
|
||||
}
|
||||
},
|
||||
redrawCanvas (oldWidth, oldHeight) {
|
||||
const canvas = this.$refs.canvas
|
||||
|
||||
if (this.pad && !this.isTextSignature && !this.pad.isEmpty()) {
|
||||
const sx = canvas.width / oldWidth
|
||||
const sy = canvas.height / oldHeight
|
||||
|
||||
const scaledData = this.pad.toData().map((stroke) => ({
|
||||
...stroke,
|
||||
points: stroke.points.map((p) => ({ ...p, x: p.x * sx, y: p.y * sy }))
|
||||
}))
|
||||
|
||||
this.pad.fromData(scaledData)
|
||||
} else if (this.isTextSignature && this.$refs.textInput) {
|
||||
this.updateWrittenSignature({ target: { value: this.$refs.textInput.value } })
|
||||
}
|
||||
},
|
||||
remove () {
|
||||
this.$emit('update:model-value', '')
|
||||
|
||||
|
||||
@@ -145,7 +145,9 @@
|
||||
@click-formula="isShowFormulaModal = true"
|
||||
@click-font="isShowFontModal = true"
|
||||
@click-description="isShowDescriptionModal = true"
|
||||
@add-custom-field="$emit('add-custom-field')"
|
||||
@click-condition="isShowConditionsModal = true"
|
||||
@save="save"
|
||||
@scroll-to="[selectedAreasRef.value = [$event], $emit('scroll-to', $event)]"
|
||||
/>
|
||||
</ul>
|
||||
@@ -471,7 +473,7 @@ export default {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to'],
|
||||
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'scroll-to', 'add-custom-field'],
|
||||
data () {
|
||||
return {
|
||||
isShowFormulaModal: false,
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
/>
|
||||
<DragPlaceholder
|
||||
ref="dragPlaceholder"
|
||||
:field="fieldsDragFieldRef.value || toRaw(dragField)"
|
||||
:field="customDragFieldRef.value || fieldsDragFieldRef.value || toRaw(dragField)"
|
||||
:is-field="template.fields.includes(fieldsDragFieldRef.value)"
|
||||
:is-custom="!!customDragFieldRef.value"
|
||||
:is-default="defaultFields.includes(toRaw(dragField))"
|
||||
:is-required="defaultRequiredFields.includes(toRaw(dragField))"
|
||||
/>
|
||||
@@ -362,7 +363,7 @@
|
||||
:is-drag="!!dragField"
|
||||
:input-mode="inputMode"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:allow-draw="!onlyDefinedFields || drawField"
|
||||
:allow-draw="!onlyDefinedFields || drawField || drawCustomField"
|
||||
:with-signature-id="withSignatureId"
|
||||
:with-prefillable="withPrefillable"
|
||||
:data-document-uuid="document.uuid"
|
||||
@@ -371,14 +372,16 @@
|
||||
:with-field-placeholder="withFieldPlaceholder"
|
||||
:draw-field="drawField"
|
||||
:draw-field-type="drawFieldType"
|
||||
:draw-custom-field="drawCustomField"
|
||||
:editable="editable"
|
||||
:base-url="baseUrl"
|
||||
:with-fields-detection="withFieldsDetection"
|
||||
@draw="[onDraw($event), withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]"
|
||||
@draw="[onDraw($event), withSelectedFieldType ? '' : drawFieldType = '', drawCustomField = null, showDrawField = false]"
|
||||
@drop-field="onDropfield"
|
||||
@remove-area="removeArea"
|
||||
@paste-field="pasteField"
|
||||
@copy-field="copyField"
|
||||
@add-custom-field="addCustomField"
|
||||
@copy-selected-areas="copySelectedAreas"
|
||||
@delete-selected-areas="deleteSelectedAreas"
|
||||
@align-selected-areas="alignSelectedAreas"
|
||||
@@ -436,15 +439,15 @@
|
||||
v-if="withFieldsList && !isMobile"
|
||||
id="fields_list_container"
|
||||
class="relative w-80 flex-none mt-1 pr-4 pl-0.5 hidden md:block fields-list-container"
|
||||
:class="drawField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
||||
:class="drawField || drawCustomField ? 'overflow-hidden' : 'overflow-y-auto overflow-x-hidden'"
|
||||
>
|
||||
<div
|
||||
v-if="showDrawField || drawField"
|
||||
v-if="showDrawField || drawField || drawCustomField"
|
||||
class="sticky inset-0 h-full z-20"
|
||||
:style="{ backgroundColor }"
|
||||
>
|
||||
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4 draw-field-container">
|
||||
<p v-if="(drawField?.type || drawFieldType) === 'strikethrough'">
|
||||
<p v-if="(drawField?.type || drawFieldType || drawCustomField?.type) === 'strikethrough'">
|
||||
{{ t('draw_strikethrough_the_document') }}
|
||||
</p>
|
||||
<p v-else>
|
||||
@@ -458,10 +461,10 @@
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<a
|
||||
v-if="!drawField && !drawOption && !['stamp', 'signature', 'initials', 'heading', 'strikethrough'].includes(drawField?.type || drawFieldType)"
|
||||
v-if="!drawField && !drawOption && !['stamp', 'signature', 'initials', 'heading', 'strikethrough'].includes(drawField?.type || drawFieldType || drawCustomField?.type)"
|
||||
href="#"
|
||||
class="link block mt-3 text-sm"
|
||||
@click.prevent="[addField(drawFieldType), drawField = null, drawOption = null, withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]"
|
||||
@click.prevent="drawCustomField ? addCustomFieldWithoutDraw() : [addField(drawFieldType), drawField = null, drawOption = null, withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]"
|
||||
>
|
||||
{{ t('or_add_field_without_drawing') }}
|
||||
</a>
|
||||
@@ -477,6 +480,8 @@
|
||||
:with-help="withHelp"
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field-type="drawFieldType"
|
||||
:custom-fields="customFields"
|
||||
:with-custom-fields="withCustomFields"
|
||||
:with-fields-search="withFieldsSearch"
|
||||
:default-fields="[...defaultRequiredFields, ...defaultFields]"
|
||||
:template="template"
|
||||
@@ -493,6 +498,7 @@
|
||||
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
||||
@select-submitter="selectedSubmitter = $event"
|
||||
@set-draw-type="[drawFieldType = $event, showDrawField = true]"
|
||||
@set-draw-custom-field="[drawCustomField = $event, showDrawField = true]"
|
||||
@set-drag="dragField = $event"
|
||||
@set-drag-placeholder="$refs.dragPlaceholder.dragPlaceholder = $event"
|
||||
@change-submitter="selectedSubmitter = $event"
|
||||
@@ -632,10 +638,12 @@ export default {
|
||||
isPaymentConnected: this.isPaymentConnected,
|
||||
withFormula: this.withFormula,
|
||||
withConditions: this.withConditions,
|
||||
withCustomFields: this.withCustomFields,
|
||||
isInlineSize: this.isInlineSize,
|
||||
defaultDrawFieldType: this.defaultDrawFieldType,
|
||||
selectedAreasRef: computed(() => this.selectedAreasRef),
|
||||
fieldsDragFieldRef: computed(() => this.fieldsDragFieldRef),
|
||||
customDragFieldRef: computed(() => this.customDragFieldRef),
|
||||
isSelectModeRef: computed(() => this.isSelectModeRef),
|
||||
isCmdKeyRef: computed(() => this.isCmdKeyRef)
|
||||
}
|
||||
@@ -705,6 +713,16 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withCustomFields: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
customFields: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
withAddPageButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -902,6 +920,7 @@ export default {
|
||||
pendingFieldAttachmentUuids: [],
|
||||
drawField: null,
|
||||
drawFieldType: null,
|
||||
drawCustomField: null,
|
||||
drawOption: null,
|
||||
dragField: null,
|
||||
isDragFile: false
|
||||
@@ -912,6 +931,7 @@ export default {
|
||||
isSelectModeRef: () => ref(false),
|
||||
isCmdKeyRef: () => ref(false),
|
||||
fieldsDragFieldRef: () => ref(),
|
||||
customDragFieldRef: () => ref(),
|
||||
selectedAreasRef: () => ref([]),
|
||||
language () {
|
||||
return this.locale.split('-')[0].toLowerCase()
|
||||
@@ -1062,6 +1082,30 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
toRaw,
|
||||
addCustomField (field) {
|
||||
return this.$refs.fields.addCustomField(field)
|
||||
},
|
||||
addCustomFieldWithoutDraw () {
|
||||
const customField = this.drawCustomField
|
||||
|
||||
const field = JSON.parse(JSON.stringify(customField))
|
||||
|
||||
field.uuid = v4()
|
||||
field.submitter_uuid = this.selectedSubmitter.uuid
|
||||
field.areas = []
|
||||
|
||||
if (field.options?.length) {
|
||||
field.options = field.options.map(opt => ({ ...opt, uuid: v4() }))
|
||||
}
|
||||
|
||||
delete field.conditions
|
||||
|
||||
this.insertField(field)
|
||||
this.save()
|
||||
|
||||
this.drawCustomField = null
|
||||
this.showDrawField = false
|
||||
},
|
||||
toggleSelectMode () {
|
||||
this.isSelectModeRef.value = !this.isSelectModeRef.value
|
||||
|
||||
@@ -1514,6 +1558,7 @@ export default {
|
||||
clearDrawField () {
|
||||
this.drawField = null
|
||||
this.drawOption = null
|
||||
this.drawCustomField = null
|
||||
this.showDrawField = false
|
||||
|
||||
if (!this.withSelectedFieldType) {
|
||||
@@ -1951,6 +1996,10 @@ export default {
|
||||
}
|
||||
},
|
||||
onDraw ({ area, isTooSmall }) {
|
||||
if (this.drawCustomField) {
|
||||
return this.onDrawCustomField(area)
|
||||
}
|
||||
|
||||
if (this.drawField) {
|
||||
if (this.drawOption) {
|
||||
const areaWithoutOption = this.drawField.areas?.find((a) => !a.option_uuid)
|
||||
@@ -2061,6 +2110,10 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.customDragFieldRef.value) {
|
||||
return this.dropCustomField(area)
|
||||
}
|
||||
|
||||
const field = this.fieldsDragFieldRef.value || {
|
||||
name: '',
|
||||
uuid: v4(),
|
||||
@@ -2153,6 +2206,126 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
dropCustomField (area) {
|
||||
const customField = this.customDragFieldRef.value
|
||||
const customAreas = customField.areas || []
|
||||
|
||||
const field = JSON.parse(JSON.stringify(customField))
|
||||
|
||||
field.uuid = v4()
|
||||
field.submitter_uuid = this.selectedSubmitter.uuid
|
||||
field.areas = []
|
||||
|
||||
if (field.options?.length) {
|
||||
field.options = field.options.map(opt => ({ ...opt, uuid: v4() }))
|
||||
}
|
||||
|
||||
delete field.conditions
|
||||
|
||||
const dropX = (area.x - 6) / area.maskW
|
||||
const dropY = area.y / area.maskH
|
||||
|
||||
if (customAreas.length > 0) {
|
||||
const refArea = customAreas[0]
|
||||
|
||||
customAreas.forEach((customArea) => {
|
||||
const fieldArea = {
|
||||
x: dropX + (customArea.x - refArea.x),
|
||||
y: dropY + (customArea.y - refArea.y) - (customArea.h / 2),
|
||||
w: customArea.w,
|
||||
h: customArea.h,
|
||||
page: area.page,
|
||||
attachment_uuid: area.attachment_uuid
|
||||
}
|
||||
|
||||
if (customArea.cell_w) {
|
||||
fieldArea.cell_w = customArea.cell_w
|
||||
}
|
||||
|
||||
if (customArea.option_uuid && field.options?.length) {
|
||||
const optionIndex = customField.options.findIndex(o => o.uuid === customArea.option_uuid)
|
||||
if (optionIndex !== -1) {
|
||||
fieldArea.option_uuid = field.options[optionIndex].uuid
|
||||
}
|
||||
}
|
||||
|
||||
field.areas.push(fieldArea)
|
||||
})
|
||||
} else {
|
||||
const fieldArea = {
|
||||
x: dropX,
|
||||
y: dropY,
|
||||
page: area.page,
|
||||
attachment_uuid: area.attachment_uuid
|
||||
}
|
||||
|
||||
this.assignDropAreaSize(fieldArea, field, area)
|
||||
|
||||
field.areas.push(fieldArea)
|
||||
}
|
||||
|
||||
this.selectedAreasRef.value = [field.areas[0]]
|
||||
|
||||
this.insertField(field)
|
||||
this.save()
|
||||
|
||||
document.activeElement?.blur()
|
||||
},
|
||||
onDrawCustomField (area) {
|
||||
const customField = this.drawCustomField
|
||||
const customAreas = customField.areas || []
|
||||
|
||||
const field = JSON.parse(JSON.stringify(customField))
|
||||
|
||||
field.uuid = v4()
|
||||
field.submitter_uuid = this.selectedSubmitter.uuid
|
||||
field.areas = []
|
||||
|
||||
if (field.options?.length) {
|
||||
field.options = field.options.map(opt => ({ ...opt, uuid: v4() }))
|
||||
}
|
||||
|
||||
delete field.conditions
|
||||
|
||||
const isClick = area.w === 0 || area.h === 0
|
||||
|
||||
const firstArea = {
|
||||
x: area.x,
|
||||
y: area.y,
|
||||
w: area.w || customAreas[0]?.w,
|
||||
h: area.h || customAreas[0]?.h,
|
||||
page: area.page,
|
||||
attachment_uuid: area.attachment_uuid
|
||||
}
|
||||
|
||||
if (!firstArea.w || !firstArea.h) {
|
||||
if (customAreas[0]) {
|
||||
firstArea.w = customAreas[0].w
|
||||
firstArea.h = customAreas[0].h
|
||||
} else {
|
||||
this.setDefaultAreaSize(firstArea, field.type)
|
||||
}
|
||||
}
|
||||
|
||||
if (isClick) {
|
||||
firstArea.x -= firstArea.w / 2
|
||||
firstArea.y -= firstArea.h / 2
|
||||
}
|
||||
|
||||
if (field.options?.length) {
|
||||
firstArea.option_uuid = field.options[0].uuid
|
||||
}
|
||||
|
||||
field.areas.push(firstArea)
|
||||
|
||||
this.selectedAreasRef.value = [field.areas[0]]
|
||||
|
||||
this.insertField(field)
|
||||
this.save()
|
||||
|
||||
this.drawCustomField = null
|
||||
this.showDrawField = false
|
||||
},
|
||||
assignDropAreaSize (fieldArea, field, area) {
|
||||
const fieldType = field.type || 'text'
|
||||
|
||||
|
||||
@@ -6,18 +6,24 @@
|
||||
<span
|
||||
ref="contenteditable"
|
||||
dir="auto"
|
||||
:contenteditable="editable"
|
||||
style="min-width: 2px"
|
||||
:class="[iconInline ? 'inline' : 'block', hideIcon ? 'focus:block' : '']"
|
||||
class="peer outline-none"
|
||||
:contenteditable="editable && (!editableOnButton || isEditable)"
|
||||
:data-placeholder="placeholder"
|
||||
:data-empty="isEmpty"
|
||||
:style="{ minWidth }"
|
||||
:class="[iconInline ? (isEmpty ? 'inline-block' : 'inline') : 'block', hideIcon ? 'focus:block' : '']"
|
||||
class="peer relative inline-block outline-none before:pointer-events-none before:absolute before:left-0 before:top-0 before:select-none before:whitespace-pre before:text-neutral-400 before:content-[attr(data-placeholder)] before:opacity-0 data-[empty=true]:before:opacity-100"
|
||||
@paste.prevent="onPaste"
|
||||
@keydown.enter.prevent="blurContenteditable"
|
||||
@input="updateInputValue"
|
||||
@cut="updateInputValue"
|
||||
@focus="$emit('focus', $event)"
|
||||
@blur="onBlur"
|
||||
@click="editable && (!editableOnButton || isEditable) ? '' : $emit('click-contenteditable')"
|
||||
>
|
||||
{{ value }}
|
||||
</span>
|
||||
<span
|
||||
v-if="withButton"
|
||||
class="relative inline"
|
||||
:class="{ 'peer-focus:hidden': hideIcon, 'peer-focus:invisible': !hideIcon }"
|
||||
>
|
||||
@@ -28,7 +34,7 @@
|
||||
:class="{ invisible: !editable, 'absolute top-1/2 -translate-y-1/2': !iconInline || floatIcon, 'inline align-bottom': iconInline, 'left-0': floatIcon }"
|
||||
:width="iconWidth + 4"
|
||||
:stroke-width="iconStrokeWidth"
|
||||
@click="[focusContenteditable(), selectOnEditClick && selectContent()]"
|
||||
@click="clickEdit"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -49,6 +55,16 @@ export default {
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
withButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
iconInline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -74,6 +90,16 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
editableOnButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
minWidth: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '2px'
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -85,21 +111,34 @@ export default {
|
||||
default: 2
|
||||
}
|
||||
},
|
||||
emits: ['update:model-value', 'focus', 'blur'],
|
||||
emits: ['update:model-value', 'focus', 'blur', 'click-contenteditable'],
|
||||
data () {
|
||||
return {
|
||||
isEditable: false,
|
||||
inputValue: '',
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isEmpty () {
|
||||
return !this.inputValue.replace(/\u200B/g, '').replace(/\u00A0/g, ' ').trim()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler (value) {
|
||||
this.value = value
|
||||
this.value = value || ''
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.updateInputValue()
|
||||
},
|
||||
methods: {
|
||||
updateInputValue () {
|
||||
this.inputValue = this.$refs.contenteditable?.textContent || ''
|
||||
},
|
||||
onPaste (e) {
|
||||
const text = (e.clipboardData || window.clipboardData).getData('text/plain')
|
||||
|
||||
@@ -110,6 +149,20 @@ export default {
|
||||
selection.getRangeAt(0).insertNode(document.createTextNode(text))
|
||||
selection.collapseToEnd()
|
||||
}
|
||||
|
||||
this.updateInputValue()
|
||||
},
|
||||
clickEdit (e) {
|
||||
this.focusContenteditable()
|
||||
|
||||
if (this.selectOnEditClick) {
|
||||
this.selectContent()
|
||||
}
|
||||
},
|
||||
setText (text) {
|
||||
this.$refs.contenteditable.innerText = text
|
||||
|
||||
this.updateInputValue()
|
||||
},
|
||||
selectContent () {
|
||||
const el = this.$refs.contenteditable
|
||||
@@ -129,10 +182,18 @@ export default {
|
||||
this.value = this.$refs.contenteditable.innerText.trim() || this.modelValue
|
||||
this.$emit('update:model-value', this.value)
|
||||
this.$emit('blur', e)
|
||||
|
||||
this.isEditable = false
|
||||
}, 1)
|
||||
},
|
||||
focusContenteditable () {
|
||||
this.$refs.contenteditable.focus()
|
||||
this.isEditable = true
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.contenteditable.focus()
|
||||
|
||||
this.updateInputValue()
|
||||
})
|
||||
},
|
||||
blurContenteditable () {
|
||||
this.$refs.contenteditable.blur()
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div
|
||||
class="list-field group"
|
||||
>
|
||||
<div
|
||||
class="border border-dashed border-base-300 hover:border-base-content/20 rounded relative group fields-list-item transition-colors"
|
||||
:style="{ backgroundColor: backgroundColor }"
|
||||
>
|
||||
<div class="flex items-center justify-between relative group/contenteditable-container">
|
||||
<div
|
||||
v-if="!isNew"
|
||||
class="absolute top-0 bottom-0 right-0 left-0 cursor-pointer"
|
||||
@click="$emit('click', field)"
|
||||
/>
|
||||
<div
|
||||
class="absolute top-0 bottom-0 left-0 flex items-center transition-all cursor-grab group-hover:bg-base-200/50"
|
||||
@click="$emit('click', field)"
|
||||
>
|
||||
<IconDrag style="margin-left: 1px" />
|
||||
</div>
|
||||
<div class="flex items-center p-1 pl-6 space-x-1">
|
||||
<FieldType
|
||||
v-model="field.type"
|
||||
:editable="false"
|
||||
:button-width="20"
|
||||
@click="$emit('click', field)"
|
||||
/>
|
||||
<Contenteditable
|
||||
ref="name"
|
||||
:model-value="field.name"
|
||||
:placeholder="'Field Name'"
|
||||
:icon-inline="true"
|
||||
:icon-width="18"
|
||||
:min-width="isNew ? '100px' : '2px'"
|
||||
:icon-stroke-width="1.6"
|
||||
:editable-on-button="!isNew"
|
||||
:with-button="!isNew"
|
||||
:class="{ 'cursor-pointer': !isNew }"
|
||||
@click-contenteditable="$emit('click', field)"
|
||||
@focus="onNameFocus"
|
||||
@blur="onNameBlur"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-1"
|
||||
>
|
||||
<PaymentSettings
|
||||
v-if="field.type === 'payment' && !isNew"
|
||||
:field="field"
|
||||
:with-condition="false"
|
||||
:with-force-open="false"
|
||||
@click-description="isShowDescriptionModal = true"
|
||||
@click-formula="isShowFormulaModal = true"
|
||||
/>
|
||||
<span
|
||||
v-else-if="!isNew"
|
||||
class="dropdown dropdown-end field-settings-dropdown"
|
||||
@mouseenter="renderDropdown = true"
|
||||
@touchstart="renderDropdown = true"
|
||||
>
|
||||
<label
|
||||
tabindex="0"
|
||||
:title="t('settings')"
|
||||
class="cursor-pointer text-transparent group-hover:text-base-content"
|
||||
>
|
||||
<IconSettings
|
||||
:width="18"
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
</label>
|
||||
<ul
|
||||
v-if="renderDropdown"
|
||||
tabindex="0"
|
||||
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow rounded-box w-52 z-10"
|
||||
:style="{ backgroundColor: dropdownBgColor }"
|
||||
draggable="true"
|
||||
@dragstart.prevent.stop
|
||||
@click="closeDropdown"
|
||||
>
|
||||
<FieldSettings
|
||||
:field="field"
|
||||
:with-signature-id="withSignatureId"
|
||||
:with-prefillable="withPrefillable"
|
||||
:background-color="dropdownBgColor"
|
||||
:with-areas="false"
|
||||
:with-copy-to-all-pages="false"
|
||||
:with-condition="false"
|
||||
@click-formula="isShowFormulaModal = true"
|
||||
@click-font="isShowFontModal = true"
|
||||
@click-description="isShowDescriptionModal = true"
|
||||
@save="$emit('save')"
|
||||
/>
|
||||
</ul>
|
||||
</span>
|
||||
<button
|
||||
v-if="isNew && !$refs.name"
|
||||
class="relative text-base-content pr-1 field-save-button"
|
||||
:title="t('save')"
|
||||
@click="field.name ? $emit('save', field) : focusName()"
|
||||
>
|
||||
<IconCheck
|
||||
:width="18"
|
||||
:stroke-width="2"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
class="relative group-hover:text-base-content pr-1 field-remove-button"
|
||||
:class="isNew ? 'text-base-content' : 'text-transparent group-hover:text-base-content'"
|
||||
:title="t('remove')"
|
||||
@click="onRemoveClick"
|
||||
>
|
||||
<IconTrashX
|
||||
:width="18"
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Teleport
|
||||
v-if="isShowFormulaModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<FormulaModal
|
||||
:field="field"
|
||||
:default-field="defaultField"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowFormulaModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowFontModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<FontModal
|
||||
:field="field"
|
||||
:default-field="defaultField"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowFontModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport
|
||||
v-if="isShowDescriptionModal"
|
||||
:to="modalContainerEl"
|
||||
>
|
||||
<DescriptionModal
|
||||
:field="field"
|
||||
:default-field="defaultField"
|
||||
:build-default-name="buildDefaultName"
|
||||
@close="isShowDescriptionModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Contenteditable from './contenteditable'
|
||||
import FieldType from './field_type'
|
||||
import PaymentSettings from './payment_settings'
|
||||
import FieldSettings from './field_settings'
|
||||
import FormulaModal from './formula_modal'
|
||||
import FontModal from './font_modal'
|
||||
import DescriptionModal from './description_modal'
|
||||
import { IconTrashX, IconSettings, IconCheck } from '@tabler/icons-vue'
|
||||
import IconDrag from './icon_drag'
|
||||
|
||||
export default {
|
||||
name: 'CustomField',
|
||||
components: {
|
||||
Contenteditable,
|
||||
IconSettings,
|
||||
IconCheck,
|
||||
FieldSettings,
|
||||
PaymentSettings,
|
||||
IconDrag,
|
||||
FormulaModal,
|
||||
FontModal,
|
||||
DescriptionModal,
|
||||
IconTrashX,
|
||||
FieldType
|
||||
},
|
||||
inject: ['backgroundColor', 't'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isNew: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withSignatureId: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
withPrefillable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['remove', 'save', 'click'],
|
||||
data () {
|
||||
return {
|
||||
isShowFormulaModal: false,
|
||||
isShowFontModal: false,
|
||||
isShowDescriptionModal: false,
|
||||
renderDropdown: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dropdownBgColor () {
|
||||
return ['', null, 'transparent'].includes(this.backgroundColor) ? 'white' : this.backgroundColor
|
||||
},
|
||||
modalContainerEl () {
|
||||
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.field.preferences ||= {}
|
||||
},
|
||||
mounted () {
|
||||
if (this.isNew) {
|
||||
this.focusName()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buildDefaultName () {
|
||||
return this.t('custom')
|
||||
},
|
||||
focusName () {
|
||||
setTimeout(() => {
|
||||
this.$refs.name.clickEdit()
|
||||
}, 1)
|
||||
},
|
||||
onNameFocus (e) {
|
||||
if (!this.field.name) {
|
||||
setTimeout(() => {
|
||||
this.$refs.name.$refs.contenteditable.innerText = ' '
|
||||
}, 1)
|
||||
}
|
||||
},
|
||||
closeDropdown () {
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
},
|
||||
onRemoveClick () {
|
||||
if (this.isNew || window.confirm(this.t('are_you_sure_'))) {
|
||||
this.$emit('remove', this.field)
|
||||
}
|
||||
},
|
||||
onNameBlur (e) {
|
||||
const text = this.$refs.name.$refs.contenteditable.innerText.trim()
|
||||
|
||||
if (text) {
|
||||
this.field.name = text
|
||||
} else {
|
||||
this.$refs.name.setText(this.field.name)
|
||||
}
|
||||
|
||||
if (this.field.name) {
|
||||
this.$emit('save')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -19,6 +19,7 @@
|
||||
:default-submitters="defaultSubmitters"
|
||||
:draw-field="drawField"
|
||||
:draw-field-type="drawFieldType"
|
||||
:draw-custom-field="drawCustomField"
|
||||
:selected-submitter="selectedSubmitter"
|
||||
:total-pages="sortedPreviewImages.length"
|
||||
:image="image"
|
||||
@@ -28,6 +29,7 @@
|
||||
@remove-area="$emit('remove-area', $event)"
|
||||
@copy-field="$emit('copy-field', $event)"
|
||||
@paste-field="$emit('paste-field', { ...$event, attachment_uuid: document.uuid })"
|
||||
@add-custom-field="$emit('add-custom-field', $event)"
|
||||
@copy-selected-areas="$emit('copy-selected-areas')"
|
||||
@delete-selected-areas="$emit('delete-selected-areas')"
|
||||
@align-selected-areas="$emit('align-selected-areas', $event)"
|
||||
@@ -115,6 +117,11 @@ export default {
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
drawCustomField: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
baseUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
@@ -131,7 +138,7 @@ export default {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['draw', 'drop-field', 'remove-area', 'paste-field', 'copy-field', 'copy-selected-areas', 'delete-selected-areas', 'align-selected-areas', 'autodetect-fields'],
|
||||
emits: ['draw', 'drop-field', 'remove-area', 'paste-field', 'copy-field', 'copy-selected-areas', 'delete-selected-areas', 'align-selected-areas', 'autodetect-fields', 'add-custom-field'],
|
||||
data () {
|
||||
return {
|
||||
pageRefs: []
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
class="fixed z-20 pointer-events-none"
|
||||
:editable="false"
|
||||
/>
|
||||
<CustomField
|
||||
v-else-if="dragPlaceholder && isCustom && !isMask && field"
|
||||
ref="dragPlaceholder"
|
||||
:style="dragPlaceholderStyle"
|
||||
:field="field"
|
||||
class="fixed z-20 pointer-events-none opacity-90"
|
||||
/>
|
||||
<div
|
||||
v-else-if="dragPlaceholder && (isDefault || isRequired) && !isMask && field"
|
||||
ref="dragPlaceholder"
|
||||
@@ -57,6 +64,7 @@
|
||||
|
||||
<script>
|
||||
import Field from './field'
|
||||
import CustomField from './custom_field'
|
||||
import IconDrag from './icon_drag'
|
||||
import FieldType from './field_type'
|
||||
|
||||
@@ -64,6 +72,7 @@ export default {
|
||||
name: 'DragPlaceholder',
|
||||
components: {
|
||||
Field,
|
||||
CustomField,
|
||||
IconDrag
|
||||
},
|
||||
inject: ['t', 'backgroundColor'],
|
||||
@@ -87,6 +96,11 @@ export default {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
isCustom: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="list-field group mb-2"
|
||||
class="list-field group"
|
||||
>
|
||||
<div
|
||||
class="border border-base-300 rounded relative group fields-list-item"
|
||||
@@ -18,7 +18,7 @@
|
||||
:button-width="20"
|
||||
:menu-classes="'mt-1.5'"
|
||||
:menu-style="{ backgroundColor: dropdownBgColor }"
|
||||
@update:model-value="[maybeUpdateOptions(), save()]"
|
||||
@update:model-value="[maybeUpdateOptions(), $emit('save')]"
|
||||
@click="scrollToFirstArea"
|
||||
/>
|
||||
<Contenteditable
|
||||
@@ -92,9 +92,12 @@
|
||||
<PaymentSettings
|
||||
v-if="field.type === 'payment'"
|
||||
:field="field"
|
||||
:with-custom-fields="withCustomFields"
|
||||
@click-condition="isShowConditionsModal = true"
|
||||
@click-description="isShowDescriptionModal = true"
|
||||
@add-custom-field="$emit('add-custom-field', $event)"
|
||||
@click-formula="isShowFormulaModal = true"
|
||||
@save="$emit('save')"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
@@ -128,12 +131,15 @@
|
||||
:with-signature-id="withSignatureId"
|
||||
:with-prefillable="withPrefillable"
|
||||
:background-color="dropdownBgColor"
|
||||
:with-custom-fields="withCustomFields"
|
||||
@click-formula="isShowFormulaModal = true"
|
||||
@click-font="isShowFontModal = true"
|
||||
@click-description="isShowDescriptionModal = true"
|
||||
@click-condition="isShowConditionsModal = true"
|
||||
@set-draw="$emit('set-draw', $event)"
|
||||
@add-custom-field="$emit('add-custom-field', $event)"
|
||||
@remove-area="removeArea"
|
||||
@save="$emit('save')"
|
||||
@scroll-to="$emit('scroll-to', $event)"
|
||||
/>
|
||||
</ul>
|
||||
@@ -154,8 +160,10 @@
|
||||
v-if="field.options && withOptions && (isExpandOptions || field.options.length < 5)"
|
||||
ref="options"
|
||||
class="border-t border-base-300 mx-2 pt-2 space-y-1.5"
|
||||
draggable="true"
|
||||
@dragover="onOptionDragover"
|
||||
@drop="reorderOptions"
|
||||
@dragstart.prevent.stop
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in field.options"
|
||||
@@ -184,7 +192,8 @@
|
||||
required
|
||||
:placeholder="`${t('option')} ${index + 1}`"
|
||||
@keydown.enter="option.value ? addOptionAt(index + 1) : null"
|
||||
@blur="save"
|
||||
@blur="$emit('save')"
|
||||
@paste="onOptionPaste($event, index)"
|
||||
>
|
||||
<button
|
||||
:title="t('draw')"
|
||||
@@ -208,7 +217,8 @@
|
||||
dir="auto"
|
||||
@keydown.enter="option.value ? addOptionAt(index + 1) : null"
|
||||
@focus="maybeFocusOnOptionArea(option)"
|
||||
@blur="save"
|
||||
@blur="$emit('save')"
|
||||
@paste="onOptionPaste($event, index)"
|
||||
>
|
||||
<button
|
||||
v-if="editable && !defaultField"
|
||||
@@ -330,7 +340,7 @@ export default {
|
||||
IconMathFunction,
|
||||
FieldType
|
||||
},
|
||||
inject: ['template', 'save', 'backgroundColor', 'selectedAreasRef', 't', 'locale'],
|
||||
inject: ['template', 'backgroundColor', 'selectedAreasRef', 't', 'locale'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
@@ -341,6 +351,11 @@ export default {
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
withCustomFields: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withPrefillable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -362,12 +377,11 @@ export default {
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['set-draw', 'remove', 'scroll-to'],
|
||||
emits: ['set-draw', 'remove', 'scroll-to', 'save', 'add-custom-field'],
|
||||
data () {
|
||||
return {
|
||||
isExpandOptions: false,
|
||||
isNameFocus: false,
|
||||
showPaymentModal: false,
|
||||
isShowFormulaModal: false,
|
||||
isShowFontModal: false,
|
||||
isShowConditionsModal: false,
|
||||
@@ -416,7 +430,7 @@ export default {
|
||||
removeArea (area) {
|
||||
this.field.areas.splice(this.field.areas.indexOf(area), 1)
|
||||
|
||||
this.save()
|
||||
this.$emit('save')
|
||||
},
|
||||
buildDefaultName (field, fields) {
|
||||
if (field.type === 'payment' && field.preferences?.price && !field.preferences?.formula) {
|
||||
@@ -460,6 +474,35 @@ export default {
|
||||
closeDropdown () {
|
||||
this.$el.getRootNode().activeElement.blur()
|
||||
},
|
||||
onOptionPaste (e, index) {
|
||||
const text = e.clipboardData.getData('text')
|
||||
|
||||
if (text.includes('\n')) {
|
||||
e.preventDefault()
|
||||
|
||||
this.isExpandOptions = true
|
||||
|
||||
const lines = text.split(/\r?\n/).map((l) => l.trim()).filter((l) => l)
|
||||
|
||||
if (lines.length > 0) {
|
||||
const currentOption = this.field.options[index]
|
||||
|
||||
currentOption.value = (currentOption.value + lines[0]).trim()
|
||||
|
||||
const newOptions = lines.slice(1).map((line) => ({ value: line, uuid: v4() }))
|
||||
|
||||
this.field.options.splice(index + 1, 0, ...newOptions)
|
||||
|
||||
this.$nextTick(() => {
|
||||
const inputs = this.$refs.options.querySelectorAll('input')
|
||||
|
||||
inputs[index + newOptions.length]?.focus()
|
||||
})
|
||||
|
||||
this.$emit('save')
|
||||
}
|
||||
}
|
||||
},
|
||||
addOptionAt (index) {
|
||||
this.isExpandOptions = true
|
||||
|
||||
@@ -473,7 +516,7 @@ export default {
|
||||
inputs[insertAt]?.focus()
|
||||
})
|
||||
|
||||
this.save()
|
||||
this.$emit('save')
|
||||
},
|
||||
removeOption (option) {
|
||||
this.field.options.splice(this.field.options.indexOf(option), 1)
|
||||
@@ -484,7 +527,7 @@ export default {
|
||||
this.field.areas.splice(this.field.areas.findIndex((a) => a.option_uuid === option.uuid), 1)
|
||||
}
|
||||
|
||||
this.save()
|
||||
this.$emit('save')
|
||||
},
|
||||
maybeUpdateOptions () {
|
||||
delete this.field.default_value
|
||||
@@ -526,7 +569,7 @@ export default {
|
||||
|
||||
this.isNameFocus = false
|
||||
|
||||
this.save()
|
||||
this.$emit('save')
|
||||
},
|
||||
onOptionDragstart (event, option) {
|
||||
this.optionDragRef = option
|
||||
@@ -587,7 +630,7 @@ export default {
|
||||
if (newOrder.length === this.field.options.length) {
|
||||
this.field.options.splice(0, this.field.options.length, ...newOrder)
|
||||
|
||||
this.save()
|
||||
this.$emit('save')
|
||||
}
|
||||
|
||||
this.optionDragRef = null
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<select
|
||||
:placeholder="t('method')"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0 bg-transparent"
|
||||
@change="[field.preferences ||= {}, field.preferences.method = $event.target.value, save()]"
|
||||
@change="[field.preferences ||= {}, field.preferences.method = $event.target.value, $emit('save')]"
|
||||
>
|
||||
<option
|
||||
v-for="method in ['QeS', 'AeS']"
|
||||
@@ -33,7 +33,7 @@
|
||||
>
|
||||
<select
|
||||
class="select select-bordered select-xs w-full max-w-xs h-7 !outline-0 font-normal bg-transparent"
|
||||
@change="[field.preferences ||= {}, field.preferences.align = $event.target.value, save()]"
|
||||
@change="[field.preferences ||= {}, field.preferences.align = $event.target.value, $emit('save')]"
|
||||
>
|
||||
<option
|
||||
v-for="value in ['left', 'right', field.type === 'cells' ? null : 'center'].filter(Boolean)"
|
||||
@@ -61,7 +61,7 @@
|
||||
:placeholder="t('default_value')"
|
||||
dir="auto"
|
||||
class="select select-bordered select-xs w-full max-w-xs h-7 !outline-0 font-normal bg-transparent"
|
||||
@change="[field.default_value = $event.target.value, !field.default_value && delete field.default_value, save()]"
|
||||
@change="[field.default_value = $event.target.value, !field.default_value && delete field.default_value, $emit('save')]"
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
@@ -97,7 +97,7 @@
|
||||
dir="auto"
|
||||
:type="field.type"
|
||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0 bg-transparent"
|
||||
@blur="save"
|
||||
@blur="$emit('save')"
|
||||
>
|
||||
<label
|
||||
v-if="field.default_value"
|
||||
@@ -159,7 +159,7 @@
|
||||
:value="lengthValidation.min"
|
||||
class="input input-bordered w-full input-xs h-7 !outline-0 bg-transparent"
|
||||
@input="field.validation.pattern = `.{${$event.target.value || 0},${lengthValidation.max || ''}}`"
|
||||
@blur="save"
|
||||
@blur="$emit('save')"
|
||||
>
|
||||
<label
|
||||
v-if="lengthValidation.min"
|
||||
@@ -178,7 +178,7 @@
|
||||
class="input input-bordered w-full input-xs h-7 !outline-0 bg-transparent"
|
||||
:value="lengthValidation.max"
|
||||
@input="field.validation.pattern = `.{${lengthValidation.min},${$event.target.value || ''}}`"
|
||||
@blur="save"
|
||||
@blur="$emit('save')"
|
||||
>
|
||||
<label
|
||||
v-if="lengthValidation.max"
|
||||
@@ -203,7 +203,7 @@
|
||||
:value="field.validation?.min"
|
||||
class="input input-bordered w-full input-xs h-7 !outline-0 bg-transparent"
|
||||
@input="[field.validation ||= {}, $event.target.value ? field.validation.min = $event.target.value : delete field.validation.min]"
|
||||
@blur="save"
|
||||
@blur="$emit('save')"
|
||||
>
|
||||
<label
|
||||
v-if="field.validation?.min"
|
||||
@@ -222,7 +222,7 @@
|
||||
class="input input-bordered w-full input-xs h-7 !outline-0 bg-transparent"
|
||||
:value="field.validation?.max"
|
||||
@input="[field.validation ||= {}, $event.target.value ? field.validation.max = $event.target.value : delete field.validation.max]"
|
||||
@blur="save"
|
||||
@blur="$emit('save')"
|
||||
>
|
||||
<label
|
||||
v-if="field.validation?.max"
|
||||
@@ -242,7 +242,7 @@
|
||||
<select
|
||||
:placeholder="t('format')"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0 bg-transparent"
|
||||
@change="[field.preferences ||= {}, field.preferences.format = $event.target.value, save()]"
|
||||
@change="[field.preferences ||= {}, field.preferences.format = $event.target.value, $emit('save')]"
|
||||
>
|
||||
<option
|
||||
v-for="format in numberFormats"
|
||||
@@ -272,7 +272,7 @@
|
||||
:placeholder="t('regexp_validation')"
|
||||
dir="auto"
|
||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0 bg-transparent"
|
||||
@blur="save"
|
||||
@blur="$emit('save')"
|
||||
>
|
||||
<label
|
||||
v-if="field.validation.pattern"
|
||||
@@ -293,7 +293,7 @@
|
||||
:placeholder="t('error_message')"
|
||||
dir="auto"
|
||||
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0 bg-transparent"
|
||||
@blur="save"
|
||||
@blur="$emit('save')"
|
||||
>
|
||||
<label
|
||||
v-if="field.validation.message"
|
||||
@@ -313,7 +313,7 @@
|
||||
v-model="field.preferences.format"
|
||||
:placeholder="t('format')"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0 bg-transparent"
|
||||
@change="save"
|
||||
@change="$emit('save')"
|
||||
>
|
||||
<option
|
||||
v-for="format in dateFormats"
|
||||
@@ -339,7 +339,7 @@
|
||||
<select
|
||||
:placeholder="t('format')"
|
||||
class="select select-bordered select-xs font-normal w-full max-w-xs !h-7 !outline-0 bg-transparent"
|
||||
@change="[field.preferences.format = $event.target.value, save()]"
|
||||
@change="[field.preferences.format = $event.target.value, $emit('save')]"
|
||||
>
|
||||
<option
|
||||
value="any"
|
||||
@@ -374,7 +374,7 @@
|
||||
type="checkbox"
|
||||
:disabled="!editable || (defaultField && [true, false].includes(defaultField.required))"
|
||||
class="toggle toggle-xs"
|
||||
@change="[field.preferences ||= {}, field.preferences.with_signature_id = $event.target.checked, save()]"
|
||||
@change="[field.preferences ||= {}, field.preferences.with_signature_id = $event.target.checked, $emit('save')]"
|
||||
>
|
||||
<span class="label-text">{{ t('signature_id') }}</span>
|
||||
</label>
|
||||
@@ -389,7 +389,7 @@
|
||||
type="checkbox"
|
||||
:disabled="!editable || (defaultField && [true, false].includes(defaultField.required))"
|
||||
class="toggle toggle-xs"
|
||||
@update:model-value="save"
|
||||
@update:model-value="$emit('save')"
|
||||
>
|
||||
<span class="label-text">{{ t('required') }}</span>
|
||||
</label>
|
||||
@@ -403,7 +403,7 @@
|
||||
:checked="field.preferences?.with_logo != false"
|
||||
type="checkbox"
|
||||
class="toggle toggle-xs"
|
||||
@change="[field.preferences ||= {}, field.preferences.with_logo = field.preferences.with_logo == false, save()]"
|
||||
@change="[field.preferences ||= {}, field.preferences.with_logo = field.preferences.with_logo == false, $emit('save')]"
|
||||
>
|
||||
<span class="label-text">{{ t('with_logo') }}</span>
|
||||
</label>
|
||||
@@ -417,7 +417,7 @@
|
||||
v-model="field.default_value"
|
||||
type="checkbox"
|
||||
class="toggle toggle-xs"
|
||||
@update:model-value="[field.default_value = $event, field.readonly = $event, save()]"
|
||||
@update:model-value="[field.default_value = $event, field.readonly = $event, $emit('save')]"
|
||||
>
|
||||
<span class="label-text">{{ t('checked') }}</span>
|
||||
</label>
|
||||
@@ -431,7 +431,7 @@
|
||||
v-model="field.readonly"
|
||||
type="checkbox"
|
||||
class="toggle toggle-xs"
|
||||
@update:model-value="[field.default_value = $event ? '{{date}}' : null, field.readonly = $event, save()]"
|
||||
@update:model-value="[field.default_value = $event ? '{{date}}' : null, field.readonly = $event, $emit('save')]"
|
||||
>
|
||||
<span class="label-text">{{ t('set_signing_date') }}</span>
|
||||
</label>
|
||||
@@ -446,7 +446,7 @@
|
||||
type="checkbox"
|
||||
class="toggle toggle-xs"
|
||||
:disabled="!editable || (defaultField && [true, false].includes(defaultField.readonly))"
|
||||
@update:model-value="save"
|
||||
@update:model-value="$emit('save')"
|
||||
>
|
||||
<span class="label-text">{{ t('read_only') }}</span>
|
||||
</label>
|
||||
@@ -461,7 +461,7 @@
|
||||
type="checkbox"
|
||||
:disabled="!editable || (defaultField && [true, false].includes(defaultField.prefillable))"
|
||||
class="toggle toggle-xs"
|
||||
@update:model-value="save"
|
||||
@update:model-value="$emit('save')"
|
||||
>
|
||||
<span class="label-text">{{ t('prefillable') }}</span>
|
||||
</label>
|
||||
@@ -499,7 +499,7 @@
|
||||
</label>
|
||||
</li>
|
||||
<li
|
||||
v-if="field.type != 'stamp' && field.type != 'heading'"
|
||||
v-if="withCondition && field.type != 'stamp' && field.type != 'heading'"
|
||||
>
|
||||
<label
|
||||
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||
@@ -526,7 +526,10 @@
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<hr class="pb-0.5 mt-0.5">
|
||||
<hr
|
||||
v-if="withCopyToAllPages || withAreas || withCustomFields"
|
||||
class="pb-0.5 mt-0.5"
|
||||
>
|
||||
<template v-if="withAreas">
|
||||
<li
|
||||
v-for="(area, index) in sortedAreas"
|
||||
@@ -564,7 +567,7 @@
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="field.areas?.length === 1 && ['date', 'signature', 'initials', 'text', 'cells', 'stamp'].includes(field.type)">
|
||||
<li v-if="withCopyToAllPages && field.areas?.length === 1 && ['date', 'signature', 'initials', 'text', 'cells', 'stamp'].includes(field.type)">
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm py-1 px-2"
|
||||
@@ -577,10 +580,23 @@
|
||||
{{ t('copy_to_all_pages') }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="withCustomFields">
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm py-1 px-2"
|
||||
@click.prevent="$emit('add-custom-field', field)"
|
||||
>
|
||||
<IconForms
|
||||
:width="20"
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
{{ t('save_as_custom_field') }}
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconRouteAltLeft, IconTypography, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy } from '@tabler/icons-vue'
|
||||
import { IconRouteAltLeft, IconTypography, IconShape, IconX, IconMathFunction, IconNewSection, IconInfoCircle, IconCopy, IconForms } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FieldSettings',
|
||||
@@ -589,17 +605,33 @@ export default {
|
||||
IconInfoCircle,
|
||||
IconMathFunction,
|
||||
IconRouteAltLeft,
|
||||
IconForms,
|
||||
IconCopy,
|
||||
IconNewSection,
|
||||
IconTypography,
|
||||
IconX
|
||||
},
|
||||
inject: ['template', 'save', 't'],
|
||||
inject: ['template', 't'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
withCondition: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
withCustomFields: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withCopyToAllPages: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
withSignatureId: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -636,7 +668,7 @@ export default {
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'click-font', 'remove-area'],
|
||||
emits: ['set-draw', 'scroll-to', 'click-formula', 'click-description', 'click-condition', 'click-font', 'remove-area', 'save', 'add-custom-field'],
|
||||
data () {
|
||||
return {
|
||||
selectedValidation: ''
|
||||
@@ -730,7 +762,7 @@ export default {
|
||||
delete this.field.validation
|
||||
}
|
||||
|
||||
this.save()
|
||||
this.$emit('save')
|
||||
},
|
||||
copyToAllPages (field) {
|
||||
const areaString = JSON.stringify(field.areas[0])
|
||||
@@ -747,7 +779,7 @@ export default {
|
||||
|
||||
this.$emit('scroll-to', this.field.areas[this.field.areas.length - 1])
|
||||
|
||||
this.save()
|
||||
this.$emit('save')
|
||||
},
|
||||
formatNumber (number, format) {
|
||||
if (format === 'comma') {
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
</div>
|
||||
<div
|
||||
ref="fields"
|
||||
class="fields mb-1 mt-2"
|
||||
class="fields mt-2"
|
||||
:class="{ 'mb-1': !withCustomFields || !customFields.length }"
|
||||
@dragover.prevent="onFieldDragover"
|
||||
@drop="fieldsDragFieldRef.value ? reorderFields() : null"
|
||||
>
|
||||
@@ -30,7 +31,11 @@
|
||||
:with-prefillable="withPrefillable"
|
||||
:default-field="defaultFieldsIndex[field.name]"
|
||||
:draggable="editable"
|
||||
:with-custom-fields="withCustomFields"
|
||||
class="mb-1.5"
|
||||
@add-custom-field="addCustomField"
|
||||
@dragstart="[fieldsDragFieldRef.value = field, removeDragOverlay($event), setDragPlaceholder($event)]"
|
||||
@save="save"
|
||||
@dragend="[fieldsDragFieldRef.value = null, $emit('set-drag-placeholder', null)]"
|
||||
@remove="removeField"
|
||||
@scroll-to="$emit('scroll-to-area', $event)"
|
||||
@@ -105,7 +110,92 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="editable && !onlyDefinedFields"
|
||||
v-if="editable && withCustomFields && (customFields.length || newCustomField)"
|
||||
class="tabs w-full mb-1.5"
|
||||
>
|
||||
<a
|
||||
class="tab tab-bordered w-1/2 border-base-300"
|
||||
:class="{ 'tab-active': !showCustomTab }"
|
||||
:style="{ '--tab-border': showCustomTab ? '0px' : '0.5px' }"
|
||||
@click="setFieldsTab('default')"
|
||||
>{{ t('default') }}</a>
|
||||
<a
|
||||
class="tab tab-bordered w-1/2 border-base-300"
|
||||
:class="{ 'tab-active': showCustomTab }"
|
||||
:style="{ '--tab-border': showCustomTab ? '0.5px' : '0px' }"
|
||||
@click="setFieldsTab('custom')"
|
||||
>{{ t('custom') }}</a>
|
||||
</div>
|
||||
<div
|
||||
v-if="showCustomTab && editable"
|
||||
ref="customFields"
|
||||
class="custom-fields"
|
||||
@dragover.prevent="onCustomFieldDragover"
|
||||
@drop="customDragFieldRef.value ? reorderCustomFields() : null"
|
||||
>
|
||||
<template v-if="isShowCustomFieldSearch">
|
||||
<input
|
||||
v-model="customFieldsSearch"
|
||||
:placeholder="t('search_field')"
|
||||
class="input input-ghost input-xs px-0 text-base mb-1 !outline-0 !rounded bg-transparent w-full"
|
||||
>
|
||||
<hr class="mb-2">
|
||||
</template>
|
||||
<div
|
||||
ref="customFieldsList"
|
||||
class="overflow-auto relative"
|
||||
:style="{
|
||||
maxHeight: isShowCustomFieldSearch ? '320px' : '',
|
||||
minHeight: '320px'
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="!filteredCustomFields.length && customFieldsSearch"
|
||||
class="top-0 bottom-0 text-center absolute flex items-center justify-center w-full flex-col"
|
||||
>
|
||||
<div>
|
||||
{{ t('field_not_found') }}
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
class="link"
|
||||
@click.prevent="customFieldsSearch = ''"
|
||||
>
|
||||
{{ t('clear') }}
|
||||
</a>
|
||||
</div>
|
||||
<CustomField
|
||||
v-if="newCustomField"
|
||||
:key="newCustomField.uuid"
|
||||
ref="newCustomField"
|
||||
:data-uuid="newCustomField.uuid"
|
||||
:is-new="true"
|
||||
:field="newCustomField"
|
||||
:draggable="false"
|
||||
class="mb-1.5"
|
||||
@save="onNewCustomFieldSave"
|
||||
@remove="newCustomField = null"
|
||||
/>
|
||||
<CustomField
|
||||
v-for="field in filteredCustomFields"
|
||||
:key="field.uuid"
|
||||
:data-uuid="field.uuid"
|
||||
:field="field"
|
||||
:draggable="true"
|
||||
class="mb-1.5"
|
||||
:with-signature-id="withSignatureId"
|
||||
:with-prefillable="withPrefillable"
|
||||
@save="saveCustomFields"
|
||||
@click="$emit('set-draw-custom-field', field)"
|
||||
@dragstart="[customDragFieldRef.value = field, removeDragOverlay($event), setDragPlaceholder($event)]"
|
||||
@dragend="[customDragFieldRef.value = null, $emit('set-drag-placeholder', null)]"
|
||||
@remove="removeCustomField"
|
||||
@set-draw="$emit('set-draw', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="editable && !onlyDefinedFields && (!showCustomTab || (!customFields.length && !newCustomField))"
|
||||
id="field-types-grid"
|
||||
class="grid grid-cols-3 gap-1 pb-2 fields-grid"
|
||||
>
|
||||
@@ -266,15 +356,18 @@
|
||||
|
||||
<script>
|
||||
import Field from './field'
|
||||
import CustomField from './custom_field'
|
||||
import FieldType from './field_type'
|
||||
import FieldSubmitter from './field_submitter'
|
||||
import { IconLock, IconCirclePlus, IconInnerShadowTop, IconSparkles } from '@tabler/icons-vue'
|
||||
import IconDrag from './icon_drag'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
export default {
|
||||
name: 'TemplateFields',
|
||||
components: {
|
||||
Field,
|
||||
CustomField,
|
||||
FieldType,
|
||||
IconCirclePlus,
|
||||
IconSparkles,
|
||||
@@ -283,12 +376,22 @@ export default {
|
||||
IconDrag,
|
||||
IconLock
|
||||
},
|
||||
inject: ['save', 'backgroundColor', 'withPhone', 'withVerification', 'withKba', 'withPayment', 't', 'fieldsDragFieldRef', 'baseFetch', 'selectedAreasRef'],
|
||||
inject: ['save', 'backgroundColor', 'withPhone', 'withVerification', 'withKba', 'withPayment', 't', 'fieldsDragFieldRef', 'customDragFieldRef', 'baseFetch', 'selectedAreasRef'],
|
||||
props: {
|
||||
fields: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
customFields: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
withCustomFields: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withFieldsSearch: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -372,12 +475,15 @@ export default {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['add-field', 'set-draw', 'set-draw-type', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter', 'set-drag-placeholder', 'select-submitter'],
|
||||
emits: ['add-field', 'set-draw', 'set-draw-type', 'set-draw-custom-field', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter', 'set-drag-placeholder', 'select-submitter'],
|
||||
data () {
|
||||
return {
|
||||
fieldPagesLoaded: null,
|
||||
analyzingProgress: 0,
|
||||
defaultFieldsSearch: ''
|
||||
newCustomField: null,
|
||||
showCustomTab: false,
|
||||
defaultFieldsSearch: '',
|
||||
customFieldsSearch: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -430,6 +536,23 @@ export default {
|
||||
} else {
|
||||
return this.submitterDefaultFields
|
||||
}
|
||||
},
|
||||
isShowCustomFieldSearch () {
|
||||
return this.customFields.length > 8
|
||||
},
|
||||
filteredCustomFields () {
|
||||
if (this.customFieldsSearch) {
|
||||
return this.customFields.filter((f) => f.name.toLowerCase().includes(this.customFieldsSearch.toLowerCase()))
|
||||
} else {
|
||||
return this.customFields
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
try {
|
||||
this.showCustomTab = localStorage.getItem('docuseal_builder_tab') === 'custom'
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -440,6 +563,61 @@ export default {
|
||||
|
||||
this.$emit('set-drag', field)
|
||||
},
|
||||
onNewCustomFieldSave () {
|
||||
if (this.newCustomField.name) {
|
||||
this.customFields.unshift(this.newCustomField)
|
||||
this.newCustomField = null
|
||||
|
||||
this.saveCustomFields()
|
||||
} else {
|
||||
this.newCustomField = null
|
||||
}
|
||||
},
|
||||
addCustomField (field) {
|
||||
const customField = JSON.parse(JSON.stringify(field))
|
||||
|
||||
customField.uuid = v4()
|
||||
|
||||
delete customField.submitter_uuid
|
||||
delete customField.prefillable
|
||||
delete customField.conditions
|
||||
|
||||
customField.areas?.forEach((area) => {
|
||||
delete area.attachment_uuid
|
||||
delete area.page
|
||||
})
|
||||
|
||||
if (customField.name) {
|
||||
this.customFields.unshift(customField)
|
||||
this.saveCustomFields()
|
||||
} else {
|
||||
this.newCustomField = customField
|
||||
}
|
||||
|
||||
this.setFieldsTab('custom')
|
||||
},
|
||||
setFieldsTab (type) {
|
||||
try {
|
||||
localStorage.setItem('docuseal_builder_tab', type)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
this.showCustomTab = type === 'custom'
|
||||
},
|
||||
saveCustomFields () {
|
||||
return this.baseFetch('/account_custom_fields', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
value: this.customFields
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}).then(async (resp) => {
|
||||
const fields = await resp.json()
|
||||
|
||||
this.customFields.splice(0, this.customFields.length, ...fields)
|
||||
})
|
||||
},
|
||||
detectFields () {
|
||||
const fields = []
|
||||
|
||||
@@ -617,6 +795,47 @@ export default {
|
||||
if (save) {
|
||||
this.save()
|
||||
}
|
||||
},
|
||||
removeCustomField (field) {
|
||||
this.customFields.splice(this.customFields.indexOf(field), 1)
|
||||
|
||||
if (!this.customFields.length) {
|
||||
this.setFieldsTab('default')
|
||||
}
|
||||
|
||||
this.saveCustomFields()
|
||||
},
|
||||
onCustomFieldDragover (e) {
|
||||
if (this.customDragFieldRef.value && this.customFields.includes(this.customDragFieldRef.value)) {
|
||||
const container = this.$refs.customFieldsList
|
||||
const targetField = e.target.closest('[data-uuid]')
|
||||
const dragField = container.querySelector(`[data-uuid="${this.customDragFieldRef.value.uuid}"]`)
|
||||
|
||||
if (dragField && targetField && targetField !== dragField) {
|
||||
const fields = Array.from(container.children)
|
||||
const currentIndex = fields.indexOf(dragField)
|
||||
const targetIndex = fields.indexOf(targetField)
|
||||
|
||||
if (currentIndex < targetIndex) {
|
||||
targetField.after(dragField)
|
||||
} else {
|
||||
targetField.before(dragField)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
reorderCustomFields () {
|
||||
if (!this.customFields.includes(this.customDragFieldRef.value)) {
|
||||
return
|
||||
}
|
||||
|
||||
const reorderedFields = Array.from(this.$refs.customFieldsList.children).map((el) => {
|
||||
return this.customFields.find((f) => f.uuid === el.dataset.uuid)
|
||||
}).filter(Boolean)
|
||||
|
||||
this.customFields.splice(0, this.customFields.length, ...reorderedFields)
|
||||
|
||||
this.saveCustomFields()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
const en = {
|
||||
default: 'Default',
|
||||
save_as_custom_field: 'Save as Custom Field',
|
||||
kba: 'KBA',
|
||||
analyzing_: 'Analyzing...',
|
||||
download: 'Download',
|
||||
downloading_: 'Downloading...',
|
||||
view: 'View',
|
||||
autodetect_fields: 'Autodetect fields',
|
||||
autodetect_fields: 'Autodetect Fields',
|
||||
payment_link: 'Payment link',
|
||||
strikeout: 'Strikeout',
|
||||
draw_strikethrough_the_document: 'Draw strikethrough the document',
|
||||
@@ -200,6 +202,8 @@ const en = {
|
||||
}
|
||||
|
||||
const es = {
|
||||
default: 'Predeterminado',
|
||||
save_as_custom_field: 'Guardar como personalizado',
|
||||
kba: 'KBA',
|
||||
autodetect_fields: 'Autodetectar campos',
|
||||
analyzing_: 'Analizando...',
|
||||
@@ -401,6 +405,8 @@ const es = {
|
||||
}
|
||||
|
||||
const it = {
|
||||
default: 'Predefinito',
|
||||
save_as_custom_field: 'Salva come personalizzato',
|
||||
kba: 'KBA',
|
||||
autodetect_fields: 'Rileva campi',
|
||||
analyzing_: 'Analisi...',
|
||||
@@ -602,6 +608,8 @@ const it = {
|
||||
}
|
||||
|
||||
const pt = {
|
||||
default: 'Padrão',
|
||||
save_as_custom_field: 'Salvar como personalizado',
|
||||
kba: 'KBA',
|
||||
autodetect_fields: 'Detectar campos',
|
||||
analyzing_: 'Analisando...',
|
||||
@@ -803,6 +811,8 @@ const pt = {
|
||||
}
|
||||
|
||||
const fr = {
|
||||
default: 'Par défaut',
|
||||
save_as_custom_field: 'Enregistrer comme personnalisé',
|
||||
kba: 'KBA',
|
||||
autodetect_fields: 'Détecter les champs',
|
||||
analyzing_: 'Analyse...',
|
||||
@@ -1004,6 +1014,8 @@ const fr = {
|
||||
}
|
||||
|
||||
const de = {
|
||||
default: 'Standard',
|
||||
save_as_custom_field: 'Als benutzerdefiniert speichern',
|
||||
kba: 'KBA',
|
||||
autodetect_fields: 'Felder erkennen',
|
||||
analyzing_: 'Analysiere...',
|
||||
@@ -1205,6 +1217,8 @@ const de = {
|
||||
}
|
||||
|
||||
const nl = {
|
||||
default: 'Standaard',
|
||||
save_as_custom_field: 'Opslaan als aangepast',
|
||||
kba: 'KBA',
|
||||
autodetect_fields: 'Velden detecteren',
|
||||
analyzing_: 'Analyseren...',
|
||||
|
||||
@@ -52,15 +52,22 @@
|
||||
@stop-resize="resizeDirection = null"
|
||||
@remove="$emit('remove-area', item.area)"
|
||||
@scroll-to="$emit('scroll-to', $event)"
|
||||
@add-custom-field="$emit('add-custom-field', $event)"
|
||||
@contextmenu="openAreaContextMenu($event, item.area, item.field)"
|
||||
/>
|
||||
<FieldArea
|
||||
v-if="newArea"
|
||||
v-for="(area, index) in newAreas"
|
||||
:key="index"
|
||||
:is-draw="true"
|
||||
:page-width="width"
|
||||
:page-height="height"
|
||||
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || dragFieldPlaceholder?.type || defaultFieldType }"
|
||||
:area="newArea"
|
||||
:field="{ submitter_uuid: selectedSubmitter.uuid, type: newAreaFieldType }"
|
||||
:area="area"
|
||||
/>
|
||||
<div
|
||||
v-if="newAreas.length > 1"
|
||||
class="absolute outline-dashed outline-gray-400 pointer-events-none z-20"
|
||||
:style="newAreasBoxStyle"
|
||||
/>
|
||||
<div
|
||||
v-if="selectionRect"
|
||||
@@ -93,7 +100,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="resizeDirection || isDrag || showMask || (drawField && isMobile) || fieldsDragFieldRef.value || selectionRect"
|
||||
v-show="resizeDirection || isDrag || showMask || (drawField && isMobile) || fieldsDragFieldRef.value || customDragFieldRef?.value || selectionRect"
|
||||
id="mask"
|
||||
ref="mask"
|
||||
class="top-0 bottom-0 left-0 right-0 absolute"
|
||||
@@ -103,7 +110,7 @@
|
||||
@contextmenu="openContextMenu"
|
||||
@dragover.prevent="onDragover"
|
||||
@dragenter="onDragenter"
|
||||
@dragleave="newArea = null"
|
||||
@dragleave="newAreas = []"
|
||||
@drop="onDrop"
|
||||
@pointerup="onPointerup"
|
||||
/>
|
||||
@@ -122,7 +129,7 @@ export default {
|
||||
ContextMenu,
|
||||
SelectionBox
|
||||
},
|
||||
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'assignDropAreaSize', 'selectedAreasRef', 'template', 'isSelectModeRef'],
|
||||
inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'customDragFieldRef', 'assignDropAreaSize', 'selectedAreasRef', 'template', 'isSelectModeRef'],
|
||||
props: {
|
||||
image: {
|
||||
type: Object,
|
||||
@@ -191,6 +198,11 @@ export default {
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
drawCustomField: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -216,13 +228,13 @@ export default {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['draw', 'drop-field', 'remove-area', 'copy-field', 'paste-field', 'scroll-to', 'copy-selected-areas', 'delete-selected-areas', 'align-selected-areas', 'autodetect-fields'],
|
||||
emits: ['draw', 'drop-field', 'remove-area', 'copy-field', 'paste-field', 'scroll-to', 'copy-selected-areas', 'delete-selected-areas', 'align-selected-areas', 'autodetect-fields', 'add-custom-field'],
|
||||
data () {
|
||||
return {
|
||||
areaRefs: [],
|
||||
showMask: false,
|
||||
resizeDirection: null,
|
||||
newArea: null,
|
||||
newAreas: [],
|
||||
contextMenu: null,
|
||||
selectionRect: null,
|
||||
selectionContextMenu: null
|
||||
@@ -274,6 +286,14 @@ export default {
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
newAreaFieldType () {
|
||||
if (this.drawField?.type) return this.drawField.type
|
||||
if (this.drawCustomField?.type) return this.drawCustomField.type
|
||||
if (this.dragFieldPlaceholder?.type) return this.dragFieldPlaceholder.type
|
||||
if (this.customDragFieldRef?.value?.type) return this.customDragFieldRef.value.type
|
||||
|
||||
return this.defaultFieldType
|
||||
},
|
||||
defaultFieldType () {
|
||||
if (this.drawFieldType) {
|
||||
return this.drawFieldType
|
||||
@@ -302,6 +322,21 @@ export default {
|
||||
height () {
|
||||
return this.image.metadata.height
|
||||
},
|
||||
newAreasBoxStyle () {
|
||||
if (this.newAreas.length < 2) return {}
|
||||
|
||||
const minX = Math.min(...this.newAreas.map(a => a.x))
|
||||
const minY = Math.min(...this.newAreas.map(a => a.y))
|
||||
const maxX = Math.max(...this.newAreas.map(a => a.x + a.w))
|
||||
const maxY = Math.max(...this.newAreas.map(a => a.y + a.h))
|
||||
|
||||
return {
|
||||
left: minX * 100 + '%',
|
||||
top: minY * 100 + '%',
|
||||
width: (maxX - minX) * 100 + '%',
|
||||
height: (maxY - minY) * 100 + '%'
|
||||
}
|
||||
},
|
||||
selectionRectStyle () {
|
||||
if (!this.selectionRect) return {}
|
||||
|
||||
@@ -331,7 +366,7 @@ export default {
|
||||
|
||||
const rect = this.$refs.image.getBoundingClientRect()
|
||||
|
||||
this.newArea = null
|
||||
this.newAreas = []
|
||||
this.showMask = false
|
||||
|
||||
this.contextMenu = {
|
||||
@@ -351,7 +386,7 @@ export default {
|
||||
|
||||
const rect = this.$refs.image.getBoundingClientRect()
|
||||
|
||||
this.newArea = null
|
||||
this.newAreas = []
|
||||
this.showMask = false
|
||||
|
||||
this.contextMenu = {
|
||||
@@ -390,7 +425,7 @@ export default {
|
||||
},
|
||||
closeContextMenu () {
|
||||
this.contextMenu = null
|
||||
this.newArea = null
|
||||
this.newAreas = []
|
||||
this.showMask = false
|
||||
},
|
||||
handleCopy () {
|
||||
@@ -410,7 +445,7 @@ export default {
|
||||
this.closeContextMenu()
|
||||
},
|
||||
handlePaste () {
|
||||
this.newArea = null
|
||||
this.newAreas = []
|
||||
this.showMask = false
|
||||
|
||||
this.$emit('paste-field', {
|
||||
@@ -435,22 +470,66 @@ export default {
|
||||
}
|
||||
},
|
||||
onDragenter (e) {
|
||||
this.newArea = {}
|
||||
const customField = this.customDragFieldRef?.value
|
||||
const customAreas = customField?.areas || []
|
||||
const dropX = (e.offsetX - 6) / this.$refs.mask.clientWidth
|
||||
const dropY = e.offsetY / this.$refs.mask.clientHeight
|
||||
|
||||
this.assignDropAreaSize(this.newArea, this.dragFieldPlaceholder, {
|
||||
maskW: this.$refs.mask.clientWidth,
|
||||
maskH: this.$refs.mask.clientHeight
|
||||
})
|
||||
if (customAreas.length > 1) {
|
||||
const refArea = customAreas[0]
|
||||
|
||||
this.newArea.x = (e.offsetX - 6) / this.$refs.mask.clientWidth
|
||||
this.newArea.y = e.offsetY / this.$refs.mask.clientHeight - this.newArea.h / 2
|
||||
this.newAreas = customAreas.map((customArea) => ({
|
||||
x: dropX + (customArea.x - refArea.x),
|
||||
y: dropY + (customArea.y - refArea.y) - (customArea.h / 2),
|
||||
w: customArea.w,
|
||||
h: customArea.h
|
||||
}))
|
||||
} else {
|
||||
const newArea = {}
|
||||
|
||||
if (customAreas.length === 1) {
|
||||
newArea.w = customAreas[0].w
|
||||
newArea.h = customAreas[0].h
|
||||
} else if (customField) {
|
||||
this.assignDropAreaSize(newArea, customField, {
|
||||
maskW: this.$refs.mask.clientWidth,
|
||||
maskH: this.$refs.mask.clientHeight
|
||||
})
|
||||
} else {
|
||||
this.assignDropAreaSize(newArea, this.dragFieldPlaceholder, {
|
||||
maskW: this.$refs.mask.clientWidth,
|
||||
maskH: this.$refs.mask.clientHeight
|
||||
})
|
||||
}
|
||||
|
||||
newArea.x = dropX
|
||||
newArea.y = dropY - newArea.h / 2
|
||||
|
||||
this.newAreas = [newArea]
|
||||
}
|
||||
},
|
||||
onDragover (e) {
|
||||
this.newArea.x = (e.offsetX - 6) / this.$refs.mask.clientWidth
|
||||
this.newArea.y = e.offsetY / this.$refs.mask.clientHeight - this.newArea.h / 2
|
||||
const customField = this.customDragFieldRef?.value
|
||||
const customAreas = customField?.areas || []
|
||||
const dropX = (e.offsetX - 6) / this.$refs.mask.clientWidth
|
||||
const dropY = e.offsetY / this.$refs.mask.clientHeight
|
||||
|
||||
if (customAreas.length > 1) {
|
||||
const refArea = customAreas[0]
|
||||
|
||||
this.newAreas.forEach((newArea, index) => {
|
||||
const customArea = customAreas[index]
|
||||
|
||||
newArea.x = dropX + (customArea.x - refArea.x)
|
||||
newArea.y = dropY + (customArea.y - refArea.y) - (customArea.h / 2)
|
||||
})
|
||||
} else if (this.newAreas.length) {
|
||||
this.newAreas[0].x = dropX
|
||||
this.newAreas[0].y = dropY - this.newAreas[0].h / 2
|
||||
}
|
||||
},
|
||||
onDrop (e) {
|
||||
this.newArea = null
|
||||
this.newAreas = []
|
||||
|
||||
this.$emit('drop-field', {
|
||||
x: e.offsetX,
|
||||
@@ -479,7 +558,7 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isMobile && !this.drawField) {
|
||||
if (this.isMobile && !this.drawField && !this.drawCustomField) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -490,14 +569,14 @@ export default {
|
||||
this.showMask = true
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.newArea = {
|
||||
this.newAreas = [{
|
||||
initialX: e.offsetX / this.$refs.mask.clientWidth,
|
||||
initialY: e.offsetY / this.$refs.mask.clientHeight,
|
||||
x: e.offsetX / this.$refs.mask.clientWidth,
|
||||
y: e.offsetY / this.$refs.mask.clientHeight,
|
||||
w: 0,
|
||||
h: 0
|
||||
}
|
||||
}]
|
||||
})
|
||||
},
|
||||
startSelectionRect (e) {
|
||||
@@ -563,28 +642,30 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.newArea) {
|
||||
const dx = e.offsetX / this.$refs.mask.clientWidth - this.newArea.initialX
|
||||
const dy = e.offsetY / this.$refs.mask.clientHeight - this.newArea.initialY
|
||||
const drawArea = this.newAreas[0]
|
||||
|
||||
if (drawArea?.initialX !== undefined) {
|
||||
const dx = e.offsetX / this.$refs.mask.clientWidth - drawArea.initialX
|
||||
const dy = e.offsetY / this.$refs.mask.clientHeight - drawArea.initialY
|
||||
|
||||
if (dx > 0) {
|
||||
this.newArea.x = this.newArea.initialX
|
||||
drawArea.x = drawArea.initialX
|
||||
} else {
|
||||
this.newArea.x = e.offsetX / this.$refs.mask.clientWidth
|
||||
drawArea.x = e.offsetX / this.$refs.mask.clientWidth
|
||||
}
|
||||
|
||||
if (dy > 0) {
|
||||
this.newArea.y = this.newArea.initialY
|
||||
drawArea.y = drawArea.initialY
|
||||
} else {
|
||||
this.newArea.y = e.offsetY / this.$refs.mask.clientHeight
|
||||
drawArea.y = e.offsetY / this.$refs.mask.clientHeight
|
||||
}
|
||||
|
||||
if ((this.drawField?.type || this.drawFieldType) === 'cells') {
|
||||
this.newArea.cell_w = this.newArea.h * (this.$refs.mask.clientHeight / this.$refs.mask.clientWidth)
|
||||
if ((this.drawField?.type || this.drawCustomField?.type || this.drawFieldType) === 'cells') {
|
||||
drawArea.cell_w = drawArea.h * (this.$refs.mask.clientHeight / this.$refs.mask.clientWidth)
|
||||
}
|
||||
|
||||
this.newArea.w = Math.abs(dx)
|
||||
this.newArea.h = Math.abs(dy)
|
||||
drawArea.w = Math.abs(dx)
|
||||
drawArea.h = Math.abs(dy)
|
||||
}
|
||||
},
|
||||
onPointerup (e) {
|
||||
@@ -601,29 +682,33 @@ export default {
|
||||
})
|
||||
|
||||
this.selectionRect = null
|
||||
} else if (this.newArea) {
|
||||
const area = {
|
||||
x: this.newArea.x,
|
||||
y: this.newArea.y,
|
||||
w: this.newArea.w,
|
||||
h: this.newArea.h,
|
||||
page: this.number
|
||||
} else {
|
||||
const drawArea = this.newAreas[0]
|
||||
|
||||
if (drawArea?.initialX !== undefined) {
|
||||
const area = {
|
||||
x: drawArea.x,
|
||||
y: drawArea.y,
|
||||
w: drawArea.w,
|
||||
h: drawArea.h,
|
||||
page: this.number
|
||||
}
|
||||
|
||||
if ('cell_w' in drawArea) {
|
||||
area.cell_w = drawArea.cell_w
|
||||
}
|
||||
|
||||
const dx = Math.abs(e.offsetX - this.$refs.mask.clientWidth * drawArea.initialX)
|
||||
const dy = Math.abs(e.offsetY - this.$refs.mask.clientHeight * drawArea.initialY)
|
||||
|
||||
const isTooSmall = dx < 8 && dy < 8
|
||||
|
||||
this.$emit('draw', { area, isTooSmall })
|
||||
}
|
||||
|
||||
if ('cell_w' in this.newArea) {
|
||||
area.cell_w = this.newArea.cell_w
|
||||
}
|
||||
|
||||
const dx = Math.abs(e.offsetX - this.$refs.mask.clientWidth * this.newArea.initialX)
|
||||
const dy = Math.abs(e.offsetY - this.$refs.mask.clientHeight * this.newArea.initialY)
|
||||
|
||||
const isTooSmall = dx < 8 && dy < 8
|
||||
|
||||
this.$emit('draw', { area, isTooSmall })
|
||||
}
|
||||
|
||||
this.showMask = false
|
||||
this.newArea = null
|
||||
this.newAreas = []
|
||||
},
|
||||
rectsOverlap (r1, r2) {
|
||||
return !(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<span
|
||||
class="dropdown dropdown-end field-settings-dropdown"
|
||||
:class="{ 'dropdown-open': ((!field.preferences?.price && !field.preferences?.formula && !field.preferences?.price_id && !field.preferences?.payment_link_id) || !isConnected) && !isLoading }"
|
||||
:class="{ 'dropdown-open': withForceOpen && ((!field.preferences?.price && !field.preferences?.formula && !field.preferences?.price_id && !field.preferences?.payment_link_id) || !isConnected) && !isLoading }"
|
||||
>
|
||||
<label
|
||||
tabindex="0"
|
||||
@@ -231,7 +231,10 @@
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li class="mt-1">
|
||||
<li
|
||||
v-if="withCondition"
|
||||
class="mt-1"
|
||||
>
|
||||
<label
|
||||
class="label-text cursor-pointer text-center w-full flex items-center"
|
||||
@click="$emit('click-condition')"
|
||||
@@ -244,12 +247,29 @@
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<hr
|
||||
v-if="withCustomFields"
|
||||
class="pb-0.5 mt-0.5"
|
||||
>
|
||||
<li v-if="withCustomFields">
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm py-1 px-2"
|
||||
@click.prevent="$emit('add-custom-field', field)"
|
||||
>
|
||||
<IconForms
|
||||
:width="20"
|
||||
:stroke-width="1.6"
|
||||
/>
|
||||
{{ t('save_as_custom_field') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconMathFunction, IconSettings, IconCircleCheck, IconInfoCircle, IconBrandStripe, IconInnerShadowTop, IconRouteAltLeft } from '@tabler/icons-vue'
|
||||
import { IconMathFunction, IconSettings, IconCircleCheck, IconInfoCircle, IconBrandStripe, IconInnerShadowTop, IconRouteAltLeft, IconForms } from '@tabler/icons-vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const isConnected = ref(false)
|
||||
@@ -261,6 +281,7 @@ export default {
|
||||
IconCircleCheck,
|
||||
IconRouteAltLeft,
|
||||
IconInfoCircle,
|
||||
IconForms,
|
||||
IconMathFunction,
|
||||
IconInnerShadowTop,
|
||||
IconBrandStripe
|
||||
@@ -270,9 +291,24 @@ export default {
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
withForceOpen: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
withCustomFields: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withCondition: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['click-condition', 'click-description', 'click-formula'],
|
||||
emits: ['click-condition', 'click-description', 'click-formula', 'add-custom-field'],
|
||||
data () {
|
||||
return {
|
||||
isLoading: false
|
||||
|
||||
@@ -47,12 +47,14 @@ class AccountConfig < ApplicationRecord
|
||||
WITH_SIGNATURE_ID_REASON_KEY = 'with_signature_id_reason'
|
||||
RECIPIENT_FORM_FIELDS_KEY = 'recipient_form_fields'
|
||||
WITH_AUDIT_VALUES_KEY = 'with_audit_values'
|
||||
WITH_AUDIT_SENDER_KEY = 'with_audit_sender'
|
||||
WITH_SUBMITTER_TIMEZONE_KEY = 'with_submitter_timezone'
|
||||
REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason'
|
||||
REUSE_SIGNATURE_KEY = 'reuse_signature'
|
||||
WITH_FIELD_LABELS_KEY = 'with_field_labels'
|
||||
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
|
||||
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
|
||||
TEMPLATE_CUSTOM_FIELDS_KEY = 'template_custom_fields'
|
||||
POLICY_LINKS_KEY = 'policy_links'
|
||||
|
||||
DEFAULT_VALUES = {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<% uuid = local_assigns[:uuid] || SecureRandom.uuid %>
|
||||
<label id="<%= local_assigns[:button_id] %>" for="<%= uuid %>" class="<%= local_assigns[:btn_class] %>"><%= local_assigns[:btn_text] %></label>
|
||||
<input type="checkbox" id="<%= uuid %>" class="modal-toggle">
|
||||
<div id="<%= local_assigns[:id] %>" class="modal items-start !animate-none overflow-y-auto">
|
||||
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none">
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<% link = pagy_anchor(pagy) %>
|
||||
<% if pagy.pages > 1 %>
|
||||
<div class="flex my-6 justify-center md:justify-between">
|
||||
<div class="hidden md:block text-sm">
|
||||
@@ -12,8 +11,8 @@
|
||||
<div class="flex items-center space-x-1.5">
|
||||
<%= local_assigns[:right_additional_html] %>
|
||||
<div class="join">
|
||||
<% if pagy.prev %>
|
||||
<%== link.call(pagy.prev, '«', classes: 'join-item btn min-h-full h-10') %>
|
||||
<% if pagy.previous %>
|
||||
<%= link_to '«', url_for(params: request.query_parameters.merge('page' => pagy.previous)), class: 'join-item btn min-h-full h-10' %>
|
||||
<% else %>
|
||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">«</span>
|
||||
<% end %>
|
||||
@@ -23,7 +22,7 @@
|
||||
<% if local_assigns[:next_page_path].present? %>
|
||||
<%= link_to '»', local_assigns[:next_page_path], class: 'join-item btn min-h-full h-10' %>
|
||||
<% elsif pagy.next %>
|
||||
<%== link.call(pagy.next, '»', classes: 'join-item btn min-h-full h-10') %>
|
||||
<%= link_to '»', url_for(params: request.query_parameters.merge('page' => pagy.next)), class: 'join-item btn min-h-full h-10' %>
|
||||
<% else %>
|
||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">»</span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
<form action="<%= url_for %>" method="get" class="items-center flex">
|
||||
<% if params[:status].present? %>
|
||||
<input name="status" value="<%= params[:status] %>" class="hidden">
|
||||
<% end %>
|
||||
<% Submissions::Filter::ALLOWED_PARAMS.each do |key| %>
|
||||
<% if params[key].present? %>
|
||||
<input name="<%= key %>" value="<%= params[key] %>" class="hidden">
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
|
||||
<% schema = Submissions.filtered_conditions_schema(@submitter.submission, values:, include_submitter_uuid: @submitter.uuid) %>
|
||||
<% font_scale = 1000.0 / PdfUtils::US_LETTER_W %>
|
||||
<% decline_modal_checkbox_uuid = nil %>
|
||||
<div style="max-height: -webkit-fill-available;">
|
||||
<div id="scrollbox">
|
||||
<div class="mx-auto block pb-72" style="max-width: 1000px">
|
||||
@@ -20,12 +21,7 @@
|
||||
</div>
|
||||
<div class="flex items-center space-x-2" style="margin-left: 20px; flex-shrink: 0">
|
||||
<% if @form_configs[:with_decline] %>
|
||||
<% decline_modal_checkbox_uuid = SecureRandom.uuid %>
|
||||
<div>
|
||||
<%= render 'shared/html_modal', title: t(:decline), btn_text: t(:decline), btn_class: 'btn btn-sm !px-5', button_id: 'decline_button', uuid: decline_modal_checkbox_uuid do %>
|
||||
<%= render 'submit_form/decline_form', submitter: @submitter %>
|
||||
<% end %>
|
||||
</div>
|
||||
<label id="decline_button" for="<%= decline_modal_checkbox_uuid = SecureRandom.uuid %>" class="btn btn-sm !px-5"><%= t(:decline) %></label>
|
||||
<% end %>
|
||||
<% if @form_configs[:with_partial_download] %>
|
||||
<download-button data-src="<%= submit_form_download_index_path(@submitter.slug) %>" class="btn btn-neutral text-white btn-sm !px-4">
|
||||
@@ -102,11 +98,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed bottom-0 w-full h-0 z-20">
|
||||
<div class="fixed bottom-0 w-full h-0 z-50">
|
||||
<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'), schema: %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if @form_configs[:with_decline] %>
|
||||
<%= render 'shared/html_modal', title: t(:decline), uuid: decline_modal_checkbox_uuid do %>
|
||||
<%= render 'submit_form/decline_form', submitter: @submitter %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render 'scripts/autosize_field' %>
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
<%= button_to nil, user_configs_path, method: :post, params: { user_config: { key: UserConfig::SHOW_APP_TOUR, value: true } }, class: 'hidden', id: 'start_tour_button' %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<template-builder class="grid" data-template="<%= @template_data %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
|
||||
<template-builder class="grid" data-template="<%= @template_data %>" data-custom-fields="<%= (current_account.account_configs.find_or_initialize_by(key: AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY).value || []).to_json %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>"></template-builder>
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
</div>
|
||||
<div class="flex items-center space-x-1.5">
|
||||
<div class="join">
|
||||
<% if @pagy.prev %>
|
||||
<%= link_to '«', url_for(page: @pagy.prev, anchor: 'log'), class: 'join-item btn min-h-full h-10' %>
|
||||
<% if @pagy.previous %>
|
||||
<%= link_to '«', url_for(params: request.query_parameters.merge('page' => @pagy.previous), anchor: 'log'), class: 'join-item btn min-h-full h-10' %>
|
||||
<% else %>
|
||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">«</span>
|
||||
<% end %>
|
||||
@@ -114,7 +114,7 @@
|
||||
<%= "Page #{@pagy.page}" %>
|
||||
</span>
|
||||
<% if @pagy.next %>
|
||||
<%= link_to '»', url_for(page: @pagy.next, anchor: 'log'), class: 'join-item btn min-h-full h-10' %>
|
||||
<%= link_to '»', url_for(params: request.query_parameters.merge('page' => @pagy.next), anchor: 'log'), class: 'join-item btn min-h-full h-10' %>
|
||||
<% else %>
|
||||
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">»</span>
|
||||
<% end %>
|
||||
|
||||
@@ -17,7 +17,7 @@ Bundler.require(*Rails.groups)
|
||||
|
||||
module DocuSeal
|
||||
class Application < Rails::Application
|
||||
config.load_defaults 8.0
|
||||
config.load_defaults 8.1
|
||||
|
||||
config.autoload_lib(ignore: %w[assets tasks puma])
|
||||
|
||||
|
||||
+20
-4
@@ -60,12 +60,28 @@ if ENV['RAILS_ENV'] == 'production'
|
||||
ENV['DATABASE_URL'] = ENV['DATABASE_URL'].to_s.empty? ? database_url : ENV.fetch('DATABASE_URL', nil)
|
||||
end
|
||||
|
||||
unless Process.uid == 2000
|
||||
unless Process.euid == 2000
|
||||
begin
|
||||
Process::Sys.setgid(2000)
|
||||
Process::Sys.setuid(2000)
|
||||
test_file = "#{ENV.fetch('WORKDIR', '.')}/test"
|
||||
|
||||
orig_euid = Process.euid
|
||||
orig_egid = Process.egid
|
||||
|
||||
Process::Sys.setegid(2000)
|
||||
Process::Sys.seteuid(2000)
|
||||
|
||||
File.open(test_file, 'w') { true }
|
||||
rescue StandardError
|
||||
puts 'Unable to run as 2000:2000'
|
||||
Process::Sys.seteuid(orig_euid)
|
||||
Process::Sys.setegid(orig_egid)
|
||||
|
||||
puts "Unable to run as 2000:2000, running as #{orig_euid}:#{orig_egid}"
|
||||
ensure
|
||||
begin
|
||||
File.unlink(test_file)
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -86,7 +86,7 @@ Rails.application.configure do
|
||||
user_name: ENV.fetch('SMTP_USERNAME', nil),
|
||||
password: ENV.fetch('SMTP_PASSWORD', nil),
|
||||
authentication: ENV.fetch('SMTP_PASSWORD', nil).present? ? ENV.fetch('SMTP_AUTHENTICATION', 'plain') : nil,
|
||||
enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] != 'false'
|
||||
enable_starttls: ENV['SMTP_ENABLE_STARTTLS'] != 'false'
|
||||
}.compact
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
autoload :CSV, 'csv'
|
||||
autoload :CSVSafe, 'csv-safe'
|
||||
autoload :RubyXL, 'rubyXL'
|
||||
autoload :Zip, 'zip'
|
||||
autoload :Numo, 'numo/narray'
|
||||
autoload :OnnxRuntime, 'onnxruntime'
|
||||
autoload :RQRCode, 'rqrcode'
|
||||
autoload :ArabicLetterConnector, 'arabic-letter-connector/logic'
|
||||
autoload :JWT, 'jwt'
|
||||
@@ -1,3 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
@@ -1,10 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pagy/extras/countless'
|
||||
|
||||
Pagy::DEFAULT[:limit] = 10
|
||||
Pagy::DEFAULT.freeze
|
||||
|
||||
ActiveSupport.on_load(:action_view) do
|
||||
include Pagy::Frontend
|
||||
end
|
||||
Pagy.options[:limit] = 10
|
||||
|
||||
@@ -29,6 +29,8 @@ en: &en
|
||||
pro: Pro
|
||||
thanks: Thanks
|
||||
private: Private
|
||||
make_all_newly_created_templates_private_to_their_creator_and_admins_by_default: Make all newly created templates private to their creator and admins by default.
|
||||
create_templates_with_admin_access_by_default: Create templates with admin access by default
|
||||
require_email_2fa: Require email 2FA
|
||||
when_checked_each_signer_must_verify_their_email_with_a_one_time_code_before_accessing_the_document: When checked, each signer must verify their email with a one-time code before accessing the document.
|
||||
require_phone_2fa: Require phone 2FA
|
||||
@@ -1029,6 +1031,8 @@ es: &es
|
||||
stripe_account_has_been_connected: La cuenta de Stripe ha sido conectada.
|
||||
re_connect_stripe: Volver a conectar Stripe
|
||||
private: Privado
|
||||
make_all_newly_created_templates_private_to_their_creator_and_admins_by_default: Hacer que todas las plantillas recién creadas sean privadas para su creador y los administradores por defecto.
|
||||
create_templates_with_admin_access_by_default: Crear plantillas con acceso de administrador por defecto
|
||||
require_email_2fa: Requerir 2FA por correo electrónico
|
||||
when_checked_each_signer_must_verify_their_email_with_a_one_time_code_before_accessing_the_document: Cuando está marcado, cada firmante debe verificar su correo electrónico con un código de un solo uso antes de acceder al documento.
|
||||
require_phone_2fa: Requerir 2FA por teléfono
|
||||
@@ -2011,6 +2015,8 @@ it: &it
|
||||
stripe_account_has_been_connected: L'account Stripe è stato collegato.
|
||||
re_connect_stripe: Ricollega Stripe
|
||||
private: Privato
|
||||
make_all_newly_created_templates_private_to_their_creator_and_admins_by_default: Rendere tutte le nuove template private per il creatore e gli amministratori per impostazione predefinita.
|
||||
create_templates_with_admin_access_by_default: Crea modelli con accesso amministratore per impostazione predefinita
|
||||
require_email_2fa: Richiedi 2FA email
|
||||
when_checked_each_signer_must_verify_their_email_with_a_one_time_code_before_accessing_the_document: Quando selezionato, ogni firmatario deve verificare la propria email con un codice monouso prima di accedere al documento.
|
||||
require_phone_2fa: Richiedi 2FA telefono
|
||||
@@ -2994,6 +3000,8 @@ fr: &fr
|
||||
stripe_account_has_been_connected: Le compte Stripe a été connecté.
|
||||
re_connect_stripe: Reconnecter Stripe
|
||||
private: Privé
|
||||
make_all_newly_created_templates_private_to_their_creator_and_admins_by_default: Rendre tous les nouveaux modèles privés pour leur créateur et les administrateurs par défaut.
|
||||
create_templates_with_admin_access_by_default: Créer des modèles avec un accès administrateur par défaut
|
||||
require_email_2fa: Exiger la 2FA par email
|
||||
when_checked_each_signer_must_verify_their_email_with_a_one_time_code_before_accessing_the_document: Lorsque coché, chaque signataire doit vérifier son email avec un code à usage unique avant d'accéder au document.
|
||||
require_phone_2fa: Exiger la 2FA par téléphone
|
||||
@@ -3973,6 +3981,8 @@ pt: &pt
|
||||
stripe_account_has_been_connected: Conta Stripe foi conectada.
|
||||
re_connect_stripe: Reconectar Stripe
|
||||
private: Privado
|
||||
make_all_newly_created_templates_private_to_their_creator_and_admins_by_default: Tornar todos os modelos recém-criados privados para seu criador e administradores por padrão.
|
||||
create_templates_with_admin_access_by_default: Criar modelos com acesso de administrador por padrão
|
||||
require_email_2fa: Exigir 2FA por email
|
||||
when_checked_each_signer_must_verify_their_email_with_a_one_time_code_before_accessing_the_document: Quando marcado, cada signatário deve verificar seu email com um código de uso único antes de acessar o documento.
|
||||
require_phone_2fa: Exigir 2FA por telefone
|
||||
@@ -4941,6 +4951,8 @@ de: &de
|
||||
pro: Pro
|
||||
thanks: Danke
|
||||
private: Privat
|
||||
make_all_newly_created_templates_private_to_their_creator_and_admins_by_default: Alle neu erstellten Vorlagen standardmäßig nur für ihren Ersteller und Administratoren sichtbar machen.
|
||||
create_templates_with_admin_access_by_default: Vorlagen standardmäßig mit Administratorzugriff erstellen
|
||||
require_email_2fa: E-Mail 2FA erforderlich
|
||||
when_checked_each_signer_must_verify_their_email_with_a_one_time_code_before_accessing_the_document: Wenn aktiviert, muss jeder Unterzeichner seine E-Mail mit einem einmaligen Code verifizieren, bevor er auf das Dokument zugreifen kann.
|
||||
require_phone_2fa: Telefon 2FA erforderlich
|
||||
@@ -6311,6 +6323,8 @@ nl: &nl
|
||||
pro: Pro
|
||||
thanks: Bedankt
|
||||
private: Privé
|
||||
make_all_newly_created_templates_private_to_their_creator_and_admins_by_default: Maak alle nieuw aangemaakte sjablonen standaard privé voor hun maker en admins.
|
||||
create_templates_with_admin_access_by_default: Sjablonen standaard met admin-toegang maken
|
||||
require_email_2fa: E-mail 2FA vereist
|
||||
when_checked_each_signer_must_verify_their_email_with_a_one_time_code_before_accessing_the_document: Wanneer aangevinkt, moet elke ondertekenaar zijn e-mail verifiëren met een eenmalige code voordat hij toegang krijgt tot het document.
|
||||
require_phone_2fa: Telefoon 2FA vereist
|
||||
|
||||
@@ -53,6 +53,7 @@ Rails.application.routes.draw do
|
||||
resources :verify_pdf_signature, only: %i[create]
|
||||
resource :mfa_setup, only: %i[show new edit create destroy], controller: 'mfa_setup'
|
||||
resources :account_configs, only: %i[create destroy]
|
||||
resources :account_custom_fields, only: %i[create]
|
||||
resources :user_configs, only: %i[create]
|
||||
resources :encrypted_user_configs, only: %i[destroy]
|
||||
resources :timestamp_server, only: %i[create]
|
||||
|
||||
@@ -8,7 +8,7 @@ default: &default
|
||||
shakapacker_precompile: true
|
||||
webpack_compile_output: true
|
||||
additional_paths: []
|
||||
webpack_loader: 'babel'
|
||||
javascript_transpiler: 'babel'
|
||||
compiler_strategy: digest
|
||||
cache_manifest: false
|
||||
ensure_consistent_versioning: false
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ google:
|
||||
cache_control: "public, max-age=31536000"
|
||||
|
||||
azure:
|
||||
service: AzureStorage
|
||||
service: AzureBlob
|
||||
storage_account_name: <%= ENV['AZURE_STORAGE_ACCOUNT_NAME'] %>
|
||||
storage_access_key: <%= ENV['AZURE_STORAGE_ACCESS_KEY'] %>
|
||||
container: <%= ENV['AZURE_CONTAINER'] %>
|
||||
|
||||
@@ -45,6 +45,9 @@ module ActionMailerConfigsInterceptor
|
||||
def build_smtp_configs_hash(email_configs)
|
||||
value = email_configs.value
|
||||
|
||||
is_tls = value['security'] == 'tls' || (value['security'].blank? && value['port'].to_s == '465')
|
||||
is_ssl = value['security'] == 'ssl'
|
||||
|
||||
{
|
||||
user_name: value['username'],
|
||||
password: value['password'],
|
||||
@@ -53,11 +56,11 @@ module ActionMailerConfigsInterceptor
|
||||
domain: value['domain'],
|
||||
openssl_verify_mode: value['security'] == 'noverify' ? OpenSSL::SSL::VERIFY_NONE : nil,
|
||||
authentication: value['password'].present? ? value.fetch('authentication', 'plain') : nil,
|
||||
enable_starttls_auto: value['security'] != 'tls',
|
||||
enable_starttls: !is_tls && !is_ssl,
|
||||
open_timeout: OPEN_TIMEOUT,
|
||||
read_timeout: READ_TIMEOUT,
|
||||
ssl: value['security'] == 'ssl',
|
||||
tls: value['security'] == 'tls' || (value['security'].blank? && value['port'].to_s == '465')
|
||||
ssl: is_ssl,
|
||||
tls: is_tls
|
||||
}.compact_blank
|
||||
end
|
||||
end
|
||||
|
||||
+1
-3
@@ -364,9 +364,7 @@ class Pdfium
|
||||
@closed
|
||||
end
|
||||
|
||||
def form_handle
|
||||
@document.form_handle
|
||||
end
|
||||
delegate :form_handle, to: :@document
|
||||
|
||||
def ensure_not_closed!
|
||||
raise PdfiumError, 'Page is closed.' if closed?
|
||||
|
||||
@@ -9,7 +9,7 @@ Puma::Plugin.create do
|
||||
|
||||
@puma_pid = $PROCESS_ID
|
||||
|
||||
launcher.events.on_booted do
|
||||
launcher.events.after_booted do
|
||||
@redis_server_pid = fork_redis
|
||||
end
|
||||
|
||||
@@ -19,8 +19,8 @@ Puma::Plugin.create do
|
||||
stop_redis_server if Process.pid == @puma_pid
|
||||
end
|
||||
|
||||
launcher.events.on_stopped { stop_redis_server }
|
||||
launcher.events.on_restart { stop_redis_server }
|
||||
launcher.events.after_stopped { stop_redis_server }
|
||||
launcher.events.before_restart { stop_redis_server }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -14,14 +14,14 @@ Puma::Plugin.create do
|
||||
end
|
||||
|
||||
def start(launcher)
|
||||
launcher.events.on_booted do
|
||||
launcher.events.after_booted do
|
||||
next if Puma.stats_hash[:workers].to_i != 0
|
||||
|
||||
start_sidekiq!
|
||||
end
|
||||
|
||||
launcher.events.on_stopped { Thread.new { @sidekiq&.stop }.join }
|
||||
launcher.events.on_restart { Thread.new { @sidekiq&.stop }.join }
|
||||
launcher.events.after_stopped { Thread.new { @sidekiq&.stop }.join }
|
||||
launcher.events.before_restart { Thread.new { @sidekiq&.stop }.join }
|
||||
end
|
||||
|
||||
def fire_event(config, event)
|
||||
|
||||
@@ -115,6 +115,8 @@ module Submissions
|
||||
next if item['invite_by_uuid'].blank? && item['optional_invite_by_uuid'].blank?
|
||||
next if submission.template_submitters.any? { |e| e['uuid'] == item['uuid'] }
|
||||
|
||||
item = item.merge('order' => submitter_attr['order']) if submitter_attr && submitter_attr['order'].present?
|
||||
|
||||
if index.zero?
|
||||
submission.template_submitters.insert(1, item)
|
||||
elsif submission.template_submitters.size > index
|
||||
|
||||
@@ -19,7 +19,7 @@ module Submissions
|
||||
total_wait_time ||= 0
|
||||
key = ['result_attachments', submitter.id].join(':')
|
||||
|
||||
return submitter.documents if ApplicationRecord.uncached { LockEvent.exists?(key:, event_name: :complete) }
|
||||
return submitter.documents.reset if ApplicationRecord.uncached { LockEvent.exists?(key:, event_name: :complete) }
|
||||
|
||||
events = ApplicationRecord.uncached { LockEvent.where(key:).order(:id).to_a }
|
||||
|
||||
@@ -60,7 +60,7 @@ module Submissions
|
||||
LockEvent.where(key: ['result_attachments', submitter.id].join(':')).order(:id).last
|
||||
end
|
||||
|
||||
break submitter.documents.reload if last_event.event_name.in?(%w[complete fail])
|
||||
break submitter.documents.reset if last_event.event_name.in?(%w[complete fail])
|
||||
|
||||
raise WaitForCompleteTimeout if total_wait_time > CHECK_COMPLETE_TIMEOUT
|
||||
end
|
||||
|
||||
@@ -116,6 +116,7 @@ module Submissions
|
||||
configs = submission.account.account_configs.where(key: [AccountConfig::WITH_AUDIT_VALUES_KEY,
|
||||
AccountConfig::WITH_SIGNATURE_ID,
|
||||
AccountConfig::WITH_FILE_LINKS_KEY,
|
||||
AccountConfig::WITH_AUDIT_SENDER_KEY,
|
||||
AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY])
|
||||
|
||||
last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at)
|
||||
@@ -123,6 +124,7 @@ module Submissions
|
||||
with_signature_id = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true
|
||||
with_file_links = configs.find { |c| c.key == AccountConfig::WITH_FILE_LINKS_KEY }&.value == true
|
||||
with_audit_values = configs.find { |c| c.key == AccountConfig::WITH_AUDIT_VALUES_KEY }&.value != false
|
||||
with_audit_sender = configs.find { |c| c.key == AccountConfig::WITH_AUDIT_SENDER_KEY }&.value == true
|
||||
with_submitter_timezone = configs.find { |c| c.key == AccountConfig::WITH_SUBMITTER_TIMEZONE_KEY }&.value == true
|
||||
|
||||
timezone = account.timezone
|
||||
@@ -464,6 +466,11 @@ module Submissions
|
||||
name].join(' ')
|
||||
I18n.t('submission_event_names.invite_party_by_html', invited_submitter_name:,
|
||||
submitter_name:)
|
||||
elsif with_audit_sender && (event.event_type == 'send_email' || event.event_type == 'send_sms')
|
||||
[
|
||||
I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:),
|
||||
"<b>#{I18n.t(:from)}</b> #{submission.created_by_user.full_name} #{submission.created_by_user.email}"
|
||||
].join("\n")
|
||||
elsif event.event_type.include?('send_')
|
||||
I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:)
|
||||
else
|
||||
@@ -472,11 +479,20 @@ module Submissions
|
||||
|
||||
bold_text, normal_text = text.match(%r{<b>(.*?)</b>(.*)}).captures
|
||||
|
||||
text_box = [{ text: bold_text, font: [FONT_NAME, { variant: :bold }] }, normal_text]
|
||||
|
||||
if text.include?("\n")
|
||||
text_box = text.split("\n")[1..].reduce(text_box) do |acc, row|
|
||||
bold_text, normal_text = row.match(%r{<b>(.*?)</b>(.*)}).captures
|
||||
|
||||
[*acc, "\n", { text: bold_text, font: [FONT_NAME, { variant: :bold }] }, normal_text]
|
||||
end
|
||||
end
|
||||
|
||||
[
|
||||
"#{I18n.l(event.event_timestamp.in_time_zone(timezone), format: :long, locale: account.locale)} " \
|
||||
"#{TimeUtils.timezone_abbr(timezone, event.event_timestamp)}",
|
||||
composer.document.layout.formatted_text_box([{ text: bold_text, font: [FONT_NAME, { variant: :bold }] },
|
||||
normal_text])
|
||||
composer.document.layout.formatted_text_box(text_box)
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ module Submissions
|
||||
width = page.box.width
|
||||
height = page.box.height
|
||||
|
||||
preferences_font_size = field.dig('preferences', 'font_size').then { |num| num.present? ? num.to_i : nil }
|
||||
preferences_font_size = field.dig('preferences', 'font_size').then { |num| num.presence&.to_i }
|
||||
|
||||
font_size = preferences_font_size
|
||||
font_size ||= (([page.box.width, page.box.height].min / A4_SIZE[0].to_f) * FONT_SIZE).to_i
|
||||
|
||||
@@ -35,7 +35,7 @@ module Submissions
|
||||
conn = Faraday.new(uri.origin) do |c|
|
||||
c.options.read_timeout = TIMEOUT
|
||||
c.options.open_timeout = TIMEOUT
|
||||
c.basic_auth(uri.user, uri.password) if uri.password.present?
|
||||
c.request :authorization, :basic, uri.user, uri.password if uri.password.present?
|
||||
end
|
||||
|
||||
response = conn.post(uri.request_uri, build_payload(digest.digest),
|
||||
|
||||
@@ -55,7 +55,7 @@ module Submitters
|
||||
def serialize_events(events)
|
||||
events.map do |event|
|
||||
event.as_json(only: %i[id submitter_id event_type event_timestamp])
|
||||
.merge('data' => event.data.slice('reason', 'firstname', 'lastname', 'method', 'country'))
|
||||
.merge('data' => event.data.slice('reason', 'firstname', 'lastname', 'method', 'country', 'idcode'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+13
-11
@@ -22,34 +22,36 @@
|
||||
"canvas-confetti": "^1.6.0",
|
||||
"chart.js": "^4.5.1",
|
||||
"codemirror": "^6.0.2",
|
||||
"compression-webpack-plugin": "10.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"compression-webpack-plugin": "11.1.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"daisyui": "^3.9.4",
|
||||
"driver.js": "^1.3.5",
|
||||
"mathjs": "^12.4.0",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"mini-css-extract-plugin": "^2.10.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-loader": "^7.3.0",
|
||||
"qr-creator": "^1.0.0",
|
||||
"rollbar": "^2.26.4",
|
||||
"sass": "^1.62.1",
|
||||
"sass-loader": "^13.2.2",
|
||||
"shakapacker": "8.0.0",
|
||||
"sass-loader": "^16.0.6",
|
||||
"shakapacker": "9.5.0",
|
||||
"signature_pad": "^4.1.5",
|
||||
"snarkdown": "^2.0.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"terser-webpack-plugin": "5.3.8",
|
||||
"terser-webpack-plugin": "5.3.16",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.3.2",
|
||||
"vue-loader": "^17.1.1",
|
||||
"webpack": "5.94.0",
|
||||
"webpack-assets-manifest": "5",
|
||||
"webpack": "5.104.1",
|
||||
"webpack-assets-manifest": "6.5.0",
|
||||
"webpack-bundle-analyzer": "^4.7.0",
|
||||
"webpack-cli": "5.1.1",
|
||||
"webpack-dev-server": "^4.15.0",
|
||||
"webpack-merge": "5"
|
||||
"webpack-cli": "6.0.1",
|
||||
"webpack-dev-server": "^5.2.3",
|
||||
"webpack-merge": "6.0.1",
|
||||
"webpack-subresource-integrity": "^5.1.0"
|
||||
},
|
||||
"packageManager": "yarn@1.0",
|
||||
"version": "0.1.0",
|
||||
|
||||
Reference in New Issue
Block a user