o2c-opendoc-theme 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +2 -0
  4. data/_includes/directory.html +127 -0
  5. data/_includes/document-title.txt +57 -0
  6. data/_includes/toc.html +127 -0
  7. data/_includes/toolbar.html +68 -0
  8. data/_includes/welcome.html +5 -0
  9. data/_layouts/default.html +147 -0
  10. data/_layouts/home.html +4 -0
  11. data/_layouts/iframe.html +13 -0
  12. data/_layouts/page.html +4 -0
  13. data/_layouts/print.html +27 -0
  14. data/_sass/_base.scss +398 -0
  15. data/_sass/_constants.scss +87 -0
  16. data/_sass/_iframe.scss +7 -0
  17. data/_sass/_layout.scss +419 -0
  18. data/_sass/_nav.scss +592 -0
  19. data/_sass/_print.scss +70 -0
  20. data/_sass/_syntax-highlighting.scss +61 -0
  21. data/_sass/_toolbar.scss +372 -0
  22. data/_sass/_welcome.scss +41 -0
  23. data/assets/export.md +30 -0
  24. data/assets/images/chevron-up-white.svg +1 -0
  25. data/assets/images/chevron-up.svg +1 -0
  26. data/assets/images/close.svg +17 -0
  27. data/assets/images/favicon.ico +0 -0
  28. data/assets/images/feedback-hover.svg +3 -0
  29. data/assets/images/feedback-mobile.svg +1 -0
  30. data/assets/images/feedback.svg +1 -0
  31. data/assets/images/github-hover.svg +3 -0
  32. data/assets/images/github.svg +1 -0
  33. data/assets/images/home.svg +14 -0
  34. data/assets/images/index-img.png +0 -0
  35. data/assets/images/logo-order2cash.svg +65 -0
  36. data/assets/images/logo.png +0 -0
  37. data/assets/images/menu.svg +1 -0
  38. data/assets/images/opendoc-logo-full.svg +10 -0
  39. data/assets/images/pdf-hover.svg +11 -0
  40. data/assets/images/pdf.svg +9 -0
  41. data/assets/images/search-icon-dark.svg +19 -0
  42. data/assets/images/search-icon-white.svg +12 -0
  43. data/assets/images/share.svg +1 -0
  44. data/assets/images/sidebar-hover.svg +3 -0
  45. data/assets/images/sidebar.svg +1 -0
  46. data/assets/images/vertical-dots.svg +1 -0
  47. data/assets/images/x-mobile.svg +1 -0
  48. data/assets/index.html +5 -0
  49. data/assets/js/banner.js +20 -0
  50. data/assets/js/google_analytics.js +11 -0
  51. data/assets/js/header.js +31 -0
  52. data/assets/js/helpers.js +24 -0
  53. data/assets/js/lunr.min.js +6 -0
  54. data/assets/js/navigation.js +214 -0
  55. data/assets/js/page-index.js +57 -0
  56. data/assets/js/pqueue.js +373 -0
  57. data/assets/js/pre-loader.js +43 -0
  58. data/assets/js/search.js +580 -0
  59. data/assets/js/toolbar.js +144 -0
  60. data/assets/pdfs/empty +0 -0
  61. data/assets/siteIndex.json +56 -0
  62. data/assets/startup/build.sh +41 -0
  63. data/assets/startup/docprint.html +20 -0
  64. data/assets/startup/pdf-gen.js +397 -0
  65. data/assets/startup/prebuild-lunr-index.js +52 -0
  66. data/assets/styles/main.scss +13 -0
  67. data/assets/styles/normalize.css +427 -0
  68. data/assets/vendor/babel-polyfill.min.js +3 -0
  69. data/assets/vendor/dom4.js +2 -0
  70. data/assets/vendor/fetch.umd.js +531 -0
  71. data/assets/vendor/headroom.min.js +7 -0
  72. data/assets/vendor/jump.min.js +2 -0
  73. data/assets/vendor/mark.min.js +7 -0
  74. data/assets/vendor/popper.min.js +5 -0
  75. data/assets/vendor/web-share-shim.bundle.min.js +2 -0
  76. metadata +159 -0
