hdoc-tools 0.7.26 → 0.8.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Hornbill Docs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,69 +1,86 @@
1
- 'use strict';
1
+ (function () {
2
+ 'use strict';
2
3
 
3
- // GS: based on markdown-it-tips extension but customized for our specific needs
4
+ // GS: based on markdown-it-tips extension but customized for our specific needs
4
5
 
5
- var container = require('markdown-it-container');
6
+ var container = require('markdown-it-container');
6
7
 
7
- module.exports = function md_tip_plugin(md, options) {
8
- var containerOpenCount = 0;
9
- var links = options ? options.links : true;
10
- init();
11
- return;
8
+ module.exports = function md_tip_plugin(md, options) {
9
+ var containerOpenCount = 0;
10
+ var links = options ? options.links : true;
11
+ init();
12
+ return;
12
13
 
13
- function icon_from_type(name) {
14
- switch (name) {
15
- case "note": return { class: "bi bi-exclamation-circle", title: "Note" };
16
- case "tip": return { class: "bi bi-lightbulb", title: "Tip" };
17
- case "important": return { class: "bi bi-megaphone", title: "Important" };
18
- case "caution": return { class: "bi bi-x-circle", title: "Caution" };
19
- case "warning": return { class: "bi bi-exclamation-triangle", title: "Warning" };
14
+ function icon_from_type(name) {
15
+ switch (name) {
16
+ case "note":
17
+ return {
18
+ class: "bi bi-exclamation-circle", title: "Note"
19
+ };
20
+ case "tip":
21
+ return {
22
+ class: "bi bi-lightbulb", title: "Tip"
23
+ };
24
+ case "important":
25
+ return {
26
+ class: "bi bi-megaphone", title: "Important"
27
+ };
28
+ case "caution":
29
+ return {
30
+ class: "bi bi-x-circle", title: "Caution"
31
+ };
32
+ case "warning":
33
+ return {
34
+ class: "bi bi-exclamation-triangle", title: "Warning"
35
+ };
36
+ }
37
+ }
38
+
39
+ function setupContainer(name) {
40
+ md.use(container, name, {
41
+ render: function (tokens, idx) {
42
+ if (tokens[idx].nesting === 1) {
43
+ containerOpenCount += 1;
44
+ let inf = icon_from_type(name);
45
+ return '<div class="hdoc-alert alert-icon-' + name + '">\n<p class="hdoc-alert-title"><span class="' + inf.class + '"></span>' + inf.title + '</p>\n';
46
+ } else {
47
+ containerOpenCount -= 1;
48
+ return '</div>\n';
49
+ }
50
+ }
51
+ });
52
+ }
53
+
54
+ function isContainerOpen() {
55
+ return containerOpenCount > 0;
56
+ }
57
+
58
+ function selfRender(tokens, idx, options, env, self) {
59
+ return self.renderToken(tokens, idx, options);
20
60
  }
21
- }
22
61
 
23
- function setupContainer(name) {
24
- md.use(container, name, {
25
- render: function (tokens, idx) {
26
- if (tokens[idx].nesting === 1) {
27
- containerOpenCount += 1;
28
- let inf = icon_from_type(name);
29
- return '<div class="hdoc-alert alert-icon-' + name + '">\n<p class="hdoc-alert-title"><span class="' + inf.class + '"></span>' + inf.title + '</p>\n';
30
- } else {
31
- containerOpenCount -= 1;
32
- return '</div>\n';
62
+ function setupLinks() {
63
+ var defaultRender = md.renderer.rules.link_open || selfRender;
64
+
65
+ md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
66
+ if (isContainerOpen()) {
67
+ tokens[idx].attrPush(['class', 'md-tip-link']);
33
68
  }
69
+
70
+ return defaultRender(tokens, idx, options, env, self);
71
+ };
72
+ }
73
+
74
+ function init() {
75
+ setupContainer('note');
76
+ setupContainer('tip');
77
+ setupContainer('important');
78
+ setupContainer('caution');
79
+ setupContainer('warning');
80
+
81
+ if (links) {
82
+ setupLinks();
34
83
  }
35
- });
36
- }
37
-
38
- function isContainerOpen() {
39
- return containerOpenCount > 0;
40
- }
41
-
42
- function selfRender(tokens, idx, options, env, self) {
43
- return self.renderToken(tokens, idx, options);
44
- }
45
-
46
- function setupLinks() {
47
- var defaultRender = md.renderer.rules.link_open || selfRender;
48
-
49
- md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
50
- if (isContainerOpen()) {
51
- tokens[idx].attrPush(['class', 'md-tip-link']);
52
- }
53
-
54
- return defaultRender(tokens, idx, options, env, self);
55
- };
56
- }
57
-
58
- function init() {
59
- setupContainer('note');
60
- setupContainer('tip');
61
- setupContainer('important');
62
- setupContainer('caution');
63
- setupContainer('warning');
64
-
65
- if (links) {
66
- setupLinks();
67
- }
68
- }
69
- };
84
+ }
85
+ };
86
+ })();
package/hdoc-build.js CHANGED
@@ -1,14 +1,30 @@
1
+ const {
2
+ stat
3
+ } = require('fs');
4
+
1
5
  (function () {
2
6
  'use strict';
3
7
 
4
8
  const dree = require('dree'),
5
9
  fs = require('fs-extra'),
10
+ mdfm = require('markdown-it-front-matter'),
6
11
  path = require('path'),
12
+ Database = require('better-sqlite3'),
7
13
  URL = require("url").URL,
8
14
  validate = require(path.join(__dirname, 'hdoc-validate.js')),
9
15
  hdoc = require(path.join(__dirname, 'hdoc-module.js')),
16
+ hdoc_index = require(path.join(__dirname, 'hdoc-db.js')),
10
17
  zipper = require('zip-local');
11
-
18
+
19
+ const h_tags_to_search = ['h1', 'h2', 'h3'],
20
+ index_cols = [
21
+ 'relative_url',
22
+ 'book_id',
23
+ 'book_audience',
24
+ 'book_tags',
25
+ 'doc_title',
26
+ 'doc_content'
27
+ ];
12
28
 
13
29
  let conversion_attempted = 0,
14
30
  conversion_success = 0,
@@ -16,17 +32,89 @@
16
32
  includes_found = 0,
17
33
  includes_success = 0,
18
34
  includes_failed = 0,
35
+ hdocbook_project,
19
36
  docId = '',
20
- md_files = [];
37
+ md_files = [],
38
+ static_html_files = [],
39
+ index_records = [];
40
+
41
+ const transform_static_html = function (file_path) {
42
+ if (fs.existsSync(file_path.path)) {
43
+ // Load HTML file
44
+ let html_txt = fs.readFileSync(file_path.path, 'utf8');
45
+ html_txt = html_txt.replace(/\r/gm, ''); // Remove CR's so we're just dealing with newlines
46
+
47
+ let html_txt_updated = false;
48
+
49
+ // Check if we have a frontmatter comment
50
+ const fm_header = hdoc.getHTMLFrontmatterHeader(html_txt);
51
+ if (Object.keys(fm_header.fm_properties).length > 0) {
52
+ // We have some frontmatter headers, check if title is one of them
53
+ let fm_title_found = false;
54
+ if (fm_header.fm_properties && fm_header.fm_properties.title !== undefined) {
55
+ // We have a title - but does the title have a value
56
+ if (fm_header.fm_properties.title === '') {
57
+ // No value - remove title from the properties map
58
+ // so we don't end up with 2 title properties, one empty and one with a value
59
+ delete fm_header.fm_properties.title;
60
+ } else {
61
+ // We have a value for the title property
62
+ fm_title_found = true;
63
+ }
64
+ }
65
+
66
+ if (!fm_title_found) {
67
+ // No frontmatter title found in properties
68
+ // Go get title from h tags in html
69
+ const html_heading = hdoc.getFirstHTMLHeading(html_txt, h_tags_to_search);
70
+
71
+ if (html_heading && html_heading[0] && html_heading[0].children && html_heading[0].children[0] && html_heading[0].children[0].data) {
72
+ // We've found a heading tag, add that as a title to the existing frontmatter properties
73
+ let frontmatter_header = `[[FRONTMATTER\ntitle: ${html_heading[0].children[0].data}`;
74
+ for (const key in fm_header.fm_properties) {
75
+ if (fm_header.fm_properties.hasOwnProperty(key)) {
76
+ frontmatter_header += `\n${key}: ${fm_header.fm_properties[key]}`;
77
+ }
78
+ }
79
+ frontmatter_header += '\n]]';
80
+ html_txt = html_txt.replace(fm_header.fm_header, frontmatter_header);
81
+ html_txt_updated = true;
82
+ } else {
83
+ // No header tag, no frontmatter title, output a warning
84
+ console.log(`No frontmatter title property, or ${h_tags_to_search.join(', ')} tags detected in ${file_path.path}`);
85
+ }
86
+ }
87
+ } else {
88
+ // We have no frontmatter headers, get and build one from the html headings
89
+ const html_heading = hdoc.getFirstHTMLHeading(html_txt, h_tags_to_search);
90
+ if (html_heading && html_heading[0] && html_heading[0].children && html_heading[0].children[0] && html_heading[0].children[0].data) {
91
+ // We've found a heading tag, add that as a title to the frontmatter content
92
+ const frontmatter_header = `<!--[[FRONTMATTER\r\ntitle: ${html_heading[0].children[0].data}\r\n]]-->\r\n`;
93
+ html_txt = frontmatter_header + html_txt;
94
+ html_txt_updated = true;
95
+ } else {
96
+ // No header tag, no frontmatter title, output a warning
97
+ console.log(`No frontmatter title property, or ${h_tags_to_search.join(', ')} tags detected in ${file_path.path}`);
98
+ }
99
+ }
100
+ index_records.push({relative_path: file_path.relativePath, index_html: hdoc_index.transform_html_for_index(html_txt)});
101
+ if (html_txt_updated) {
102
+ // Save HTML into HTML file
103
+ fs.writeFile(file_path.path, html_txt, function writeJSON(err) {
104
+ if (err) return console.log('Error writing:', target_file, '\r\n', err);
105
+ });
106
+ }
107
+ }
108
+ };
21
109
 
22
- const transform_markdown_and_save_html = function (file_path, md) {
110
+ const transform_markdown_and_save_html = function (file_path) {
23
111
  conversion_attempted++;
24
- if (fs.existsSync(file_path)) {
112
+ if (fs.existsSync(file_path.path)) {
25
113
  // Load markdown file
26
- let md_txt = hdoc.expand_variables(fs.readFileSync(file_path, 'utf8'));
114
+ let md_txt = hdoc.expand_variables(fs.readFileSync(file_path.path, 'utf8'));
27
115
 
28
116
  // Pull in external includes
29
- const includes_processed = hdoc.process_includes(file_path, md_txt);
117
+ const includes_processed = hdoc.process_includes(file_path.path, md_txt);
30
118
  md_txt = includes_processed.body;
31
119
  includes_found += includes_processed.found;
32
120
  includes_success += includes_processed.success;
@@ -37,60 +125,94 @@
37
125
  }
38
126
  }
39
127
 
40
- const mdfm = require('markdown-it-front-matter');
128
+ // One markdown parser per file. Seems wrong, but doesn't work with a global one once past the first md file
129
+ // Steve - revisit this
41
130
  const md = require('markdown-it')({
42
- // enable everything
43
131
  html: true,
44
132
  linkify: true,
45
- typographer: true,
46
- // highlight: function (str, lang) {
47
- // if (lang && hljs.getLanguage(lang)) {
48
- // try {
49
- // return hljs.highlight(lang, str, true).value;
50
- // } catch (__) {}
51
- // }
52
- // }
53
- });
54
-
55
- var frontmatter_content = "";
133
+ typographer: true
134
+ });
135
+
136
+ // Process Frontmatter tags
137
+ let frontmatter_content = "";
56
138
  md.use(mdfm, function (fm) {
57
139
  frontmatter_content = fm;
58
- });
140
+ });
59
141
 
142
+ // Process tips
60
143
  const tips = require(__dirname + '/custom_modules/tips.js');
61
144
  md.use(tips, {
62
- links: true
145
+ links: true
63
146
  });
64
147
 
65
148
  // Render markdown into HTML
66
- var html_txt = md.render(md_txt.toString());
149
+ let html_txt = md.render(md_txt.toString());
150
+
151
+ // Does frontmatter tag contain a title property
152
+ let fm_contains_title = false;
153
+ let fm_content = frontmatter_content.split(/\r?\n/);
154
+ if (fm_content.length >= 0) {
155
+ fm_content.forEach(function (fm_prop) {
156
+ const fm_property = fm_prop.split(':');
157
+ if (fm_property[0] && fm_property[0] === 'title' && fm_property[1] && fm_property[1].length > 0) fm_contains_title = true;
158
+ });
159
+ }
67
160
 
161
+ if (!fm_contains_title) {
162
+ // Frontmatter tags don't contain a title property - go pull the first one from the html heading tags
163
+ const html_heading = hdoc.getFirstHTMLHeading(html_txt, h_tags_to_search);
164
+
165
+ if (html_heading && html_heading[0] && html_heading[0].children && html_heading[0].children[0] && html_heading[0].children[0].data) {
166
+ // We've found a heading tag, add that as a title to the frontmatter content
167
+ if (frontmatter_content.length > 0) frontmatter_content += '\r\n';
168
+ frontmatter_content += `title: ${html_heading[0].children[0].data}`;
169
+ } else {
170
+ // No header tag, no frontmatter title, output a warning
171
+ console.log(`No frontmatter title property, or h1, h2 or h3 header tags detected in ${file_path}`);
172
+ }
173
+ }
174
+
175
+ // Add frontmatter tags as comment to front of HTML
68
176
  if (frontmatter_content.length) {
69
177
  html_txt = "<!--[[FRONTMATTER\r\n" + frontmatter_content + "\r\n]]-->\r\n" + html_txt;
70
178
  }
71
-
179
+
72
180
  // Save HTML into HTML file
73
- const target_file = file_path.replace(path.extname(file_path), '.html');
181
+ const target_file = file_path.path.replace(path.extname(file_path.path), '.html');
182
+ const relative_path = file_path.relativePath.replace(path.extname(file_path.path), '.html');
74
183
  fs.writeFile(target_file, html_txt, function writeJSON(err) {
75
184
  if (err) return console.log('Error writing:', target_file, '\r\n', err);
76
185
  });
186
+
187
+ index_records.push({relative_path: relative_path, index_html: hdoc_index.transform_html_for_index(html_txt)});
188
+
189
+ // Delete MD file from _work path
190
+ try {
191
+ fs.unlinkSync(file_path.path);
192
+ } catch (e) {
193
+ console.log(`Error deleting ${file_path.path}: ${e}`);
194
+ }
77
195
  conversion_success++;
78
196
  return true;
79
197
  }
