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 SERVERNAME=localhost
ENV UPLOAD_MAX_FILESIZE=24M ENV UPLOAD_MAX_FILESIZE=24M
ENV POST_MAX_SIZE=24M ENV POST_MAX_SIZE=24M
ENV MAX_FILE_UPLOADS=201 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 COPY . /usr/local/signaturepdf
RUN chown -R www-data:www-data /usr/local/signaturepdf && chmod 750 -R /usr/local/signaturepdf && \ RUN envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini && \
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/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 a2enmod rewrite && a2ensite signaturepdf
WORKDIR /usr/local/signaturepdf WORKDIR /usr/local/signaturepdf
CMD envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf && \ CMD /usr/local/signaturepdf/entrypoint.sh
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

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.24eme.fr](https://pdf.24eme.fr)
- [pdf.libreon.fr](https://pdf.libreon.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 ## License
@ -15,15 +19,17 @@ Logiciel libre sous license AGPL V3
## Installation ## Installation
### Debian/Ubuntu
Dépendances : Dépendances :
- php >= 5.6 - php >= 5.6
- rsvg-convert - rsvg-convert
- pdftk - pdftk
- imagemagick - imagemagick
- potrace - potrace
Sur debian : Installation des dépendances :
``` ```
sudo aptitude install php librsvg2-bin pdftk imagemagick potrace sudo aptitude install php librsvg2-bin pdftk imagemagick potrace
@ -41,7 +47,7 @@ Pour le lancer :
php -S localhost:8000 -t public php -S localhost:8000 -t public
``` ```
### Configuration de PHP #### Configuration de PHP
``` ```
upload_max_filesize = 24M # Taille maximum du fichier PDF à signer 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 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 DocumentRoot /path/to/signaturepdf/public
@ -63,6 +69,149 @@ DocumentRoot /path/to/signaturepdf/public
</Directory> </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 ### 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. 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,} 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 PDF_STORAGE_PATH=/path/to/folder
``` ```
Créer ce dossier : Créer ce dossier :
``` ```
mkdir /path/to/folder mkdir /path/to/folder
``` ```
@ -88,40 +239,39 @@ mkdir /path/to/folder
Le serveur web devra avoir les droits en écriture sur ce dossier. Le serveur web devra avoir les droits en écriture sur ce dossier.
Par exemple pour apache : Par exemple pour apache :
``` ```
chown www-data /path/to/folder/to/store/pdf 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 ### Cacher ou modifier le lien de PDF de démo
docker build -t signaturepdf .
````
#### 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 ### Champs chargés par défaut pour l'édition de métadonnéés
docker run -d --name=signaturepdf -p 8080:80 signaturepdf
````
[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| La dernière version stable est sur la branche `master`, pour la mise à jour il suffit de récupérer les dernières modifications :
|-----|-----|-----|-----|
|`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|
```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 ## 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) - **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) - **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) - **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 : Pour les tests :
- **Jest** Framework de Test Javascript : https://jestjs.io/ (MIT) - **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) - **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('ROOT', __DIR__);
$f3->set('UI', $f3->get('ROOT')."/templates/"); $f3->set('UI', $f3->get('ROOT')."/templates/");
$f3->set('UPLOADS', sys_get_temp_dir()."/"); $f3->set('UPLOADS', sys_get_temp_dir()."/");
$f3->set('COMMIT', getCommit());
$f3->config(__DIR__.'/config/config.ini'); $f3->config(__DIR__.'/config/config.ini');
if($f3->get('PDF_STORAGE_PATH') && !preg_match('|/$|', $f3->get('PDF_STORAGE_PATH'))) { if($f3->get('PDF_STORAGE_PATH') && !preg_match('|/$|', $f3->get('PDF_STORAGE_PATH'))) {
$f3->set('PDF_STORAGE_PATH', $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 /', $f3->route('GET /',
function($f3) { function($f3) {
$f3->reroute('/signature'); $f3->reroute('/signature');
@ -140,8 +151,8 @@ $f3->route('POST /sign',
$f3->route('POST /share', $f3->route('POST /share',
function($f3) { function($f3) {
$hash = substr(hash('sha512', uniqid().rand()), 0, 20); $hash = substr(hash('sha512', uniqid().rand()), 0, 20);
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash."/"; $sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$f3->set('UPLOADS', $sharingFolder); $f3->set('UPLOADS', $sharingFolder."/");
if (!is_dir($f3->get('PDF_STORAGE_PATH'))) { if (!is_dir($f3->get('PDF_STORAGE_PATH'))) {
$f3->error(500, 'Sharing folder doesn\'t exist'); $f3->error(500, 'Sharing folder doesn\'t exist');
} }
@ -149,6 +160,9 @@ $f3->route('POST /share',
$f3->error(500, 'Sharing folder is not writable'); $f3->error(500, 'Sharing folder is not writable');
} }
mkdir($sharingFolder); 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"; $filename = "original.pdf";
$tmpfile = tempnam($sharingFolder, date('YmdHis')); $tmpfile = tempnam($sharingFolder, date('YmdHis'));
$svgFiles = ""; $svgFiles = "";
@ -164,7 +178,7 @@ $f3->route('POST /share',
return true; return true;
}, false, function($fileBaseName, $formFieldName) use ($tmpfile, $filename, $sharingFolder, &$svgFiles) { }, false, function($fileBaseName, $formFieldName) use ($tmpfile, $filename, $sharingFolder, &$svgFiles) {
if($formFieldName == "pdf") { if($formFieldName == "pdf") {
file_put_contents($sharingFolder."filename.txt", $fileBaseName); file_put_contents($sharingFolder."/filename.txt", $fileBaseName);
return $filename; return $filename;
} }
if($formFieldName == "svg") { if($formFieldName == "svg") {
@ -196,7 +210,7 @@ $f3->route('GET /signature/@hash/pdf',
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash; $sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$files = scandir($sharingFolder); $files = scandir($sharingFolder);
$originalFile = $sharingFolder.'/original.pdf'; $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'; $filename = $f3->get('PARAMS.hash').'.pdf';
if(file_exists($sharingFolder."/filename.txt")) { if(file_exists($sharingFolder."/filename.txt")) {
$filename = file_get_contents($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); $filename = str_replace('.pdf', '_signe-'.count($layers).'x.pdf', $filename);
copy($originalFile, $finalFile); copy($originalFile, $finalFile);
$bufferFile = str_replace('.pdf', '_tmp.pdf', $originalFile); $bufferFile = $finalFile.".tmp";
foreach($layers as $layerFile) { foreach($layers as $layerFile) {
shell_exec(sprintf("pdftk %s multistamp %s output %s", $finalFile, $layerFile, $bufferFile)); shell_exec(sprintf("pdftk %s multistamp %s output %s", $finalFile, $layerFile, $bufferFile));
rename($bufferFile, $finalFile); rename($bufferFile, $finalFile);
} }
Web::instance()->send($finalFile, null, 0, TRUE, $filename); Web::instance()->send($finalFile, null, 0, TRUE, $filename);
if($f3->get('DEBUG')) {
return;
}
array_map('unlink', glob($finalFile."*"));
} }
); );
$f3->route('POST /signature/@hash/save', $f3->route('POST /signature/@hash/save',
function($f3) { function($f3) {
$hash = Web::instance()->slug($f3->get('PARAMS.hash')); $hash = Web::instance()->slug($f3->get('PARAMS.hash'));
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash.'/'; $sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$f3->set('UPLOADS', $sharingFolder); $f3->set('UPLOADS', $sharingFolder.'/');
$tmpfile = tempnam($sharingFolder, date('YmdHis')); $tmpfile = tempnam($sharingFolder, date('YmdHis'));
unlink($tmpfile); unlink($tmpfile);
$svgFiles = ""; $svgFiles = "";
$expireFile = $sharingFolder.".expire";
touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));
$files = Web::instance()->receive(function($file,$formFieldName){ $files = Web::instance()->receive(function($file,$formFieldName){
if($formFieldName == "svg" && strpos(Web::instance()->mime($file['tmp_name'], true), 'image/svg+xml') !== 0) { if($formFieldName == "svg" && strpos(Web::instance()->mime($file['tmp_name'], true), 'image/svg+xml') !== 0) {
$f3->error(403); $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', $f3->route('GET /organization',
function($f3) { function($f3) {
$f3->set('maxSize', min(array(convertPHPSizeToBytes(ini_get('post_max_size')), convertPHPSizeToBytes(ini_get('upload_max_filesize'))))); $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', $f3->route('POST /organize',
function($f3) { function($f3) {
$filename = null; $filenames = array();
$tmpfile = tempnam($f3->get('UPLOADS'), 'pdfsignature_organize'); $tmpfile = tempnam($f3->get('UPLOADS'), 'pdfsignature_organize');
unlink($tmpfile); 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){ $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); $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); $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')) { if($f3->get('DEBUG')) {
return; return;
} }
array_map('unlink', glob($tmpfile."*")); 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) function convertPHPSizeToBytes($sSize)
{ {

View File

@ -1,5 +1,14 @@
[globals] [globals]
# Path to which stored pdf to activate the mode of sharing a signature to several. ; 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 ; To deactivate this mode, simply do not configure it or leave it empty
PDF_STORAGE_PATH=/path/to/folder 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; 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; font-size: 30px;
cursor: move; cursor: move;
background: rgb(255,255,255,0.6); background: rgb(255,255,255,0.6);
opacity: 0;
} }
.canvas-container:hover .btn-drag { .canvas-container .btn-drag-here-left, .canvas-container .btn-drag-here-right, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
opacity: 1; 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 windowWidth = window.innerWidth;
var menu = null;
var menuOffcanvas = null;
var is_mobile = function() { var is_mobile = function() {
return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none"); 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; var nbPagePerLine = 5;
if(is_mobile()) { if(is_mobile()) {
nbPagePerLine = 2; nbPagePerLine = 2;
@ -9,151 +59,650 @@ if(is_mobile()) {
var pdfjsLib = window['pdfjs-dist/build/pdf']; var pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy'; pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
var nbPDF = 0; var nbPDF = 0;
var pages = [];
var pdfRenderTasks = [];
var loadPDF = async function(pdfBlob, filename, pdfIndex) { var loadPDF = async function(pdfBlob, filename, pdfIndex) {
let url = await URL.createObjectURL(pdfBlob); let url = await URL.createObjectURL(pdfBlob);
let dataTransfer = new DataTransfer(); 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(document.getElementById('input_pdf').files[i]);
} }
dataTransfer.items.add(new File([pdfBlob], filename, { dataTransfer.items.add(new File([pdfBlob], filename, {
type: 'application/pdf' type: 'application/pdf'
})); }));
document.getElementById('input_pdf').files = dataTransfer.files; document.getElementById('input_pdf').files = dataTransfer.files;
updateListePDF();
let pdfLetter = String.fromCharCode(96 + i+1).toUpperCase();
let loadingTask = pdfjsLib.getDocument(url); let loadingTask = pdfjsLib.getDocument(url);
loadingTask.promise.then(function(pdf) { await loadingTask.promise.then(function(pdf) {
for(var pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) { for(var pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) { pdf.getPage(pageNumber).then(function(page) {
let viewport = page.getViewport({scale: 1}); let pageIndex = pdfLetter + "_" + (page.pageNumber - 1);
let scale = (document.getElementById('container-pages').clientWidth - (12*nbPagePerLine) - 12) / viewport.width / nbPagePerLine; pages[pageIndex] = page;
viewport = page.getViewport({scale: scale});
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) { 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('.container-resize').classList.add('d-none');
this.querySelector('.canvas-pdf').classList.add('shadow-lg'); this.querySelector('.canvas-pdf').classList.add('shadow-lg');
this.querySelector('.canvas-pdf').style.border = '2px dashed #777'; this.querySelector('.canvas-pdf').style.border = '2px dashed #777';
e.dataTransfer.setData('element', this.id); e.dataTransfer.setData('element', this.id);
this.style.opacity = 0.4; this.style.opacity = 0.4;
document.querySelector('#container-bar').classList.add('d-none');
}); });
canvasContainer.addEventListener('dragend', function(e) { canvasContainer.addEventListener('dragend', function(e) {
this.querySelector('.container-checkbox').classList.remove('d-none');
this.querySelector('.container-resize').classList.remove('d-none'); this.querySelector('.container-resize').classList.remove('d-none');
this.querySelector('.canvas-pdf').classList.remove('shadow-lg'); 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; this.style.opacity = 1;
document.querySelector('#container-bar').classList.remove('d-none'); updatePageState(this);
stateCheckbox(this.querySelector('input[type=checkbox]'));
}); });
canvasContainer.addEventListener('dragover', function(e) { canvasContainer.addEventListener('dragover', function(e) {
if (e.preventDefault) { if (e.preventDefault) {
e.preventDefault(); e.preventDefault();
} }
if(e.layerX > e.target.clientWidth / 2) { let pdfOver = this;
this.insertAdjacentElement('beforebegin', document.querySelector('#'+e.dataTransfer.getData('element'))); let pdfMoving = document.querySelector('#'+e.dataTransfer.getData('element'));
} else {
this.insertAdjacentElement('afterend', 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; return false;
}); });
canvasContainer.querySelector('.btn-delete').addEventListener('click', function(e) {
canvasContainer.querySelector('input[type=checkbox]').addEventListener('click', function(e) {
e.stopPropagation(); e.stopPropagation();
toggleDeletePage(this.parentNode);
}); });
canvasContainer.querySelector('input[type=checkbox]').addEventListener('change', function(e) { canvasContainer.querySelector('.btn-restore').addEventListener('click', function(e) {
stateCheckbox(this); e.stopPropagation();
stateCheckboxAll(); toggleDeletePage(this.parentNode);
}); });
canvasContainer.addEventListener('click', function(e) { canvasContainer.querySelector('.btn-select').addEventListener('click', function(e) {
let checkbox = this.querySelector('input[type=checkbox]'); e.stopPropagation();
checkbox.checked = !checkbox.checked; toggleSelectPage(this.parentNode);
stateCheckbox(checkbox);
stateCheckboxAll();
}); });
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'); pageRender(pageIndex);
// 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);
}); });
} }
}, function (reason) { }, function (reason) {
console.error(reason); console.error(reason);
}); });
return loadingTask;
}; };
var stateCheckbox = function(checkbox) { var pageRenderAll = function() {
let checkboxContainer = checkbox.parentNode.parentNode.parentNode; for(pageIndex in pages) {
pageRender(pageIndex);
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 stateCheckboxAll = function() { var pageRender = async function(pageIndex) {
document.querySelector('#checkbox_all_pages').checked = (document.querySelectorAll('.checkbox-page:checked').length == document.querySelectorAll('.checkbox-page').length); 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() { if(viewportWidth.height > sizeWidth) {
document.querySelector('#checkbox_all_pages').addEventListener('change', function() { viewport = viewportHeight;
let checkboxAll = this; } else {
document.querySelectorAll('.checkbox-page').forEach(function(checkbox) { viewport = viewportWidth;
checkbox.checked = checkboxAll.checked; }
stateCheckbox(checkbox);
}); 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 = []; return files;
document.querySelectorAll('.checkbox-page').forEach(function(checkbox) { }
if(checkbox.checked) {
order.push(checkbox.value); 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"); alert("Le PDF ne doit pas dépasser " + Math.round(maxSize/1024/1024) + " Mo");
this.value = ""; break;
return;
} }
const cache = await caches.open('pdf'); let filename = input_upload.files[i].name;
let filename = this.files[0].name; let response = new Response(input_upload.files[i], { "status" : 200, "statusText" : "OK" });
let response = new Response(this.files[0], { "status" : 200, "statusText" : "OK" });
let urlPdf = '/pdf/'+filename; let urlPdf = '/pdf/'+filename;
await cache.put(urlPdf, response); await cache.put(urlPdf, response);
let pdfBlob = await getPDFBlobFromCache(urlPdf); let pdfBlob = await getPDFBlobFromCache(urlPdf);
nbPDF++; 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 = ''; 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) { async function getPDFBlobFromCache(cacheUrl) {
@ -190,40 +739,33 @@ async function uploadFromUrl(url) {
} }
var pageUpload = async function() { var pageUpload = async function() {
document.querySelector('body').classList.remove('bg-light');
document.getElementById('input_pdf_upload').value = ''; document.getElementById('input_pdf_upload').value = '';
document.getElementById('page-upload').classList.remove('d-none'); document.getElementById('page-upload').classList.remove('d-none');
document.getElementById('page-organization').classList.add('d-none'); document.getElementById('page-organization').classList.add('d-none');
document.getElementById('input_pdf_upload').focus(); 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) { document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
if(document.getElementById('input_pdf_upload').files[0].size > maxSize) { uploadAndLoadPDF(this);
pageOrganization(null);
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)
}); });
} }
var pageOrganization = async function(url) { var pageOrganization = async function() {
let filename = url.replace('/pdf/', ''); document.querySelector('body').classList.add('bg-light');
document.title = filename + ' - ' + document.title;
document.getElementById('page-upload').classList.add('d-none'); document.getElementById('page-upload').classList.add('d-none');
document.getElementById('page-organization').classList.remove('d-none'); document.getElementById('page-organization').classList.remove('d-none');
menu = document.getElementById('sidebarTools');
let pdfBlob = await getPDFBlobFromCache(url); menuOffcanvas = new bootstrap.Offcanvas(menu);
if(!pdfBlob) { responsiveDisplay();
document.location = '/organization';
return;
}
createEventsListener(); createEventsListener();
loadPDF(pdfBlob, filename, nbPDF);
}; };
(function () { (function () {
@ -231,12 +773,14 @@ var pageOrganization = async function(url) {
let hashUrl = window.location.hash.replace(/^\#/, ''); let hashUrl = window.location.hash.replace(/^\#/, '');
pageUpload(); pageUpload();
uploadFromUrl(hashUrl); uploadFromUrl(hashUrl);
} else if(window.location.hash) {
pageOrganization('/pdf/'+window.location.hash.replace(/^\#/, ''));
} else { } else {
pageUpload(); pageUpload();
} }
window.addEventListener('hashchange', function() { window.addEventListener('hashchange', function() {
window.location.reload(); window.location.reload();
}) })
if (hasTouch()) {
disabledHoverStyle();
}
})(); })();

View File

@ -16,13 +16,17 @@ var menu = null;
var menuOffcanvas = null; var menuOffcanvas = null;
var currentCursor = null; var currentCursor = null;
var signaturePad = null; var signaturePad = null;
var nblayers = null;
var hasModifications = false;
var currentTextScale = 1;
var loadPDF = async function(pdfBlob, filename) { 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'; pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
let url = await URL.createObjectURL(pdfBlob); 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.querySelector('span').innerText = filename;
text_document_name.setAttribute('title', filename); text_document_name.setAttribute('title', filename);
@ -36,7 +40,7 @@ var loadPDF = async function(pdfBlob, filename) {
if(document.getElementById('input_pdf_share')) { if(document.getElementById('input_pdf_share')) {
document.getElementById('input_pdf_share').files = dataTransfer.files; document.getElementById('input_pdf_share').files = dataTransfer.files;
} }
var loadingTask = pdfjsLib.getDocument(url); let loadingTask = pdfjsLib.getDocument(url);
loadingTask.promise.then(function(pdf) { loadingTask.promise.then(function(pdf) {
if(pdf.numPages > maxPage) { if(pdf.numPages > maxPage) {
@ -44,10 +48,10 @@ var loadPDF = async function(pdfBlob, filename) {
document.location = "/"; document.location = "/";
return; return;
} }
for(var pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) { for(let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) { pdf.getPage(pageNumber).then(function(page) {
var scale = 1.5; let scale = 1.5;
var viewport = page.getViewport({scale: scale}); let viewport = page.getViewport({scale: scale});
if(viewport.width > document.getElementById('container-pages').clientWidth - 40) { if(viewport.width > document.getElementById('container-pages').clientWidth - 40) {
viewport = page.getViewport({scale: 1}); viewport = page.getViewport({scale: 1});
scale = (document.getElementById('container-pages').clientWidth - 40) / viewport.width; scale = (document.getElementById('container-pages').clientWidth - 40) / viewport.width;
@ -56,29 +60,29 @@ var loadPDF = async function(pdfBlob, filename) {
currentScale = scale; 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('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>'); 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); let canvasPDF = document.getElementById('canvas-pdf-' + pageIndex);
var canvasEditionHTML = document.getElementById('canvas-edition-' + pageIndex); let canvasEditionHTML = document.getElementById('canvas-edition-' + pageIndex);
// Prepare canvas using PDF page dimensions // Prepare canvas using PDF page dimensions
var context = canvasPDF.getContext('2d'); let context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height; canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width; canvasPDF.width = viewport.width;
canvasEditionHTML.height = canvasPDF.height; canvasEditionHTML.height = canvasPDF.height;
canvasEditionHTML.width = canvasPDF.width; canvasEditionHTML.width = canvasPDF.width;
var renderContext = { let renderContext = {
canvasContext: context, canvasContext: context,
viewport: viewport, viewport: viewport,
enhanceTextSelection: true enhanceTextSelection: true
}; };
var renderTask = page.render(renderContext); let renderTask = page.render(renderContext);
pdfRenderTasks.push(renderTask); pdfRenderTasks.push(renderTask);
pdfPages.push(page); pdfPages.push(page);
var canvasEdition = new fabric.Canvas('canvas-edition-' + pageIndex, { let canvasEdition = new fabric.Canvas('canvas-edition-' + pageIndex, {
selection : false, selection : false,
allowTouchScrolling: true allowTouchScrolling: true
}); });
@ -133,6 +137,10 @@ var loadPDF = async function(pdfBlob, filename) {
} }
}); });
canvasEdition.on('object:scaled', function(event) { canvasEdition.on('object:scaled', function(event) {
if (event.target instanceof fabric.IText) {
currentTextScale = event.target.scaleX;
return;
}
var item = getSvgItem(event.target.svgOrigin); var item = getSvgItem(event.target.svgOrigin);
if(!item) { if(!item) {
return; 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() { var is_mobile = function() {
return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none"); return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none");
}; };
@ -180,7 +205,7 @@ var storeCollections = function () {
var getSvgItem = function(svg) { var getSvgItem = function(svg) {
for (index in svgCollections) { for (index in svgCollections) {
svgItem = svgCollections[index]; let svgItem = svgCollections[index];
if(svgItem.svg == svg) { if(svgItem.svg == svg) {
return svgItem; return svgItem;
@ -242,7 +267,7 @@ var svgChange = function(input, event) {
stateAddLock(false); 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") { if(input_selected && !input_selected.value.match(/^data:/) && input_selected.value != "text") {
input_selected = null; input_selected = null;
@ -269,7 +294,7 @@ var svgChange = function(input, event) {
}; };
var getHtmlSvg = function(svg, i) { var getHtmlSvg = function(svg, i) {
var inputRadio = document.createElement('input'); let inputRadio = document.createElement('input');
inputRadio.type = "radio"; inputRadio.type = "radio";
inputRadio.classList.add("btn-check"); inputRadio.classList.add("btn-check");
inputRadio.id="radio_svg_"+i; inputRadio.id="radio_svg_"+i;
@ -279,7 +304,7 @@ var getHtmlSvg = function(svg, i) {
inputRadio.addEventListener('change', function() { inputRadio.addEventListener('change', function() {
svgChange(this, event); svgChange(this, event);
}); });
var svgButton = document.createElement('label'); let svgButton = document.createElement('label');
svgButton.id = "label_svg_"+i; svgButton.id = "label_svg_"+i;
svgButton.classList.add('position-relative'); svgButton.classList.add('position-relative');
svgButton.classList.add('btn'); svgButton.classList.add('btn');
@ -312,12 +337,12 @@ var getHtmlSvg = function(svg, i) {
svgButton.addEventListener('mouseout', function(event) { svgButton.addEventListener('mouseout', function(event) {
this.style.removeProperty('cursor'); this.style.removeProperty('cursor');
}) })
var svgImg = document.createElement('img'); let svgImg = document.createElement('img');
svgImg.src = svg.svg; svgImg.src = svg.svg;
svgImg.draggable = false; svgImg.draggable = false;
svgImg.style = "max-width: 180px;max-height: 70px;"; svgImg.style = "max-width: 180px;max-height: 70px;";
svgButton.appendChild(svgImg); svgButton.appendChild(svgImg);
var svgContainer = document.createElement('div'); let svgContainer = document.createElement('div');
svgContainer.classList.add('d-grid'); svgContainer.classList.add('d-grid');
svgContainer.classList.add('gap-2'); svgContainer.classList.add('gap-2');
svgContainer.appendChild(inputRadio); svgContainer.appendChild(inputRadio);
@ -330,8 +355,8 @@ var stateAddLock = function(state) {
if(forceAddLock) { if(forceAddLock) {
state = true; state = true;
} }
var checkbox = document.getElementById('add-lock-checkbox'); let checkbox = document.getElementById('add-lock-checkbox');
var input_selected = document.querySelector('input[name="svg_2_add"]:checked'); let input_selected = document.querySelector('input[name="svg_2_add"]:checked');
addLock = state; addLock = state;
@ -342,13 +367,8 @@ var stateAddLock = function(state) {
checkbox.disabled = false; checkbox.disabled = false;
} }
/*document.querySelectorAll('.btn-svg').forEach(function(item) {
item.style.borderWidth = "1px";
});*/
if(addLock && input_selected) { if(addLock && input_selected) {
var svgButton = document.querySelector('.btn-svg[for="'+input_selected.id+'"]'); let svgButton = document.querySelector('.btn-svg[for="'+input_selected.id+'"]');
//svgButton.style.borderWidth = "2px";
checkbox.checked = true; checkbox.checked = true;
return; return;
} }
@ -365,7 +385,7 @@ var displaysSVG = function() {
item.classList.remove('d-none'); item.classList.remove('d-none');
}); });
svgCollections.forEach((svg, i) => { svgCollections.forEach((svg, i) => {
var svgHtmlChild = getHtmlSvg(svg, i); let svgHtmlChild = getHtmlSvg(svg, i);
if(svg.type) { if(svg.type) {
document.getElementById('svg_list_'+svg.type).appendChild(svgHtmlChild); document.getElementById('svg_list_'+svg.type).appendChild(svgHtmlChild);
return; return;
@ -392,7 +412,7 @@ var displaysSVG = function() {
}; };
function dataURLtoBlob(dataurl) { 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); bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){ while(n--){
u8arr[n] = bstr.charCodeAt(n); u8arr[n] = bstr.charCodeAt(n);
@ -410,7 +430,7 @@ function trimSvgWhitespace(svgContent) {
return null; return null;
} }
var svgContainer = document.createElement("div") let svgContainer = document.createElement("div")
svgContainer.classList.add('invisible'); svgContainer.classList.add('invisible');
svgContainer.classList.add('position-absolute'); svgContainer.classList.add('position-absolute');
svgContainer.classList.add('top-0'); svgContainer.classList.add('top-0');
@ -418,8 +438,8 @@ function trimSvgWhitespace(svgContent) {
svgContainer.style = "z-index: -1;"; svgContainer.style = "z-index: -1;";
svgContainer.innerHTML = svgContent; svgContainer.innerHTML = svgContent;
document.body.appendChild(svgContainer); document.body.appendChild(svgContainer);
var svg = svgContainer.querySelector('svg'); let svg = svgContainer.querySelector('svg');
var box = svg.getBBox(); let box = svg.getBBox();
svg.setAttribute("viewBox", [box.x, box.y, box.width, box.height].join(" ")); svg.setAttribute("viewBox", [box.x, box.y, box.width, box.height].join(" "));
svgContent = svgContainer.innerHTML; svgContent = svgContainer.innerHTML;
document.body.removeChild(svgContainer) 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_spinner').classList.remove('d-none');
document.getElementById('btn_modal_ajouter_check').classList.add('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.open( 'POST', document.getElementById('form-image-upload').action, true );
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
@ -481,9 +501,15 @@ var addObjectInCanvas = function(canvas, item) {
}; };
var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) { 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')) { if(document.getElementById('save')) {
document.getElementById('save').removeAttribute('disabled'); document.getElementById('save').removeAttribute('disabled');
} }
hasModifications = true;
if(document.getElementById('save_mobile')) { if(document.getElementById('save_mobile')) {
document.getElementById('save_mobile').removeAttribute('disabled'); document.getElementById('save_mobile').removeAttribute('disabled');
} }
@ -502,7 +528,7 @@ var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
} }
if(item == 'text') { if(item == 'text') {
var textbox = new fabric.Textbox('Texte à modifier', { let textbox = new fabric.Textbox('Texte à modifier', {
left: x, left: x,
top: y - 20, top: y - 20,
fontSize: 20, fontSize: 20,
@ -512,6 +538,8 @@ var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
addObjectInCanvas(canvas, textbox).setActiveObject(textbox); addObjectInCanvas(canvas, textbox).setActiveObject(textbox);
textbox.keysMap[13] = "exitEditing"; textbox.keysMap[13] = "exitEditing";
textbox.lockScalingFlip = true; textbox.lockScalingFlip = true;
textbox.scaleX = currentTextScale;
textbox.scaleY = currentTextScale;
textbox.enterEditing(); textbox.enterEditing();
textbox.selectAll(); textbox.selectAll();
@ -520,14 +548,14 @@ var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
} }
fabric.loadSVGFromURL(item, function(objects, options) { fabric.loadSVGFromURL(item, function(objects, options) {
var svg = fabric.util.groupSVGElements(objects, options); let svg = fabric.util.groupSVGElements(objects, options);
svg.svgOrigin = item; svg.svgOrigin = item;
svg.lockScalingFlip = true; svg.lockScalingFlip = true;
svg.scaleToHeight(height); svg.scaleToHeight(height);
if(svg.getScaledWidth() > 200) { if(svg.getScaledWidth() > 200) {
svg.scaleToWidth(200); svg.scaleToWidth(200);
} }
var svgItem = getSvgItem(item); let svgItem = getSvgItem(item);
if(svgItem && svgItem.scale) { if(svgItem && svgItem.scale) {
svg.scaleToWidth(canvas.width * svgItem.scale); svg.scaleToWidth(canvas.width * svgItem.scale);
} }
@ -548,7 +576,7 @@ var zoomChange = function (inOrOut) {
return; return;
} }
var deltaScale = 0.2 * inOrOut; let deltaScale = 0.2 * inOrOut;
if(currentScale + deltaScale < 0) { if(currentScale + deltaScale < 0) {
return return
@ -578,7 +606,7 @@ var resizePDF = function (scale = 'auto') {
} }
pdfPages.forEach(function(page, pageIndex) { 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) { 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; scale = (document.getElementById('container-pages').clientWidth - 40) / page.getViewport({scale: 1}).width;
@ -588,18 +616,18 @@ var resizePDF = function (scale = 'auto') {
scale = 1.5; scale = 1.5;
} }
var viewport = page.getViewport({scale: scale}); let viewport = page.getViewport({scale: scale});
currentScale = scale; currentScale = scale;
var canvasPDF = document.getElementById('canvas-pdf-' + pageIndex); let canvasPDF = document.getElementById('canvas-pdf-' + pageIndex);
var context = canvasPDF.getContext('2d'); let context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height; canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width; canvasPDF.width = viewport.width;
canvasEdition = canvasEditions[pageIndex]; canvasEdition = canvasEditions[pageIndex];
var scaleMultiplier = canvasPDF.width / canvasEdition.width; let scaleMultiplier = canvasPDF.width / canvasEdition.width;
var objects = canvasEdition.getObjects(); let objects = canvasEdition.getObjects();
for (var i in objects) { for (let i in objects) {
objects[i].scaleX = objects[i].scaleX * scaleMultiplier; objects[i].scaleX = objects[i].scaleX * scaleMultiplier;
objects[i].scaleY = objects[i].scaleY * scaleMultiplier; objects[i].scaleY = objects[i].scaleY * scaleMultiplier;
objects[i].left = objects[i].left * scaleMultiplier; objects[i].left = objects[i].left * scaleMultiplier;
@ -612,7 +640,7 @@ var resizePDF = function (scale = 'auto') {
canvasEdition.renderAll(); canvasEdition.renderAll();
canvasEdition.calcOffset(); canvasEdition.calcOffset();
var renderContext = { let renderContext = {
canvasContext: context, canvasContext: context,
viewport: viewport, viewport: viewport,
enhanceTextSelection: true enhanceTextSelection: true
@ -662,7 +690,7 @@ var createEventsListener = function() {
}); });
document.getElementById('btn_modal_ajouter').addEventListener('click', function() { document.getElementById('btn_modal_ajouter').addEventListener('click', function() {
var svgItem = {}; let svgItem = {};
if(document.getElementById('input-svg-type').value) { if(document.getElementById('input-svg-type').value) {
svgItem.type = 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; svgItem.svg = document.getElementById('img-upload').src;
} }
if(document.getElementById('nav-type-tab').classList.contains('active')) { if(document.getElementById('nav-type-tab').classList.contains('active')) {
var fontPath = fontCaveat.getPath(document.getElementById('input-text-signature').value, 0, 0, 42); let fontPath = fontCaveat.getPath(document.getElementById('input-text-signature').value, 0, 0, 42);
var fabricPath = new fabric.Path(fontPath.toPathData()); let fabricPath = new fabric.Path(fontPath.toPathData());
fabricPath.top = 0; fabricPath.top = 0;
fabricPath.left = 0; fabricPath.left = 0;
fabricPath.height = fabricPath.getScaledHeight(); fabricPath.height = fabricPath.getScaledHeight();
var textCanvas = document.createElement('canvas'); let textCanvas = document.createElement('canvas');
textCanvas.width = fabricPath.getScaledWidth(); textCanvas.width = fabricPath.getScaledWidth();
textCanvas.height = fabricPath.getScaledHeight(); textCanvas.height = fabricPath.getScaledHeight();
var textCanvas = new fabric.Canvas(textCanvas); let textCanvasFabric = new fabric.Canvas(textCanvas);
textCanvas.add(fabricPath).renderAll(); textCanvasFabric.add(fabricPath).renderAll();
svgItem.svg = svgToDataUrl(textCanvas.toSVG()); svgItem.svg = svgToDataUrl(textCanvasFabric.toSVG());
} }
if(document.getElementById('nav-import-tab').classList.contains('active')) { if(document.getElementById('nav-import-tab').classList.contains('active')) {
svgItem.svg = document.getElementById('img-upload').src; svgItem.svg = document.getElementById('img-upload').src;
@ -689,12 +717,16 @@ var createEventsListener = function() {
displaysSVG(); displaysSVG();
localStorage.setItem('svgCollections', JSON.stringify(svgCollections)); localStorage.setItem('svgCollections', JSON.stringify(svgCollections));
var svg_list_id = "svg_list"; let svg_list_id = "svg_list";
if(svgItem.type) { if(svgItem.type) {
svg_list_id = svg_list_id + "_" + svgItem.type; svg_list_id = svg_list_id + "_" + svgItem.type;
} }
document.querySelector('#'+svg_list_id+' label:last-child').click(); 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) { 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) { if(firstInput) {
firstInput.focus(); firstInput.focus();
} }
@ -712,11 +744,11 @@ var createEventsListener = function() {
document.getElementById('modalAddSvg').addEventListener('shown.bs.modal', function (event) { document.getElementById('modalAddSvg').addEventListener('shown.bs.modal', function (event) {
document.querySelector('#modalAddSvg #nav-tab button:first-child').focus(); 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')) { if(tab.querySelector('input')) {
tab.querySelector('input').focus(); 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) { if(input_selected) {
input_selected.checked = false; input_selected.checked = false;
input_selected.dispatchEvent(new Event("change")); input_selected.dispatchEvent(new Event("change"));
@ -742,27 +774,39 @@ var createEventsListener = function() {
}) })
document.getElementById('input-image-upload').addEventListener('change', function(event) { 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]); data.append('file', document.getElementById('input-image-upload').files[0]);
uploadSVG(data); uploadSVG(data);
event.preventDefault(); 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')) { if(document.getElementById('save')) {
document.getElementById('save').addEventListener('click', function(event) { document.getElementById('save').addEventListener('click', function(event) {
var dataTransfer = new DataTransfer(); let dataTransfer = new DataTransfer();
canvasEditions.forEach(function(canvasEdition, index) { canvasEditions.forEach(function(canvasEdition, index) {
dataTransfer.items.add(new File([canvasEdition.toSVG()], index+'.svg', { dataTransfer.items.add(new File([canvasEdition.toSVG()], index+'.svg', {
type: 'image/svg+xml' type: 'image/svg+xml'
})); }));
}) })
document.getElementById('input_svg').files = dataTransfer.files; document.getElementById('input_svg').files = dataTransfer.files;
hasModifications = false;
}); });
} }
if(document.getElementById('save_share')) { if(document.getElementById('save_share')) {
document.getElementById('save_share').addEventListener('click', function(event) { document.getElementById('save_share').addEventListener('click', function(event) {
var dataTransfer = new DataTransfer(); let dataTransfer = new DataTransfer();
if(!document.getElementById('save').hasAttribute('disabled')) { if(!document.getElementById('save').hasAttribute('disabled')) {
canvasEditions.forEach(function(canvasEdition, index) { canvasEditions.forEach(function(canvasEdition, index) {
dataTransfer.items.add(new File([canvasEdition.toSVG()], index+'.svg', { dataTransfer.items.add(new File([canvasEdition.toSVG()], index+'.svg', {
@ -771,6 +815,7 @@ var createEventsListener = function() {
}) })
} }
document.getElementById('input_svg_share').files = dataTransfer.files; 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) { 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); stateAddLock(false);
input_selected.checked = false; input_selected.checked = false;
@ -793,8 +838,7 @@ var createEventsListener = function() {
document.addEventListener('click', function(event) { document.addEventListener('click', function(event) {
if(event.target.nodeName == "DIV") { if(event.target.nodeName == "DIV") {
let input_selected = document.querySelector('input[name="svg_2_add"]:checked');
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(!input_selected) { if(!input_selected) {
return; return;
} }
@ -806,7 +850,7 @@ var createEventsListener = function() {
document.addEventListener('keydown', function(event) { document.addEventListener('keydown', function(event) {
if(event.key == 'Escape' && (event.target.tagName == "BODY" || event.target.name == "svg_2_add")) { 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) { if(!input_selected) {
return; return;
} }
@ -890,6 +934,15 @@ var createEventsListener = function() {
zoomChange(1) zoomChange(1)
}); });
window.addEventListener('beforeunload', function(event) {
if(!hasModifications) {
return;
}
event.preventDefault();
return true;
});
if(hash) { if(hash) {
updateNbLayers(); updateNbLayers();
setInterval(function() { setInterval(function() {
@ -901,14 +954,13 @@ var createEventsListener = function() {
var createSignaturePad = function() { var createSignaturePad = function() {
signaturePad = new SignaturePad(document.getElementById('signature-pad'), { signaturePad = new SignaturePad(document.getElementById('signature-pad'), {
penColor: 'rgb(0, 0, 0)', penColor: 'rgb(0, 0, 0)',
minWidth: 1.25, minWidth: 1,
maxWidth: 2, maxWidth: 2,
throttle: 0,
onEnd: function() { onEnd: function() {
const file = new File([dataURLtoBlob(signaturePad.toDataURL())], "draw.png", { const file = new File([dataURLtoBlob(signaturePad.toDataURL())], "draw.png", {
type: 'image/png' type: 'image/png'
}); });
var data = new FormData(); let data = new FormData();
data.append('file', file); data.append('file', file);
uploadSVG(data); uploadSVG(data);
} }
@ -930,11 +982,11 @@ async function getPDFBlobFromCache(cacheUrl) {
async function uploadFromUrl(url) { async function uploadFromUrl(url) {
history.replaceState({}, '', '/signature'); history.replaceState({}, '', '/signature');
var response = await fetch(url); let response = await fetch(url);
if(response.status != 200) { if(response.status != 200) {
return; return;
} }
var pdfBlob = await response.blob(); let pdfBlob = await response.blob();
if(pdfBlob.type != 'application/pdf' && pdfBlob.type != 'application/octet-stream') { if(pdfBlob.type != 'application/pdf' && pdfBlob.type != 'application/octet-stream') {
return; 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() { var pageUpload = async function() {
document.querySelector('body').classList.remove('bg-light');
document.getElementById('input_pdf_upload').value = ''; document.getElementById('input_pdf_upload').value = '';
document.getElementById('page-upload').classList.remove('d-none'); document.getElementById('page-upload').classList.remove('d-none');
document.getElementById('page-signature').classList.add('d-none'); document.getElementById('page-signature').classList.add('d-none');
document.getElementById('input_pdf_upload').focus(); 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) { document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
if(document.getElementById('input_pdf_upload').files[0].size > maxSize) { 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.open('GET', '/signature/'+hash+'/nblayers', true);
xhr.onload = function() { xhr.onload = function() {
if (xhr.status == 200) { 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) { document.querySelectorAll('.nblayers').forEach(function(item) {
item.innerHTML = nblayers; item.innerHTML = nblayers;
}); });
@ -1013,6 +1083,7 @@ var updateNbLayers = function() {
}; };
var pageSignature = async function(url) { var pageSignature = async function(url) {
document.querySelector('body').classList.add('bg-light');
modalSharing(); modalSharing();
document.getElementById('page-upload').classList.add('d-none'); document.getElementById('page-upload').classList.add('d-none');
document.getElementById('page-signature').classList.remove('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')); 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) { opentype.load('/vendor/fonts/Caveat-Regular.ttf', function(err, font) {
fontCaveat = font; fontCaveat = font;
}); });
@ -1034,7 +1109,7 @@ var pageSignature = async function(url) {
let filename = url.replace('/pdf/', ''); let filename = url.replace('/pdf/', '');
if(hash) { if(hash) {
var response = await fetch(url); let response = await fetch(url);
if(response.status != 200) { if(response.status != 200) {
return; return;
} }
@ -1062,6 +1137,9 @@ var pageSignature = async function(url) {
}; };
(function () { (function () {
if(sharingMode) {
setTimeout(function() { runCron() }, 2000);
}
if(hash) { if(hash) {
pageSignature('/signature/'+hash+'/pdf'); pageSignature('/signature/'+hash+'/pdf');
window.addEventListener('hashchange', function() { 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.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.5.0" 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"> <link rel="icon" type="image/x-icon" href="/favicon-organization.ico">
<title>Organiser un PDF</title> <title>Organiser un PDF</title>
</head> </head>
<body class="bg-light"> <body>
<noscript> <noscript>
<div class="alert alert-danger text-center" role="alert"> <div class="alert alert-danger text-center" role="alert">
<i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé <i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé
</div> </div>
</noscript> </noscript>
<div id="page-upload"> <div id="page-upload">
<div class="px-4 py-5 my-5 text-center"> <ul class="nav justify-content-center nav-tabs mt-2">
<h1 class="display-5 fw-bold"><i class="bi bi-ui-checks-grid"></i> Organiser un PDF</h1> <li class="nav-item">
<div class="col-lg-3 mx-auto"> <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"> <div class="col-12">
<label for="input_pdf_upload" class="form-label">Choisir un PDF</label> <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" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf"> <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-1 opacity-50"><small class="text-muted">Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo</small></p> <p class="mt-2 small fw-light text-dark">Le PDF sera traité par le serveur sans être conservé ni stocké</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> <?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> </div>
</div> </div>
<footer class="text-center text-muted mb-2 fixed-bottom"> <footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
<small>Logiciel libre sous license AGPL-3.0 : <a href="https://github.com/24eme/signaturepdf">voir le code source</a></small> <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> </footer>
</div> </div>
<div id="page-organization" class="d-none"> <div id="page-organization" style="padding-right: 350px;" class="d-none">
<div id="container-pages" class="col-12 pt-1 vh-100" style="padding-bottom: 60px;"> <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>
<div id="container-bar" class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow-lg"> <div id="container-btn-zoom" class="btn-group-vertical position-fixed" style="top: 6px; right: 368px;">
<form id="form_pdf" action="/organize" method="post" enctype="multipart/form-data"> <button id="btn-zoom-increase" class="btn btn-outline-dark bg-white text-dark"><i class="bi bi-zoom-in"></i></button>
<input id="input_pdf" name="pdf" type="file" class="d-none" /> <button id="btn-zoom-decrease" class="btn btn-outline-dark bg-white text-dark"><i class="bi bi-zoom-out"></i></button>
<input id="input_pages" type="hidden" value="" name="pages" /> </div>
<div class="row"> <div id="div-margin-bottom" style="height: 55px;" class="d-md-none"></div>
<div class="col-3 d-none d-sm-none d-md-block"> <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">
<div class="form-switch mt-2 ms-2"> <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>
<input class="form-check-input" checked="checked" type="checkbox" id="checkbox_all_pages"> <div class="offcanvas-header mb-0 pb-0">
<label class="form-check-label" for="checkbox_all_pages">Séléctionner toutes les pages</label> <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> </form>
<div class="col-2 d-none d-sm-none d-md-block"> </div>
</div> </div>
<div class="col-1 d-none d-sm-none d-md-block"> </div>
<select class="form-select"> <div id="top_bar" class="position-fixed top-0 start-0 bg-white w-100 shadow-sm d-md-none p-2">
<option>3 pages</option> <div id="top_bar_action">
<option>4 pages</option> <div class="d-flex" role="group">
<option selected>5 pages</option> <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;">
<option>8 pages</option> <i class="bi bi-files"></i> <span id="liste_pdf_titre_mobile"></span>
<option>10 pages</option> </button>
</select> <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 class="col-2 d-none d-sm-none d-md-block"> </div>
</div> <div id="top_bar_action_selection" class="d-none">
<div class="col-2 d-none d-sm-none d-md-block"> <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="d-grid gap-2"> <div class="btn-group w-100">
<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> <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>
</div> <button id="btn_drag_select_mobile" type="button" class="btn btn-outline-primary"><i class="bi bi-arrows-move"></i> Déplacer</button>
<input id="input_pdf_upload_2" class="form-control d-none" type="file" accept=".pdf,application/pdf"> <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 class="col-sm-12 col-md-2"> </div>
<div class="d-grid gap-2"> </div>
<button class="btn btn-primary" type="submit" id="save"><i class="bi bi-download"></i> Télécharger le PDF</button> <div id="bottom_bar" class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow-sm d-md-none">
</div> <div id="bottom_bar_action" class="d-grid gap-2">
</div> <button class="btn btn-primary" type="submit" id="save_mobile"><i class="bi bi-download"></i> Télécharger le PDF complet</button>
</form> </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> </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> <span id="is_mobile" class="d-md-none"></span>
<script src="/vendor/bootstrap.min.js?5.1.3"></script> <script src="/vendor/bootstrap.min.js?5.1.3"></script>
@ -82,6 +157,6 @@
<script> <script>
var maxSize = <?php echo $maxSize ?>; var maxSize = <?php echo $maxSize ?>;
</script> </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> </body>
</html> </html>

View File

@ -1,38 +1,64 @@
<!doctype html> <!doctype html>
<html lang="fr_FR"> <html lang="fr">
<head> <head>
<!-- Required meta tags --> <!-- Required meta tags -->
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <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.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.8.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> <title>Signature PDF</title>
</head> </head>
<body class="bg-light"> <body>
<noscript> <noscript>
<div class="alert alert-danger text-center" role="alert"> <div class="alert alert-danger text-center" role="alert">
<i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé <i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé
</div> </div>
</noscript> </noscript>
<div id="page-upload"> <div id="page-upload">
<div class="px-4 py-5 my-5 text-center"> <?php if(!$disableOrganization): ?>
<h1 class="display-5 fw-bold"><i class="bi bi-vector-pen"></i> Signer un PDF</h1> <ul class="nav justify-content-center nav-tabs mt-2">
<div class="col-lg-3 mx-auto"> <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"> <div class="col-12">
<label for="input_pdf_upload" class="form-label">Choisir un PDF</label> <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" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf"> <input id="input_pdf_upload" placeholder="Choisir un PDF" 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> <p class="mt-2 small fw-light text-dark">Le PDF sera traité par le serveur sans être conservé ni stocké</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> <?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> </div>
</div> </div>
<footer class="text-center text-muted mb-2 fixed-bottom"> <footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
<small>Logiciel libre sous license AGPL-3.0 : <a href="https://github.com/24eme/signaturepdf">voir le code source</a></small> <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> </footer>
</div> </div>
<div id="page-signature" style="padding-right: 350px;" class="d-none"> <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 style="height: 65px;" class="d-md-none"></div>
<div id="container-pages" class="col-12 pt-1 pb-1 text-center vh-100"> <div id="container-pages" class="col-12 pt-1 pb-1 text-center vh-100">
</div> </div>
@ -80,22 +106,18 @@
<div id="form_block" class="position-absolute bottom-0 pb-2 ps-0 pe-4 w-100"> <div id="form_block" class="position-absolute bottom-0 pb-2 ps-0 pe-4 w-100">
<?php if(!isset($hash)): ?> <?php if(!isset($hash)): ?>
<?php if(!isset($noSharingMode)): ?> <?php if(!isset($noSharingMode)): ?>
<form id="form_sharing" action="/share" method="post" enctype="multipart/form-data"> <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>
<input id="input_pdf_share" name="pdf" type="file" class="d-none" /> <?php endif; ?>
<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; ?>
<form id="form_pdf" action="/sign" method="post" enctype="multipart/form-data" class="d-none d-sm-none d-md-block"> <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_pdf" name="pdf" type="file" class="d-none" />
<input id="input_svg" name="svg[]" 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> <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> </form>
<?php elseif(!isset($noSharingMode)): ?> <?php elseif(!isset($noSharingMode)): ?>
<div class="d-none d-sm-none d-md-block"> <div class="d-none d-sm-none d-md-block position-relative">
<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> <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"> <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> <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> <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-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body"> <div class="modal-body">
<nav> <nav class="nav nav-tabs" id="nav-tab" role="tablist">
<div 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 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 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" 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 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>
<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> </nav>
<div class="tab-content mt-3" id="nav-svg-add"> <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"> <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"> <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"> <input id="input-image-upload" class="form-control" name="image" type="file">
</form> </form>
</div> </div>
</div> </div>
<input id="input-svg-type" type="hidden" /> <input id="input-svg-type" type="hidden" />
</div> </div>
<div class="modal-footer"> <div class="modal-footer d-block">
<button tabindex="-1" type="button" class="btn btn-light" data-bs-dismiss="modal">Annuler</button> <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" 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> <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> </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)): ?> <?php if(isset($hash)): ?>
<div id="modal-share-informations" class="modal" tabindex="-1"> <div id="modal-share-informations" class="modal" tabindex="-1">
<div class="modal-dialog modal-md"> <div class="modal-dialog modal-md">
@ -181,14 +225,15 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <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"> <div class="input-group mb-3">
<span class="input-group-text">Lien à partager</span> <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=""> <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> <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> <script>document.querySelector('#input-share-link').value = document.location.href.replace(/#.*/, '');</script>
</div> </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>
<div class="modal-footer text-start"> <div class="modal-footer text-start">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Fermer</button> <button type="button" class="btn btn-light" data-bs-dismiss="modal">Fermer</button>
@ -226,11 +271,12 @@
<script> <script>
var maxSize = <?php echo $maxSize ?>; var maxSize = <?php echo $maxSize ?>;
var maxPage = <?php echo $maxPage ?>; var maxPage = <?php echo $maxPage ?>;
var sharingMode = <?php echo intval(!isset($noSharingMode)) ?>;
var hash = null; var hash = null;
<?php if(isset($hash)): ?> <?php if(isset($hash)): ?>
hash = "<?php echo $hash ?>"; hash = "<?php echo $hash ?>";
<?php endif; ?> <?php endif; ?>
</script> </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> </body>
</html> </html>