@@ -0,0 +1,144 @@
1
+ ---
2
+ ---
3
+ (function () {
4
+ // Hard coded max-width for mobile view
5
+ window.isMobileView = function () {
6
+ return window.innerWidth < 992
7
+ }
8
+
9
+ // Documents - Section toggle
10
+
11
+ // Site-nav
12
+ // --------------------------
13
+ var menuToggle = document.getElementById('menu-toggle')
14
+ var showMenu = function showMenu() {
15
+ menuToggle.checked = true
16
+ document.body.classList.add('menu-toggled')
17
+ }
18
+ var hideMenu = function hideMenu() {
19
+ menuToggle.checked = false
20
+ document.body.classList.remove('menu-toggled')
21
+ }
22
+ menuToggle.addEventListener('change', function () {
23
+ if (menuToggle.checked) {
24
+ showMenu()
25
+ } else {
26
+ hideMenu()
27
+ }
28
+ })
29
+
30
+ var welcomeButton = document.getElementsByClassName('welcome-button')[0]
31
+ if (welcomeButton) {
32
+ welcomeButton.onclick = showMenu
33
+ }
34
+
35
+ // Hide site-nav on navigation
36
+ window.addEventListener('link-click', function () {
37
+ if (isMobileView()) {
38
+ hideMenu()
39
+ }
40
+ })
41
+
42
+ // Edit button
43
+ // --------------------------
44
+ var editButtons = document.querySelectorAll('.edit-btn')
45
+ editButtons.forEach(function (btn) {
46
+ btn.addEventListener('click', function () {
47
+ var repoUrl = '{{ site.github.repository_url }}' + '/blob/master/'
48
+ var page = pageIndex[window.location.pathname]
49
+ var pageUrl = page ? page.escapedPath : null
50
+ if (pageUrl) {
51
+ console.log('opening:', pageUrl)
52
+ repoUrl += pageUrl
53
+ }
54
+ window.open(repoUrl, '_blank')
55
+ })
56
+ })
57
+
58
+ // Print button
59
+ // --------------------------
60
+ var printButtons = document.querySelectorAll('.print-btn')
61
+ var isProd = '{{ jekyll.environment }}' === 'production'
62
+
63
+ printButtons.forEach(function (btn) {
64
+ btn.addEventListener('click', function () {
65
+ // S3 folder name; replace slashes to avoid creating sub-folders
66
+ var replacedRepoName = '{{ site.repository }}'.replace(/\//g, '-') + (isProd ? '' : '-staging')
67
+ var pdfUrl = '{{ site.offline }}' === 'true' ?
68
+ '{{ "/assets/pdfs" | relative_url }}' :
69
+ 'https://pdf.opendoc.gov.sg/' + replacedRepoName
70
+ var page = pageIndex[window.location.pathname]
71
+ // documentTitle refers to the name of the document folder
72
+ // If page.dir is slash, that indicates the root directory
73
+ // PDF at root dir is named export.pdf
74
+ var documentTitle = page.dir !== '/' ? page.dir : '/export/'
75
+ if (documentTitle) {
76
+ pdfUrl += documentTitle.substring(0, documentTitle.length-1) + '.pdf'
77
+ }
78
+ window.open(pdfUrl, '_blank')
79
+ })
80
+ })
81
+
82
+ // Share button
83
+ // -------------------------
84
+ var shareButtons = document.querySelectorAll('.share-btn')
85
+ shareButtons.forEach(function(btn) {
86
+ btn.addEventListener('click', function() {
87
+ if (navigator.share) {
88
+ navigator.share({
89
+ title: {{ site.title | jsonify }},
90
+ text: document.title,
91
+ url: window.location.href
92
+ }).then()
93
+ }
94
+ })
95
+ })
96
+
97
+ // Floating Action Button
98
+ // -----------------------
99
+ var floatingActionButtonTrigger = document.getElementById('fab-trigger')
100
+ var floatingActionButton = document.getElementById('fab')
101
+ floatingActionButtonTrigger.addEventListener('click', function () {
102
+ floatingActionButton.classList.toggle('open')
103
+ });
104
+
105
+ var fabOverlay = document.getElementById('fab-overlay')
106
+ fabOverlay.addEventListener('click', function() {
107
+ floatingActionButton.classList.remove('open')
108
+ })
109
+
110
+ var backToTopButton = document.getElementById('back-to-top')
111
+ backToTopButton.addEventListener('click', function() {
112
+ // jump.js
113
+ Jump(-(window.pageYOffset || document.documentElement.scrollTop), {
114
+ duration: 300
115
+ })
116
+ })
117
+
118
+ // show/hide back-to-top button on scroll and on load
119
+ function scrollListener() {
120
+ var scrollTop = window.pageYOffset || document.documentElement.scrollTop
121
+ if (scrollTop > (window.innerHeight * 2)) {
122
+ backToTopButton.classList.remove('hidden')
123
+ } else {
124
+ backToTopButton.classList.add('hidden')
125
+ }
126
+ }
127
+
128
+ scrollListener()
129
+
130
+ window.addEventListener("scroll", scrollListener)
131
+
132
+ // Search Button for mobile
133
+ // --------------------------
134
+ var searchButtons = document.querySelectorAll('.search-btn')
135
+ var searchBoxElement = document.getElementById('search-box')
136
+ searchButtons.forEach(function(btn) {
137
+ btn.addEventListener('click', function() {
138
+ document.body.classList.toggle('search-toggled')
139
+ if (document.body.classList.contains('search-toggled')) {
140
+ searchBoxElement.focus()
141
+ }
142
+ })
143
+ })
144
+ })()
data/assets/pdfs/empty ADDED
File without changes
@@ -0,0 +1,56 @@
1
+ ---
2
+ ---
3
+ {% assign index_array = "" | split: ',' %}
4
+ {%- for page in site.html_pages -%}
5
+ {%- unless page.exclude -%}
6
+ {% unless page.name == 'index.html' or page.name == 'index.md' %}
7
+ {%- assign page_url = page.url -%}
8
+ {%- assign split_content = page.content | markdownify | split: '<h2' | splice: 1 -%}
9
+
10
+ {%- comment -%}Deal with h1 section{%- endcomment -%}
11
+ {%- assign page_header = split_content.first | split: '</h1>' -%}
12
+ {%- assign page_header = page_header.first | split: '">' -%}
13
+ {%- assign page_header = page_header.last | strip_html | newline_to_br | strip_newlines | replace: '<br />', ' ' | replace: '\', '' | strip | smartify | normalize_whitespace -%}
14
+ {%- assign header_section = '<h1' | append: split_content.first | strip_html | newline_to_br | strip_newlines | replace: '<br />', ' ' | replace: '\', '' | strip | smartify | normalize_whitespace -%}
15
+ {%- capture item -%}
16
+ {
17
+ "title": "{{ page_header}}",
18
+ "documentTitle": "{%- include_cached document-title.txt dir=page.dir info="title" -%}",
19
+ "url": "{{page_url | relative_url }}",
20
+ "text": "{{ header_section }}"
21
+ }
22
+ {% endcapture %}
23
+ {%- assign index_array = index_array | push: item -%}
24
+
25
+ {%- comment -%}Then deal with h2 sections {%- endcomment -%}
26
+ {%- for section in split_content offset:1 -%}
27
+ {%- assign section_header_html = section | split: 'id="' -%}
28
+ {%- assign section_header_html = section_header_html[1] | split: '">' -%}
29
+
30
+ {%- comment -%}Get section id{%- endcomment -%}
31
+ {%- assign section_id = section_header_html[0] -%}
32
+ {%- comment -%}Get section url{%- endcomment -%}
33
+ {%- assign section_url = page_url | append: '#' | append: section_id -%}
34
+
35
+ {%- comment -%}Get section header{%- endcomment -%}
36
+ {%- assign section_header = section_header_html | shift | join | split: '</h2' -%}
37
+ {%- assign section_header = section_header[0] | strip_html -%}
38
+
39
+ {%- comment -%}Get section body{%- endcomment -%}
40
+ {%- assign full_section = '<h2 ' | append:section | strip_html | newline_to_br | strip_newlines | replace: '<br />', ' ' | replace: '\', '' | strip | smartify | normalize_whitespace -%}
41
+ {%- capture item -%}
42
+ {
43
+ "title": "{{ section_header | smartify }}",
44
+ "documentTitle": "{%- include_cached document-title.txt dir=page.dir info="title" -%}",
45
+ "url": "{{ section_url | relative_url }}",
46
+ "text": "{{ full_section }}"
47
+ }
48
+ {% endcapture %}
49
+ {%- assign index_array = index_array | push: item -%}
50
+
51
+ {%- endfor -%}
52
+ {%- endunless -%}
53
+ {%- endunless -%}
54
+ {%- endfor -%}
55
+
56
+ [{{ index_array | join: ','}}]
@@ -0,0 +1,41 @@
1
+ ---
2
+ ---
3
+ #!/bin/bash
4
+
5
+ echo 'Started script to generate PDFs'
6
+ echo 'Installing node dependencies'
7
+ npm i glob@7.1.6 jsdom@16.4.0 js-yaml@4.0.0 p-all@3.0.0
8
+ if [ "{{ site.offline }}" == "true" ]; then
9
+ node _site/assets/startup/prebuild-lunr-index.js
10
+ echo 'Generating lunr index complete'
11
+ npm i html-pdf@2.2.0
12
+ else
13
+ if ! [ -x "$(command -v aws)" ];
14
+ then
15
+ echo 'Warning: aws is not installed. Please setup github webhooks for elasticsearch indexing'
16
+ else
17
+ APP_NAME="{{ site.repository | split: '/' | last }}"
18
+ echo "APP_NAME = $APP_NAME"
19
+ if [ "${AWS_BRANCH}" = "master" ]; then
20
+ echo "Building prod elasticsearch index for $APP_NAME";
21
+ aws lambda invoke --function-name es-lambda-prod-es --invocation-type Event \
22
+ --payload "{\"index\":\"opendocsg-$APP_NAME\", \"repoName\":\"$APP_NAME\"}" /dev/null;
23
+ fi
24
+
25
+ if [ "${AWS_BRANCH}" = "staging" ]; then
26
+ echo "Building staging elasticsearch index for $APP_NAME";
27
+ aws lambda invoke --function-name es-lambda-staging-es --invocation-type Event \
28
+ --payload "{\"index\":\"opendocsg-$APP_NAME\", \"repoName\":\"$APP_NAME\", \"branch\": \"staging\"}" /dev/null;
29
+ fi
30
+
31
+ if [ ! -z "${CUSTOM_BRANCH}" ] && [ "${AWS_BRANCH}" = "${CUSTOM_BRANCH}" ]; then
32
+ echo "Building $CUSTOM_BRANCH into staging elasticsearch index for $APP_NAME";
33
+ aws lambda invoke --function-name es-lambda-staging-es --invocation-type Event \
34
+ --payload "{\"index\":\"opendocsg-$APP_NAME\", \"repoName\":\"$APP_NAME\", \"branch\": \"$CUSTOM_BRANCH\"}" /dev/null;
35
+ fi
36
+ fi
37
+ fi
38
+ echo "key is $PDF_LAMBDA_KEY"
39
+ echo "server is $PDF_LAMBDA_SERVER"
40
+ node _site/assets/startup/pdf-gen.js
41
+ echo 'End script'
@@ -0,0 +1,20 @@
1
+ <!-- layout for printing individual documents-->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <meta charset="utf-8">
7
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1">
9
+
10
+ <link rel="icon" href="./assets/images/favicon.ico">
11
+ <link rel="stylesheet" href="./assets/styles/normalize.css">
12
+ <link rel="stylesheet" href="./assets/styles/main.css">
13
+ </head>
14
+
15
+ <body class=print-content>
16
+ <div id="main-content" class="site-main">
17
+ </div>
18
+ </body>
19
+
20
+ </html>
@@ -0,0 +1,397 @@
1
+ ---
2
+ ---
3
+ const fs = require('fs')
4
+ const crypto = require('crypto')
5
+ const pAll = require('p-all')
6
+ const https = require('https')
7
+ const glob = require('glob')
8
+ const path = require('path')
9
+ const URL = require('url').URL;
10
+ const jsdom = require('jsdom')
11
+ const jsyaml = require('js-yaml')
12
+ const SITE_PATH = __dirname + '/../..'
13
+
14
+ const IS_PROD = '{{ jekyll.environment }}' === 'production'
15
+ const GENERATE_PDF_LOCALLY = '{{ site.offline }}' === 'true' || false
16
+ const S3_STORAGE_URL = new URL('https://opendoc-theme-pdf.s3-ap-southeast-1.amazonaws.com')
17
+
18
+ // For dealing with imagess when baseurl is set, remove leading slashes, if any, for standardization
19
+ const BASEURL = '{{ site.baseurl }}'.replace('/', '')
20
+ const LOCAL_PDF_DOLER = path.join(SITE_PATH, 'assets', 'pdfs') // local folder for pdfs
21
+ // S3 folder; replace slashes to avoid creating sub-folders
22
+ const S3_PDF_FOLDER = '{{ site.repository }}'.replace(/\//g, '-') + (IS_PROD ? '' : '-staging')
23
+
24
+ const BUCKET_NAME = S3_STORAGE_URL.hostname.split('.')[0]
25
+
26
+ // CSS to be applied to the PDFs, this will be inserted in <head>
27
+ const PATH_TO_CSS = path.join(SITE_PATH, 'assets', 'styles', 'main.css')
28
+
29
+ // Hash is stored as S3 metadata and served as custom header whenever the pdf is requested
30
+ const SERIALIZED_HTML_HASH_HEADER = 'x-amz-meta-html-hash'
31
+
32
+ // Config.yml file path
33
+ const CONFIG_YAML_PATH = path.join(SITE_PATH, '..', '_config.yml')
34
+
35
+ let pdf
36
+ let pdfGenConcurrency = 1
37
+ if (GENERATE_PDF_LOCALLY) {
38
+ pdf = require('html-pdf')
39
+ console.log('Generating PDFs and storing locally instead.')
40
+ } else {
41
+ if (process.env.PDF_LAMBDA_KEY === undefined ||
42
+ process.env.PDF_LAMBDA_SERVER === undefined) {
43
+ console.log('Environment variables PDF_LAMBDA_KEY or PDF_LAMBDA_SERVER for AWS Lambda not present')
44
+ process.exit(1)
45
+ }
46
+ pdfGenConcurrency = process.env.PDF_GEN_CONCURRENCY !== undefined ?
47
+ parseInt(process.env.PDF_GEN_CONCURRENCY) :
48
+ 50 // Tuned for Netlify
49
+ console.log(`Generating PDFs on AWS Lambda with concurrency of ${pdfGenConcurrency}.`)
50
+ console.log(`PDFs will be placed in bucket: ${BUCKET_NAME} in folder ${S3_PDF_FOLDER}.`)
51
+ }
52
+
53
+ // These options are only applied when PDFs are built locally
54
+ const localPdfOptions = {
55
+ height: '594mm', // allowed units: mm, cm, in, px
56
+ width: '420mm',
57
+ base: 'file://' + SITE_PATH + '/',
58
+ border: {
59
+ right: '100px', // default is 0, units: mm, cm, in, px
60
+ left: '100px',
61
+ },
62
+ header: {
63
+ height: '80px',
64
+ },
65
+ footer: {
66
+ height: '80px',
67
+ },
68
+ }
69
+
70
+ // List of top-level folder names which may contain html but are not to be printed
71
+ const printIgnoreFolders = ['assets', 'files', 'iframes', 'images']
72
+ // List of top-level .html files which are not to be printed
73
+ const printIgnoreFiles = ['export.html', 'index.html']
74
+
75
+ // Tracking statistics
76
+ let numPdfsStarted = 0
77
+ let numPdfsUnchanged = 0
78
+ let numPdfsError = 0
79
+ let numPdfsSuccess = 0
80
+ let numTotalPdfs = 0
81
+ const TIMER = 'Time to create PDFs'
82
+
83
+ const main = async () => {
84
+ // creating exports of individual documents
85
+ console.time(TIMER)
86
+ const docFolders = getDocumentFolders(SITE_PATH, printIgnoreFolders)
87
+ await exportPdfTopLevelDocs(SITE_PATH)
88
+ await exportPdfDocFolders(SITE_PATH, docFolders)
89
+ console.log(`PDFs created with success:${numPdfsSuccess} unchanged:${numPdfsUnchanged} error:${numPdfsError} total:${numTotalPdfs}`)
90
+ console.timeEnd(TIMER)
91
+ }
92
+
93
+ const exportPdfTopLevelDocs = async (sitePath) => {
94
+ let htmlFilePaths = glob.sync('*.html', { cwd: sitePath })
95
+ htmlFilePaths = htmlFilePaths.filter((filepath) => !printIgnoreFiles.includes(filepath))
96
+ htmlFilePaths = htmlFilePaths.map((filepath) => path.join(sitePath, filepath))
97
+ // Remove folders without HTML files (don't want empty pdfs)
98
+ if (htmlFilePaths.length === 0) return
99
+ numTotalPdfs++
100
+ const orderForFolder = getOrderFromConfig('/')
101
+ if (orderForFolder && orderForFolder.length) {
102
+ htmlFilePaths = reorderHtmlFilePaths(htmlFilePaths, orderForFolder)
103
+ }
104
+ await createPdf(htmlFilePaths, sitePath, 'export')
105
+ }
106
+
107
+ const exportPdfDocFolders = (sitePath, docFolders) => {
108
+ const actions = []
109
+ for (let folder of docFolders) {
110
+ // find all the folders containing html files
111
+ const folderPath = path.join(sitePath, folder)
112
+ let htmlFilePaths = glob.sync('*.html', { cwd: folderPath })
113
+ htmlFilePaths = htmlFilePaths.filter((filepath) => !printIgnoreFiles.includes(filepath))
114
+ htmlFilePaths = htmlFilePaths.map((filepath) => path.join(folderPath, filepath))
115
+
116
+ // Remove folders without HTML files (don't want empty pdfs)
117
+ if (htmlFilePaths.length === 0) continue
118
+ numTotalPdfs++
119
+ const orderForFolder = getOrderFromConfig(folder)
120
+
121
+ if (orderForFolder && orderForFolder.length) {
122
+ htmlFilePaths = reorderHtmlFilePaths(htmlFilePaths, orderForFolder)
123
+ }
124
+ actions.push((() => createPdf(htmlFilePaths, folderPath, folder)))
125
+ }
126
+ return pAll(actions, { concurrency: pdfGenConcurrency })
127
+ }
128
+
129
+ // Concatenates the contents in .html files, and outputs export.pdf in the specified output folder
130
+ const createPdf = (htmlFilePaths, outputFolderPath, documentName) => {
131
+ logStartedPdf(outputFolderPath)
132
+ // docprint.html is our template to build pdf up from.
133
+ const exportHtmlFile = fs.readFileSync(__dirname + '/docprint.html')
134
+ let cssFile = ''
135
+ try {
136
+ cssFile = fs.readFileSync(PATH_TO_CSS)
137
+ } catch(err) {
138
+ console.log('Failed to read CSS file at ' + PATH_TO_CSS +', CSS will not be applied')
139
+ }
140
+ const exportDom = new jsdom.JSDOM(exportHtmlFile)
141
+ const exportDomBody = exportDom.window.document.body
142
+ const exportDomMain = exportDom.window.document.getElementById('main-content')
143
+ let addedTitle = false
144
+ let addedDocTitle = false
145
+
146
+ htmlFilePaths.forEach(function (filePath) {
147
+ const file = fs.readFileSync(filePath)
148
+ const dom = new jsdom.JSDOM(file, {
149
+ resources: 'usable' // to get JSDOM to load stylesheets
150
+ })
151
+
152
+ // html-pdf can't deal with these
153
+ removeTagsFromDom(dom, 'script')
154
+ removeTagsFromDom(dom, 'iframe')
155
+ inlineImages(dom, outputFolderPath)
156
+
157
+ // Site titles needs only be added once
158
+ if (!addedTitle) {
159
+ try {
160
+ const oldTitle = dom.window.document.getElementsByClassName('site-header-text')[0]
161
+ exportDomBody.insertBefore(oldTitle, exportDomMain)
162
+ addedTitle = true
163
+ } catch (error) {
164
+ console.log('Failed to append Title, skipping: ' + error)
165
+ }
166
+ }
167
+ // Document titles too
168
+ if (!addedDocTitle) {
169
+ try {
170
+ const oldDocTitle = dom.window.document.getElementsByClassName('description-container')[0]
171
+ exportDomBody.insertBefore(oldDocTitle, exportDomMain)
172
+ const hr = dom.window.document.createElement('HR')
173
+ exportDomBody.insertBefore(hr, exportDomMain)
174
+ addedDocTitle = true
175
+ } catch (error) {
176
+ console.log('Failed to append Doc Title, skipping: ' + error)
177
+ }
178
+ }
179
+
180
+ // Concat all the id:main-content divs
181
+ try {
182
+ const oldNode = dom.window.document.getElementById('main-content')
183
+ exportDomMain.innerHTML += oldNode.innerHTML
184
+ } catch (error) {
185
+ console.log('Failed to append Node, skipping: ' + error)
186
+ }
187
+ dom.window.close()
188
+ })
189
+ const serializedHtmlHash = crypto.createHash('md5').update(exportDom.serialize()).digest('base64')
190
+ exportDom.window.document.head.innerHTML += '<style>' + cssFile + '</style>'
191
+ console.log('createpdf hash for:' + outputFolderPath + ': ' + serializedHtmlHash)
192
+ if (GENERATE_PDF_LOCALLY) {
193
+ exportDomBody.className += ' print-content-large'
194
+ // Generate and store locally
195
+ return new Promise((resolve, reject) => {
196
+ const url = path.join(LOCAL_PDF_DOLER, documentName + '.pdf')
197
+ pdf.create(exportDom.serialize(), localPdfOptions).toFile(url, (err, res) => {
198
+ if (err) {
199
+ logErrorPdf('Creating PDFs locally', err)
200
+ return reject()
201
+ }
202
+ logSuccessPdf(res.filename)
203
+ resolve()
204
+ })
205
+ exportDom.window.close()
206
+ })
207
+ } else {
208
+ // Apply small font sizes because puppeteer tends to print big
209
+ exportDomBody.className += ' print-content-small'
210
+ // Code for this API lives at https://github.com/opendocsg/pdf-lambda
211
+ const pdfName = `${documentName}.pdf`
212
+ return new Promise(function (resolve, reject) {
213
+ // Promise resolves if PDF is present and hash matches. Else reject.
214
+ const pdfS3Url = S3_STORAGE_URL.toString() + S3_PDF_FOLDER + '/' + pdfName
215
+ const options = {
216
+ method: 'HEAD'
217
+ }
218
+ const pdfExistsRequest = https.request(pdfS3Url, options, function (res) {
219
+ if (res.statusCode === 404) {
220
+ return reject('PDF not present')
221
+ }
222
+ if (!(SERIALIZED_HTML_HASH_HEADER in res.headers)) {
223
+ return reject('HTML hash header not present')
224
+ }
225
+ if (res.headers[SERIALIZED_HTML_HASH_HEADER] !== serializedHtmlHash) {
226
+ return reject('PDF hash does not match')
227
+ }
228
+ logUnchangedPdf(pdfName, pdfS3Url)
229
+ resolve()
230
+ })
231
+ pdfExistsRequest.on('error', function (err) {
232
+ console.log(`pdfExistsRequest encountered error for ${pdfName}:, ${err}`)
233
+ return reject()
234
+ })
235
+ pdfExistsRequest.end()
236
+ }).then(() => {},
237
+ function (rejected) {
238
+ // Rejected: send to lambda function to create PDF
239
+ const options = {
240
+ method: 'POST',
241
+ headers: {
242
+ 'x-api-key': process.env.PDF_LAMBDA_KEY,
243
+ 'content-type': 'application/json',
244
+ }
245
+ }
246
+
247
+ const pdfCreationBody = {
248
+ 'serializedHTML': exportDom.serialize(),
249
+ 'serializedHTMLName': S3_PDF_FOLDER + '/' + pdfName,
250
+ 'serializedHTMLHash': serializedHtmlHash,
251
+ 'bucketName': BUCKET_NAME
252
+ }
253
+ return new Promise(function (resolve, reject) {
254
+ const pdfCreationRequest = https.request(process.env.PDF_LAMBDA_SERVER, options, function (res) {
255
+ if (res.statusCode < 200 || res.statusCode >= 300) {
256
+ logErrorPdf(`pdfCreationRequest status code for ${pdfName}: `, res.statusCode)
257
+ return reject()
258
+ }
259
+ logSuccessPdf(pdfName)
260
+ return resolve()
261
+ })
262
+ pdfCreationRequest.on('error', function(err) {
263
+ logErrorPdf(`pdfCreationRequest encountered error for ${pdfName}:`, err)
264
+ return reject()
265
+ })
266
+ pdfCreationRequest.write(JSON.stringify(pdfCreationBody))
267
+ pdfCreationRequest.end()
268
+ }).catch((error) => {
269
+ logErrorPdf(`pdfCreation promise error for ${pdfName}`, error)
270
+ }).finally(() => {
271
+ exportDom.window.close()
272
+ })
273
+
274
+ })
275
+ }
276
+ }
277
+
278
+ const logStartedPdf = (outputFolderPath) => {
279
+ numPdfsStarted++
280
+ console.log(`createpdf started for:${outputFolderPath} (${numPdfsStarted}/${numTotalPdfs})`)
281
+ }
282
+
283
+ const logUnchangedPdf = (outputFolderPath, pdfUrl) => {
284
+ numPdfsUnchanged++
285
+ console.log(`createpdf unchanged for:${outputFolderPath} at ${pdfUrl} (${numPdfsUnchanged}/${numTotalPdfs})`)
286
+ }
287
+
288
+ const logErrorPdf = (origin, error) => {
289
+ numPdfsError++
290
+ console.log(`createpdf error for: ${origin}: ${error}(${numPdfsError}/${numTotalPdfs})`)
291
+ }
292
+
293
+ const logSuccessPdf = (outputPdfPath) => {
294
+ numPdfsSuccess++
295
+ console.log(`createpdf success for:${outputPdfPath} (${numPdfsSuccess}/${numTotalPdfs})`)
296
+ }
297
+
298
+ const imageType = {
299
+ '.png':'image/png',
300
+ '.jpg':'image/jpeg',
301
+ '.jpeg':'image/jpeg',
302
+ '.bmp':'image/bmp',
303
+ '.webp':'image/webp',
304
+ }
305
+
306
+ // Load images and inline them
307
+ const inlineImages = (dom, outputFolderPath) => {
308
+ const imgs = dom.window.document.getElementsByTagName('img')
309
+ for (let i = 0; i < imgs.length; i++) {
310
+ const img = imgs[i]
311
+ const originalImagePath = img.src
312
+ if (!originalImagePath.startsWith('http://') && !originalImagePath.startsWith('https://')) {
313
+ // Convert all file paths into absolute file paths
314
+ let imgPath
315
+ if (originalImagePath.startsWith('/')) {
316
+ // If baseurl is set, remove baseurl for images to be found
317
+ if (BASEURL.length > 0) {
318
+ imgPath = path.join(__dirname, '..', '..', originalImagePath.replace('/' + BASEURL, ''))
319
+ } else {
320
+ imgPath = path.join(__dirname, '..', '..', originalImagePath)
321
+ }
322
+ } else {
323
+ // relative path
324
+ imgPath = path.join(outputFolderPath, originalImagePath).toString()
325
+ }
326
+ if (fs.existsSync(imgPath)) {
327
+ const imgRaw = fs.readFileSync(imgPath)
328
+ if (path.extname(imgPath) === '.svg') { // don't encode svgs in base64, simply insert them
329
+ img.src = 'data:image/svg+xml;utf8,' + imgRaw.toString('utf-8')
330
+ } else {
331
+ const dataType = imageType[path.extname(imgPath)] || 'image/png'
332
+ const uri = 'data:' + dataType + ';base64,' + imgRaw.toString('base64')
333
+ img.src = uri
334
+ }
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ // Returns a list of the valid document (i.e. folder) paths
341
+ const getDocumentFolders = (sitePath, printIgnoreFolders) => {
342
+ return fs.readdirSync(sitePath).filter(function (filePath) {
343
+ return fs.statSync(path.join(sitePath, filePath)).isDirectory() &&
344
+ !printIgnoreFolders.includes(filePath)
345
+ })
346
+ }
347
+
348
+ // Returns true if config file has order for particular folder
349
+ const getOrderFromConfig = (folderName) => {
350
+ try {
351
+ const configYml = yamlToJs(CONFIG_YAML_PATH)
352
+ const folders = configYml.folders
353
+ for (folder of folders) {
354
+ if (folder.name.toLowerCase() === folderName.toLowerCase()) {
355
+ return folder.order
356
+ }
357
+ }
358
+ return null
359
+ } catch (error) {
360
+ return null
361
+ }
362
+ }
363
+
364
+
365
+ // Mutates the htmlFilepath array to match order provided in order
366
+ const reorderHtmlFilePaths = (htmlFilePaths, order) => {
367
+ const orderedHtmlFilePaths = []
368
+ for (let i = 0; i < order.length; i++) {
369
+ const name = path.basename(order[i], '.md')
370
+ htmlFilePaths.some((filePath) => {
371
+ if (path.basename(filePath, '.html') === name) {
372
+ orderedHtmlFilePaths.push(filePath)
373
+ }
374
+ })
375
+ }
376
+ return orderedHtmlFilePaths
377
+ }
378
+
379
+ // Removes <tag></tag> from dom and everything in between them
380
+ const removeTagsFromDom = (dom, tagname) => {
381
+ const tags = dom.window.document.getElementsByTagName(tagname)
382
+ for (let i = tags.length - 1; i >= 0; i--) {
383
+ tags[i].parentNode.removeChild(tags[i])
384
+ }
385
+ }
386
+
387
+ // converts .md to JS Object
388
+ const markdownToJs = (filepath) => {
389
+ const configString = fs.readFileSync(filepath).toString().replace(/---/g, '')
390
+ return jsyaml.load(configString)
391
+ }
392
+
393
+ const yamlToJs = (filepath) => {
394
+ return jsyaml.load(fs.readFileSync(filepath))
395
+ }
396
+
397
+ main()