80
198
  conversion_failed++;
81
- console.error('MD file does not exist:', file_path);
199
+ console.error('MD file does not exist:', file_path.path);
82
200
  return false;
83
201
  };
84
202
 
85
203
  // File callbacks for scans
86
204
  const fileCallback = function (element) {
87
- md_files.push(element.path);
205
+ if (element.extension === 'md') {
206
+ md_files.push(element);
207
+ } else {
208
+ static_html_files.push(element);
209
+ }
88
210
  };
89
211
 
90
212
  const dreeOptions = {
91
213
  descendants: true,
92
214
  depth: 10,
93
- extensions: ['md'],
215
+ extensions: ['md', 'html', 'htm'],
94
216
  hash: false,
95
217
  normalize: true,
96
218
  size: false,
@@ -99,13 +221,13 @@
99
221
  symbolicLinks: false
100
222
  };
101
223
 
102
- exports.run = function (source_path, md, verbose) {
224
+ exports.run = function (source_path, verbose) {
103
225
  // GERRY: The purpose of this function is to create a zip file containing the hdocbook content,
104
226
  // * Create a _work folder
105
227
  // * copy the hdocbook content to the work folder
106
228
  // * Render all markdown into side-by-side HTML file
107
229
  // * Replace SERVER_VARS embedded in documents with the right version information etc.
108
- // * Build an index (sqlite FTS5) by extracting text from all HTML content in the work
230
+ // * Build an index (sqlite FTS5) by extracting text from all HTML content in the work
109
231
  // folder, conceptually we are making a little mini website crawler to index all of the content
110
232
  // within the book.
111
233
  // * Package everything up into a ZIP file, ready for the build controller to package and publish
@@ -115,8 +237,8 @@
115
237
 
116
238
  // Load the hdocbook-project.json file to get the docId
117
239
  // use the docId to get the book config
118
- const hdocbook_project_config_path = path.join(source_path, 'hdocbook-project.json'),
119
- hdocbook_project = require(hdocbook_project_config_path);
240
+ const hdocbook_project_config_path = path.join(source_path, 'hdocbook-project.json');
241
+ hdocbook_project = require(hdocbook_project_config_path);
120
242
 
121
243
  docId = hdocbook_project.docId;
122
244
 
@@ -150,8 +272,15 @@
150
272
 
151
273
  // Work through MD files and convert to HTML
152
274
  md_files.forEach(function (md_file) {
153
- transform_markdown_and_save_html(md_file, md);
275
+ transform_markdown_and_save_html(md_file);
276
+ });
277
+
278
+ // Work through Static HTML files and add Frontmatter tags
279
+ static_html_files.forEach(function (static_html_file) {
280
+ transform_static_html(static_html_file);
154
281
  });
282
+
283
+
155
284
  console.log(` MD files found: ${conversion_attempted}`);
156
285
  console.log(`Successfully converted to HTML: ${conversion_success}`);
157
286
  console.log(` Failed to convert: ${conversion_failed}\r\n`);
@@ -159,15 +288,50 @@
159
288
  console.log(` Includes Success: ${includes_success}`);
160
289
  console.log(` Includes Failed: ${includes_failed}\r\n`);
161
290
 
291
+ console.log(` Static HTML Files Found: ${static_html_files.length}\r\n`);
292
+
293
+ // Validate content
162
294
  const validation_success = validate.run(work_path, docId, verbose);
163
295
  if (!validation_success) {
164
296
  process.exit(1);
165
297
  }
166
298
 
167
- const zip_path = path.join(work_path, docId + '.zip');
168
- zipper.sync.zip(path.join(work_path, docId)).compress().save(zip_path);
299
+ // Now build the index
300
+ console.log('Performing SQlite index creation...\n');
301
+ let db_name = path.join(work_path, docId, docId + '.db');
302
+ const db = new Database(db_name);
303
+ console.log(`Database created: ${db_name}`);
304
+
305
+ // Now add the table
306
+ const table_name = 'hdoc_index';
307
+ const table_created = hdoc_index.create_virtual_table(db, table_name, index_cols);
308
+
309
+ if (table_created !== null) {
310
+ console.log(`Error creating table: ${table_created}`);
311
+ } else {
312
+ console.log(`Virtual table created: ${table_created}`);
313
+ if (!hdocbook_config.tags) hdocbook_config.tags = [];
314
+ for (let i = 0; i < index_records.length; i++) {
315
+ const index_vals = [
316
+ index_records[i].relative_path.replace('\\', '/').replace(`${hdocbook_config.docId}/`, ''),
317
+ hdocbook_config.docId,
318
+ hdocbook_config.audience.join(','),
319
+ hdocbook_config.tags.join(','),
320
+ index_records[i].index_html.fm_props.title,
321
+ index_records[i].index_html.text
322
+ ];
323
+ hdoc_index.insert_record(db, table_name, index_cols, index_vals, hdocbook_config.docId, index_records[i].index_html.fm_props.title);
324
+ }
325
+ }
326
+
327
+ try {
328
+ const zip_path = path.join(work_path, docId + '.zip');
329
+ zipper.sync.zip(path.join(work_path, docId)).compress().save(zip_path);
330
+ console.log(`\nZIP Creation Success: ${zip_path}\r\n`);
331
+ console.log('Build Complete\r\n');
332
+ } catch (e) {
333
+ console.log('\nError creating ZIP: ' + e);
334
+ }
169
335
 
170
- console.log(`ZIP Creation Success: ${zip_path}\r\n`);
171
- console.log('Build Complete\r\n');
172
336
  };
173
337
  })();
