Compare commits

...

188 Commits

Author SHA1 Message Date
Vincent LAURENT c4c2183132 Preserve the last text size for a pdf session 2023-07-22 01:18:40 +02:00
Gabriel Poma c375b43b14 Modal to inform that changes will be lost if the page is reloaded or closed 2023-07-19 14:34:41 +02:00
Vincent LAURENT 06bac144dd Bug js all cases was not planned 2023-07-18 10:36:38 +02:00
Vincent LAURENT d5f4eca0ff Button to display help point 2023-07-18 10:28:00 +02:00
Vincent LAURENT f4bab4ac5f Help message for signing a pdf with several people for the first time, because the interface could suggest that creating the signature is enough. 2023-07-18 01:57:22 +02:00
Vincent LAURENT 26aac0af94 Merge branch 'master' of github.com:24eme/signaturepdf 2023-05-20 02:08:15 +02:00
Vincent LAURENT f9c413d85a Retrieving the current commit number to display it in the footer of each page 2023-05-20 02:05:24 +02:00
Gabriel 00bf081319
Merge pull request #60 from kosssi/master 2023-05-15 14:35:25 +02:00
Gabriel 1023ebd347
Merge pull request #59 from xgaia/fix/docker 2023-05-12 16:36:10 +02:00
Simon C 5f703890ac feat: Update php version on Docker image 2023-05-12 16:01:32 +02:00
xgaia 37a678e9db chore: fix owner of the data dir 2023-05-12 12:22:58 +02:00
Vincent LAURENT 357bffc55f organization: The first upload field allows you to choose several files 2023-05-04 00:45:20 +02:00
Vincent LAURENT fee27a0311 Force js update 2023-05-03 01:49:14 +02:00
Étienne Deparis a1509a554f Message to inform that in private browsing mode (on firefox) the application does not work because opening the cache is impossible 2023-05-03 01:45:37 +02:00
Vincent LAURENT 30e7ffef89 metadata: the variable maxSize was not defined 2023-05-03 01:10:56 +02:00
Vincent LAURENT ada82de930 Force js reload to activate revert commit "prompt rechargement de la
page"
2023-05-03 01:03:46 +02:00
Vincent LAURENT 4646ad35a7 The true value of the PDF_DEMO_LINK option was not working 2023-05-03 01:00:41 +02:00
Vincent LAURENT c19f1af646
metadata: adding pdf-lib dépendance in documentation 2023-05-02 23:54:01 +02:00
Vincent LAURENT d6b6b43ac2 Merge pull request #55 from xgaia/fix/dockerfile
Fix/dockerfile
2023-05-02 23:46:23 +02:00
xgaia 7d4ae2907f style: prettier on README 2023-05-02 23:46:12 +02:00
Gabriel Poma 96b21fb850 Merge branch 'master' of github.com:24eme/signaturepdf 2023-05-02 12:32:09 +02:00
Gabriel Poma 5925d4e4c6 Revert "prompt rechargement de la page"
This reverts commit 818f08d24c.
2023-05-02 12:18:57 +02:00
xgaia c119d8d1cd chore(docker): fix dockerbuild permission issue 2023-04-21 12:32:35 +02:00
Vincent LAURENT de66c05f2a
metadata: Document of configuration defaults fields 2023-04-20 16:36:48 +02:00
Vincent LAURENT 9b36ae02e5 metadata: favicon 2023-04-20 16:29:58 +02:00
Vincent LAURENT e488e20fad metadata: wording 2023-04-20 16:24:23 +02:00
Vincent LAURENT a04e26d2ce metadata: bug loading value 2023-04-20 16:11:53 +02:00
Gabriel Poma c89c8d53dd Merge branch 'metadata' 2023-04-20 15:57:35 +02:00
Gabriel Poma 60044ee371 Merge branch 'master' of github.com:gpoma/signaturepdf 2023-04-20 15:57:30 +02:00
Gabriel Poma c30c89ad60 Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-20 15:55:58 +02:00
Gabriel Poma 96c663493c filename global + on reprends le filename au dl 2023-04-20 15:55:35 +02:00
Vincent LAURENT 841cc18ed9 metadata: textes 2023-04-20 15:53:50 +02:00
Gabriel Poma 2653effbc8 suppression métadonnées 2023-04-20 15:52:27 +02:00
Vincent LAURENT 9abd4e848b metadata: new tab on each page 2023-04-20 15:47:12 +02:00
Vincent LAURENT 4b0ca82652 metadata: If any field focus on add field 2023-04-20 15:44:59 +02:00
Vincent LAURENT 4965d7878b metadata: autofocus on first input 2023-04-20 15:43:20 +02:00
Vincent LAURENT b30be5b344 metadata: fields list by default define in configuration 2023-04-20 15:37:01 +02:00
Gabriel Poma 8d9f49a3e8 on sort la fct de download 2023-04-20 15:17:49 +02:00
Gabriel Poma 4f4d3a0583 fix enregistrement 2023-04-20 15:11:33 +02:00
Vincent LAURENT 49bf6e50ab Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-20 14:38:39 +02:00
Vincent LAURENT 6cc0461975 metadata: delete max upload size control 2023-04-20 14:36:09 +02:00
Gabriel Poma 784e15f252 Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-20 13:22:09 +02:00
Gabriel Poma 6bed0fdc1d on sauve les métadonnées et on DL le pdf 2023-04-20 13:21:44 +02:00
Vincent LAURENT 75434aee71 metadata: logo 2023-04-20 13:12:03 +02:00
Vincent LAURENT 6c28618fbe metadata: New formatting of the add metadata form 2023-04-20 00:07:42 +02:00
Vincent LAURENT a2843a329c Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-19 17:27:33 +02:00
Vincent LAURENT 9bdd28c7df metadata: render pdf in right column 2023-04-19 17:26:55 +02:00
Gabriel Poma eab2e0c127 suppression: confirm 2023-04-19 15:42:48 +02:00
Gabriel Poma 011c9094e1 suppression: croix dans input 2023-04-19 15:37:51 +02:00
Gabriel Poma cbea7de840 metadata: suppression metadonnée 2023-04-19 13:00:30 +02:00
Gabriel Poma b037685c57 metadata: nom de la métadonnée requis 2023-04-19 11:03:57 +02:00
Gabriel Poma f9af7f969e Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-19 10:48:47 +02:00
Vincent LAURENT f0eb416ced metadata: adding a new metadata entrie 2023-04-19 09:03:35 +02:00
Vincent LAURENT e2f9637426 metadata: read metadata informations and display it in form input 2023-04-19 02:29:24 +02:00
Vincent LAURENT 186a9cc225 metadata: new tab and page metadata 2023-04-18 23:44:38 +02:00
Gabriel Poma eec274bf0f Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-18 18:59:20 +02:00
Vincent LAURENT 9a63402a2a Documentation precision in the README and in the config.ini file and use of ";" instead of "#" for comments 2023-03-18 00:54:26 +01:00
Vincent LAURENT 68be39ed23 feat: Added option to remove or edit demo pdf link 2023-03-18 00:34:18 +01:00
xgaia c9f0d25027 feat: Add possibility to disable Organization 2023-03-17 11:22:40 +01:00
Vincent LAURENT 7d7e666bc7
Larger version 2022-12-30 16:46:05 +01:00
Vincent LAURENT f27abba511
General logo while waiting for a more personalized version 2022-12-30 16:42:46 +01:00
Gabriel Poma 818f08d24c prompt rechargement de la page
TODO: checker si modification
2022-12-26 18:18:09 +01:00
Vincent LAURENT 5a4309fcf3 [Organization] The download full PDF button is disabled when pages are selected 2022-12-07 22:46:12 +01:00
Vincent LAURENT 1eb0f5101b [Organization] A click on a neutral element of the page deselects the pages 2022-12-07 01:48:24 +01:00
Jonas Chopin-Revel 66cc24abd1 Edit Dockerfile, two changes to reduce the image size and avoid to be as root in the container 2022-12-06 23:04:44 +01:00
Vincent LAURENT 3ee536cff3 Buttons near page dragged not hide 2022-11-20 01:06:28 +01:00
Vincent LAURENT 4f9697d409 Change icon to drag here 2022-11-20 00:54:48 +01:00
Vincent LAURENT e9bd3f9704 Merge branch 'master' of github.com:24eme/signaturepdf 2022-10-29 00:21:55 +02:00
Vincent LAURENT b3087129a8 css not use 2022-10-29 00:21:32 +02:00
Gabriel Poma 50ed5b5973 Merge branch 'master' of github.com:24eme/signaturepdf 2022-10-21 17:57:47 +02:00
Vincent LAURENT 9cbf0107a2 [Organization] If selection mode it will not active hover action page buttons 2022-10-18 00:02:41 +02:00
Gabriel Poma 4efb3d9ad1 Télécharger plus explicite 2022-10-17 15:50:46 +02:00
Gabriel Poma b979fd3d02 typo 2022-10-17 15:44:48 +02:00
Gabriel Poma ea354ca8cb cursor help 2022-10-17 15:31:43 +02:00
Vincent LAURENT a04123b846 [Organization] Modal to manage docs in mobile 2022-10-17 00:49:32 +02:00
Vincent LAURENT e7dacf528c Formatting home page 2022-10-16 23:38:37 +02:00
Vincent LAURENT 36d0e0df4b [Organization] PDF Document name on mobile truncate 2022-10-15 01:51:00 +02:00
Vincent LAURENT cf418043f2 [Organization] Shorter title 2022-10-14 00:25:26 +02:00
Vincent LAURENT 25fdbbba58 [Organization] Modal to move page in click mode 2022-10-14 00:22:47 +02:00
Vincent LAURENT 9a70988f78 [Organization] Disabled hover style on touch screen 2022-10-11 02:08:34 +02:00
Vincent LAURENT c48ca163d0 Padding adjust 2022-10-11 00:57:47 +02:00
Vincent LAURENT d1431afbae Message below 2022-10-11 00:54:52 +02:00
Vincent LAURENT c66126b865 Message to inform how select page in mobile version and padding top
adapted to select bar menu
2022-10-11 00:52:43 +02:00
Vincent LAURENT 7a91ab5ef5 Button cancel séléction more visible in desktop and mobile mode 2022-10-11 00:42:45 +02:00
Vincent LAURENT bf849a4d30 Text clearer 2022-10-10 23:26:20 +02:00
Vincent LAURENT 4d55c69c91 Footer smaller on mobile 2022-10-10 23:19:41 +02:00
Vincent LAURENT 89a7e24e59 Subtitle more discreet 2022-10-10 23:13:13 +02:00
Vincent LAURENT f3ff57dd00 [Organization] Drag start disabled in dragging mode 2022-10-09 01:51:38 +02:00
Vincent LAURENT e112050623 [Organization] Button global to cancel draging mode 2022-10-09 01:46:17 +02:00
Vincent LAURENT 233c9a8e1e [Organization] Click on drag button active dragging mode 2022-10-09 00:55:13 +02:00
Vincent LAURENT 653036f28d [Organization] Move page on left or right 2022-10-09 00:44:04 +02:00
Vincent LAURENT a848163e2d [Organization] Buttons to drag here near the page to drag are hidden 2022-10-09 00:25:41 +02:00
Vincent LAURENT ccf0afd5e2 [Organization] Button do drag here at left and right of the page 2022-10-09 00:04:44 +02:00
Vincent LAURENT 692245f763 [Organization] Hide top and bottom margin in dragging mode mobile 2022-10-08 11:05:03 +02:00
Vincent LAURENT ed36de8bd6 [Organization] 2 pages per line in mobile and fix scroll in backdrop div 2022-10-08 10:57:39 +02:00
Vincent LAURENT 7ae60e41a8 [Organization] Hide button when a page is selected 2022-10-08 10:34:51 +02:00
Vincent LAURENT 3f9d8f380f [Organization] Display page number when page is selected 2022-10-08 10:28:18 +02:00
Vincent LAURENT 96172423c6 Hover button more visible on pdf page 2022-10-08 01:34:31 +02:00
Vincent LAURENT 1da99c5c29 [Organization] stop propagation when page drop 2022-10-08 01:20:21 +02:00
Vincent LAURENT e45aaa4eb3 [Organization] drag and drop multiple 2022-10-08 01:07:49 +02:00
Vincent LAURENT 2e0044eb64 [Organization] button restore always visible on mobile 2022-10-08 00:32:57 +02:00
Vincent LAURENT 6588ff91b7 [Organization] Blur button after action on mobile 2022-10-08 00:29:16 +02:00
Vincent LAURENT 6c5a1f7006 Disable drag and drop on mobile 2022-10-08 00:09:41 +02:00
Vincent LAURENT e1d6e5bdad Subtitle size smaller on mobile 2022-10-08 00:00:59 +02:00
Vincent LAURENT 3c87f0fb3d Subtitle lighter 2022-10-07 23:55:49 +02:00
Vincent LAURENT 90def30fef [Organization] on mobile mode selection and buttons on top and bottom bar 2022-10-07 23:45:33 +02:00
Vincent LAURENT ad7439a419 Click on page to select it 2022-10-07 19:50:25 +02:00
Vincent LAURENT 3bc1343701 Merge branch 'master' of github.com:24eme/signaturepdf 2022-10-07 17:49:51 +02:00
Vincent LAURENT 8fb39606b6 Subtitle signature 2022-10-07 17:49:30 +02:00
Vincent LAURENT 0a4cce8d56
Encouragement to reference instance 2022-10-07 14:05:07 +02:00
Vincent LAURENT b193b022d7
Add nebulae instance 2022-10-07 13:27:57 +02:00
Vincent LAURENT 09e5985258
Add hostux instance 2022-10-07 11:29:37 +02:00
Vincent LAURENT 7219519f0c Subtitle with explain 2022-10-07 11:04:13 +02:00
Vincent LAURENT eb758a1b9a Drag and drop with only detecting change and direction more easier et
reliable
2022-10-07 10:47:13 +02:00
Vincent LAURENT ebc92a17e5 Fixed drag and drop when page changes line 2022-10-07 01:41:30 +02:00
Vincent LAURENT c300673ab1 Merge branch 'organization' 2022-09-24 09:43:11 +02:00
Vincent LAURENT 6aa3bfe804 Button to cancel move mode smaller 2022-09-24 09:41:26 +02:00
Vincent LAURENT 8216041378 No opacity on sub text in modal like other 2022-09-24 09:37:35 +02:00
Vincent LAURENT e5dd867883 Reorganization of documentation 2022-07-18 21:14:57 +02:00
Vincent LAURENT 8d25b8cafa
Merge pull request #36 from battosai30/patch-1
Installation doc on alpine
2022-07-18 21:08:57 +02:00
battosai30 9cdb4bcf6b
Update README.md 2022-07-08 11:38:39 +02:00
Vincent LAURENT f63d5ee0a5 Fix typo "à main levée" 2022-06-20 09:50:15 +02:00
Vincent LAURENT 91d526ae01 Fix typo "à main levée" 2022-06-20 09:49:42 +02:00
Vincent LAURENT d888334299 Move a page in clique instead of drag and drop for mobile mode 2022-05-31 01:27:13 +02:00
Vincent LAURENT 0739e2e7c3 Right margin on mobile adjust 2022-05-25 01:08:19 +02:00
Vincent LAURENT bc4bded932 Top bar in mobile mode with a button to open sidebar and zoom buttons suitable for mobile 2022-05-24 00:43:34 +02:00
Vincent LAURENT a3236c8368 Upload multiple files at the same time 2022-05-23 23:57:14 +02:00
Vincent LAURENT 9c07ef9355 Button download on mobile at the wrong place 2022-05-21 10:05:17 +02:00
Vincent LAURENT 62c5bbab91 Downoad button in mobile mode 2022-05-21 10:01:17 +02:00
Vincent LAURENT f0e86c61da Checkbox of a file placed at the end 2022-05-21 02:19:09 +02:00
Vincent LAURENT 8b8aa16fa7 The "deselect the selected pages" button become a cross at the top right of the block 2022-05-21 01:43:49 +02:00
Vincent LAURENT 60522a1c18 Checkbox files related to page selection 2022-05-21 01:07:07 +02:00
Vincent LAURENT 02bad421bd Button downloaded the selected pages in the block rather than at the bottom instead of the global downloaded button 2022-05-18 21:28:49 +02:00
Vincent LAURENT ec797574eb Block of actions of selected pages 2022-05-18 10:04:49 +02:00
Vincent LAURENT cfcd34829e In mobile mode the buttons are displayed in click on the page 2022-05-18 01:42:41 +02:00
Vincent LAURENT eb170fcd63 Button download hide by default 2022-05-18 01:39:38 +02:00
Vincent LAURENT 5bb3ef0f64 Centralized hover state 2022-05-18 01:28:00 +02:00
Vincent LAURENT 6ffcd6a346 centralized page status in a function and button restore a page 2022-05-18 01:02:09 +02:00
Vincent LAURENT 8fa6b5e459 Manipulation buttons of the selected pages above and like the add button of pdf 2022-05-17 01:54:45 +02:00
Vincent LAURENT 492e20dda0 Page in size 4/3 in height 2022-05-15 01:48:38 +02:00
Vincent LAURENT 02e8007ec1 Title to the overflying buttons of a page 2022-05-12 00:39:14 +02:00
Vincent LAURENT 3f2b002804 The page number and the name of the document is displayed only at the top and a border and a background also 2022-05-12 00:26:35 +02:00
Vincent LAURENT 17b0b22f8e [organization] Dedicated button for deleting and rotating selected pages 2022-05-11 09:29:46 +02:00
Vincent LAURENT a7ab90c698 [organization] Shadow and background on the div of the pdf and not of the whole block 2022-05-11 08:04:20 +02:00
Vincent LAURENT 9199e27d9d [organization] In selection mode the rotate, download and delete buttons are linked 2022-05-11 00:57:49 +02:00
Vincent LAURENT a694d62be4 Organisation menu hide on mobile mode 2022-05-10 23:57:53 +02:00
Vincent LAURENT abda679784 Navigation between the signature and the organization 2022-05-08 23:07:24 +02:00
Vincent LAURENT 9671751ef3 Uniformity of the upload block 2022-05-07 11:11:40 +02:00
Vincent LAURENT 156859993f Force update js 2022-05-07 02:08:56 +02:00
Vincent LAURENT 9b25ef4c6e Allows to cancel the rendering of a page in case it is not finished and there is a new zoom level 2022-05-07 02:06:30 +02:00
Vincent LAURENT 6198c55d3a Button upload a new pdf more visible 2022-05-07 01:22:18 +02:00
Vincent LAURENT d842917573 The download button of a page allows to download the page 2022-05-07 01:14:52 +02:00
Vincent LAURENT b1e7f02215 PDF download button different in selection mode and a new "Cancel selection" button 2022-05-07 00:42:24 +02:00
Vincent LAURENT 24437112b8 The selection button allows you to select a page and activate the "selection" mode to download only the selected pages 2022-05-06 00:44:15 +02:00
Vincent LAURENT 89a5b58961 The HTML content of a page is divided into several lines for clarity 2022-05-06 00:18:36 +02:00
Vincent LAURENT e6a5c45d92 The selection checkbox becomes a delete button.
2 other new buttons not yet developed : the selection and the download of a page
2022-05-06 00:07:57 +02:00
Vincent LAURENT f48e78642b Merge branch 'master' of github.com:24eme/signaturepdf 2022-04-28 21:28:41 +02:00
Vincent LAURENT 36a692e34b List of loaded pdf 2022-04-28 19:13:45 +02:00
Vincent LAURENT abb63e8f90 Zoom button with a magnifying glass icon in the upper right corner 2022-04-28 19:09:56 +02:00
Vincent LAURENT 14e8765b6e
Formatting 2022-04-27 13:02:26 +02:00
Vincent LAURENT 5b8b4872e9
Code and financial contributors 2022-04-27 13:01:28 +02:00
Vincent LAURENT eca93fb359 PDF storage expiration time for multi-signature mode 2022-04-27 00:41:44 +02:00
Vincent LAURENT b2ca7e81f3 Multi-signature pdf generation uses temporary files with unique names to avoid collisions 2022-04-15 01:11:51 +02:00
Vincent LAURENT b2c7b8986b Merge branch 'master' of github.com:24eme/signaturepdf 2022-04-15 00:50:38 +02:00
Vincent LAURENT ebf7e0e889 Automatically reload the pdf if there is a new signature 2022-04-15 00:47:36 +02:00
Vincent LAURENT 3327a64774
Documentation update to last version 2022-04-14 10:56:48 +02:00
Vincent LAURENT 39380bd641 Force js update 2022-04-14 10:30:57 +02:00
Vincent LAURENT 4964e98a56 Meta description 2022-04-14 10:29:49 +02:00
Vincent LAURENT f703d61f24 FirstPage with white background 2022-04-14 09:09:48 +02:00
Vincent LAURENT 992081a882 let instead of var 2022-04-14 09:09:01 +02:00
Vincent LAURENT 8ca0115391 signature pad size minimum less small 2022-04-14 00:50:17 +02:00
Vincent LAURENT a787b82d45 Modal to start a more responsive and simple text sharing 2022-04-14 00:19:31 +02:00
Vincent LAURENT f8ee51f1e9 Modal for creating a signature more responsive with tab subtitles specifying the type of signature 2022-04-14 00:14:19 +02:00
Vincent LAURENT 62d6a7c0c3 Informative sentence about the exchanges with the server simpler 2022-04-14 00:13:17 +02:00
Vincent LAURENT 8c4adb0216 The signature pad more fluid and with a slightly finer minimum line 2022-04-14 00:10:10 +02:00
Vincent LAURENT a6e9b444ce Clarification of the information text in the modal share 2022-04-13 00:41:31 +02:00
Vincent LAURENT ac8279e43e Modal to inform that the PDF will be stored on the server before enabling multi-signature PDF sharing 2022-04-13 00:40:35 +02:00
Vincent LAURENT 48107c16fd Merge branch 'master' of github.com:24eme/signaturepdf 2022-04-12 23:36:37 +02:00
Vincent LAURENT ad93b7ccef Clarification on not storing the pdf on the server 2022-04-12 23:36:28 +02:00
Vincent LAURENT c7b7a3e2ee
Merge pull request #25 from logilab/docker-multi-signature
feat(docker): update dockerfile to enable pdf storage for multi-signature
2022-04-11 10:40:09 +02:00
Simon Chabot 05550b03c9 feat(docker): update dockerfile to enable pdf storage for multi-signature 2022-04-11 09:34:18 +02:00
Vincent LAURENT 7106b79298 Column on the right for the different actions instead of the bar at the bottom 2022-04-10 01:35:42 +02:00
Vincent LAURENT 152d54b397 The rotation of pdf pages are taken into account on the server side with pdftk 2022-04-08 01:06:35 +02:00
Vincent LAURENT 43992abcff Reduction of the space between pages 2022-04-08 00:18:28 +02:00
Vincent LAURENT 75813eefaa Plus and minus button to change the number of pages displayed per line 2022-04-07 00:13:22 +02:00
Vincent LAURENT 720c0ba34d All the blocks containing the pages have the same size and the pdf fits inside 2022-04-06 23:17:27 +02:00
Vincent LAURENT c7811b437b Page rotation 2022-04-05 01:11:03 +02:00
Vincent LAURENT 5332629032 pdtfk work with additionnal pdf 2022-04-04 00:53:24 +02:00
16 changed files with 1794 additions and 322 deletions

