o2c-opendoc-theme 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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()