package/hdoc-db.js CHANGED
@@ -1,158 +1,71 @@
1
- const { nextTick } = require('process');
2
-
3
- (function() {
1
+ (function () {
4
2
  'use strict';
5
3
 
6
- const dree = require('dree'),
7
- fs = require('fs-extra'),
8
- html2text = require('html-to-text'),
9
- sqlite3 = require('sqlite3'),
10
- path = require('path');
11
-
12
- const table_name = 'hbdocs';
13
-
14
- let db_name = 'hbdocs.db',
15
- hdocbook_meta = null,
16
- md_files = [];
4
+ const html2text = require('html-to-text'),
5
+ path = require('path'),
6
+ hdoc = require(path.join(__dirname, 'hdoc-module.js'));
17
7
 
18
- // File callbacks for scan
19
- const file_callback = function (element) {
20
- if (path.extname(element.path) === '.md' && !element.path.includes('_work')) {
21
- md_files.push(element);
22
- } else if (element.name === 'hdocbook.json') {
23
- // hdocbook meta data, read and store
24
- hdocbook_meta = require(element.path);
8
+ exports.create_virtual_table = function (db, table_name, columns) {
9
+ let create_sql = [];
10
+ create_sql.push(`create VIRTUAL table ${table_name} USING fts5(`);
11
+ for (let i = 0; i < columns.length; i++) {
12
+ if (i !== 0) create_sql.push(`,${columns[i]}`);
13
+ else create_sql.push(columns[i]);
14
+ }
15
+ create_sql.push(');');
16
+ try {
17
+ db.exec(create_sql.join('\n'));
18
+ return null;
19
+ } catch (e) {
20
+ return e;
25
21
  }
26
22
  };
27
23
 
28
- const create_table = function(db) {
29
- db.exec(`
30
- create table ${table_name} (
31
- relative_url text primary key not null,
32
- book_id text not null,
33
- title text not null,
34
- content text not null,
35
- tags text null,
36
- audience text not null
37
- );
38
- `, (err) => {
39
- if (err !== null) {
40
- console.error(`Error creating table [${table_name}]: ${err}`);
41
- return false;
24
+ exports.insert_record = function (db, table, columns, values, book_id, doc_title) {
25
+ let queryProps = [];
26
+ queryProps.push(`INSERT INTO ${table}`);
27
+ let cols = '(';
28
+ let vals = 'VALUES (';
29
+ for (let i = 0; i < columns.length; i++) {
30
+ if (i === 0) {
31
+ cols += `${columns[i]}`;
32
+ vals += '?';
42
33
  } else {
43
- console.log(` Table created successfully: ${table_name}`);
44
- return true;
34
+ cols += `,${columns[i]}`;
35
+ vals += ',?';
45
36
  }
46
- });
47
- return true;
48
- };
49
-
50
- const build_db = function(db, md, book_def, md_files) {
51
- for (let i = 0; i < md_files.length; i++) {
52
- db.exec(`
53
- insert into ${table_name}
54
- (book_id, title, relative_url, content, tags, audience)
55
- values
56
- (
57
- '${book_def.docId}',
58
- '${book_def.title}',
59
- '${md_files[i].relativePath}',
60
- 'Some file content',
61
- '${book_def.tags.join(';')}',
62
- '${book_def.audience.join(';')}'
63
- );
64
- `, (err) => {
65
- if (err !== null) {
66
- console.error(`Error inserting record [${md_files[i].relativePath}]: ${err}`);
67
- return false;
68
- } else {
69
- console.log(` Record inserted successfully: ${md_files[i].relativePath}`);
70
- return true;
71
- }
72
- });
37
+ }
38
+ cols += ')';
39
+ vals += ')';
40
+ queryProps.push(cols);
41
+ queryProps.push(vals);
42
+
43
+ try {
44
+ const stmt = db.prepare(queryProps.join('\n'));
45
+ const info = stmt.run(values);
46
+ console.log(`Inserted index record ${info.lastInsertRowid}: ${book_id} - ${doc_title}`);
47
+ } catch (e) {
48
+ console.log(`Index record creation failed - ${book_id}/${doc_title}: ${e}`);
73
49
  }
74
50
  };
75
51
 
76
- const transform_markdown = function (file_path, md) {
77
- if (fs.existsSync(file_path)) {
78
- // Load markdown file
79
- let md_txt = hdoc.expand_variables(fs.readFileSync(file_path, 'utf8'));
80
-
81
- // Pull in external includes
82
- const includes_processed = hdoc.process_includes(file_path, md_txt);
83
- md_txt = includes_processed.body;
84
- includes_found += includes_processed.found;
85
- includes_success += includes_processed.success;
86
- includes_failed += includes_processed.failed;
87
- if (includes_processed.errors.length > 0) {
88
- for (let i = 0; i < includes_processed.errors.length; i++) {
89
- console.error(includes_processed.errors[i]);
90
- }
91
- }
52
+ exports.transform_html_for_index = function (html_txt) {
53
+ let response = {
54
+ text: '',
55
+ fm_props: {}
56
+ };
92
57
 
93
- // Render markdown into HTML
94
- var html_txt = md.render(md_txt.toString());
58
+ // Get frontmatter properties
59
+ const fm_headers = hdoc.getHTMLFrontmatterHeader(html_txt);
60
+ response.fm_props = fm_headers.fm_properties;
95
61
 
96
- // Save HTML into HTML file
97
- const target_file = file_path.replace(path.extname(file_path), '.html');
98
- fs.writeFile(target_file, html_txt, function writeJSON(err) {
99
- if (err) return console.log('Error writing:', target_file, '\r\n', err);
100
- });
101
- conversion_success++;
102
- return true;
103
- }
104
- conversion_failed++;
105
- console.error('MD file does not exist:', file_path);
106
- return false;
107
- };
62
+ // Convert HTML into plain text
63
+ response.text = html2text.convert(html_txt, {
64
+ wordwrap: null
65
+ });
108
66
 
109
- const dree_options = {
110
- descendants: true,
111
- depth: 10,
112
- extensions: ['md','json'],
113
- hash: false,
114
- normalize: true,
115
- size: false,
116
- sizeInBytes: false,
117
- stat: false,
118
- symbolicLinks: false
67
+ return response;
119
68
  };
120
69
 
121
- exports.run = function(source_path, md) {
122
- /*
123
- STEVE - The purpose of this command is to allow content developers to do local builds of
124
- the SQlite database and index file, for validation before commit and build
125
- */
126
- dree.scan(source_path, dree_options, file_callback);
127
-
128
- if (hdocbook_meta === null) {
129
- console.error('hdocbook.json was not found, or has no content');
130
- process.exit(1);
131
- }
132
- if (md_files.length === 0) {
133
- console.error('No markdown files detected in', source_path);
134
- process.exit(1);
135
- }
136
70
 
137
- db_name = path.join(source_path, db_name);
138
- if (fs.existsSync(db_name)) {
139
- try {
140
- fs.removeSync(db_name);
141
- } catch (e) {
142
- console.error(`Failed to delete existing db file: ${db_name}`);
143
- }
144
- }
145
-
146
- let db = new sqlite3.Database(db_name, (err) => {
147
- if (err) {
148
- console.error('Error creating database:', err);
149
- process.exit(1);
150
- }
151
- console.log(`DB file created successfully: ${db_name}`);
152
- if (create_table(db)) {
153
- if (!hdocbook_meta.tags) hdocbook_meta.tags = [];
154
- build_db(db, md, hdocbook_meta, md_files );
155
- }
156
- });
157
- };
158
71
  })();
package/hdoc-module.js CHANGED
@@ -1,7 +1,8 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- const request = require('sync-request');
4
+ const cheerio = require('cheerio'),
5
+ request = require('sync-request');
5
6
 
6
7
  let includesCache = {};
7
8
 
@@ -143,4 +144,45 @@
143
144
  response.body = body;
144
145
  return response;
145
146
  };
147
+
148
+ // Takes html, returns the first heading detected in the order provided in h_to_search
149
+ // Looks for h1 tags first, then hX, hY, hZ in order
150
+ exports.getFirstHTMLHeading = function (html_body, h_to_search = ['h1']) {
151
+ const $ = cheerio.load(html_body);
152
+ for (let i = 0; i < h_to_search.length; i++) {
153
+ let heading = $(h_to_search[i]).map(function (i) {
154
+ return $(this);
155
+ }).get();
156
+ if (heading.length > 0) {
157
+ return heading[0];
158
+ }
159
+ }
160
+ return false;
161
+ };
162
+
163
+ exports.getHTMLFrontmatterHeader = function(html_body) {
164
+ let response = {
165
+ fm_header: '',
166
+ fm_properties: {}
167
+ };
168
+ const $ = cheerio.load(html_body, { decodeEntities: false });
169
+ if ($._root && $._root.children && $._root.children instanceof Array && $._root.children.length > 0) {
170
+ $._root.children.forEach(function(child){
171
+ if (child.type === 'comment' && child.data && child.data.startsWith('[[FRONTMATTER')) {
172
+ // We have a Frontmatter header - return each property in an array
173
+ const fm_properties = child.data.split(/\r?\n/);
174
+ for (let i = 0; i < fm_properties.length; i++) {
175
+ const property_details = fm_properties[i].split(':', 2);
176
+ if (property_details.length > 1) {
177
+ response.fm_properties[property_details[0].trim().toLowerCase()] = property_details[1].trim();
178
+ }
179
+ }
180
+
181
+ // And return the header as a whole so it can be easily replaced
182
+ response.fm_header = child.data;
183
+ }
184
+ });
185
+ }
186
+ return response;
187
+ };
146
188
  })();
package/hdoc-serve.js CHANGED
@@ -92,7 +92,6 @@
92
92
  }
93
93
  }
94
94
 
95
- const mdfm = require('markdown-it-front-matter');
96
95
  const md = require('markdown-it')({
97
96
  // enable everything
98
97
  html: true,
package/hdoc.js CHANGED
@@ -1,37 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { createCipheriv } = require('crypto');
4
-
5
3
  (function () {
6
4
  'use strict';
7
5
 
8
- const preRun = require('./validateNodeVer.js');
9
- const fs = require('fs');
10
- const path = require('path');
11
- const stream = require('stream');
12
- const yaml = require('js-yaml');
13
- const mdfm = require('markdown-it-front-matter');
14
- const hljs = require('highlight.js'); // https://highlightjs.org/
15
- const md = require('markdown-it')({
16
- // enable everything
17
- html: true,
18
- linkify: true,
19
- typographer: true,
20
- // highlight: function (str, lang) {
21
- // if (lang && hljs.getLanguage(lang)) {
22
- // try {
23
- // return hljs.highlight(lang, str, true).value;
24
- // } catch (__) {}
25
- // }
26
- // }
27
- });
28
-
29
- var frontmatter_content;
30
- md.use(mdfm, function (fm) {
31
- frontmatter_content = fm;
32
- });
6
+ const preRun = require('./validateNodeVer.js'),
7
+ fs = require('fs'),
8
+ path = require('path');
33
9
 
34
10
  const packageFile = path.join(__dirname, 'package.json');
11
+
35
12
  const getHdocPackageVersion = function (packagePath) {
36
13
  if (fs.existsSync(packagePath)) {
37
14
  try {
@@ -44,19 +21,13 @@ const { createCipheriv } = require('crypto');
44
21
  console.error('Package file not found: ', packagePath);
45
22
  }
46
23
  };
47
- const tips = require(__dirname + '/custom_modules/tips.js');
48
-
49
- md.use(tips, {
50
- links: true
51
- });
52
24
 
53
25
  // Default source path to working directory
54
26
  let source_path = process.cwd();
55
27
  let ui_path = path.join(__dirname, 'ui');
56
28
 
57
- let command = '', // Our command to run
58
- verbose = false,
59
- build_index = false;
29
+ let command = '', // Our command to run
30
+ verbose = false;
60
31
 
61
32
  // Get options from command args
62
33
  for (let x = 0; x < process.argv.length; x++) {
@@ -93,22 +64,19 @@ const { createCipheriv } = require('crypto');
93
64
 
94
65
  if (command == 'serve') {
95
66
  const server = require(path.join(__dirname, 'hdoc-serve.js'));
96
- server.run(ui_path, source_path, md);
67
+ server.run(ui_path, source_path);
97
68
  } else if (command == 'build') {
98
69
  const builder = require(path.join(__dirname, 'hdoc-build.js'));
99
- builder.run(source_path, md, verbose);
70
+ builder.run(source_path, verbose);
100
71
  } else if (command == 'stats') {
101
72
  const stats = require(path.join(__dirname, 'hdoc-stats.js'));
102
- stats.run(ui_path, source_path, md, verbose);
73
+ stats.run(ui_path, source_path, verbose);
103
74
  } else if (command == 'init') {
104
75
  const init = require(path.join(__dirname, 'hdoc-init.js'));
105
- init.run(__dirname, source_path, md);
76
+ init.run(__dirname, source_path);
106
77
  } else if (command == 'help') {
107
78
  const help = require(path.join(__dirname, 'hdoc-help.js'));
108
79
  help.run();
109
- } else if (command == 'buildindex') {
110
- const build_db = require(path.join(__dirname, 'hdoc-db.js'));
111
- build_db.run(source_path, md);
112
80
  } else {
113
81
  console.log('Unknown command:', command, '\r\n');
114
82
  console.log('Run hdoc help for information regarding this tool.\r\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hdoc-tools",
3
- "version": "0.7.26",
3
+ "version": "0.8.0",
4
4
  "description": "Hornbill HDocBook Development Support Tool",
5
5
  "main": "hdoc.js",
6
6
  "bin": {
@@ -28,6 +28,7 @@
28
28
  "author": "Hornbill Technologies Ltd",
29
29
  "license": "ISC",
30
30
  "dependencies": {
31
+ "better-sqlite3": "^8.0.1",
31
32
  "body-parser": "^1.20.1",
32
33
  "cheerio": "^1.0.0-rc.12",
33
34
  "cookie-parser": "^1.4.6",
@@ -42,7 +43,6 @@
42
43
  "markdown-it-front-matter": "^0.2.3",
43
44
  "multer": "^1.4.5-lts.1",
44
45
  "prompt": "^1.3.0",
45
- "sqlite3": "^5.1.4",
46
46
  "stream": "0.0.2",
47
47
  "sync-request": "^6.1.0",
48
48
  "words-count": "^2.0.2",