View File

@ -1,22 +1,24 @@
FROM php:7.4-apache
FROM php:8.2-apache
ENV SERVERNAME=localhost
ENV UPLOAD_MAX_FILESIZE=24M
ENV POST_MAX_SIZE=24M
ENV MAX_FILE_UPLOADS=201
ENV PDF_STORAGE_PATH=/data
ENV DISABLE_ORGANIZATION=false
ENV PDF_DEMO_LINK=true
RUN apt update && apt install -y gettext-base librsvg2-bin pdftk imagemagick potrace
RUN apt update && \
apt install -y gettext-base librsvg2-bin pdftk imagemagick potrace && \
rm -rf /var/lib/apt/lists/*
COPY . /usr/local/signaturepdf
RUN chown -R www-data:www-data /usr/local/signaturepdf && chmod 750 -R /usr/local/signaturepdf && \
envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini && \
RUN envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini && \
envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf && \
envsubst < /usr/local/signaturepdf/config/config.ini.tpl > /usr/local/signaturepdf/config/config.ini && \
a2enmod rewrite && a2ensite signaturepdf
WORKDIR /usr/local/signaturepdf
CMD envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf && \
envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini && \
chown -R www-data:www-data /usr/local/signaturepdf && chmod 750 -R /usr/local/signaturepdf && \
apache2-foreground
CMD /usr/local/signaturepdf/entrypoint.sh

212
README.md
View File

@ -8,6 +8,10 @@ Liste des instances permettant d'utiliser ce logiciel :
- [pdf.24eme.fr](https://pdf.24eme.fr)
- [pdf.libreon.fr](https://pdf.libreon.fr)
- [pdf.hostux.net](https://pdf.hostux.net)
- [pdf.nebulae.co](https://pdf.nebulae.co)
_N'hésitez pas à rajouter la votre via une issue ou une pull request_
## License
@ -15,15 +19,17 @@ Logiciel libre sous license AGPL V3
## Installation
### Debian/Ubuntu
Dépendances :
- php >= 5.6
- php >= 5.6
- rsvg-convert
- pdftk
- imagemagick
- potrace
Sur debian :
Installation des dépendances :
```
sudo aptitude install php librsvg2-bin pdftk imagemagick potrace
@ -41,7 +47,7 @@ Pour le lancer :
php -S localhost:8000 -t public
```
### Configuration de PHP
#### Configuration de PHP
```
upload_max_filesize = 24M # Taille maximum du fichier PDF à signer
@ -49,7 +55,7 @@ post_max_size = 24M # Taille maximum du fichier PDF à signer
max_file_uploads = 201 # Nombre de pages maximum du PDF, ici 200 pages + le PDF d'origine
```
### Déployer avec apache
#### Configuration d'apache
```
DocumentRoot /path/to/signaturepdf/public
@ -63,6 +69,149 @@ DocumentRoot /path/to/signaturepdf/public
</Directory>
```
### Déployer avec docker
#### Construction de l'image
```bash
docker build -t signaturepdf .
```
#### Lancement d'un conteneur
```bash
docker run -d --name=signaturepdf -p 8080:80 signaturepdf
```
[localhost:8080](http://localhost:8080)
#### Configuration
Les variables suivantes permettent de configurer le déployement :
| Variable | description | exemple | defaut |
| ---------------------- | ------------------------------------------------------------------ | -------------------------------- | --------- |
| `SERVERNAME` | url de déploiement | `pdf.24eme.fr` | localhost |
| `UPLOAD_MAX_FILESIZE` | Taille maximum du fichier PDF à signer | 48M | 24M |
| `POST_MAX_SIZE` | Taille maximum du fichier PDF à signer | 48M | 24M |
| `MAX_FILE_UPLOADS` | Nombre de pages maximum du PDF, ici 200 pages + le PDF d'origine | 401 | 201 |
| `PDF_STORAGE_PATH` | chemin vers lequel les fichiers pdf uploadés pourront être stockés | /data | /data |
| `DISABLE_ORGANIZATION` | Desactiver la route Organiser | true | false |
| `PDF_DEMO_LINK` | Afficher, retirer ou changer le lien de PDF de démo | false, `link` or `relative path` | true |
```bash
docker run -d --name=signaturepdf -p 8080:80 -e SERVERNAME=pdf.example.org -e UPLOAD_MAX_FILESIZE=48M -e POST_MAX_SIZE=48M -e MAX_FILE_UPLOADS=401 -e PDF_STORAGE_PATH=/data signaturepdf
```
### Alpine
Voici un script permettant d'installer la solution sous Linux Alpine (testé en version 3.15).
Pensez à éditer la variable "domain" en début de script pour correspondre à l'URL avec laquelle elle sera appelée.
Les composants principaux sont :
- php 8 + php-fpm
- Nginx
- pdftk (installation "manuelle" nécessitant openjdk8)
- imagick
- potrace
- librsvg
Ce que fait le script :
- Installation des dépendances
- Configuration de php et php-fpm
- Configuration d'Nginx
- Configuration du config.ini
- Git clone du repo
```
#!/bin/sh
domain='sign.example.com'
apk update
apk add bash nginx git php8 php8-fpm php8-session php8-gd php8-fileinfo openjdk8 imagemagick potrace librsvg
cd /tmp
wget https://gitlab.com/pdftk-java/pdftk/-/jobs/924565145/artifacts/raw/build/libs/pdftk-all.jar
mv pdftk-all.jar pdftk.jar
cat <<EOF >>pdftk
#!/usr/bin/env bash
/usr/bin/java -jar "\$0.jar" "\$@"
EOF
chmod 775 pdftk*
mv pdftk* /usr/bin
sed -i 's/user = nobody/user = nginx/g' /etc/php8/php-fpm.d/www.conf
sed -i 's/;listen.owner = nginx/listen.owner = nginx/g' /etc/php8/php-fpm.d/www.conf
sed -i 's/post_max_size = 8M/post_max_size = 50M/g' /etc/php8/php.ini
sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 50M/g' /etc/php8/php.ini
sed -i 's/max_file_uploads = 20 /max_file_uploads = 300/g' /etc/php8/php.ini
service php-fpm8 restart
cd /var/www
git clone https://github.com/24eme/signaturepdf.git
cat <<EOF >>/etc/nginx/http.d/signaturepdf.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name ${domain};
client_max_body_size 0;
root /var/www/signaturepdf/public/;
index index.php index.html;
location / {
# URLs to attempt, including pretty ones.
try_files \$uri \$uri/ /index.php?\$query_string;
}
location ~ [^/]\.php(/|$) {
root /var/www/signaturepdf/public/;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_buffer_size 128k;
fastcgi_buffers 128 128k;
fastcgi_param PATH_INFO \$fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
}
}
EOF
rm /etc/nginx/http.d/default.conf
rm -R /var/www/localhost
service nginx restart
rc-update add nginx
rc-update add php-fpm8
mkdir /var/www/signaturepdf/tmp
chown nginx /var/www/signaturepdf/tmp
cat <<EOF >>/var/www/signaturepdf/config/config.ini
PDF_STORAGE_PATH=/var/www/signaturepdf/tmp
EOF
```
## Configuration
### Activation et configuration du mode partage de signature à plusieurs
Ce mode permet de proposer la signature d'un pdf à plusieurs personnes mais il nécessite que les PDF soient stockés sur le serveur, il convient donc de définir un dossier qui contiendra ces PDF.
@ -75,12 +224,14 @@ Créer le fichier `config/config.ini`
cp config/config.ini{.example,}
```
Dans ce fichier `config/config.ini`, il suffit ce configurer la variable `PDF_STORAGE_PATH` avec le chemin vers lequel les fichiers pdf uploadé pourront être stockés :
Dans ce fichier `config/config.ini`, il suffit ce configurer la variable `PDF_STORAGE_PATH` avec le chemin vers lequel les fichiers pdf uploadés pourront être stockés :
```
PDF_STORAGE_PATH=/path/to/folder
```
Créer ce dossier :
```
mkdir /path/to/folder
```
@ -88,40 +239,39 @@ mkdir /path/to/folder
Le serveur web devra avoir les droits en écriture sur ce dossier.
Par exemple pour apache :
```
chown www-data /path/to/folder/to/store/pdf
```
### Déployer avec docker
### Desactivation du mode Organiser
#### Construction de l'image
Pour desactiver le mode Organiser, ajouter `DISABLE_ORGANIZATION=true` dans le fichier
`config/config.ini`.
```bash
docker build -t signaturepdf .
````
### Cacher ou modifier le lien de PDF de démo
#### Lancement d'un conteneur
Pour cacher le lien de pdf de démo, ajouter `PDF_DEMO_LINK=false` dans le fichier
`config/config.ini`.
```bash
docker run -d --name=signaturepdf -p 8080:80 signaturepdf
````
### Champs chargés par défaut pour l'édition de métadonnéés
[localhost:8080](http://localhost:8080)
Dans le fichier de configuration `config/config.ini` il est possible de rajouter autant de champs que l'on souhaite avec le type HTML de l'input (text, date, number email, etc ...) qui seront préchargées pour chaque PDF.
#### Configuration
```
METADATA_DEFAULT_FIELDS[field1].type = "text"
METADATA_DEFAULT_FIELDS[field2].type = "text"
METADATA_DEFAULT_FIELDS[field3].type = "date"
METADATA_DEFAULT_FIELDS[field4].type = "number"
```
Les variables suivantes permettent de configurer le déployement :
## Mise à jour
|Variable|description|exemple|defaut|
|-----|-----|-----|-----|
|`SERVERNAME`|url de déploiement|`pdf.24eme.fr`|localhost|
|`UPLOAD_MAX_FILESIZE`|Taille maximum du fichier PDF à signer|48M|24M|
|`POST_MAX_SIZE`|Taille maximum du fichier PDF à signer|48M|24M|
|`MAX_FILE_UPLOADS`|Nombre de pages maximum du PDF, ici 200 pages + le PDF d'origine|401|201|
La dernière version stable est sur la branche `master`, pour la mise à jour il suffit de récupérer les dernières modifications :
```bash
docker run -d --name=signaturepdf -p 8080:80 -e SERVERNAME=pdf.example.org -e UPLOAD_MAX_FILESIZE=48M -e POST_MAX_SIZE=48M -e MAX_FILE_UPLOADS=401 signaturepdf
````
```
git pull -r
```
## Tests
@ -151,11 +301,21 @@ DEBUG=1 make test
- **OpenType.js** outils de transformation d'un texte et sa police en chemin : https://github.com/opentypejs/opentype.js (MIT)
- **ImageMagick** ensemble d'outils de manipulation d'images : https://imagemagick.org/ (Apache-2.0)
- **Caveat** police de caractères style écriture à la main : https://github.com/googlefonts/caveat (OFL-1.1)
- **PDF-LIB** librairie js permettant de manipuler un PDF qui est utilisé pour écrire dans les métadonnées : https://pdf-lib.js.org/ (MIT)
Pour les tests :
- **Jest** Framework de Test Javascript : https://jestjs.io/ (MIT)
- **Puppeteer** librairie Node.js pour contrôler un navigateur : https://github.com/puppeteer/puppeteer (Apache-2.0)
## Contributeurs
- Vincent LAURENT (24ème)
- Le Metayer Jean-Baptiste (24ème)
- Xavier Garnier (Logilab)
- Simon Chabot (Logilab)
- Gabriel POMA (24ème)
Logilab a apporté une contribution financière de 1 365 € TTC à la société 24ème pour développer le mode multi signature.
Le développement du logiciel a principalement été réalisé sur le temps de travail de salariés du 24ème.

109
app.php
View File

@ -10,12 +10,23 @@ $f3->set('XFRAME', null); // Allow use in an iframe
$f3->set('ROOT', __DIR__);
$f3->set('UI', $f3->get('ROOT')."/templates/");
$f3->set('UPLOADS', sys_get_temp_dir()."/");
$f3->set('COMMIT', getCommit());
$f3->config(__DIR__.'/config/config.ini');
if($f3->get('PDF_STORAGE_PATH') && !preg_match('|/$|', $f3->get('PDF_STORAGE_PATH'))) {
$f3->set('PDF_STORAGE_PATH', $f3->get('PDF_STORAGE_PATH').'/');
}
if($f3->get('PDF_DEMO_LINK') === null || $f3->get('PDF_DEMO_LINK') === true) {
$f3->set('PDF_DEMO_LINK', 'https://raw.githubusercontent.com/24eme/signaturepdf/master/tests/files/document.pdf');
}
$f3->set('disableOrganization', false);
if($f3->get('DISABLE_ORGANIZATION')) {
$f3->set('disableOrganization', $f3->get('DISABLE_ORGANIZATION'));
}
$f3->route('GET /',
function($f3) {
$f3->reroute('/signature');
@ -140,8 +151,8 @@ $f3->route('POST /sign',
$f3->route('POST /share',
function($f3) {
$hash = substr(hash('sha512', uniqid().rand()), 0, 20);
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash."/";
$f3->set('UPLOADS', $sharingFolder);
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$f3->set('UPLOADS', $sharingFolder."/");
if (!is_dir($f3->get('PDF_STORAGE_PATH'))) {
$f3->error(500, 'Sharing folder doesn\'t exist');
}
@ -149,6 +160,9 @@ $f3->route('POST /share',
$f3->error(500, 'Sharing folder is not writable');
}
mkdir($sharingFolder);
$expireFile = $sharingFolder.".expire";
file_put_contents($expireFile, $f3->get('POST.duration'));
touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));
$filename = "original.pdf";
$tmpfile = tempnam($sharingFolder, date('YmdHis'));
$svgFiles = "";
@ -164,7 +178,7 @@ $f3->route('POST /share',
return true;
}, false, function($fileBaseName, $formFieldName) use ($tmpfile, $filename, $sharingFolder, &$svgFiles) {
if($formFieldName == "pdf") {
file_put_contents($sharingFolder."filename.txt", $fileBaseName);
file_put_contents($sharingFolder."/filename.txt", $fileBaseName);
return $filename;
}
if($formFieldName == "svg") {
@ -196,7 +210,7 @@ $f3->route('GET /signature/@hash/pdf',
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$files = scandir($sharingFolder);
$originalFile = $sharingFolder.'/original.pdf';
$finalFile = $sharingFolder.'/'.$f3->get('PARAMS.hash').'.pdf';
$finalFile = $sharingFolder.'/'.$f3->get('PARAMS.hash').uniqid().'.pdf';
$filename = $f3->get('PARAMS.hash').'.pdf';
if(file_exists($sharingFolder."/filename.txt")) {
$filename = file_get_contents($sharingFolder."/filename.txt");
@ -212,24 +226,32 @@ $f3->route('GET /signature/@hash/pdf',
}
$filename = str_replace('.pdf', '_signe-'.count($layers).'x.pdf', $filename);
copy($originalFile, $finalFile);
$bufferFile = str_replace('.pdf', '_tmp.pdf', $originalFile);
$bufferFile = $finalFile.".tmp";
foreach($layers as $layerFile) {
shell_exec(sprintf("pdftk %s multistamp %s output %s", $finalFile, $layerFile, $bufferFile));
rename($bufferFile, $finalFile);
}
Web::instance()->send($finalFile, null, 0, TRUE, $filename);
if($f3->get('DEBUG')) {
return;
}
array_map('unlink', glob($finalFile."*"));
}
);
$f3->route('POST /signature/@hash/save',
function($f3) {
$hash = Web::instance()->slug($f3->get('PARAMS.hash'));
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash.'/';
$f3->set('UPLOADS', $sharingFolder);
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$f3->set('UPLOADS', $sharingFolder.'/');
$tmpfile = tempnam($sharingFolder, date('YmdHis'));
unlink($tmpfile);
$svgFiles = "";
$expireFile = $sharingFolder.".expire";
touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));
$files = Web::instance()->receive(function($file,$formFieldName){
if($formFieldName == "svg" && strpos(Web::instance()->mime($file['tmp_name'], true), 'image/svg+xml') !== 0) {
$f3->error(403);
@ -270,6 +292,20 @@ $f3->route('GET /signature/@hash/nblayers',
}
);
$f3->route('GET /cron', function($f3) {
$sharingFolder = $f3->get('PDF_STORAGE_PATH');
foreach(glob($sharingFolder.'*.expire') as $expireFile) {
if(filemtime($expireFile) > time()) {
continue;
}
$expiredFolder = str_replace('.expire', '', $expireFile);
array_map('unlink', glob($expiredFolder."/*"));
rmdir($expiredFolder);
unlink($expireFile);
}
});
if (!$f3->get('disableOrganization')) {
$f3->route('GET /organization',
function($f3) {
$f3->set('maxSize', min(array(convertPHPSizeToBytes(ini_get('post_max_size')), convertPHPSizeToBytes(ini_get('upload_max_filesize')))));
@ -280,37 +316,70 @@ $f3->route('GET /organization',
$f3->route('POST /organize',
function($f3) {
$filename = null;
$filenames = array();
$tmpfile = tempnam($f3->get('UPLOADS'), 'pdfsignature_organize');
unlink($tmpfile);
$pages = explode(',', $f3->get('POST.pages'));
$pages = explode(',', preg_replace('/[^A-Z0-9a-z,]+/', '', $f3->get('POST.pages')));
$files = Web::instance()->receive(function($file,$formFieldName){
if($formFieldName == "pdf" && strpos(Web::instance()->mime($file['tmp_name'], true), 'application/pdf') !== 0) {
if(strpos(Web::instance()->mime($file['tmp_name'], true), 'application/pdf') !== 0) {
$f3->error(403);
}
return true;
}, false, function($fileBaseName, $formFieldName) use ($f3, $tmpfile, &$filename, $pages) {
if($formFieldName == "pdf") {
$filename = str_replace(".pdf", "_page_".implode("-", $pages).".pdf", $fileBaseName);
return basename($tmpfile).".pdf";
}
});
if(!is_file($tmpfile.".pdf")) {
return true;
}, false, function($fileBaseName, $formFieldName) use ($tmpfile, &$filenames) {
$filenames[] = str_replace('.pdf', '', $fileBaseName);
return basename($tmpfile).uniqid().".pdf";
});
if(!count($files)) {
$f3->error(403);
}
shell_exec(sprintf("pdftk %s cat %s output %s", $tmpfile.".pdf", implode(" ", $pages), $tmpfile.'_organize.pdf'));
$pdfs = array();
foreach(array_keys($files) as $i => $file) {
$pdfs[] = chr(65 + $i)."=".$file;
}
Web::instance()->send($tmpfile."_organize.pdf", null, 0, TRUE, $filename);
shell_exec(sprintf("pdftk %s cat %s output %s", implode(" ", $pdfs), implode(" ", $pages), $tmpfile.'_final.pdf'));
Web::instance()->send($tmpfile."_final.pdf", null, 0, TRUE, implode('_', $filenames));
if($f3->get('DEBUG')) {
return;
}
array_map('unlink', glob($tmpfile."*"));
}
);
}
$f3->route('GET /metadata',
function($f3) {
echo View::instance()->render('metadata.html.php');
}
);
function getCommit() {
if(!file_exists(__DIR__.'/.git/HEAD')) {
return null;
}
$head = str_replace(["ref: ", "\n"], "", file_get_contents(__DIR__.'/.git/HEAD'));
$commit = null;
if(strpos($head, "refs/") !== 0) {
$commit = $head;
}
if(file_exists(__DIR__.'/.git/'.$head)) {
$commit = str_replace("\n", "", file_get_contents(__DIR__.'/.git/'.$head));
}
return substr($commit, 0, 7);
}
function convertPHPSizeToBytes($sSize)
{

View File

@ -1,5 +1,14 @@
[globals]
# Path to which stored pdf to activate the mode of sharing a signature to several.
# To deactivate this mode, simply do not configure it or leave it empty
PDF_STORAGE_PATH=/path/to/folder
; Path to which stored pdf to activate the mode of sharing a signature to several.
; To deactivate this mode, simply do not configure it or leave it empty
PDF_STORAGE_PATH=/path/to/folder
; Disable organization tab and routes
;DISABLE_ORGANIZATION=false
; Manage demo link pdf : true (by default, show with default link), false (hide), or custom link
;PDF_DEMO_LINK=true
; Metadata default fields
;METADATA_DEFAULT_FIELDS[metadata_key].type = "text"

11
config/config.ini.tpl Normal file
View File

@ -0,0 +1,11 @@
[globals]
; Path to which stored pdf to activate the mode of sharing a signature to several.
; To deactivate this mode, simply do not configure it or leave it empty
PDF_STORAGE_PATH=${PDF_STORAGE_PATH}
; Disable organization tab and routes
DISABLE_ORGANIZATION=${DISABLE_ORGANIZATION}
; Manage demo link pdf : true (by default, show), false (hide), or custom link
PDF_DEMO_LINK=${PDF_DEMO_LINK}

12
entrypoint.sh Executable file
View File

@ -0,0 +1,12 @@
#! /bin/bash
envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf
envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini
envsubst < /usr/local/signaturepdf/config/config.ini.tpl > /usr/local/signaturepdf/config/config.ini
if [[ ! -z $PDF_STORAGE_PATH ]] ; then
mkdir -p $PDF_STORAGE_PATH
chown www-data:www-data $PDF_STORAGE_PATH
fi
apache2-foreground

View File

@ -34,13 +34,51 @@
max-height: 200px;
}
.canvas-container .btn-drag {
.canvas-container .btn-drag, .canvas-container .btn-rotate, .canvas-container .btn-delete, .canvas-container .btn-select, .canvas-container .btn-download, .canvas-container .btn-restore, .canvas-container .btn-drag-here-left, .canvas-container .btn-drag-here-right, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
font-size: 30px;
cursor: move;
background: rgb(255,255,255,0.6);
opacity: 0;
}
.canvas-container:hover .btn-drag {
opacity: 1;
.canvas-container .btn-drag-here-left, .canvas-container .btn-drag-here-right, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
cursor: pointer;
z-index: 9999;
}
.canvas-container .btn-rotate, .canvas-container .btn-delete, .canvas-container .btn-select, .canvas-container .btn-download, .canvas-container .btn-restore, .canvas-container .btn-drag-here-left, .canvas-container .btn-drag-here-right {
cursor: pointer;
font-size: 25px;
}
.canvas-container .btn-rotate:hover, .canvas-container .btn-delete:hover, .canvas-container .btn-select:hover, .canvas-container .btn-download:hover, .canvas-container .btn-restore:hover, .canvas-container .btn-drag:hover {
background: rgb(255, 255, 255, 1);
box-shadow: 0 .5rem 1rem rgba(0,0,0,.15) !important;
}
.canvas-container .btn-cancel {
font-size: 20px;
}
.border-transparent {
border-color: transparent !important;
}
.delete-metadata {
display: none;
cursor: pointer;
position: absolute;
right: 10px;
top: 0;
font-size: 1.2rem;
user-select: none;
}
.input-metadata:hover > .delete-metadata {
display: block;
}
@media (max-width: 480px) {
.subtitle {
font-size: .875em
}
}

BIN
public/favicon-metadata.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

300
public/js/metadata.js Normal file
View File

@ -0,0 +1,300 @@
var windowWidth = window.innerWidth;
var menu = null;
var menuOffcanvas = null;
var is_mobile = function() {
return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none");
};
var responsiveDisplay = function() {
if(is_mobile()) {
menu.classList.remove('show');
menuOffcanvas.hide();
} else {
menuOffcanvas.show();
}
menu.classList.remove('d-md-block');
menu.classList.remove('d-none');
};
var pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
var nbPDF = 0;
var pages = [];
var pdfRenderTasks = [];
let filename = null
let pdffile = null
let deletedMetadata = [];
var loadPDF = async function(pdfBlob, filename, pdfIndex) {
let url = await URL.createObjectURL(pdfBlob);
pdffile = pdfBlob
let loadingTask = pdfjsLib.getDocument(url);
document.querySelector('#text_document_name span').innerText = filename;
await loadingTask.promise.then(function(pdf) {
pdf.getMetadata().then(function(metadata) {
console.log(metadata);
for(fieldKey in defaultFields) {
addMetadata(fieldKey, null, defaultFields[fieldKey]['type'], false);
}
for(metaKey in metadata.info) {
if(metaKey == "Custom" || metaKey == "PDFFormatVersion" || metaKey.match(/^Is/) || metaKey == "Trapped") {
continue;
}
addMetadata(metaKey, metadata.info[metaKey], "text", false);
}
for(metaKey in metadata.info.Custom) {
if(metaKey == "sha256") {
continue;
}
addMetadata(metaKey, metadata.info.Custom[metaKey], "text", false);
}
for(let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) {
let pageIndex = (page.pageNumber - 1);
pages[pageIndex] = page;
pageRender(pageIndex);
});
}
if(document.querySelector('.input-metadata input')) {
document.querySelector('.input-metadata input').focus();
} else {
document.getElementById('input_metadata_key').focus();
}
});
}, function (reason) {
console.error(reason);
});
return loadingTask;
}
var pageRender = async function(pageIndex) {
let page = pages[pageIndex];
let viewport = page.getViewport({scale: 1});
let sizeWidth = document.getElementById('container-pages').offsetWidth;
let scaleWidth = sizeWidth / viewport.width;
let viewportWidth = page.getViewport({scale: scaleWidth });
viewport = viewportWidth;
let canvasPDF = document.createElement('canvas');
canvasPDF.classList.add('shadow-sm');
document.getElementById('container-pages').appendChild(canvasPDF);
let context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width;
if(pdfRenderTasks[pageIndex]) {
pdfRenderTasks[pageIndex].cancel();
}
pdfRenderTasks[pageIndex] = await page.render({
canvasContext: context,
viewport: viewport,
});
}
var addMetadata = function(key, value, type, focus) {
let input = document.querySelector('.input-metadata input[name="'+key+'"]');
if(input && !input.value) {
input.value = value;
}
if(input && focus) {
input.focus();
}
if(input) {
return;
}
let div = document.createElement('div');
div.classList.add('form-floating', 'mt-3', 'input-metadata');
input = document.createElement('input');
input.value = value;
input.type = type;
input.name = key;
input.classList.add('form-control');
let label = document.createElement('label');
label.innerText = key;
let deleteButton = document.createElement('div')
deleteButton.title = "Supprimer cette metadonnée"
deleteButton.innerHTML = "×"
deleteButton.classList.add('delete-metadata')
div.appendChild(input);
div.appendChild(label);
div.appendChild(deleteButton);
document.getElementById('form-metadata-container').appendChild(div);
if(focus) {
input.focus();
}
}
const deleteMetadata = function(el) {
if (confirm("Souhaitez-vous supprimer ce champ ?") === false) return;
const input = el.closest('.input-metadata')
const label = input.querySelector('label').innerText
deletedMetadata.push(label)
input.remove()
}
const DL = function (d,f) {
let a = document.createElement("a"),
u = URL.createObjectURL(d);
a.download = f,
a.href = u,
a.click(),
setTimeout(() => URL.revokeObjectURL(u))
}
const save = async function () {
const PDFDocument = window['PDFLib'].PDFDocument
const PDFHexString = window['PDFLib'].PDFHexString
const PDFName = window['PDFLib'].PDFName
const arrayBuffer = await pdffile.arrayBuffer();
const pdf = await PDFDocument.load(arrayBuffer);
deletedMetadata.forEach(function (el) {
pdf.getInfoDict().delete(PDFName.of(el))
});
([...document.getElementsByClassName('input-metadata')] || []).forEach(function (el) {
const label = el.querySelector('label').innerText
const input = el.querySelector('input').value
pdf.getInfoDict().set(PDFName.of(label), PDFHexString.fromText(input));
});
const newPDF = new Blob([await pdf.save()], {type: "application/pdf"});
DL(newPDF, filename)
}
var createEventsListener = function() {
document.getElementById('form_metadata_add').addEventListener('submit', function(e) {
let formData = new FormData(this);
addMetadata(formData.get('metadata_key'), "", "text", true);
this.classList.add('invisible');
setTimeout(function() { document.getElementById('form_metadata_add').classList.remove('invisible'); }, 400);
this.reset();
e.preventDefault();
});
document.getElementById('input_metadata_value').addEventListener('focus', function(e) {
if(document.getElementById('input_metadata_key').value) {
document.querySelector('#form_metadata_add button').click();
}
});
document.addEventListener('click', function (event) {
if (event.target.closest(".delete-metadata")) {
deleteMetadata(event.target)
}
})
document.getElementById('save').addEventListener('click', function (e) {
save()
})
}
async function getPDFBlobFromCache(cacheUrl) {
const cache = await caches.open('pdf');
let responsePdf = await cache.match(cacheUrl);
if(!responsePdf) {
return null;
}
let pdfBlob = await responsePdf.blob();
return pdfBlob;
}
async function uploadFromUrl(url) {
history.replaceState({}, '', '/metadata');
var response = await fetch(url);
if(response.status != 200) {
return;
}
var pdfBlob = await response.blob();
if(pdfBlob.type != 'application/pdf' && pdfBlob.type != 'application/octet-stream') {
return;
}
let dataTransfer = new DataTransfer();
let filename = url.replace(/^.*\//, '');
dataTransfer.items.add(new File([pdfBlob], filename, {
type: 'application/pdf'
}));
document.getElementById('input_pdf_upload').files = dataTransfer.files;
document.getElementById('input_pdf_upload').dispatchEvent(new Event("change"));
}
var pageUpload = async function() {
document.querySelector('body').classList.remove('bg-light');
document.getElementById('input_pdf_upload').value = '';
document.getElementById('page-upload').classList.remove('d-none');
document.getElementById('page-metadata').classList.add('d-none');
document.getElementById('input_pdf_upload').focus();
let cache;
try {
cache = await caches.open('pdf');
} catch (e) {
console.error(e)
alert("Erreur d'accès au cache. Cette application ne fonctionne pas en mode de navigation privée");
return;
}
document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
let filename = document.getElementById('input_pdf_upload').files[0].name;
let response = new Response(document.getElementById('input_pdf_upload').files[0], { "status" : 200, "statusText" : "OK" });
let urlPdf = '/pdf/'+filename;
await cache.put(urlPdf, response);
history.pushState({}, '', '/metadata#'+filename);
pageMetadata(urlPdf)
});
}
var pageMetadata = async function(url) {
filename = url.replace('/pdf/', '');
document.title = filename + ' - ' + document.title;
document.querySelector('body').classList.add('bg-light');
document.getElementById('page-upload').classList.add('d-none');
document.getElementById('page-metadata').classList.remove('d-none');
menu = document.getElementById('sidebarTools');
menuOffcanvas = new bootstrap.Offcanvas(menu);
responsiveDisplay();
let pdfBlob = await getPDFBlobFromCache(url);
if(!pdfBlob) {
document.location = '/metadata';
return;
}
createEventsListener();
loadPDF(pdfBlob, filename, nbPDF);
};
(function () {
if(window.location.hash && window.location.hash.match(/^\#http/)) {
let hashUrl = window.location.hash.replace(/^\#/, '');
pageUpload();
uploadFromUrl(hashUrl);
} else if(window.location.hash) {
pageMetadata('/pdf/'+window.location.hash.replace(/^\#/, ''));
} else {
pageUpload();
}
window.addEventListener('hashchange', function() {
window.location.reload();
})
})();

View File

@ -1,7 +1,57 @@
var windowWidth = window.innerWidth;
var menu = null;
var menuOffcanvas = null;
var is_mobile = function() {
return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none");
};
var hasTouch = function() {
return 'ontouchstart' in document.documentElement
|| navigator.maxTouchPoints > 0
|| navigator.msMaxTouchPoints > 0;
}
var disabledHoverStyle = function() {
try { // prevent exception on browsers not supporting DOM styleSheets properly
for (var si in document.styleSheets) {
var styleSheet = document.styleSheets[si];
if (!styleSheet.rules) continue;
for (var ri = styleSheet.rules.length - 1; ri >= 0; ri--) {
if (!styleSheet.rules[ri].selectorText) continue;
if (styleSheet.rules[ri].selectorText.match(':hover')) {
styleSheet.deleteRule(ri);
}
}
}
} catch (ex) {}
}
var responsiveDisplay = function() {
if(is_mobile()) {
document.getElementById('page-organization').style.paddingRight = "inherit";
menu.classList.remove('show');
menuOffcanvas.hide();
document.getElementById('container-pages').classList.remove('vh-100');
document.getElementById('container-btn-zoom').style.top = '62px';
document.getElementById('container-btn-zoom').style.right = '6px';
document.getElementById('container-btn-zoom').classList.add('d-none');
} else {
menuOffcanvas.show();
document.getElementById('page-organization').style.paddingRight = "350px";
document.getElementById('container-pages').classList.add('vh-100');
document.getElementById('container-btn-zoom').style.top = '6px';
document.getElementById('container-btn-zoom').style.right = '368px';
document.getElementById('container-btn-zoom').classList.remove('d-none');
}
menu.classList.remove('d-md-block');
menu.classList.remove('d-none');
};
var isSelectionMode = function() {
return document.querySelectorAll('.canvas-container .input-select:checked').length > 0;
}
var isDraggedMode = function() {
return document.querySelectorAll('.canvas-container .input-drag:checked').length > 0;
}
var nbPagePerLine = 5;
if(is_mobile()) {
nbPagePerLine = 2;
@ -9,151 +59,650 @@ if(is_mobile()) {
var pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
var nbPDF = 0;
var pages = [];
var pdfRenderTasks = [];
var loadPDF = async function(pdfBlob, filename, pdfIndex) {
let url = await URL.createObjectURL(pdfBlob);
let dataTransfer = new DataTransfer();
for (var i = 0; i < document.getElementById('input_pdf').files.length; i++) {
let i = 0;
for (i = 0; i < document.getElementById('input_pdf').files.length; i++) {
dataTransfer.items.add(document.getElementById('input_pdf').files[i]);
}
dataTransfer.items.add(new File([pdfBlob], filename, {
type: 'application/pdf'
}));
document.getElementById('input_pdf').files = dataTransfer.files;
updateListePDF();
let pdfLetter = String.fromCharCode(96 + i+1).toUpperCase();
let loadingTask = pdfjsLib.getDocument(url);
loadingTask.promise.then(function(pdf) {
await loadingTask.promise.then(function(pdf) {
for(var pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) {
let viewport = page.getViewport({scale: 1});
let scale = (document.getElementById('container-pages').clientWidth - (12*nbPagePerLine) - 12) / viewport.width / nbPagePerLine;
viewport = page.getViewport({scale: scale});
let pageIndex = pdfLetter + "_" + (page.pageNumber - 1);
pages[pageIndex] = page;
var pageIndex = page.pageNumber - 1;
let pageHTML = '<div class="position-relative mt-0 ms-1 me-0 mb-1 canvas-container d-flex align-items-center justify-content-center bg-transparent bg-opacity-25 border border-2 border-transparent" id="canvas-container-' + pageIndex +'" draggable="true">';
pageHTML += '<canvas class="canvas-pdf shadow-sm"></canvas>';
pageHTML += '<div title="Séléctionner cette page" class="position-absolute top-0 start-50 translate-middle-x p-2 ps-3 pe-3 mt-2 rounded-circle btn-select d-none"><i class="bi bi-check-square"></i></div>';
pageHTML += '<div title="Supprimer cette page" class="position-absolute top-50 start-0 translate-middle-y p-2 ps-3 pe-3 ms-2 rounded-circle btn-delete d-none"><i class="bi bi-trash"></i></div>';
pageHTML += '<div title="Restaurer cette page" class="position-absolute top-50 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-restore d-none"><i class="bi bi-recycle"></i></div>';
pageHTML += '<div title="Déplacer cette page" class="position-absolute top-50 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag d-none"><i class="bi bi-arrows-move"></i></div>';
pageHTML += '<div title="Déplacer ici" class="position-absolute start-0 top-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here-left bg-white shadow d-none" style="left: -5px !important;"><i class="bi bi-arrow-up-square"></i></div>';
pageHTML += '<div title="Déplacer ici" class="position-absolute start-100 top-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here-right bg-white shadow d-none" style="margin-left: 3px !important;"><i class="bi bi-arrow-up-square"></i></div>';
pageHTML += '<div title="Déplacer ici" class="position-absolute top-100 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here_mobile bg-white shadow d-none"><i class="bi bi-arrows-collapse"></i></div>';
pageHTML += '<div title="Tourner cette page" class="position-absolute top-50 end-0 translate-middle-y p-2 ps-3 pe-3 me-2 rounded-circle container-rotate btn-rotate d-none"><i class="bi bi-arrow-clockwise"></i></div>';
pageHTML += '<div title="Télécharger cette page" class="position-absolute bottom-0 start-50 translate-middle-x p-2 ps-3 pe-3 mb-3 rounded-circle btn-download d-none"><i class="bi bi-download"></i></div>';
pageHTML += '<p class="page-title position-absolute text-center w-100 ps-2 pe-2 pb-0 pt-0 mb-1 bg-white opacity-75 d-none" style="bottom: -4px; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">Page '+page.pageNumber+' - '+filename+'</p>';
pageHTML += '<input form="form_pdf" class="checkbox-page d-none" role="switch" type="checkbox" checked="checked" value="'+pdfLetter+page.pageNumber+'" />';
pageHTML += '<input type="hidden" class="input-rotate" value="0" id="input_rotate_'+pageIndex+'" />';
pageHTML += '<input type="checkbox" class="input-select d-none" value="'+pdfLetter+page.pageNumber+'" id="input_select_'+pageIndex+'" />';
pageHTML += '<input type="checkbox" class="input-hover d-none" value="'+pdfLetter+page.pageNumber+'" id="input_select_'+pageIndex+'" />';
pageHTML += '<input type="checkbox" class="input-drag d-none" value="'+pdfLetter+page.pageNumber+'" id="input_drag_'+pageIndex+'" />';
pageHTML += '</div>';
document.getElementById('container-pages').insertAdjacentHTML('beforeend', '<div class="position-relative mt-0 ms-1 me-1 mb-0 d-inline-block canvas-container" id="canvas-container-' + pdfIndex + "_" + pageIndex +'" draggable="true"><canvas class="shadow-sm canvas-pdf" style="border: 2px solid transparent;"></canvas><div class="position-absolute top-50 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag"><i class="bi bi-arrows-move"></i></div><div class="position-absolute text-center w-100 pt-1 container-checkbox pb-4" style="background: rgb(255,255,255,0.8); bottom: 7px; cursor: pointer;"><div class="form-switch"><input form="form_pdf" class="form-check-input checkbox-page" role="switch" type="checkbox" checked="checked" style="cursor: pointer;" value="'+page.pageNumber+'"" /></div></div><p class="position-absolute text-center w-100 ps-2 pe-2 pb-0 mb-1 opacity-75" style="bottom: 7px; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">Page '+page.pageNumber+' - '+filename+'</p></div>');
document.getElementById('container-pages').insertAdjacentHTML('beforeend', pageHTML);
let canvasContainer = document.getElementById('canvas-container-' + pdfIndex + "_" + pageIndex);
let canvasContainer = document.getElementById('canvas-container-' + pageIndex);
canvasContainer.addEventListener('click', function(e) {
if(isPageDeleted(this) || isPageDragged(this)) {
return;
}
canvasContainer.querySelector('.btn-select').click();
});
canvasContainer.addEventListener('mouseenter', function(e) {
if(is_mobile()) {
return false;
}
this.querySelector('input[type=checkbox].input-hover').checked = true;
updatePageState(this);
});
canvasContainer.addEventListener('mouseleave', function(e) {
this.querySelector('input[type=checkbox].input-hover').checked = false;
updatePageState(this);
});
canvasContainer.addEventListener('dragstart', function(e) {
this.querySelector('.container-checkbox').classList.add('d-none');
if(is_mobile()) {
return false;
}
if(isDraggedMode()) {
return false;
}
if(isSelectionMode()) {
return false;
}
this.querySelector('.container-resize').classList.add('d-none');
this.querySelector('.canvas-pdf').classList.add('shadow-lg');
this.querySelector('.canvas-pdf').style.border = '2px dashed #777';
e.dataTransfer.setData('element', this.id);
this.style.opacity = 0.4;
document.querySelector('#container-bar').classList.add('d-none');
});
canvasContainer.addEventListener('dragend', function(e) {
this.querySelector('.container-checkbox').classList.remove('d-none');
this.querySelector('.container-resize').classList.remove('d-none');
this.querySelector('.canvas-pdf').classList.remove('shadow-lg');
this.querySelector('.canvas-pdf').style.border = '2px solid transparent';
this.querySelector('.canvas-pdf').style.removeProperty('border');
this.style.opacity = 1;
document.querySelector('#container-bar').classList.remove('d-none');
stateCheckbox(this.querySelector('input[type=checkbox]'));
updatePageState(this);
});
canvasContainer.addEventListener('dragover', function(e) {
if (e.preventDefault) {
e.preventDefault();
}
if(e.layerX > e.target.clientWidth / 2) {
this.insertAdjacentElement('beforebegin', document.querySelector('#'+e.dataTransfer.getData('element')));
} else {
this.insertAdjacentElement('afterend', document.querySelector('#'+e.dataTransfer.getData('element')));
let pdfOver = this;
let pdfMoving = document.querySelector('#'+e.dataTransfer.getData('element'));
if(pdfOver.id == pdfMoving.id) {
return;
}
let leftRight = false;
let topBottom = false;
if(pdfOver.offsetTop < pdfMoving.offsetTop) {
topBottom = 'top';
}
if(pdfOver.offsetTop > pdfMoving.offsetTop) {
topBottom = 'bottom';
}
if(pdfOver.offsetLeft > pdfMoving.offsetLeft) {
leftRight = 'right';
}
if(pdfOver.offsetLeft < pdfMoving.offsetLeft) {
leftRight = 'left';
}
if (leftRight == 'left' || topBottom == 'top') {
pdfOver.insertAdjacentElement('beforebegin', pdfMoving);
}
if (leftRight == 'right' || topBottom == 'bottom') {
pdfOver.insertAdjacentElement('afterend', pdfMoving);
}
return false;
});
canvasContainer.querySelector('input[type=checkbox]').addEventListener('click', function(e) {
canvasContainer.querySelector('.btn-delete').addEventListener('click', function(e) {
e.stopPropagation();
toggleDeletePage(this.parentNode);
});
canvasContainer.querySelector('input[type=checkbox]').addEventListener('change', function(e) {
stateCheckbox(this);
stateCheckboxAll();
canvasContainer.querySelector('.btn-restore').addEventListener('click', function(e) {
e.stopPropagation();
toggleDeletePage(this.parentNode);
});
canvasContainer.addEventListener('click', function(e) {
let checkbox = this.querySelector('input[type=checkbox]');
checkbox.checked = !checkbox.checked;
stateCheckbox(checkbox);
stateCheckboxAll();
canvasContainer.querySelector('.btn-select').addEventListener('click', function(e) {
e.stopPropagation();
toggleSelectPage(this.parentNode);
});
canvasContainer.querySelector('.btn-drag').addEventListener('click', function(e) {
e.stopPropagation();
toggleDragPage(this.parentNode);
});
canvasContainer.querySelector('.btn-drag-here_mobile').addEventListener('click', function(e) {
e.stopPropagation();
movePagesDragged(this.parentNode, 'right');
});
canvasContainer.querySelector('.btn-drag-here-right').addEventListener('click', function(e) {
e.stopPropagation();
movePagesDragged(this.parentNode, 'right');
});
canvasContainer.querySelector('.btn-drag-here-left').addEventListener('click', function(e) {
e.stopPropagation();
movePagesDragged(this.parentNode, 'left');
});
canvasContainer.querySelector('.btn-download').addEventListener('click', function(e) {
e.stopPropagation();
let container = this.parentNode;
let pageValue = container.querySelector('.checkbox-page').value;
let orientation = degreesToOrientation(container.querySelector('.input-rotate').value);
if(orientation) {
pageValue = pageValue + "-" + orientation;
}
document.querySelector('#input_pages').value = pageValue;
document.querySelector('#form_pdf').submit();
});
canvasContainer.querySelector('.btn-rotate').addEventListener('click', function(e) {
e.stopPropagation();
let inputRotate = this.parentNode.querySelector('.input-rotate');
inputRotate.value = (parseInt(inputRotate.value) + 90) % 360;
pageRender(pageIndex);
})
var canvasPDF = canvasContainer.querySelector('.canvas-pdf');
// Prepare canvas using PDF page dimensions
var context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width;
var renderContext = {
canvasContext: context,
viewport: viewport,
enhanceTextSelection: true
};
page.render(renderContext);
pageRender(pageIndex);
});
}
}, function (reason) {
console.error(reason);
});
return loadingTask;
};
var stateCheckbox = function(checkbox) {
let checkboxContainer = checkbox.parentNode.parentNode.parentNode;
if(checkbox.checked) {
checkboxContainer.querySelector('.canvas-pdf').style.opacity = '1';
checkboxContainer.querySelector('.canvas-pdf').style.cursor = 'inherit';
checkboxContainer.querySelector('.container-resize').classList.remove('d-none');
checkboxContainer.querySelector('.container-checkbox').style.background = 'rgb(255,255,255,0.8)';
} else {
checkboxContainer.querySelector('.canvas-pdf').style.opacity = '0.3';
checkboxContainer.querySelector('.canvas-pdf').style.cursor = 'pointer';
checkboxContainer.querySelector('.container-resize').classList.add('d-none');
checkboxContainer.querySelector('.container-checkbox').style.background = 'transparent';
var pageRenderAll = function() {
for(pageIndex in pages) {
pageRender(pageIndex);
}
};
}
var stateCheckboxAll = function() {
document.querySelector('#checkbox_all_pages').checked = (document.querySelectorAll('.checkbox-page:checked').length == document.querySelectorAll('.checkbox-page').length);
};
var pageRender = async function(pageIndex) {
let scrollWidth = 12;
if(is_mobile()) {
scrollWidth = -4;
}
let page = pages[pageIndex];
let rotation = parseInt(document.querySelector('#input_rotate_'+pageIndex).value);
let viewport = page.getViewport({scale: 1, rotation: rotation});
let sizeWidth = Math.floor((document.getElementById('container-pages').offsetWidth - (8*(nbPagePerLine+1)) - scrollWidth) / nbPagePerLine);
let sizeHeight = sizeWidth * 1.25;
let scaleWidth = sizeWidth / viewport.width;
let scaleHeight = sizeHeight / viewport.height;
let viewportWidth = page.getViewport({scale: scaleWidth, rotation: rotation});
let viewportHeight = page.getViewport({scale: scaleHeight, rotation: rotation});
var createEventsListener = function() {
document.querySelector('#checkbox_all_pages').addEventListener('change', function() {
let checkboxAll = this;
document.querySelectorAll('.checkbox-page').forEach(function(checkbox) {
checkbox.checked = checkboxAll.checked;
stateCheckbox(checkbox);
});
if(viewportWidth.height > sizeWidth) {
viewport = viewportHeight;
} else {
viewport = viewportWidth;
}
let canvasContainer = document.getElementById('canvas-container-' + pageIndex);
canvasContainer.style.height = (sizeHeight + 4) + "px";
canvasContainer.style.width = (sizeWidth + 4) + "px";
let canvasPDF = canvasContainer.querySelector('.canvas-pdf');
let context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width;
if(pdfRenderTasks[pageIndex]) {
pdfRenderTasks[pageIndex].cancel();
}
pdfRenderTasks[pageIndex] = await page.render({
canvasContext: context,
viewport: viewport,
});
}
var getFileIndex = function(page) {
return page.id.replace('canvas-container-', '').replace(/_.*$/, '');
}
var getFilesStats = function() {
let files = [];
document.querySelectorAll('.canvas-container').forEach(function(page) {
let fileIndex = getFileIndex(page);
if(!files[fileIndex]) {
files[fileIndex] = { nbPage: 0, nbPageSelected: 0, nbPageDeleted: 0};
}
if(isPageDeleted(page)) {
files[fileIndex].nbPageDeleted++;
} else {
files[fileIndex].nbPage++;
}
if(isPageSelected(page)) {
files[fileIndex].nbPageSelected++;
}
});
document.getElementById('save').addEventListener('click', function(event) {
let order = [];
document.querySelectorAll('.checkbox-page').forEach(function(checkbox) {
if(checkbox.checked) {
order.push(checkbox.value);
}
return files;
}
var updateListePDF = function() {
document.querySelector('#list_pdf').innerHTML = "";
let nbFiles = document.querySelector('#input_pdf').files.length;
for (var i = 0; i < nbFiles; i++) {
let pdfLetter = String.fromCharCode(96 + i+1).toUpperCase();
const pdfFile = document.querySelector('#input_pdf').files.item(i);
document.querySelector('#list_pdf').insertAdjacentHTML('beforeend', '<li id="file_' + pdfLetter + '" class="list-group-item small ps-2 pe-5" title="'+decodeURI(pdfFile.name)+'" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden;"><i class="bi bi-files"></i><span class="ms-2">'+decodeURI(pdfFile.name)+'</span> <input class="form-check-input float-end position-absolute" style="right: 10px;" type="checkbox" /> </li>');
let fileItem = document.querySelector('#file_' + pdfLetter);
fileItem.querySelector('input[type=checkbox]').addEventListener('change', function(e) {
document.querySelectorAll('.canvas-container').forEach(function(page) {
if(getFileIndex(page) == pdfLetter && !isPageDeleted(page)) {
selectPage(page, e.target.checked);
}
});
updateGlobalState();
});
document.querySelector('#input_pages').value = order.join(',');
document.querySelector('#liste_pdf_titre_mobile').innerText = decodeURI(pdfFile.name);
document.querySelector('#btn_liste_pdf_bar span').innerText = nbFiles;
if(nbFiles > 1) {
document.querySelector('#liste_pdf_titre_mobile').innerText = nbFiles + ' documents PDF';
}
}
updateGlobalState();
}
var getPagesSelected = function() {
let pages = [];
document.querySelectorAll('.canvas-container .input-select:checked').forEach(function(item) {
pages[item.parentNode.id.replace('canvas-container-', '')] = item.parentNode;
});
document.getElementById('input_pdf_upload_2').addEventListener('change', async function(event) {
if(this.files[0].size > maxSize) {
return pages;
}
var selectPage = function(page, state) {
page.querySelector('input[type=checkbox].input-select').checked = state;
updatePageState(page);
}
var toggleSelectPage = function(page) {
selectPage(page, !isPageSelected(page));
updateGlobalState();
}
var isPageSelected = function(page) {
return page.querySelector('input[type=checkbox].input-select').checked;
}
var dragPage = function(page, state) {
page.querySelector('input[type=checkbox].input-drag').checked = state;
updatePageState(page);
}
var toggleDragPage = function(page) {
dragPage(page, !isPageDragged(page));
updateGlobalState();
document.querySelectorAll('.canvas-container').forEach(function(page) {
updatePageState(page);
});
}
var isPageDragged = function(page) {
return page.querySelector('input[type=checkbox].input-drag').checked;
}
var movePagesDragged = function(pageHere, position) {
document.querySelectorAll('.canvas-container .input-drag:checked').forEach(function(item) {
let page = item.parentNode;
if(position == 'right') {
pageHere.insertAdjacentElement('afterend', page);
} else {
pageHere.insertAdjacentElement('beforebegin', page);
}
});
bootstrap.Modal.getOrCreateInstance(document.querySelector('#modalDrag')).hide();
}
var toggleDeletePage = function(page) {
deletePage(page, isPageDeleted(page))
updateGlobalState();
}
var deletePage = function(page, state) {
page.querySelector('input[type=checkbox].checkbox-page').checked = state;
page.querySelector('input[type=checkbox].input-select').checked = false;
updatePageState(page);
}
var isPageDeleted = function(page) {
return !page.querySelector('input[type=checkbox].checkbox-page').checked;
}
var isPageHover = function(page) {
return page.querySelector('input[type=checkbox].input-hover').checked;
}
var updatePageState = function(page) {
page.classList.remove('border-primary', 'shadow-sm', 'bg-primary', 'border-secondary', 'bg-secondary');
page.classList.add('border-transparent', 'bg-transparent');
page.querySelector('.canvas-pdf').style.opacity = '1';
page.querySelector('.canvas-pdf').style.zIndex = 'inherit';
page.querySelector('.canvas-pdf').classList.add('shadow-sm');
page.querySelector('.canvas-pdf').classList.remove('shadow');
page.querySelector('.btn-rotate').classList.add('d-none');
page.querySelector('.btn-download').classList.add('d-none');
page.querySelector('.btn-delete').classList.add('d-none');
page.querySelector('.btn-select').classList.add('d-none');
page.querySelector('.btn-select').classList.remove('text-primary');
page.querySelector('.btn-drag').classList.add('d-none');
page.querySelector('.btn-drag-here-left').classList.add('d-none');
page.querySelector('.btn-drag-here-right').classList.add('d-none');
page.querySelector('.btn-drag-here_mobile').classList.add('d-none');
page.querySelector('.btn-restore').classList.add('d-none');
page.querySelector('.page-title').classList.add('d-none');
page.querySelector('.canvas-pdf').classList.remove('opacity-50');
page.classList.remove('page-dragged');
if(isPageDeleted(page)) {
page.querySelector('.canvas-pdf').style.opacity = '0.15';
}
if(isPageHover(page) && !isPageDeleted(page) && !isPageDragged(page) && !isPageSelected(page) && !isDraggedMode()) {
page.querySelector('.page-title').classList.remove('d-none');
page.classList.add('border-secondary', 'bg-secondary');
page.classList.remove('border-transparent', 'bg-transparent');
page.querySelector('.btn-select').classList.remove('d-none')
}
if(isPageHover(page) && !isPageDeleted(page) && !isPageDragged(page) && !isPageSelected(page) && !isDraggedMode() && !isSelectionMode()) {
page.querySelector('.btn-rotate').classList.remove('d-none');
page.querySelector('.btn-download').classList.remove('d-none');
page.querySelector('.btn-delete').classList.remove('d-none');
page.querySelector('.btn-drag').classList.remove('d-none');
}
if(isPageHover(page) && isPageDeleted(page)) {
page.querySelector('.btn-restore').classList.remove('d-none');
}
if(is_mobile() && isPageDeleted(page)) {
page.querySelector('.btn-restore').classList.remove('d-none');
}
if(isPageSelected(page) && !isDraggedMode()) {
page.querySelector('.page-title').classList.remove('d-none');
page.classList.add('border-primary', 'shadow-sm', 'bg-primary');
page.classList.remove('border-transparent', 'bg-transparent', 'border-secondary', 'bg-secondary');
page.querySelector('.btn-select').classList.add('text-primary');
page.querySelector('.btn-select').classList.remove('d-none');
}
if(isPageDragged(page)) {
page.classList.add('page-dragged');
page.querySelector('.canvas-pdf').classList.remove('shadow-sm');
page.querySelector('.canvas-pdf').classList.add('shadow');
}
if(!isPageDragged(page) && isDraggedMode()) {
page.querySelector('.canvas-pdf').classList.add('opacity-50');
page.querySelector('.btn-drag-here-left').classList.remove('d-none');
page.querySelector('.btn-drag-here-right').classList.remove('d-none');
}
}
var updateFilesState = function() {
let filesStats = getFilesStats();
for(fileIndex in filesStats) {
let checkbox = document.querySelector('#file_'+fileIndex+' input[type=checkbox]');
let fileStat = filesStats[fileIndex];
checkbox.checked = (fileStat.nbPageSelected > 0 && fileStat.nbPageSelected == fileStat.nbPage);
checkbox.indeterminate = (fileStat.nbPageSelected > 0 && fileStat.nbPageSelected < fileStat.nbPage);
document.querySelector('#file_'+fileIndex+' span').classList.remove('text-primary');
if(fileStat.nbPageSelected > 0) {
document.querySelector('#file_'+fileIndex+' span').classList.add('text-primary');
}
}
}
var updateGlobalState = function() {
updateFilesState();
if(!is_mobile()) {
document.querySelector('#container-btn-zoom').classList.remove('d-none');
}
document.querySelector('#container_btn_select').classList.add('opacity-50');
document.querySelector('#container_btn_select').classList.remove('border-primary');
document.querySelector('#container_btn_select .card-header').classList.remove('bg-primary', 'text-white');
document.querySelector('#container_btn_select .card-header').classList.add('text-muted');
document.querySelectorAll('#container_btn_select .card-body button').forEach(function(button) {
button.classList.add('btn-outline-secondary');
button.classList.remove('btn-outline-primary');
button.setAttribute('disabled', 'disabled');
});
document.querySelector('#container_btn_select .card-header span').innerText = "Aucune";
document.querySelector('#container_btn_select .card-footer').classList.add('d-none');
document.querySelector('#top_bar_action').classList.remove('d-none');
document.querySelector('#top_bar_action_selection').classList.add('d-none');
document.querySelector('#bottom_bar_action').classList.remove('d-none');
document.querySelector('#bottom_bar_action_selection').classList.add('d-none');
document.querySelector('#save').removeAttribute('disabled');
if(isSelectionMode()) {
document.querySelector('#container_btn_select .card-header span').innerText = document.querySelectorAll('.canvas-container .input-select:checked').length;
document.querySelector('#top_bar_action_selection_recap_nb_pages').innerText = document.querySelectorAll('.canvas-container .input-select:checked').length;
document.querySelector('#container_btn_select').classList.remove('opacity-50');
document.querySelector('#container_btn_select').classList.add('border-primary');
document.querySelector('#container_btn_select .card-header').classList.remove('text-muted');
document.querySelector('#container_btn_select .card-header').classList.add('bg-primary', 'text-white');
document.querySelectorAll('#container_btn_select .card-body button').forEach(function(button) {
button.classList.add('btn-outline-primary');
button.classList.remove('btn-outline-secondary');
button.removeAttribute('disabled');
});
document.querySelector('#container_btn_select .card-footer').classList.remove('d-none');
document.querySelectorAll('.canvas-container .btn-add').forEach(function(button) {
button.classList.remove('d-none');
});
document.querySelector('#top_bar_action_selection').classList.remove('d-none');
document.querySelector('#top_bar_action').classList.add('d-none');
document.querySelector('#bottom_bar_action_selection').classList.remove('d-none');
document.querySelector('#bottom_bar_action').classList.add('d-none');
document.querySelector('#save').setAttribute('disabled', 'disabled');
}
if(isDraggedMode()) {
document.querySelector('#modalDrag .modal-body').insertAdjacentElement('afterbegin', document.querySelector('#container-pages'));
document.querySelector('#container-pages').style.overflow = 'visible';
bootstrap.Modal.getOrCreateInstance(document.querySelector('#modalDrag')).show();
}
}
var degreesToOrientation = function(degrees) {
if(degrees == 90) { return "east"; }
if(degrees == 180) { return "south"; }
if(degrees == 270) { return "west"; }
return null;
}
var uploadAndLoadPDF = async function(input_upload) {
const cache = await caches.open('pdf');
for (let i = 0; i < input_upload.files.length; i++) {
if(input_upload.files[i].size > maxSize) {
alert("Le PDF ne doit pas dépasser " + Math.round(maxSize/1024/1024) + " Mo");
this.value = "";
return;
break;
}
const cache = await caches.open('pdf');
let filename = this.files[0].name;
let response = new Response(this.files[0], { "status" : 200, "statusText" : "OK" });
let filename = input_upload.files[i].name;
let response = new Response(input_upload.files[i], { "status" : 200, "statusText" : "OK" });
let urlPdf = '/pdf/'+filename;
await cache.put(urlPdf, response);
let pdfBlob = await getPDFBlobFromCache(urlPdf);
nbPDF++;
loadPDF(pdfBlob, filename, nbPDF);
await loadPDF(pdfBlob, filename, nbPDF);
}
}
var createEventsListener = function() {
document.getElementById('save-select_mobile').addEventListener('click', function(event) {
document.getElementById('save').click();
});
document.getElementById('save-select').addEventListener('click', function(event) {
document.getElementById('save').click();
});
document.getElementById('save').addEventListener('click', function(event) {
let order = [];
let selectionMode = isSelectionMode();
document.querySelectorAll('.canvas-container').forEach(function(canvasContainer) {
let checkbox = canvasContainer.querySelector('.checkbox-page');
if(selectionMode) {
checkbox = canvasContainer.querySelector('.input-select');
}
let inputRotate = canvasContainer.querySelector('.input-rotate');
let pageValue = "";
if(checkbox.checked) {
pageValue = checkbox.value;
}
let orientation = degreesToOrientation(inputRotate.value);
if(pageValue && orientation) {
pageValue = pageValue + "-" + orientation;
}
if(pageValue) {
order.push(pageValue);
}
});
document.querySelector('#input_pages').value = order.join(',');
});
document.getElementById('save_mobile').addEventListener('click', function(event) {
document.getElementById('save').click();
});
document.getElementById('input_pdf_upload_2').addEventListener('change', async function(event) {
await uploadAndLoadPDF(this);
this.value = '';
});
document.getElementById('btn-zoom-decrease').addEventListener('click', function(event) {
nbPagePerLine++;
pageRenderAll();
});
document.getElementById('btn-zoom-increase').addEventListener('click', function(event) {
nbPagePerLine--;
pageRenderAll();
});
document.getElementById('btn_cancel_select_footer').addEventListener('click', function(event) {
document.getElementById('btn_cancel_select').click();
});
document.getElementById('btn_cancel_select_mobile').addEventListener('click', function(event) {
document.getElementById('btn_cancel_select').click();
});
document.getElementById('btn_cancel_select').addEventListener('click', function(event) {
document.querySelectorAll('.input-select:checked').forEach(function(input) {
input.parentNode.querySelector('.btn-select').click();
});
});
document.getElementById('btn_delete_select_mobile').addEventListener('click', function(event) {
document.getElementById('btn_delete_select').click();
});
document.getElementById('btn_delete_select').addEventListener('click', function(event) {
let pages = getPagesSelected();
for(index in pages) {
deletePage(pages[index]);
}
updateGlobalState();
});
document.getElementById('btn_rotate_select_mobile').addEventListener('click', function(event) {
document.getElementById('btn_rotate_select').click();
});
document.getElementById('btn_rotate_select').addEventListener('click', function(event) {
let pages = getPagesSelected();
for(index in pages) {
let inputRotate = pages[index].querySelector('.input-rotate');
inputRotate.value = (parseInt(inputRotate.value) + 90) % 360;
pageRender(index);
}
});
document.getElementById('btn_drag_select').addEventListener('click', function(event) {
let pages = getPagesSelected();
for(index in pages) {
toggleDragPage(pages[index]);
}
});
document.getElementById('btn_drag_select_mobile').addEventListener('click', function(event) {
document.getElementById('btn_drag_select').click();
});
document.querySelector('#modalDrag').addEventListener('shown.bs.modal', event => {
document.querySelector('#modalDrag .modal-body').scrollTop = document.querySelector('.page-dragged').offsetTop;
});
document.querySelector('#modalDrag').addEventListener('hide.bs.modal', event => {
document.querySelector('#container-main').insertAdjacentElement('afterbegin', document.querySelector('#container-pages'));
document.querySelector('#container-pages').style.overflowY = 'scroll';
document.querySelector('#container-pages').style.overflowX = 'hidden';
});
document.querySelector('#modalDrag').addEventListener('hidden.bs.modal', event => {
if(is_mobile()) {
window.scrollTo(0, document.querySelector('.page-dragged').offsetTop);
} else {
document.querySelector('#container-pages').scrollTop = document.querySelector('.page-dragged').offsetTop;
}
document.querySelectorAll('.canvas-container .input-drag:checked').forEach(function(item) {
let page = item.parentNode;
page.querySelector('input[type=checkbox].input-drag').checked = false;
});
document.querySelectorAll('.canvas-container').forEach(function(page) {
page.querySelector('input[type=checkbox].input-hover').checked = false;
updatePageState(page);
});
updateGlobalState();
});
document.querySelector('#btn_liste_pdf').addEventListener('click', function(event) {
bootstrap.Modal.getOrCreateInstance(document.querySelector('#modalFichier')).show();
document.querySelector('#modalFichier .modal-body').insertAdjacentElement('afterbegin', document.querySelector('#list_pdf'));
});
document.querySelector('#btn_liste_pdf_bar').addEventListener('click', function(event) {
document.querySelector('#btn_liste_pdf').click();
});
document.querySelector('#modalDrag').addEventListener('hidden.bs.modal', event => {
document.querySelector('#list_pdf_container').insertAdjacentElement('afterbegin', document.querySelector('#list_pdf'));
});
document.querySelector('body').addEventListener('click', function(event) {
if(!event.originalTarget.classList.contains('offcanvas-header') && !event.originalTarget.classList.contains('offcanvas-body') && event.originalTarget.id != 'container-pages' && event.originalTarget.id != 'sidebarToolsLabel' && event.originalTarget.id != 'btn_container') {
return;
}
document.getElementById('btn_cancel_select').click();
});
}
async function getPDFBlobFromCache(cacheUrl) {
@ -190,40 +739,33 @@ async function uploadFromUrl(url) {
}
var pageUpload = async function() {
document.querySelector('body').classList.remove('bg-light');
document.getElementById('input_pdf_upload').value = '';
document.getElementById('page-upload').classList.remove('d-none');
document.getElementById('page-organization').classList.add('d-none');
document.getElementById('input_pdf_upload').focus();
const cache = await caches.open('pdf');
let cache;
try {
cache = await caches.open('pdf');
} catch (e) {
console.error(e)
alert("Erreur d'accès au cache. Cette application ne fonctionne pas en mode de navigation privée");
return;
}
document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
if(document.getElementById('input_pdf_upload').files[0].size > maxSize) {
alert("Le PDF ne doit pas dépasser " + Math.round(maxSize/1024/1024) + " Mo");
document.getElementById('input_pdf_upload').value = "";
return;
}
let filename = document.getElementById('input_pdf_upload').files[0].name;
let response = new Response(document.getElementById('input_pdf_upload').files[0], { "status" : 200, "statusText" : "OK" });
let urlPdf = '/pdf/'+filename;
await cache.put(urlPdf, response);
history.pushState({}, '', '/organization#'+filename);
pageOrganization(urlPdf)
uploadAndLoadPDF(this);
pageOrganization(null);
});
}
var pageOrganization = async function(url) {
let filename = url.replace('/pdf/', '');
document.title = filename + ' - ' + document.title;
var pageOrganization = async function() {
document.querySelector('body').classList.add('bg-light');
document.getElementById('page-upload').classList.add('d-none');
document.getElementById('page-organization').classList.remove('d-none');
let pdfBlob = await getPDFBlobFromCache(url);
if(!pdfBlob) {
document.location = '/organization';
return;
}
menu = document.getElementById('sidebarTools');
menuOffcanvas = new bootstrap.Offcanvas(menu);
responsiveDisplay();
createEventsListener();
loadPDF(pdfBlob, filename, nbPDF);
};
(function () {
@ -231,12 +773,14 @@ var pageOrganization = async function(url) {
let hashUrl = window.location.hash.replace(/^\#/, '');
pageUpload();
uploadFromUrl(hashUrl);
} else if(window.location.hash) {
pageOrganization('/pdf/'+window.location.hash.replace(/^\#/, ''));
} else {
pageUpload();
}
window.addEventListener('hashchange', function() {
window.location.reload();
})
if (hasTouch()) {
disabledHoverStyle();
}
})();

View File

@ -16,13 +16,17 @@ var menu = null;
var menuOffcanvas = null;
var currentCursor = null;
var signaturePad = null;
var nblayers = null;
var hasModifications = false;
var currentTextScale = 1;
var loadPDF = async function(pdfBlob, filename) {
var pdfjsLib = window['pdfjs-dist/build/pdf'];
const pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
let url = await URL.createObjectURL(pdfBlob);
let text_document_name = document.querySelector('#text_document_name');
text_document_name.querySelector('span').innerText = filename;
text_document_name.setAttribute('title', filename);
@ -36,7 +40,7 @@ var loadPDF = async function(pdfBlob, filename) {
if(document.getElementById('input_pdf_share')) {
document.getElementById('input_pdf_share').files = dataTransfer.files;
}
var loadingTask = pdfjsLib.getDocument(url);
let loadingTask = pdfjsLib.getDocument(url);
loadingTask.promise.then(function(pdf) {
if(pdf.numPages > maxPage) {
@ -44,10 +48,10 @@ var loadPDF = async function(pdfBlob, filename) {
document.location = "/";
return;
}
for(var pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
for(let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) {
var scale = 1.5;
var viewport = page.getViewport({scale: scale});
let scale = 1.5;
let viewport = page.getViewport({scale: scale});
if(viewport.width > document.getElementById('container-pages').clientWidth - 40) {
viewport = page.getViewport({scale: 1});
scale = (document.getElementById('container-pages').clientWidth - 40) / viewport.width;
@ -56,29 +60,29 @@ var loadPDF = async function(pdfBlob, filename) {
currentScale = scale;
var pageIndex = page.pageNumber - 1;
let pageIndex = page.pageNumber - 1;
document.getElementById('form_block').insertAdjacentHTML('beforeend', '<input name="svg[' + pageIndex + ']" id="data-svg-' + pageIndex + '" type="hidden" value="" />');
document.getElementById('container-pages').insertAdjacentHTML('beforeend', '<div class="position-relative mt-1 ms-1 me-1 d-inline-block" id="canvas-container-' + pageIndex +'"><canvas id="canvas-pdf-'+pageIndex+'" class="shadow-sm canvas-pdf"></canvas><div class="position-absolute top-0 start-0"><canvas id="canvas-edition-'+pageIndex+'"></canvas></div></div>');
var canvasPDF = document.getElementById('canvas-pdf-' + pageIndex);
var canvasEditionHTML = document.getElementById('canvas-edition-' + pageIndex);
let canvasPDF = document.getElementById('canvas-pdf-' + pageIndex);
let canvasEditionHTML = document.getElementById('canvas-edition-' + pageIndex);
// Prepare canvas using PDF page dimensions
var context = canvasPDF.getContext('2d');
let context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width;
canvasEditionHTML.height = canvasPDF.height;
canvasEditionHTML.width = canvasPDF.width;
var renderContext = {
let renderContext = {
canvasContext: context,
viewport: viewport,
enhanceTextSelection: true
};
var renderTask = page.render(renderContext);
let renderTask = page.render(renderContext);
pdfRenderTasks.push(renderTask);
pdfPages.push(page);
var canvasEdition = new fabric.Canvas('canvas-edition-' + pageIndex, {
let canvasEdition = new fabric.Canvas('canvas-edition-' + pageIndex, {
selection : false,
allowTouchScrolling: true
});
@ -133,6 +137,10 @@ var loadPDF = async function(pdfBlob, filename) {
}
});
canvasEdition.on('object:scaled', function(event) {
if (event.target instanceof fabric.IText) {
currentTextScale = event.target.scaleX;
return;
}
var item = getSvgItem(event.target.svgOrigin);
if(!item) {
return;
@ -155,6 +163,23 @@ var loadPDF = async function(pdfBlob, filename) {
});
};
var reloadPDF = async function(url) {
const pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
pdfjsLib.getDocument(url).promise.then(function(pdf) {
for(let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) {
page.render({
canvasContext: document.getElementById('canvas-pdf-' + (page.pageNumber - 1)).getContext('2d'),
viewport: page.getViewport({scale: currentScale}),
enhanceTextSelection: true
});
});
}
});
}
var is_mobile = function() {
return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none");
};
@ -180,7 +205,7 @@ var storeCollections = function () {
var getSvgItem = function(svg) {
for (index in svgCollections) {
svgItem = svgCollections[index];
let svgItem = svgCollections[index];
if(svgItem.svg == svg) {
return svgItem;
@ -242,7 +267,7 @@ var svgChange = function(input, event) {
stateAddLock(false);
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
let input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(input_selected && !input_selected.value.match(/^data:/) && input_selected.value != "text") {
input_selected = null;
@ -269,7 +294,7 @@ var svgChange = function(input, event) {
};
var getHtmlSvg = function(svg, i) {
var inputRadio = document.createElement('input');
let inputRadio = document.createElement('input');
inputRadio.type = "radio";
inputRadio.classList.add("btn-check");
inputRadio.id="radio_svg_"+i;
@ -279,7 +304,7 @@ var getHtmlSvg = function(svg, i) {
inputRadio.addEventListener('change', function() {
svgChange(this, event);
});
var svgButton = document.createElement('label');
let svgButton = document.createElement('label');
svgButton.id = "label_svg_"+i;
svgButton.classList.add('position-relative');
svgButton.classList.add('btn');
@ -312,12 +337,12 @@ var getHtmlSvg = function(svg, i) {
svgButton.addEventListener('mouseout', function(event) {
this.style.removeProperty('cursor');
})
var svgImg = document.createElement('img');
let svgImg = document.createElement('img');
svgImg.src = svg.svg;
svgImg.draggable = false;
svgImg.style = "max-width: 180px;max-height: 70px;";
svgButton.appendChild(svgImg);
var svgContainer = document.createElement('div');
let svgContainer = document.createElement('div');
svgContainer.classList.add('d-grid');
svgContainer.classList.add('gap-2');
svgContainer.appendChild(inputRadio);
@ -330,8 +355,8 @@ var stateAddLock = function(state) {
if(forceAddLock) {
state = true;
}
var checkbox = document.getElementById('add-lock-checkbox');
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
let checkbox = document.getElementById('add-lock-checkbox');
let input_selected = document.querySelector('input[name="svg_2_add"]:checked');
addLock = state;
@ -342,13 +367,8 @@ var stateAddLock = function(state) {
checkbox.disabled = false;
}
/*document.querySelectorAll('.btn-svg').forEach(function(item) {
item.style.borderWidth = "1px";
});*/
if(addLock && input_selected) {
var svgButton = document.querySelector('.btn-svg[for="'+input_selected.id+'"]');
//svgButton.style.borderWidth = "2px";
let svgButton = document.querySelector('.btn-svg[for="'+input_selected.id+'"]');
checkbox.checked = true;
return;
}
@ -365,7 +385,7 @@ var displaysSVG = function() {
item.classList.remove('d-none');
});
svgCollections.forEach((svg, i) => {
var svgHtmlChild = getHtmlSvg(svg, i);
let svgHtmlChild = getHtmlSvg(svg, i);
if(svg.type) {
document.getElementById('svg_list_'+svg.type).appendChild(svgHtmlChild);
return;
@ -392,7 +412,7 @@ var displaysSVG = function() {
};
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
@ -410,7 +430,7 @@ function trimSvgWhitespace(svgContent) {
return null;
}
var svgContainer = document.createElement("div")
let svgContainer = document.createElement("div")
svgContainer.classList.add('invisible');
svgContainer.classList.add('position-absolute');
svgContainer.classList.add('top-0');
@ -418,8 +438,8 @@ function trimSvgWhitespace(svgContent) {
svgContainer.style = "z-index: -1;";
svgContainer.innerHTML = svgContent;
document.body.appendChild(svgContainer);
var svg = svgContainer.querySelector('svg');
var box = svg.getBBox();
let svg = svgContainer.querySelector('svg');
let box = svg.getBBox();
svg.setAttribute("viewBox", [box.x, box.y, box.width, box.height].join(" "));
svgContent = svgContainer.innerHTML;
document.body.removeChild(svgContainer)
@ -432,7 +452,7 @@ var uploadSVG = function(formData) {
document.getElementById('btn_modal_ajouter_spinner').classList.remove('d-none');
document.getElementById('btn_modal_ajouter_check').classList.add('d-none');
xhr = new XMLHttpRequest();
let xhr = new XMLHttpRequest();
xhr.open( 'POST', document.getElementById('form-image-upload').action, true );
xhr.onreadystatechange = function () {
@ -481,9 +501,15 @@ var addObjectInCanvas = function(canvas, item) {
};
var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
if(document.querySelector('#alert-signature-help')) {
document.querySelector('#alert-signature-help').classList.add('d-none');
}
if(document.getElementById('save')) {
document.getElementById('save').removeAttribute('disabled');
}
hasModifications = true;
if(document.getElementById('save_mobile')) {
document.getElementById('save_mobile').removeAttribute('disabled');
}
@ -502,7 +528,7 @@ var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
}
if(item == 'text') {
var textbox = new fabric.Textbox('Texte à modifier', {
let textbox = new fabric.Textbox('Texte à modifier', {
left: x,
top: y - 20,
fontSize: 20,
@ -512,6 +538,8 @@ var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
addObjectInCanvas(canvas, textbox).setActiveObject(textbox);
textbox.keysMap[13] = "exitEditing";
textbox.lockScalingFlip = true;
textbox.scaleX = currentTextScale;
textbox.scaleY = currentTextScale;
textbox.enterEditing();
textbox.selectAll();
@ -520,14 +548,14 @@ var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
}
fabric.loadSVGFromURL(item, function(objects, options) {
var svg = fabric.util.groupSVGElements(objects, options);
let svg = fabric.util.groupSVGElements(objects, options);
svg.svgOrigin = item;
svg.lockScalingFlip = true;
svg.scaleToHeight(height);
if(svg.getScaledWidth() > 200) {
svg.scaleToWidth(200);
}
var svgItem = getSvgItem(item);
let svgItem = getSvgItem(item);
if(svgItem && svgItem.scale) {
svg.scaleToWidth(canvas.width * svgItem.scale);
}
@ -548,7 +576,7 @@ var zoomChange = function (inOrOut) {
return;
}
var deltaScale = 0.2 * inOrOut;
let deltaScale = 0.2 * inOrOut;
if(currentScale + deltaScale < 0) {
return
@ -578,7 +606,7 @@ var resizePDF = function (scale = 'auto') {
}
pdfPages.forEach(function(page, pageIndex) {
var renderTask = pdfRenderTasks[pageIndex];
let renderTask = pdfRenderTasks[pageIndex];
if(scale == 'auto' && page.getViewport({scale: 1.5}).width > document.getElementById('container-pages').clientWidth - 40) {
scale = (document.getElementById('container-pages').clientWidth - 40) / page.getViewport({scale: 1}).width;
@ -588,18 +616,18 @@ var resizePDF = function (scale = 'auto') {
scale = 1.5;
}
var viewport = page.getViewport({scale: scale});
let viewport = page.getViewport({scale: scale});
currentScale = scale;
var canvasPDF = document.getElementById('canvas-pdf-' + pageIndex);
var context = canvasPDF.getContext('2d');
let canvasPDF = document.getElementById('canvas-pdf-' + pageIndex);
let context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width;
canvasEdition = canvasEditions[pageIndex];
var scaleMultiplier = canvasPDF.width / canvasEdition.width;
var objects = canvasEdition.getObjects();
for (var i in objects) {
let scaleMultiplier = canvasPDF.width / canvasEdition.width;
let objects = canvasEdition.getObjects();
for (let i in objects) {
objects[i].scaleX = objects[i].scaleX * scaleMultiplier;
objects[i].scaleY = objects[i].scaleY * scaleMultiplier;
objects[i].left = objects[i].left * scaleMultiplier;
@ -612,7 +640,7 @@ var resizePDF = function (scale = 'auto') {
canvasEdition.renderAll();
canvasEdition.calcOffset();
var renderContext = {
let renderContext = {
canvasContext: context,
viewport: viewport,
enhanceTextSelection: true
@ -662,7 +690,7 @@ var createEventsListener = function() {
});
document.getElementById('btn_modal_ajouter').addEventListener('click', function() {
var svgItem = {};
let svgItem = {};
if(document.getElementById('input-svg-type').value) {
svgItem.type = document.getElementById('input-svg-type').value;
}
@ -670,17 +698,17 @@ var createEventsListener = function() {
svgItem.svg = document.getElementById('img-upload').src;
}
if(document.getElementById('nav-type-tab').classList.contains('active')) {
var fontPath = fontCaveat.getPath(document.getElementById('input-text-signature').value, 0, 0, 42);
var fabricPath = new fabric.Path(fontPath.toPathData());
let fontPath = fontCaveat.getPath(document.getElementById('input-text-signature').value, 0, 0, 42);
let fabricPath = new fabric.Path(fontPath.toPathData());
fabricPath.top = 0;
fabricPath.left = 0;
fabricPath.height = fabricPath.getScaledHeight();
var textCanvas = document.createElement('canvas');
let textCanvas = document.createElement('canvas');
textCanvas.width = fabricPath.getScaledWidth();
textCanvas.height = fabricPath.getScaledHeight();
var textCanvas = new fabric.Canvas(textCanvas);
textCanvas.add(fabricPath).renderAll();
svgItem.svg = svgToDataUrl(textCanvas.toSVG());
let textCanvasFabric = new fabric.Canvas(textCanvas);
textCanvasFabric.add(fabricPath).renderAll();
svgItem.svg = svgToDataUrl(textCanvasFabric.toSVG());
}
if(document.getElementById('nav-import-tab').classList.contains('active')) {
svgItem.svg = document.getElementById('img-upload').src;
@ -689,12 +717,16 @@ var createEventsListener = function() {
displaysSVG();
localStorage.setItem('svgCollections', JSON.stringify(svgCollections));
var svg_list_id = "svg_list";
let svg_list_id = "svg_list";
if(svgItem.type) {
svg_list_id = svg_list_id + "_" + svgItem.type;
}
document.querySelector('#'+svg_list_id+' label:last-child').click();
if(document.querySelector('#save').disabled && document.querySelector('#alert-signature-help.auto-open') && !is_mobile()) {
document.querySelector('#alert-signature-help').classList.remove('d-none');
}
});
@ -704,7 +736,7 @@ var createEventsListener = function() {
})
document.querySelectorAll('#modalAddSvg .nav-link').forEach(function(item) { item.addEventListener('shown.bs.tab', function (event) {
var firstInput = document.querySelector(event.target.dataset.bsTarget).querySelector('input');
let firstInput = document.querySelector(event.target.dataset.bsTarget).querySelector('input');
if(firstInput) {
firstInput.focus();
}
@ -712,11 +744,11 @@ var createEventsListener = function() {
document.getElementById('modalAddSvg').addEventListener('shown.bs.modal', function (event) {
document.querySelector('#modalAddSvg #nav-tab button:first-child').focus();
var tab = document.querySelector('#modalAddSvg .tab-pane.active');
let tab = document.querySelector('#modalAddSvg .tab-pane.active');
if(tab.querySelector('input')) {
tab.querySelector('input').focus();
}
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
let input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(input_selected) {
input_selected.checked = false;
input_selected.dispatchEvent(new Event("change"));
@ -742,27 +774,39 @@ var createEventsListener = function() {
})
document.getElementById('input-image-upload').addEventListener('change', function(event) {
var data = new FormData();
let data = new FormData();
data.append('file', document.getElementById('input-image-upload').files[0]);
uploadSVG(data);
event.preventDefault();
});
if(document.querySelector('#alert-signature-help')) {
document.getElementById('btn-signature-help').addEventListener('click', function(event) {
document.querySelector('#alert-signature-help').classList.remove('d-none');
event.preventDefault();
});
document.querySelector('#alert-signature-help .btn-close').addEventListener('click', function(event) {
document.querySelector('#alert-signature-help').classList.add('d-none');
event.preventDefault();
});
}
if(document.getElementById('save')) {
document.getElementById('save').addEventListener('click', function(event) {
var dataTransfer = new DataTransfer();
let dataTransfer = new DataTransfer();
canvasEditions.forEach(function(canvasEdition, index) {
dataTransfer.items.add(new File([canvasEdition.toSVG()], index+'.svg', {
type: 'image/svg+xml'
}));
})
document.getElementById('input_svg').files = dataTransfer.files;
hasModifications = false;
});
}
if(document.getElementById('save_share')) {
document.getElementById('save_share').addEventListener('click', function(event) {
var dataTransfer = new DataTransfer();
let dataTransfer = new DataTransfer();
if(!document.getElementById('save').hasAttribute('disabled')) {
canvasEditions.forEach(function(canvasEdition, index) {
dataTransfer.items.add(new File([canvasEdition.toSVG()], index+'.svg', {
@ -771,6 +815,7 @@ var createEventsListener = function() {
})
}
document.getElementById('input_svg_share').files = dataTransfer.files;
hasModifications = false;
});
}
@ -783,7 +828,7 @@ var createEventsListener = function() {
});
document.getElementById('btn_svg_selected_close').addEventListener('click', function(event) {
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
let input_selected = document.querySelector('input[name="svg_2_add"]:checked');
stateAddLock(false);
input_selected.checked = false;
@ -793,8 +838,7 @@ var createEventsListener = function() {
document.addEventListener('click', function(event) {
if(event.target.nodeName == "DIV") {
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
let input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(!input_selected) {
return;
}
@ -806,7 +850,7 @@ var createEventsListener = function() {
document.addEventListener('keydown', function(event) {
if(event.key == 'Escape' && (event.target.tagName == "BODY" || event.target.name == "svg_2_add")) {
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
let input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(!input_selected) {
return;
}
@ -890,6 +934,15 @@ var createEventsListener = function() {
zoomChange(1)
});
window.addEventListener('beforeunload', function(event) {
if(!hasModifications) {
return;
}
event.preventDefault();
return true;
});
if(hash) {
updateNbLayers();
setInterval(function() {
@ -901,14 +954,13 @@ var createEventsListener = function() {
var createSignaturePad = function() {
signaturePad = new SignaturePad(document.getElementById('signature-pad'), {
penColor: 'rgb(0, 0, 0)',
minWidth: 1.25,
minWidth: 1,
maxWidth: 2,
throttle: 0,
onEnd: function() {
const file = new File([dataURLtoBlob(signaturePad.toDataURL())], "draw.png", {
type: 'image/png'
});
var data = new FormData();
let data = new FormData();
data.append('file', file);
uploadSVG(data);
}
@ -930,11 +982,11 @@ async function getPDFBlobFromCache(cacheUrl) {
async function uploadFromUrl(url) {
history.replaceState({}, '', '/signature');
var response = await fetch(url);
let response = await fetch(url);
if(response.status != 200) {
return;
}
var pdfBlob = await response.blob();
let pdfBlob = await response.blob();
if(pdfBlob.type != 'application/pdf' && pdfBlob.type != 'application/octet-stream') {
return;
@ -972,12 +1024,26 @@ var modalSharing = function() {
}
}
var runCron = function() {
const xhr = new XMLHttpRequest();
xhr.open('GET', '/cron');
xhr.send();
}
var pageUpload = async function() {
document.querySelector('body').classList.remove('bg-light');
document.getElementById('input_pdf_upload').value = '';
document.getElementById('page-upload').classList.remove('d-none');
document.getElementById('page-signature').classList.add('d-none');
document.getElementById('input_pdf_upload').focus();
const cache = await caches.open('pdf');
let cache;
try {
cache = await caches.open('pdf');
} catch (e) {
console.error(e)
alert("Erreur d'accès au cache. Cette application ne fonctionne pas en mode de navigation privée");
return;
}
document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
if(document.getElementById('input_pdf_upload').files[0].size > maxSize) {
@ -999,7 +1065,11 @@ var updateNbLayers = function() {
xhr.open('GET', '/signature/'+hash+'/nblayers', true);
xhr.onload = function() {
if (xhr.status == 200) {
let nblayers = xhr.response;
let newNblayers = xhr.response;
if(nblayers !== null && nblayers != newNblayers) {
reloadPDF('/signature/'+hash+'/pdf');
}
nblayers = newNblayers;
document.querySelectorAll('.nblayers').forEach(function(item) {
item.innerHTML = nblayers;
});
@ -1013,6 +1083,7 @@ var updateNbLayers = function() {
};
var pageSignature = async function(url) {
document.querySelector('body').classList.add('bg-light');
modalSharing();
document.getElementById('page-upload').classList.add('d-none');
document.getElementById('page-signature').classList.remove('d-none');
@ -1026,6 +1097,10 @@ var pageSignature = async function(url) {
svgCollections = JSON.parse(localStorage.getItem('svgCollections'));
}
if(svgCollections.length == 0 && document.querySelector('#alert-signature-help')) {
document.querySelector('#alert-signature-help').classList.add('auto-open');
}
opentype.load('/vendor/fonts/Caveat-Regular.ttf', function(err, font) {
fontCaveat = font;
});
@ -1034,7 +1109,7 @@ var pageSignature = async function(url) {
let filename = url.replace('/pdf/', '');
if(hash) {
var response = await fetch(url);
let response = await fetch(url);
if(response.status != 200) {
return;
}
@ -1062,6 +1137,9 @@ var pageSignature = async function(url) {
};
(function () {
if(sharingMode) {
setTimeout(function() { runCron() }, 2000);
}
if(hash) {
pageSignature('/signature/'+hash+'/pdf');
window.addEventListener('hashchange', function() {

5
public/logo.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg class="bi bi-vector-pen" width="128" height="128" fill="currentColor" version="1.1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
<path d="m84.48 1.8008a4.4307 4.4307 0 0 1 6.2738 0l35.445 35.445a4.4307 4.4307 0 0 1 0 6.2738l-16.854 16.854-7.3461 29.358a13.292 13.292 0 0 1-9.074 9.5082l-91.671 27.506 27.506-91.68a13.292 13.292 0 0 1 9.4994-9.0652l29.358-7.3372zm-15.95 25.769-28.117 7.0271a4.4307 4.4307 0 0 0-3.1724 3.0306l-22.774 75.898 75.915-22.774a4.4307 4.4307 0 0 0 3.0129-3.1635l7.0359-28.126-31.901-31.901z" fill-rule="evenodd" stroke-width="8.8614"/>
<path d="m15.237 113.29 45.796-37.466a8.8614 8.8614 0 1 0-8.8614-8.8614l-37.466 45.796-0.2304 0.76208z" fill-rule="evenodd" stroke-width="8.8614"/>
</svg>

After

Width:  |  Height:  |  Size: 776 B

16
public/vendor/pdf-lib.min.js vendored Normal file

File diff suppressed because one or more lines are too long

107
templates/metadata.html.php Normal file
View File

@ -0,0 +1,107 @@
<!doctype html>
<html lang="fr_FR">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.5.0" rel="stylesheet">
<link href="/css/app.css?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/css/app.css") ?>" rel="stylesheet">
<link rel="icon" type="image/x-icon" href="/favicon-metadata.ico">
<title>Édition des métadonnées d'un PDF</title>
</head>
<body>
<noscript>
<div class="alert alert-danger text-center" role="alert">
<i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé
</div>
</noscript>
<div id="page-upload">
<ul class="nav justify-content-center nav-tabs mt-2">
<li class="nav-item">
<a class="nav-link" href="/signature"><i class="bi bi-vector-pen"></i> Signer</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/organization"><i class="bi bi-ui-checks-grid"></i> Organiser</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/metadata"><i class="bi bi-tags"></i> Metadonnées</a>
</li>
</ul>
<div class="px-4 py-4 text-center">
<h1 class="display-5 fw-bold mb-0 mt-3"><i class="bi bi-tags"></i> Éditer les métadonnées</h1>
<p class="fw-light mb-3 subtitle text-dark text-nowrap" style="overflow: hidden; text-overflow: ellipsis;">Ajouter, modifier ou supprimer les métadonnées d'un PDF</p>
<div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3 mx-auto">
<div class="col-12">
<label class="form-label mt-3" for="input_pdf_upload">Choisir un PDF</label>
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" />
<p class="mt-2 small fw-light text-dark">&nbsp;</p>
<?php if($PDF_DEMO_LINK): ?>
<a class="btn btn-sm btn-link opacity-75" href="#<?php echo $PDF_DEMO_LINK ?>">Tester avec un PDF de démo</a>
<?php endif; ?>
</div>
</div>
</div>
<footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0</span> : <a href="https://github.com/24eme/signaturepdf">voir le code source </a><?php if($COMMIT): ?> <span class="d-none d-md-inline small">[<a href="https://github.com/24eme/signaturepdf/tree/<?php echo $COMMIT ?>"><?php echo $COMMIT ?></a>]</span><?php endif; ?></small>
</footer>
</div>
<div id="page-metadata" class="d-none">
<div id="div-margin-top" style="height: 88px;" class="d-md-none"></div>
<div style="width: 60%; overflow: auto;" class="vh-100" id="container-main">
<div id="form-metadata" class="mx-auto w-75 pt-3 pb-5">
<h3>Liste des métadonnées du PDF</h3>
<div id="form-metadata-container">
</div>
<form id="form_metadata_add" class="position-relative">
<hr class="text-muted mt-4 mb-3" />
<div class="mb-3">
<label class="form-label text-muted" for="input_metadata_key">Ajouter une nouvelle métadonnée</label>
<div class="form-floating">
<input id="input_metadata_key" name="metadata_key" type="text" class="form-control" required value="" style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;">
<label>Clé</label>
</div>
<input id="input_metadata_value" readonly="readonly" style="border-top: 0; border-top-right-radius: 0; border-top-left-radius: 0;" name="metadata_value" type="text" class="form-control bg-light opacity-50" value="" placeholder="Valeur" style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;">
</div>
<button type="submit" type="button" class="btn btn-outline-secondary float-end"><i class="bi bi-plus-circle"></i> Ajouter</button>
</form>
</div>
</div>
<div id="div-margin-bottom" style="height: 55px;" class="d-md-none"></div>
<div style="width: 40%;" class="offcanvas offcanvas-end show d-none d-md-block shadow-sm" data-bs-backdrop="false" data-bs-scroll="true" data-bs-keyboard="false" tabindex="-1" id="sidebarTools" aria-labelledby="sidebarToolsLabel">
<a class="btn btn-close btn-sm position-absolute opacity-25 d-none d-sm-none d-md-block" title="Fermer ce PDF et retourner à l'accueil" style="position: absolute; top: 2px; right: 2px; font-size: 10px;" href="/metadata"></a>
<div class="offcanvas-header d-block mb-0 pb-0 border-bottom">
<h5 class="mb-1 d-block w-100" id="sidebarToolsLabel">Édition des métadonnées<span class="float-end me-2"><i class="bi bi-tags"></i></span></h5>
<button type="button" class="btn-close text-reset d-md-none" data-bs-dismiss="offcanvas" aria-label="Close"></button>
<p id="text_document_name" class="text-muted mb-2" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden;" title=""><i class="bi bi-files"></i> <span></span></p>
</div>
<div class="offcanvas-body bg-light" style="padding-bottom: 60px;">
<div id="container-pages">
</div>
</div>
<div class="position-absolute bg-white bottom-0 pb-2 ps-2 pe-2 w-100 border-top shadow-lg">
<div id="btn_container" class="d-grid gap-2 mt-2">
<button class="btn btn-primary" type="submit" id="save"><i class="bi bi-download"></i> Enregistrer et télécharger le PDF</button>
</div>
</div>
</div>
<div id="bottom_bar" class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow-sm d-md-none">
<div id="bottom_bar_action" class="d-grid gap-2">
<button class="btn btn-primary" id="save_mobile"><i class="bi bi-download"></i> Télécharger le PDF</button>
</div>
</div>
</div>
<span id="is_mobile" class="d-md-none"></span>
<script src="/vendor/bootstrap.min.js?5.1.3"></script>
<script src="/vendor/pdf.js?legacy"></script>
<script src="/vendor/pdf-lib.min.js?1.17.1"></script>
<script>
var defaultFields = <?php echo json_encode(isset($METADATA_DEFAULT_FIELDS) ? $METADATA_DEFAULT_FIELDS : array()); ?>;
</script>
<script src="/js/metadata.js?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/js/metadata.js") ?>"></script>
</body>
</html>

View File

@ -7,74 +7,149 @@
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.5.0" rel="stylesheet">
<link href="/css/app.css" rel="stylesheet">
<link href="/css/app.css?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/css/app.css") ?>" rel="stylesheet">
<link rel="icon" type="image/x-icon" href="/favicon-organization.ico">
<title>Organiser un PDF</title>
</head>
<body class="bg-light">
<body>
<noscript>
<div class="alert alert-danger text-center" role="alert">
<i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé
</div>
</noscript>
<div id="page-upload">
<div class="px-4 py-5 my-5 text-center">
<h1 class="display-5 fw-bold"><i class="bi bi-ui-checks-grid"></i> Organiser un PDF</h1>
<div class="col-lg-3 mx-auto">
<ul class="nav justify-content-center nav-tabs mt-2">
<li class="nav-item">
<a class="nav-link" href="/signature"><i class="bi bi-vector-pen"></i> Signer</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/organization"><i class="bi bi-ui-checks-grid"></i> Organiser</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/metadata"><i class="bi bi-tags"></i> Metadonnées</a>
</li>
</ul>
<div class="px-4 py-4 text-center">
<h1 class="display-5 fw-bold mb-0 mt-3"><i class="bi bi-ui-checks-grid"></i> Organiser des PDF</h1>
<p class="fw-light mb-3 subtitle text-dark text-nowrap" style="overflow: hidden; text-overflow: ellipsis;">Fusionner, trier, pivoter, supprimer, extraire des pages</p>
<div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3 mx-auto">
<div class="col-12">
<label for="input_pdf_upload" class="form-label">Choisir un PDF</label>
<input id="input_pdf_upload" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf">
<p class="mt-1 opacity-50"><small class="text-muted">Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo</small></p>
<a class="btn btn-sm btn-link opacity-75" href="/organization#https://raw.githubusercontent.com/24eme/signaturepdf/master/tests/files/document.pdf">Tester avec un PDF de démo</a>
<label class="form-label mt-3" for="input_pdf_upload">Choisir un PDF <small class="opacity-75" style="cursor: help" title="Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo"><i class="bi bi-info-circle"></i></small></label>
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" multiple="true" />
<p class="mt-2 small fw-light text-dark">Le PDF sera traité par le serveur sans être conservé ni stocké</p>
<?php if($PDF_DEMO_LINK): ?>
<a class="btn btn-sm btn-link opacity-75" href="#<?php echo $PDF_DEMO_LINK ?>">Tester avec un PDF de démo</a>
<?php endif; ?>
</div>
</div>
</div>
<footer class="text-center text-muted mb-2 fixed-bottom">
<small>Logiciel libre sous license AGPL-3.0 : <a href="https://github.com/24eme/signaturepdf">voir le code source</a></small>
<footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0</span> : <a href="https://github.com/24eme/signaturepdf">voir le code source </a><?php if($COMMIT): ?> <span class="d-none d-md-inline small">[<a href="https://github.com/24eme/signaturepdf/tree/<?php echo $COMMIT ?>"><?php echo $COMMIT ?></a>]</span><?php endif; ?></small>
</footer>
</div>
<div id="page-organization" class="d-none">
<div id="container-pages" class="col-12 pt-1 vh-100" style="padding-bottom: 60px;">
<div id="page-organization" style="padding-right: 350px;" class="d-none">
<div id="div-margin-top" style="height: 88px;" class="d-md-none"></div>
<div style="top: 62px;" class="w-100 position-absolute text-center text-muted opacity-50 d-md-none"><em>Toucher une page pour la sélectionner</em></div>
<div id="container-main">
<div id="container-pages" class="col-12 pt-1 vh-100 d-flex align-content-start flex-wrap position-relative" style="overflow-y: scroll; overflow-x: hidden;">
</div>
</div>
<div id="container-bar" class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow-lg">
<form id="form_pdf" action="/organize" method="post" enctype="multipart/form-data">
<input id="input_pdf" name="pdf" type="file" class="d-none" />
<input id="input_pages" type="hidden" value="" name="pages" />
<div class="row">
<div class="col-3 d-none d-sm-none d-md-block">
<div class="form-switch mt-2 ms-2">
<input class="form-check-input" checked="checked" type="checkbox" id="checkbox_all_pages">
<label class="form-check-label" for="checkbox_all_pages">Séléctionner toutes les pages</label>
<div id="container-btn-zoom" class="btn-group-vertical position-fixed" style="top: 6px; right: 368px;">
<button id="btn-zoom-increase" class="btn btn-outline-dark bg-white text-dark"><i class="bi bi-zoom-in"></i></button>
<button id="btn-zoom-decrease" class="btn btn-outline-dark bg-white text-dark"><i class="bi bi-zoom-out"></i></button>
</div>
<div id="div-margin-bottom" style="height: 55px;" class="d-md-none"></div>
<div class="offcanvas offcanvas-end show d-none d-md-block shadow-sm" data-bs-backdrop="false" data-bs-scroll="true" data-bs-keyboard="false" tabindex="-1" id="sidebarTools" aria-labelledby="sidebarToolsLabel">
<a class="btn btn-close btn-sm position-absolute opacity-25 d-none d-sm-none d-md-block" title="Fermer ce PDF et retourner à l'accueil" style="position: absolute; top: 2px; right: 2px; font-size: 10px;" href="/organization"></a>
<div class="offcanvas-header mb-0 pb-0">
<h5 class="mb-1 d-block w-100" id="sidebarToolsLabel">Organisation de PDF <span class="float-end me-2" title="Ce PDF est stocké sur votre ordinateur pour être signé par vous uniquement"><i class="bi-ui-checks-grid"></i></span></h5>
<button type="button" class="btn-close text-reset d-md-none" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body pt-3" style="padding-bottom: 60px;">
<div id="list_pdf_container">
<ul id="list_pdf" class="list-group">
</ul>
</div>
<div class="d-grid gap-2 mt-2">
<button type="button" class="btn btn-sm btn-outline-dark" onclick="document.getElementById('input_pdf_upload_2').click();"><i class="bi bi-plus-circle"></i> Ajouter un PDF</button>
<input id="input_pdf_upload_2" class="form-control d-none" type="file" accept=".pdf,application/pdf" multiple="true">
</div>
<hr />
<div id="container_btn_select" class="opacity-50 card">
<div class="card-header small text-center p-1"><span>Aucune</span> page(s) sélectionnée(s) <button id="btn_cancel_select" type="button" class="btn-close btn-close-white float-end" aria-label="Close"></button></div>
<div class="card-body d-grid gap-2 p-2">
<button id="btn_rotate_select" disabled="disabled" type="button" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-clockwise"></i> Tourner de 90°</button>
<button id="btn_drag_select" disabled="disabled" type="button" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrows-move"></i> Déplacer</button>
<button id="btn_delete_select" disabled="disabled" type="button" class="btn btn-sm btn-outline-secondary"><i class="bi bi-trash"></i> Supprimer</button>
<button id="save-select" class="btn btn-sm btn-outline-secondary" disabled="disabled" form="form_pdf" type="submit"><i class="bi bi-download"></i> Extraire et télécharger</button>
</div>
<div class="card-footer d-none small text-center p-1 border-primary bg-primary bg-opacity-25"><a id="btn_cancel_select_footer" type="button" aria-label="Close" style="text-decoration: none;" class="text-primary"><i class="bi bi-x-lg"></i> Annuler la sélection</a></div>
</div>
<div class="position-absolute bottom-0 pb-2 ps-0 pe-4 w-100">
<form id="form_pdf" action="/organize" method="post" enctype="multipart/form-data">
<input id="input_pdf" name="pdf[]" type="file" class="d-none" />
<input id="input_pages" type="hidden" value="" name="pages" />
<div id="btn_container" class="d-grid gap-2 mt-2">
<button class="btn btn-primary" type="submit" id="save"><i class="bi bi-download"></i> Télécharger le PDF complet</button>
</div>
</div>
<div class="col-2 d-none d-sm-none d-md-block">
</div>
<div class="col-1 d-none d-sm-none d-md-block">
<select class="form-select">
<option>3 pages</option>
<option>4 pages</option>
<option selected>5 pages</option>
<option>8 pages</option>
<option>10 pages</option>
</select>
</div>
<div class="col-2 d-none d-sm-none d-md-block">
</div>
<div class="col-2 d-none d-sm-none d-md-block">
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-dark" onclick="document.getElementById('input_pdf_upload_2').click();"><i class="bi bi-plus-circle"></i> Ajouter un PDF</button>
</div>
<input id="input_pdf_upload_2" class="form-control d-none" type="file" accept=".pdf,application/pdf">
</div>
<div class="col-sm-12 col-md-2">
<div class="d-grid gap-2">
<button class="btn btn-primary" type="submit" id="save"><i class="bi bi-download"></i> Télécharger le PDF</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
<div id="top_bar" class="position-fixed top-0 start-0 bg-white w-100 shadow-sm d-md-none p-2">
<div id="top_bar_action">
<div class="d-flex" role="group">
<button id="btn_liste_pdf" type="button" data-bs-toggle="modal" data-bs-target="#modalFichier" class="btn btn-dark flex-grow-1 me-2" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
<i class="bi bi-files"></i> <span id="liste_pdf_titre_mobile"></span>
</button>
<button type="button" class="btn btn-outline-dark position-relative" style="padding-left: 30px;" onclick="document.getElementById('input_pdf_upload_2').click(); this.blur();"><i class="bi bi-plus-circle position-absolute" style="left: 10px;"></i>Ajouter&nbsp;un&nbsp;PDF</button>
</div>
</div>
<div id="top_bar_action_selection" class="d-none">
<div id="top_bar_action_selection_recap" class="bg-primary text-white text-center rounded-top p-1 position-relative"><button id="btn_liste_pdf_bar" type="button" style="text-decoration: none;left: 0px; top:0px;" class="btn bg-white bg-opacity-50 text-primary position-absolute p-0 ps-1 pe-1 mt-1 ms-1"><i class="bi bi-files"></i>&nbsp;<span></span> PDF</button><span id="top_bar_action_selection_recap_nb_pages">Aucune</span> page(s)<button id="btn_cancel_select_mobile" type="button" style="text-decoration: none;right: 0px; top:0px;" class="btn bg-white bg-opacity-50 text-primary position-absolute p-0 ps-1 pe-1 mt-1 me-1"><i class="bi bi-x-lg"></i>&nbsp;Annuler</button></div>
<div class="btn-group w-100">
<button id="btn_rotate_select_mobile" type="button" class="btn btn-outline-primary" style="border-top-left-radius: 0 !important;"><i class="bi bi-arrow-clockwise"></i> Tourner</button>
<button id="btn_drag_select_mobile" type="button" class="btn btn-outline-primary"><i class="bi bi-arrows-move"></i> Déplacer</button>
<button id="btn_delete_select_mobile" type="button" class="btn btn-outline-primary" style="border-top-right-radius: 0 !important;"><i class="bi bi-trash"></i> Supprimer</button>
</div>
</div>
</div>
<div id="bottom_bar" class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow-sm d-md-none">
<div id="bottom_bar_action" class="d-grid gap-2">
<button class="btn btn-primary" type="submit" id="save_mobile"><i class="bi bi-download"></i> Télécharger le PDF complet</button>
</div>
<div id="bottom_bar_action_selection" class="d-grid gap-2 d-none">
<button id="save-select_mobile" class="btn btn-outline-primary" type="submit" form="form_pdf"><i class="bi bi-download"></i> Télécharger la sélection</button>
</div>
</div>
</div>
<div class="modal fade" id="modalDrag" tabindex="-1">
<div class="modal-dialog modal-dialog-scrollable modal-xl modal-fullscreen-md-down">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Déplacement de page(s)</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body bg-light ps-5">
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalFichier" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Documents PDF</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>
<span id="is_mobile" class="d-md-none"></span>
<script src="/vendor/bootstrap.min.js?5.1.3"></script>
@ -82,6 +157,6 @@
<script>
var maxSize = <?php echo $maxSize ?>;
</script>
<script src="/js/organization.js?202203301018"></script>
<script src="/js/organization.js?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/js/organization.js") ?>"></script>
</body>
</html>
</html>

View File

@ -1,38 +1,64 @@
<!doctype html>
<html lang="fr_FR">
<html lang="fr">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Logiciel libre de signature de PDF en ligne">
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.8.1" rel="stylesheet">
<link href="/css/app.css" rel="stylesheet">
<link href="/css/app.css?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/css/app.css") ?>" rel="stylesheet">
<title>Signature PDF</title>
</head>
<body class="bg-light">
<body>
<noscript>
<div class="alert alert-danger text-center" role="alert">
<i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé
</div>
</noscript>
<div id="page-upload">
<div class="px-4 py-5 my-5 text-center">
<h1 class="display-5 fw-bold"><i class="bi bi-vector-pen"></i> Signer un PDF</h1>
<div class="col-lg-3 mx-auto">
<?php if(!$disableOrganization): ?>
<ul class="nav justify-content-center nav-tabs mt-2">
<li class="nav-item">
<a class="nav-link active" href="/signature"><i class="bi bi-vector-pen"></i> Signer</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/organization"><i class="bi bi-ui-checks-grid"></i> Organiser</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/metadata"><i class="bi bi-tags"></i> Metadonnées</a>
</li>
</ul>
<?php endif; ?>
<div class="px-4 py-4 text-center">
<h1 class="display-5 fw-bold mb-0 mt-3"><i class="bi bi-vector-pen"></i> Signer un PDF</h1>
<p class="fw-light mb-3 subtitle text-dark text-nowrap" style="overflow: hidden; text-overflow: ellipsis;">Signer, parapher, tamponner, compléter un document</p>
<div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3 mx-auto">
<div class="col-12">
<label for="input_pdf_upload" class="form-label">Choisir un PDF</label>
<input id="input_pdf_upload" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf">
<p class="mt-1 opacity-50"><small class="text-muted">Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo et <?php echo $maxPage ?> pages</small></p>
<a class="btn btn-sm btn-link opacity-75" href="/signature#https://raw.githubusercontent.com/24eme/signaturepdf/master/tests/files/document.pdf">Tester avec un PDF de démo</a>
<label class="form-label mt-3" for="input_pdf_upload">Choisir un PDF <small class="opacity-75" style="cursor: help" title="Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo et <?php echo $maxPage ?> pages"><i class="bi bi-info-circle"></i></small></label>
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" />
<p class="mt-2 small fw-light text-dark">Le PDF sera traité par le serveur sans être conservé ni stocké</p>
<?php if($PDF_DEMO_LINK): ?>
<a class="btn btn-sm btn-link opacity-75" href="#<?php echo $PDF_DEMO_LINK ?>">Tester avec un PDF de démo</a>
<?php endif; ?>
</div>
</div>
</div>
<footer class="text-center text-muted mb-2 fixed-bottom">
<small>Logiciel libre sous license AGPL-3.0 : <a href="https://github.com/24eme/signaturepdf">voir le code source</a></small>
<footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0</span> : <a href="https://github.com/24eme/signaturepdf">voir le code source </a><?php if($COMMIT): ?> <span class="d-none d-md-inline small">[<a href="https://github.com/24eme/signaturepdf/tree/<?php echo $COMMIT ?>"><?php echo $COMMIT ?></a>]</span><?php endif; ?></small>
</footer>
</div>
<div id="page-signature" style="padding-right: 350px;" class="d-none">
<?php if(isset($hash)): ?>
<div id="alert-signature-help" class="position-relative d-none">
<div class="alert alert-primary alert-dismissible position-absolute top-0 start-50 translate-middle-x text-center mt-4 pb-2 w-50 opacity-100" style="z-index: 100;" role="alert">
<h5 class="alert-heading">Comment signer ?</h5>
<strong>En cliquant directement sur la page du document</strong> pour insérer l'élément séléctionné dans la colonne de droite <small>(signature, paraphe, texte, tampon, etc ...)</small>
<div class="mt-1 fs-3"><i class="bi bi-box-arrow-down"></i></div>
<button type="button" class="btn-close btn-sm" aria-label="Close"></button>
</div>
</div>
<?php endif; ?>
<div style="height: 65px;" class="d-md-none"></div>
<div id="container-pages" class="col-12 pt-1 pb-1 text-center vh-100">
</div>
@ -80,22 +106,18 @@
<div id="form_block" class="position-absolute bottom-0 pb-2 ps-0 pe-4 w-100">
<?php if(!isset($hash)): ?>
<?php if(!isset($noSharingMode)): ?>
<form id="form_sharing" action="/share" method="post" enctype="multipart/form-data">
<input id="input_pdf_share" name="pdf" type="file" class="d-none" />
<input id="input_svg_share" name="svg[]" type="file" class="d-none" />
<div class="d-grid gap-2">
<button class="btn btn-outline-dark w-100" type="submit" id="save_share"><i class="bi bi-share"></i> Partager pour signer <i class="bi bi-people-fill"></i> à plusieurs </button>
</div>
</form>
<?php endif; ?>
<button class="btn btn-outline-dark w-100" type="button" data-bs-toggle="modal" data-bs-target="#modal-start-share"><i class="bi bi-share"></i> Partager pour signer <i class="bi bi-people-fill"></i> à plusieurs</button>
<?php endif; ?>
<form id="form_pdf" action="/sign" method="post" enctype="multipart/form-data" class="d-none d-sm-none d-md-block">
<input id="input_pdf" name="pdf" type="file" class="d-none" />
<input id="input_svg" name="svg[]" type="file" class="d-none" />
<button class="btn btn-primary w-100 mt-2" disabled="disabled" type="submit" id="save"><i class="bi bi-download"></i> Télécharger le PDF signé</button>
</form>
<?php elseif(!isset($noSharingMode)): ?>
<div class="d-none d-sm-none d-md-block">
<p id="nblayers_text" class="small d-none mb-2 opacity-75">Vous êtes <span class="badge rounded-pill border border-dark text-dark"><span class="nblayers">0</span> <i class="bi bi-people-fill"></i></span> à avoir signé ce PDF</p></div>
<div class="d-none d-sm-none d-md-block position-relative">
<a id="btn-signature-help" class="position-absolute top-0 end-0 text-dark" href="" style="z-index: 5;"><i class="bi bi-question-circle"></i></a>
<p id="nblayers_text" class="small d-none mb-2 opacity-75">Vous êtes <span class="badge rounded-pill border border-dark text-dark"><span class="nblayers">0</span> <i class="bi bi-people-fill"></i></span> à avoir signé ce PDF</p>
</div>
<div class="btn-group w-100">
<a id="btn_download" class="btn btn-outline-dark w-100" href="/signature/<?php echo $hash ?>/pdf"><i class="bi bi-download"></i> Télécharger le PDF</a>
<button class="btn btn-outline-dark" type="button" id="btn_share" data-bs-toggle="modal" data-bs-target="#modal-share-informations"><i class="bi bi-share"></i></button>
@ -138,12 +160,10 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-draw-tab" data-bs-toggle="tab" data-bs-target="#nav-draw" type="button" role="tab" aria-controls="nav-draw" aria-selected="true"><i class="bi bi-vector-pen"></i> Dessiner</button>
<button class="nav-link" id="nav-type-tab" data-bs-toggle="tab" data-bs-target="#nav-type" type="button" role="tab" aria-controls="nav-type" aria-selected="false"><i class="bi bi-fonts"></i> Saisir</button>
<button class="nav-link" id="nav-import-tab" data-bs-toggle="tab" data-bs-target="#nav-import" type="button" role="tab" aria-controls="nav-import" aria-selected="false"><i class="bi bi-image"></i> Importer</button>
</div>
<nav class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active ps-2 ps-md-3 pe-2 pe-md-3" id="nav-draw-tab" data-bs-toggle="tab" data-bs-target="#nav-draw" type="button" role="tab" aria-controls="nav-draw" aria-selected="true"><i class="bi bi-vector-pen"></i> Dessiner<br /><small>à main levée</small></button>
<button class="nav-link ps-2 ps-md-3 pe-2 pe-md-3" id="nav-type-tab" data-bs-toggle="tab" data-bs-target="#nav-type" type="button" role="tab" aria-controls="nav-type" aria-selected="false"><i class="bi bi-fonts"></i> Saisir<br /><small>du texte</small></button>
<button class="nav-link ps-2 ps-md-3 pe-2 pe-md-3" id="nav-import-tab" data-bs-toggle="tab" data-bs-target="#nav-import" type="button" role="tab" aria-controls="nav-import" aria-selected="false"><i class="bi bi-image"></i> Importer<br /><small>une image</small></button>
</nav>
<div class="tab-content mt-3" id="nav-svg-add">
<div class="tab-pane fade show active" id="nav-draw" role="tabpanel" aria-labelledby="nav-draw-tab">
@ -160,18 +180,42 @@
<form id="form-image-upload" action="/image2svg" method="POST" enctype="multipart/form-data">
<input id="input-image-upload" class="form-control" name="image" type="file">
</form>
</div>
</div>
</div>
<input id="input-svg-type" type="hidden" />
</div>
<div class="modal-footer">
<button tabindex="-1" type="button" class="btn btn-light" data-bs-dismiss="modal">Annuler</button>
<button id="btn_modal_ajouter" type="button" disabled="disabled" class="btn btn-primary" data-bs-dismiss="modal"><span id="btn_modal_ajouter_spinner" class="spinner-border spinner-border-sm d-none"></span><span id="btn_modal_ajouter_check" class="bi bi-check-circle"></span> Ajouter</button>
<div class="modal-footer d-block">
<button tabindex="-1" type="button" class="btn btn-light col-4" data-bs-dismiss="modal">Annuler</button>
<button id="btn_modal_ajouter" type="button" disabled="disabled" data-bs-dismiss="modal" class="btn btn-primary float-end col-4"><span id="btn_modal_ajouter_spinner" class="spinner-border spinner-border-sm d-none"></span><span id="btn_modal_ajouter_check" class="bi bi-check-circle"></span> Créer</button>
</div>
</div>
</div>
</div>
</div>
<?php if(!isset($hash) && !isset($noSharingMode)): ?>
<div id="modal-start-share" class="modal" tabindex="-1">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-share"></i> Partager ce PDF pour le signer à plusieurs </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>En activant le partage de ce PDF vous allez pouvoir proposer un lien aux personnes de votre choix pour qu'elles puissent signer ce PDF.</p>
<p><i class="bi bi-hdd-network"></i> Ce partage nécessite que le PDF soit transféré et stocké sur le serveur afin d'être accessible aux futurs signataires.</p>
<p class="mb-0"><i class="bi bi-hourglass-split"></i> Le PDF sera conservé <select name="duration" form="form_sharing"><option value="+1 year">un an</option><option value="+6 month">six mois</option><option value="+1 month" selected="selected">un mois</option><option value="+1 week">une semaine</option><option value="+1 day">un jour</option><option value="+1 hour">une heure</option></select> après la dernière signature.</p>
</div>
<div class="modal-footer text-center d-block">
<form id="form_sharing" clas action="/share" method="post" enctype="multipart/form-data">
<input id="input_pdf_share" name="pdf" type="file" class="d-none" />
<input id="input_svg_share" name="svg[]" type="file" class="d-none" />
<button class="btn col-9 col-md-6 btn-primary" type="submit" id="save_share"><i class="bi bi-cloud-upload"></i> Démarrer le partage</button>
</form>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if(isset($hash)): ?>
<div id="modal-share-informations" class="modal" tabindex="-1">
<div class="modal-dialog modal-md">
@ -181,14 +225,15 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Plusieurs personnes peuvent signer ce PDF en même temps sur cette page.</p>
<p>Plusieurs personnes peuvent signer ce PDF en même temps.</p>
<p>Pour cela il vous suffit de partager avec les personnes de votre choix le lien vers cette page :</p>
<div class="input-group mb-3">
<span class="input-group-text">Lien à partager</span>
<input id="input-share-link" type="text" onclick="this.select(); this.setSelectionRange(0, 99999);" readonly="readonly" class="form-control bg-light font-monospace" value="">
<button onclick="navigator.clipboard.writeText(document.getElementById('input-share-link').value); this.innerText = 'Copié !';" autofocus="autofocus" class="btn btn-primary" type="button" id="btn-copy-share-link"><i class="bi bi-clipboard"></i> Copier</button>
<script>document.querySelector('#input-share-link').value = document.location.href.replace(/#.*/, '');</script>
</div>
<p>C'est aussi depuis cette page qu'il est possible de télécharger le PDF signé par tout le monde.</p>
<p class="mb-0">Chacun des signataires pourra à tout moment télécharger la dernière version du PDF signé.</p>
</div>
<div class="modal-footer text-start">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Fermer</button>
@ -226,11 +271,12 @@
<script>
var maxSize = <?php echo $maxSize ?>;
var maxPage = <?php echo $maxPage ?>;
var sharingMode = <?php echo intval(!isset($noSharingMode)) ?>;
var hash = null;
<?php if(isset($hash)): ?>
hash = "<?php echo $hash ?>";
<?php endif; ?>
</script>
<script src="/js/signature.js?202204020154"></script>
<script src="/js/signature.js?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/js/signature.js") ?>"></script>
</body>
</html>