hdoc-tools 0.45.1 → 0.47.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/hdoc-build-pdf.js +7 -8
- package/hdoc-build.js +440 -626
- package/hdoc-create.js +1 -1
- package/hdoc-db.js +2 -24
- package/hdoc-init.js +34 -14
- package/hdoc-module.js +292 -79
- package/hdoc-serve.js +4 -6
- package/hdoc-stats.js +7 -12
- package/hdoc-validate.js +4 -6
- package/hdoc.js +3 -3
- package/npm-shrinkwrap.json +6921 -0
- package/package.json +12 -25
package/hdoc-build.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
const crypto = require("node:crypto");
|
|
3
|
-
const
|
|
4
|
-
const fs = require("fs-extra");
|
|
3
|
+
const fs = require("node:fs");
|
|
5
4
|
const mdfm = require("markdown-it-front-matter");
|
|
6
5
|
const path = require("node:path");
|
|
7
6
|
const puppeteer = require("puppeteer");
|
|
@@ -11,10 +10,8 @@
|
|
|
11
10
|
const hdoc_build_pdf = require(path.join(__dirname, "hdoc-build-pdf.js"));
|
|
12
11
|
const hdoc_index = require(path.join(__dirname, "hdoc-db.js"));
|
|
13
12
|
const archiver = require("archiver");
|
|
14
|
-
const xmlFormat = require("xml-formatter");
|
|
15
13
|
|
|
16
14
|
const { execSync } = require("child_process");
|
|
17
|
-
const tmp = require("tmp");
|
|
18
15
|
|
|
19
16
|
const h_tags_to_search = ["h1", "h2", "h3"];
|
|
20
17
|
const image_extensions = ["png", "svg", "jpg"];
|
|
@@ -101,6 +98,71 @@
|
|
|
101
98
|
let mermaid_images_path = "";
|
|
102
99
|
let verbose = false;
|
|
103
100
|
|
|
101
|
+
// Mutable references updated immediately before each md.render() call so
|
|
102
|
+
// the highlight and frontmatter plugin callbacks can identify the current
|
|
103
|
+
// file. Safe because md.render() is synchronous — nothing can interleave
|
|
104
|
+
// between setting these and reading them back within a single render.
|
|
105
|
+
let currentMdFilePath = "";
|
|
106
|
+
let currentFrontmatter = "";
|
|
107
|
+
|
|
108
|
+
// Shared markdown-it instance — created once, reused for every file.
|
|
109
|
+
// Previously recreated per file because the highlight callback closed over
|
|
110
|
+
// the file_path parameter; it now reads currentMdFilePath instead.
|
|
111
|
+
// mdfm and tips are registered once here rather than once per file.
|
|
112
|
+
const md = require("markdown-it")({
|
|
113
|
+
html: true,
|
|
114
|
+
linkify: true,
|
|
115
|
+
typographer: true,
|
|
116
|
+
highlight: function (str, lang) {
|
|
117
|
+
if (lang === "mermaid" && process.env.GITHUB_ACTIONS !== 'true') {
|
|
118
|
+
try {
|
|
119
|
+
const tmpInput = hdoc.tmp_file_sync({ postfix: ".mmd" });
|
|
120
|
+
const outputFileName = `mermaid-${crypto.createHash("sha256").update(str).digest("hex").slice(0, 16)}.svg`;
|
|
121
|
+
const outputPath = path.join(mermaid_images_path, outputFileName);
|
|
122
|
+
const outputLink = `/_books/${doc_id}/mermaid-images/${outputFileName}`;
|
|
123
|
+
|
|
124
|
+
if (!str.startsWith('---')) {
|
|
125
|
+
str = '---\n' + fs.readFileSync(mermaid_theme_path, {encoding: 'utf-8'}) + `\n---\n${str}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fs.writeFileSync(tmpInput.name, str);
|
|
129
|
+
let cmd = `${__dirname}/node_modules/.bin/mmdc`;
|
|
130
|
+
|
|
131
|
+
if (process.platform === "win32") {
|
|
132
|
+
cmd = `"${cmd}.cmd"`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
cmd = `${cmd} -i "${tmpInput.name}" -o "${outputPath}" --backgroundColor transparent --puppeteerConfigFile ${mermaid_puppeteer_config_path}`;
|
|
136
|
+
console.log(`Generating Mermaid SVG found in ${currentMdFilePath.relativePath} - ${outputPath}`);
|
|
137
|
+
execSync(cmd);
|
|
138
|
+
|
|
139
|
+
if (!fs.existsSync(outputPath)) {
|
|
140
|
+
throw new Error("mmdc did not generate output");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
tmpInput.removeCallback();
|
|
144
|
+
|
|
145
|
+
return `<img class="mermaid-diagram" src="${outputLink}" alt="Mermaid Diagram">`;
|
|
146
|
+
} catch (err) {
|
|
147
|
+
mermaid_failures.push({path: currentMdFilePath.relativePath, error: err.message});
|
|
148
|
+
return ``;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
md.linkify.set({
|
|
154
|
+
fuzzyEmail: false,
|
|
155
|
+
fuzzyLink: false,
|
|
156
|
+
fuzzyIP: false,
|
|
157
|
+
});
|
|
158
|
+
// mdfm callback writes to currentFrontmatter, which is reset to "" before
|
|
159
|
+
// each render so every file gets a clean slate.
|
|
160
|
+
md.use(mdfm, (fm) => {
|
|
161
|
+
currentFrontmatter = fm;
|
|
162
|
+
});
|
|
163
|
+
const tips = require(`${__dirname}/custom_modules/tips.js`);
|
|
164
|
+
md.use(tips, { links: true });
|
|
165
|
+
|
|
104
166
|
const pdf_path_excluded = (relative_path) => {
|
|
105
167
|
if (
|
|
106
168
|
!hdocbook_project.pdfGeneration ||
|
|
@@ -128,62 +190,202 @@
|
|
|
128
190
|
return false;
|
|
129
191
|
};
|
|
130
192
|
|
|
131
|
-
|
|
132
|
-
|
|
193
|
+
// Processes a single documentation file (markdown or static HTML), generates
|
|
194
|
+
// frontmatter headers, retrieves contributor data from GitHub, optionally
|
|
195
|
+
// generates a PDF, and writes the final HTML to disk ready for indexing.
|
|
196
|
+
// Markdown files are rendered to HTML first; static HTML files are processed
|
|
197
|
+
// in place. Both paths share the same post-processing pipeline.
|
|
198
|
+
const transform_file = async (file_path) => {
|
|
199
|
+
const is_markdown = path.extname(file_path.path) === '.md';
|
|
200
|
+
if (is_markdown) conversion_attempted++;
|
|
201
|
+
|
|
202
|
+
if (!fs.existsSync(file_path.path)) {
|
|
203
|
+
if (is_markdown) {
|
|
204
|
+
conversion_failed++;
|
|
205
|
+
console.error("MD file does not exist:", file_path.path);
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let html_txt;
|
|
212
|
+
const fm_headers = [];
|
|
213
|
+
let doc_title = "";
|
|
214
|
+
let doc_type = "Article";
|
|
215
|
+
let fm_status = false;
|
|
216
|
+
// Used only for static HTML: tracks whether the source file already had a
|
|
217
|
+
// frontmatter comment block that needs replacing in the output.
|
|
218
|
+
let existing_fm_headers = false;
|
|
219
|
+
let html_fm = null;
|
|
220
|
+
|
|
221
|
+
if (is_markdown) {
|
|
222
|
+
// Load markdown file
|
|
223
|
+
let md_txt = hdoc.expand_variables(
|
|
224
|
+
fs.readFileSync(file_path.path, "utf8"),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Pull in external includes
|
|
228
|
+
const includes_processed = await hdoc.process_includes(
|
|
229
|
+
file_path.path,
|
|
230
|
+
md_txt,
|
|
231
|
+
global_source_path,
|
|
232
|
+
);
|
|
233
|
+
md_txt = includes_processed.body.toString();
|
|
234
|
+
includes_found += includes_processed.found;
|
|
235
|
+
includes_success += includes_processed.success;
|
|
236
|
+
includes_failed += includes_processed.failed;
|
|
237
|
+
if (includes_processed.errors.length > 0) {
|
|
238
|
+
for (let i = 0; i < includes_processed.errors.length; i++) {
|
|
239
|
+
console.error(includes_processed.errors[i]);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Point the shared md instance at the current file before rendering
|
|
244
|
+
// so the highlight callback logs and reports against the right path.
|
|
245
|
+
currentMdFilePath = file_path;
|
|
246
|
+
currentFrontmatter = "";
|
|
247
|
+
|
|
248
|
+
// Tidy up ```json and ```xml code tags
|
|
249
|
+
if (md_txt.includes("```json") || md_txt.includes("```xml"))
|
|
250
|
+
md_txt = tidy_code_tags(md_txt, file_path.relativePath);
|
|
251
|
+
|
|
252
|
+
// Render markdown into HTML
|
|
253
|
+
html_txt = md.render(md_txt);
|
|
254
|
+
|
|
255
|
+
// Parse frontmatter properties from the YAML block
|
|
256
|
+
let fm_contains_title = false;
|
|
257
|
+
let fm_contains_reading_time = false;
|
|
258
|
+
let fm_contains_description = false;
|
|
259
|
+
|
|
260
|
+
const fm_content = currentFrontmatter.split(/\r?\n/);
|
|
261
|
+
if (fm_content.length >= 0) {
|
|
262
|
+
for (fm_prop of fm_content) {
|
|
263
|
+
const fm_id = fm_prop.slice(0, fm_prop.indexOf(":"));
|
|
264
|
+
const fm_val = fm_prop.slice(fm_prop.indexOf(":") + 1);
|
|
265
|
+
|
|
266
|
+
if (
|
|
267
|
+
fm_id &&
|
|
268
|
+
fm_id.trim().length > 0 &&
|
|
269
|
+
fm_val &&
|
|
270
|
+
fm_val.trim().length > 0
|
|
271
|
+
) {
|
|
272
|
+
fm_headers.push({
|
|
273
|
+
id: fm_id.trim(),
|
|
274
|
+
value: fm_val.trim(),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (fm_id.trim() === "title") {
|
|
278
|
+
fm_contains_title = true;
|
|
279
|
+
doc_title = fm_val.trim();
|
|
280
|
+
}
|
|
281
|
+
if (fm_id.trim() === "status") {
|
|
282
|
+
fm_status = true;
|
|
283
|
+
}
|
|
284
|
+
if (fm_id.trim() === "type") {
|
|
285
|
+
doc_type = fm_val.trim();
|
|
286
|
+
}
|
|
287
|
+
if (fm_id.trim() === "reading-time") {
|
|
288
|
+
book_read_time += Number.parseInt(fm_val.trim(), 10);
|
|
289
|
+
fm_contains_reading_time = true;
|
|
290
|
+
}
|
|
291
|
+
if (fm_id.trim() === "description") {
|
|
292
|
+
fm_contains_description = true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Title from heading if not in frontmatter
|
|
299
|
+
if (!fm_contains_title) {
|
|
300
|
+
const html_heading = hdoc.getFirstHTMLHeading(
|
|
301
|
+
html_txt,
|
|
302
|
+
h_tags_to_search,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
if (html_heading?.[0]?.children?.[0]?.data) {
|
|
306
|
+
fm_headers.push({
|
|
307
|
+
id: "title",
|
|
308
|
+
value: html_heading[0].children[0].data.trim(),
|
|
309
|
+
});
|
|
310
|
+
doc_title = html_heading[0].children[0].data.trim();
|
|
311
|
+
} else if (
|
|
312
|
+
file_path.name !== "description_ext.md" &&
|
|
313
|
+
file_path.name !== "article_ext.md" &&
|
|
314
|
+
file_path.name !== "internal_ext.md"
|
|
315
|
+
) {
|
|
316
|
+
console.info(
|
|
317
|
+
`[WARNING] No frontmatter title property, or ${h_tags_to_search.join(", ")} tags detected in ${file_path.path}`,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Description from first paragraph if not in frontmatter
|
|
323
|
+
if (!fm_contains_description) {
|
|
324
|
+
const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ["p"]);
|
|
325
|
+
if (html_p_tag?.[0]?.children?.[0]?.data) {
|
|
326
|
+
fm_headers.push({
|
|
327
|
+
id: "description",
|
|
328
|
+
value:
|
|
329
|
+
`${doc_title}: ${html_p_tag[0].children[0].data.split(".")[0]}.`.trim(),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Reading time from content if not in frontmatter
|
|
335
|
+
if (!fm_contains_reading_time) {
|
|
336
|
+
const read_time_mins = hdoc.get_html_read_time(html_txt);
|
|
337
|
+
book_read_time += read_time_mins;
|
|
338
|
+
fm_headers.push({
|
|
339
|
+
id: "reading-time",
|
|
340
|
+
value: read_time_mins,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
133
344
|
// Load HTML file
|
|
134
|
-
|
|
345
|
+
html_txt = fs.readFileSync(file_path.path, "utf8");
|
|
135
346
|
html_txt = html_txt.replace(/\r/gm, ""); // Remove CR's so we're just dealing with newlines
|
|
136
347
|
|
|
137
|
-
const fm_headers = [];
|
|
138
|
-
let existing_fm_headers = false;
|
|
139
|
-
let doc_type = "Article";
|
|
140
|
-
let doc_title = "";
|
|
141
|
-
|
|
142
348
|
// Check if we have a frontmatter comment
|
|
143
|
-
|
|
349
|
+
html_fm = hdoc.getHTMLFrontmatterHeader(html_txt);
|
|
144
350
|
|
|
145
|
-
|
|
146
|
-
if (rel_path.endsWith('/index')) rel_path = rel_path.substring(0, rel_path.length - 6);
|
|
147
|
-
const is_draft = draft_links.indexOf(rel_path) !== -1;
|
|
148
|
-
|
|
149
|
-
let fm_status = false;
|
|
150
|
-
if (Object.keys(fm_header.fm_properties).length > 0) {
|
|
351
|
+
if (Object.keys(html_fm.fm_properties).length > 0) {
|
|
151
352
|
existing_fm_headers = true;
|
|
152
353
|
|
|
153
|
-
if (
|
|
354
|
+
if (html_fm.fm_properties?.status) {
|
|
154
355
|
fm_status = true;
|
|
155
356
|
}
|
|
357
|
+
|
|
156
358
|
// We have some frontmatter headers, check if title is one of them
|
|
157
359
|
let fm_title_found = false;
|
|
158
360
|
if (
|
|
159
|
-
|
|
160
|
-
|
|
361
|
+
html_fm.fm_properties &&
|
|
362
|
+
html_fm.fm_properties.title !== undefined
|
|
161
363
|
) {
|
|
162
364
|
// We have a title - but does the title have a value
|
|
163
|
-
if (
|
|
164
|
-
// No value - remove title from the properties map
|
|
165
|
-
|
|
365
|
+
if (html_fm.fm_properties.title === "") {
|
|
366
|
+
// No value - remove title from the properties map so we don't end up with 2 title properties, one empty and one with a value
|
|
367
|
+
html_fm.fm_properties.title = undefined;
|
|
166
368
|
} else {
|
|
167
369
|
// We have a value for the title property
|
|
168
370
|
fm_title_found = true;
|
|
169
|
-
doc_title =
|
|
371
|
+
doc_title = html_fm.fm_properties.title.trim();
|
|
170
372
|
}
|
|
171
373
|
}
|
|
172
374
|
|
|
173
375
|
// Is reading-time in the fm headers?
|
|
174
|
-
if (
|
|
376
|
+
if (html_fm.fm_properties["reading-time"] === undefined) {
|
|
175
377
|
const read_time_mins = hdoc.get_html_read_time(html_txt);
|
|
176
378
|
book_read_time += read_time_mins;
|
|
177
|
-
|
|
379
|
+
html_fm.fm_properties["reading-time"] = read_time_mins;
|
|
178
380
|
}
|
|
179
381
|
|
|
180
|
-
for (const key in
|
|
181
|
-
if (Object.hasOwn(
|
|
182
|
-
if (key === "type") doc_type =
|
|
382
|
+
for (const key in html_fm.fm_properties) {
|
|
383
|
+
if (Object.hasOwn(html_fm.fm_properties, key)) {
|
|
384
|
+
if (key === "type") doc_type = html_fm.fm_properties[key];
|
|
183
385
|
else {
|
|
184
386
|
fm_headers.push({
|
|
185
387
|
id: key,
|
|
186
|
-
value:
|
|
388
|
+
value: html_fm.fm_properties[key],
|
|
187
389
|
});
|
|
188
390
|
}
|
|
189
391
|
}
|
|
@@ -192,10 +394,10 @@
|
|
|
192
394
|
if (
|
|
193
395
|
!fm_title_found &&
|
|
194
396
|
file_path.name !== "description_ext.md" &&
|
|
195
|
-
file_path.name !== "article_ext.md"
|
|
397
|
+
file_path.name !== "article_ext.md" &&
|
|
398
|
+
file_path.name !== "internal_ext.md"
|
|
196
399
|
) {
|
|
197
|
-
// No frontmatter title found in properties
|
|
198
|
-
// Go get title from h tags in html
|
|
400
|
+
// No frontmatter title found in properties - go get title from h tags in html
|
|
199
401
|
const html_heading = hdoc.getFirstHTMLHeading(
|
|
200
402
|
html_txt,
|
|
201
403
|
h_tags_to_search,
|
|
@@ -220,10 +422,10 @@
|
|
|
220
422
|
|
|
221
423
|
// Do we have a description header?
|
|
222
424
|
if (
|
|
223
|
-
|
|
224
|
-
|
|
425
|
+
html_fm.fm_properties &&
|
|
426
|
+
html_fm.fm_properties.description !== undefined
|
|
225
427
|
) {
|
|
226
|
-
if (
|
|
428
|
+
if (html_fm.fm_properties.description === "") {
|
|
227
429
|
const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ["p"]);
|
|
228
430
|
if (html_p_tag?.[0]?.children?.[0]?.data) {
|
|
229
431
|
fm_headers.push({
|
|
@@ -235,7 +437,7 @@
|
|
|
235
437
|
} else {
|
|
236
438
|
fm_headers.push({
|
|
237
439
|
id: "description",
|
|
238
|
-
value:
|
|
440
|
+
value: html_fm.fm_properties.description.trim(),
|
|
239
441
|
});
|
|
240
442
|
}
|
|
241
443
|
} else {
|
|
@@ -254,20 +456,20 @@
|
|
|
254
456
|
html_txt,
|
|
255
457
|
h_tags_to_search,
|
|
256
458
|
);
|
|
257
|
-
let
|
|
459
|
+
let doc_title_local = "";
|
|
258
460
|
// Add the title
|
|
259
461
|
if (html_heading?.[0]?.children?.[0]?.data) {
|
|
260
|
-
// We've found a heading tag, add that as a title to the frontmatter content
|
|
261
462
|
fm_headers.push({
|
|
262
463
|
id: "title",
|
|
263
464
|
value: html_heading[0].children[0].data,
|
|
264
465
|
});
|
|
265
|
-
|
|
466
|
+
doc_title_local = html_heading[0].children[0].data;
|
|
467
|
+
doc_title = doc_title_local;
|
|
266
468
|
} else if (
|
|
267
469
|
file_path.name !== "description_ext.md" &&
|
|
268
|
-
file_path.name !== "article_ext.md"
|
|
470
|
+
file_path.name !== "article_ext.md" &&
|
|
471
|
+
file_path.name !== "internal_ext.md"
|
|
269
472
|
) {
|
|
270
|
-
// No header tag, no frontmatter title, output a warning
|
|
271
473
|
console.info(
|
|
272
474
|
`[WARNING] No frontmatter title property, or ${h_tags_to_search.join(
|
|
273
475
|
", ",
|
|
@@ -288,633 +490,244 @@
|
|
|
288
490
|
fm_headers.push({
|
|
289
491
|
id: "description",
|
|
290
492
|
value:
|
|
291
|
-
`${
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Add doc type
|
|
297
|
-
fm_headers.push({
|
|
298
|
-
id: "type",
|
|
299
|
-
value: doc_type,
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// Add status if we don't have one defined and the nav tree defines the document as draft
|
|
303
|
-
if (!fm_status && is_draft) {
|
|
304
|
-
fm_headers.push({
|
|
305
|
-
id: "status",
|
|
306
|
-
value: "draft"
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const metadata = {};
|
|
311
|
-
|
|
312
|
-
// Remove the first <h1>title</h1> from the HTML as we'll add that in the document header
|
|
313
|
-
let html_h1 = h1_pattern.exec(html_txt);
|
|
314
|
-
if (html_h1?.[0])
|
|
315
|
-
html_h1 = html_h1[0].replace(/(<h1.*?>)/, "").replace(/(<\/h1>)/, "");
|
|
316
|
-
|
|
317
|
-
html_txt = html_txt.replace(h1_pattern, "");
|
|
318
|
-
|
|
319
|
-
// Get contributor data from Github, if exists
|
|
320
|
-
let contribs = [];
|
|
321
|
-
let last_commit = null;
|
|
322
|
-
if (
|
|
323
|
-
hdocbook_config.publicSource &&
|
|
324
|
-
hdocbook_config.publicSource !== "" &&
|
|
325
|
-
hdocbook_config.publicSource.includes("github.com/Hornbill-Docs")
|
|
326
|
-
) {
|
|
327
|
-
const github_paths = hdoc.get_github_api_path(
|
|
328
|
-
hdocbook_config.publicSource,
|
|
329
|
-
file_path.relativePath,
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
const contributors = await hdoc.get_github_contributors(
|
|
333
|
-
github_paths.api_path,
|
|
334
|
-
git_token,
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
if (!contributors.success) {
|
|
338
|
-
console.error(
|
|
339
|
-
`Error retrieving contributors from Github: ${contributors.error}`,
|
|
340
|
-
);
|
|
341
|
-
} else {
|
|
342
|
-
last_commit = contributors.last_commit_date;
|
|
343
|
-
metadata.last_commit = contributors.last_commit_date;
|
|
344
|
-
metadata.contributor_count = contributors.contributor_count;
|
|
345
|
-
metadata.edit_url = github_paths.edit_path;
|
|
346
|
-
contribs = contributors.contributors;
|
|
347
|
-
contributors.editPath = github_paths.edit_path;
|
|
348
|
-
fm_headers.push({
|
|
349
|
-
id: "contributor-count",
|
|
350
|
-
value: contributors.contributor_count,
|
|
493
|
+
`${doc_title_local}: ${html_p_tag[0].children[0].data.split(".")[0]}.`.trim(),
|
|
351
494
|
});
|
|
352
|
-
fm_headers.push({
|
|
353
|
-
id: "last-commit",
|
|
354
|
-
value: contributors.last_commit_date,
|
|
355
|
-
});
|
|
356
|
-
const target_file = file_path.path.replace(
|
|
357
|
-
path.extname(file_path.path),
|
|
358
|
-
"._info.json",
|
|
359
|
-
);
|
|
360
|
-
contributors.success = undefined;
|
|
361
|
-
contributors.error = undefined;
|
|
362
|
-
contributors.editPath = github_paths.edit_path;
|
|
363
|
-
}
|
|
364
|
-
fm_headers.push({
|
|
365
|
-
id: "edit-path",
|
|
366
|
-
value: github_paths.edit_path,
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
|
|
371
|
-
fm_headers.push({
|
|
372
|
-
id: "pdf-path",
|
|
373
|
-
value: file_path.relativePath.replace(
|
|
374
|
-
path.extname(file_path.relativePath),
|
|
375
|
-
".pdf",
|
|
376
|
-
),
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
let fm_header_content = "<!--[[FRONTMATTER\n";
|
|
381
|
-
if (fm_headers.length > 0) {
|
|
382
|
-
for (let i = 0; i < fm_headers.length; i++) {
|
|
383
|
-
fm_header_content += `${fm_headers[i].id}: ${fm_headers[i].value}\n`;
|
|
384
|
-
}
|
|
385
|
-
fm_header_content += "]]-->";
|
|
386
|
-
|
|
387
|
-
if (existing_fm_headers) {
|
|
388
|
-
html_txt = html_txt.replace(`<!--${fm_header.fm_header}-->`, "");
|
|
389
495
|
}
|
|
390
496
|
}
|
|
391
|
-
|
|
392
|
-
let doc_header = "";
|
|
393
|
-
let pdf_header = "";
|
|
394
|
-
const inline_content = file_path.relativePath.startsWith(
|
|
395
|
-
`${hdocbook_config.docId}/_inline/`,
|
|
396
|
-
);
|
|
397
|
-
if (hdocbook_config.publicSource?.includes("github.com/Hornbill-Docs")) {
|
|
398
|
-
// Build doc header from template and frontmatter tags
|
|
399
|
-
if (!inline_content)
|
|
400
|
-
doc_header = process_doc_header(
|
|
401
|
-
fm_headers,
|
|
402
|
-
file_path.relativePath,
|
|
403
|
-
doc_header_template,
|
|
404
|
-
html_h1,
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
|
|
408
|
-
pdf_header = process_doc_header(
|
|
409
|
-
fm_headers,
|
|
410
|
-
file_path.relativePath,
|
|
411
|
-
pdf_header_template,
|
|
412
|
-
html_h1,
|
|
413
|
-
);
|
|
414
|
-
} else {
|
|
415
|
-
if (!inline_content)
|
|
416
|
-
doc_header = process_doc_header(
|
|
417
|
-
fm_headers,
|
|
418
|
-
file_path.relativePath,
|
|
419
|
-
doc_header_template_non_git,
|
|
420
|
-
html_h1,
|
|
421
|
-
);
|
|
422
|
-
|
|
423
|
-
if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
|
|
424
|
-
pdf_header = process_doc_header(
|
|
425
|
-
fm_headers,
|
|
426
|
-
file_path.relativePath,
|
|
427
|
-
pdf_header_template_non_git,
|
|
428
|
-
html_h1,
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
let pdf_size = 0;
|
|
433
|
-
if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
|
|
434
|
-
let pdf_txt = await hdoc_build_pdf.process_images(file_path, html_txt);
|
|
435
|
-
pdf_txt = `${pdf_header}\n${pdf_txt}`;
|
|
436
|
-
|
|
437
|
-
// Generate PDF file from HTML
|
|
438
|
-
const pdf_file_path = file_path.path.replace(
|
|
439
|
-
path.extname(file_path.path),
|
|
440
|
-
".pdf",
|
|
441
|
-
);
|
|
442
|
-
pdf_size = await hdoc_build_pdf.generate_pdf(
|
|
443
|
-
browser,
|
|
444
|
-
pdf_template_path,
|
|
445
|
-
pdf_template,
|
|
446
|
-
hdocbook_config,
|
|
447
|
-
pdf_txt,
|
|
448
|
-
pdf_file_path,
|
|
449
|
-
css_templates,
|
|
450
|
-
verbose,
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
if (pdf_size > 0) pdf_created++;
|
|
454
|
-
|
|
455
|
-
// Wrap h2 and h3 tags, plus content, in id'd divs
|
|
456
|
-
html_txt = hdoc.wrapHContent(html_txt);
|
|
457
|
-
|
|
458
|
-
if (inline_content) html_txt = `${fm_header_content}\n${html_txt}`;
|
|
459
|
-
else html_txt = `${fm_header_content}\n${doc_header}\n${html_txt}`;
|
|
460
|
-
|
|
461
|
-
let relative_path = file_path.relativePath;
|
|
462
|
-
if (
|
|
463
|
-
!bc[relative_path.replace(".html", "")] &&
|
|
464
|
-
bc[relative_path.replace("/index.html", "")]
|
|
465
|
-
) {
|
|
466
|
-
relative_path = relative_path.replace("/index.html", "");
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const index_data = hdoc_index.transform_html_for_index(html_txt);
|
|
470
|
-
|
|
471
|
-
for (const section of index_data.sections) {
|
|
472
|
-
index_records.push({
|
|
473
|
-
relative_path: relative_path,
|
|
474
|
-
index_html: {
|
|
475
|
-
fm_props: index_data.fm_props,
|
|
476
|
-
text: section.text,
|
|
477
|
-
preview: section.preview,
|
|
478
|
-
id: section.id ? section.id : null,
|
|
479
|
-
},
|
|
480
|
-
metadata: metadata,
|
|
481
|
-
contributors: contribs,
|
|
482
|
-
pdf_size: pdf_size,
|
|
483
|
-
md5: file_path.hash,
|
|
484
|
-
lastmod: last_commit !== null ? last_commit : file_path.hb_lastmod,
|
|
485
|
-
inline: inline_content,
|
|
486
|
-
status: index_data.fm_props.status ? index_data.fm_props.status : 'release',
|
|
487
|
-
keywords: index_data.fm_props.keywords ? index_data.fm_props.keywords : '',
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Save HTML into HTML file
|
|
492
|
-
try {
|
|
493
|
-
fs.writeFileSync(file_path.path, html_txt);
|
|
494
|
-
} catch (err) {
|
|
495
|
-
console.error("Error writing:", target_file, "\n", err);
|
|
496
|
-
}
|
|
497
497
|
}
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const transform_markdown_and_save_html = async (file_path) => {
|
|
501
|
-
conversion_attempted++;
|
|
502
|
-
|
|
503
|
-
if (fs.existsSync(file_path.path)) {
|
|
504
|
-
// Load markdown file
|
|
505
|
-
let md_txt = hdoc.expand_variables(
|
|
506
|
-
fs.readFileSync(file_path.path, "utf8"),
|
|
507
|
-
);
|
|
508
498
|
|
|
509
|
-
|
|
510
|
-
const includes_processed = await hdoc.process_includes(
|
|
511
|
-
file_path.path,
|
|
512
|
-
md_txt,
|
|
513
|
-
global_source_path,
|
|
514
|
-
);
|
|
515
|
-
md_txt = includes_processed.body.toString();
|
|
516
|
-
includes_found += includes_processed.found;
|
|
517
|
-
includes_success += includes_processed.success;
|
|
518
|
-
includes_failed += includes_processed.failed;
|
|
519
|
-
if (includes_processed.errors.length > 0) {
|
|
520
|
-
for (let i = 0; i < includes_processed.errors.length; i++) {
|
|
521
|
-
console.error(includes_processed.errors[i]);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
499
|
+
// ── Shared post-processing ─────────────────────────────────────────────
|
|
524
500
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
html: true,
|
|
529
|
-
linkify: true,
|
|
530
|
-
typographer: true,
|
|
531
|
-
highlight: function (str, lang) {
|
|
532
|
-
|
|
533
|
-
if (lang === "mermaid" && process.env.GITHUB_ACTIONS !== 'true') {
|
|
534
|
-
try {
|
|
535
|
-
const tmpInput = tmp.fileSync({ postfix: ".mmd" });
|
|
536
|
-
const outputFileName = `mermaid-${crypto.createHash("sha256").update(str).digest("hex").slice(0, 16)}.svg`;
|
|
537
|
-
const outputPath = path.join(mermaid_images_path, outputFileName);
|
|
538
|
-
const outputLink = `/_books/${doc_id}/mermaid-images/${outputFileName}`
|
|
539
|
-
|
|
540
|
-
if (!str.startsWith('---')) {
|
|
541
|
-
str = '---\n' + fs.readFileSync(mermaid_theme_path, {encoding: 'utf-8'}) + `\n---\n${str}`;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
fs.writeFileSync(tmpInput.name, str);
|
|
545
|
-
let cmd = `${__dirname}/node_modules/.bin/mmdc`;
|
|
546
|
-
|
|
547
|
-
if (process.platform === "win32") {
|
|
548
|
-
cmd = `"${cmd}.cmd"`;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
cmd = `${cmd} -i "${tmpInput.name}" -o "${outputPath}" --backgroundColor transparent --puppeteerConfigFile ${mermaid_puppeteer_config_path}`;
|
|
553
|
-
console.log(`Generating Mermaid SVG found in ${file_path.relativePath} - ${outputPath}`);
|
|
554
|
-
execSync(cmd);
|
|
555
|
-
|
|
556
|
-
if (!fs.existsSync(outputPath)) {
|
|
557
|
-
throw new Error("mmdc did not generate output");
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
tmpInput.removeCallback();
|
|
561
|
-
|
|
562
|
-
return `<img class="mermaid-diagram" src="${outputLink}" alt="Mermaid Diagram">`;
|
|
563
|
-
} catch (err) {
|
|
564
|
-
mermaid_failures.push({path: file_path.relativePath, error: err.message});
|
|
565
|
-
return ``;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
md.linkify.set({
|
|
571
|
-
fuzzyEmail: false,
|
|
572
|
-
fuzzyLink: false,
|
|
573
|
-
fuzzyIP: false,
|
|
574
|
-
});
|
|
501
|
+
let rel_path = file_path.relativePath.replace(path.extname(file_path.relativePath), '');
|
|
502
|
+
if (rel_path.endsWith('/index')) rel_path = rel_path.substring(0, rel_path.length - 6);
|
|
503
|
+
const is_draft = draft_links.indexOf(rel_path) !== -1;
|
|
575
504
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
505
|
+
// Add doc type
|
|
506
|
+
fm_headers.push({
|
|
507
|
+
id: "type",
|
|
508
|
+
value: doc_type,
|
|
509
|
+
});
|
|
581
510
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
511
|
+
// Add status if we don't have one defined and the nav tree defines the document as draft
|
|
512
|
+
if (!fm_status && is_draft) {
|
|
513
|
+
fm_headers.push({
|
|
514
|
+
id: "status",
|
|
515
|
+
value: "draft"
|
|
586
516
|
});
|
|
517
|
+
}
|
|
587
518
|
|
|
588
|
-
|
|
589
|
-
if (md_txt.includes("```json") || md_txt.includes("```xml"))
|
|
590
|
-
md_txt = tidy_code_tags(md_txt, file_path.relativePath);
|
|
591
|
-
|
|
592
|
-
// Render markdown into HTML
|
|
593
|
-
let html_txt = md.render(md_txt);
|
|
594
|
-
|
|
595
|
-
// Prepare frontmatter headers
|
|
596
|
-
const fm_headers = [];
|
|
597
|
-
const fm_content = frontmatter_content.split(/\r?\n/);
|
|
598
|
-
|
|
599
|
-
let fm_contains_title = false;
|
|
600
|
-
let fm_contains_reading_time = false;
|
|
601
|
-
let fm_contains_description = false;
|
|
602
|
-
let doc_title = "";
|
|
603
|
-
let doc_type = "Article";
|
|
604
|
-
|
|
605
|
-
let rel_path = file_path.relativePath.replace(path.extname(file_path.relativePath), '');
|
|
606
|
-
if (rel_path.endsWith('/index')) rel_path = rel_path.substring(0, rel_path.length - 6);
|
|
607
|
-
const is_draft = draft_links.indexOf(rel_path) !== -1;
|
|
608
|
-
let fm_status = false;
|
|
519
|
+
const metadata = {};
|
|
609
520
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
521
|
+
// Remove the first <h1>title</h1> from the HTML as we'll add that in the document header
|
|
522
|
+
let html_h1 = h1_pattern.exec(html_txt);
|
|
523
|
+
if (html_h1?.[0])
|
|
524
|
+
html_h1 = html_h1[0].replace(/(<h1.*?>)/, "").replace(/(<\/h1>)/, "");
|
|
614
525
|
|
|
615
|
-
|
|
616
|
-
fm_id &&
|
|
617
|
-
fm_id.trim().length > 0 &&
|
|
618
|
-
fm_val &&
|
|
619
|
-
fm_val.trim().length > 0
|
|
620
|
-
) {
|
|
621
|
-
fm_headers.push({
|
|
622
|
-
id: fm_id.trim(),
|
|
623
|
-
value: fm_val.trim(),
|
|
624
|
-
});
|
|
526
|
+
html_txt = html_txt.replace(h1_pattern, "");
|
|
625
527
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}
|
|
528
|
+
// Get contributor data from Github, if exists
|
|
529
|
+
let contribs = [];
|
|
530
|
+
let last_commit = null;
|
|
531
|
+
if (
|
|
532
|
+
hdocbook_config.publicSource &&
|
|
533
|
+
hdocbook_config.publicSource !== "" &&
|
|
534
|
+
hdocbook_config.publicSource.includes("github.com/Hornbill-Docs")
|
|
535
|
+
) {
|
|
536
|
+
const github_paths = hdoc.get_github_api_path(
|
|
537
|
+
hdocbook_config.publicSource,
|
|
538
|
+
file_path.relativePath,
|
|
539
|
+
);
|
|
540
|
+
const contributors = await hdoc.get_github_contributors(
|
|
541
|
+
github_paths.api_path,
|
|
542
|
+
git_token,
|
|
543
|
+
is_markdown ? hdocbook_config.publicSource : undefined,
|
|
544
|
+
);
|
|
646
545
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
546
|
+
if (!contributors.success) {
|
|
547
|
+
console.error(
|
|
548
|
+
`Error retrieving contributors from Github: ${contributors.error}`,
|
|
549
|
+
);
|
|
550
|
+
} else {
|
|
551
|
+
last_commit = contributors.last_commit_date;
|
|
552
|
+
metadata.last_commit = contributors.last_commit_date;
|
|
553
|
+
metadata.contributor_count = contributors.contributor_count;
|
|
554
|
+
metadata.edit_url = github_paths.edit_path;
|
|
555
|
+
contribs = contributors.contributors;
|
|
556
|
+
contributors.editPath = github_paths.edit_path;
|
|
652
557
|
|
|
653
|
-
// Add status if we don't have one defined and the nav tree defines the document as draft
|
|
654
|
-
if (!fm_status && is_draft) {
|
|
655
558
|
fm_headers.push({
|
|
656
|
-
id: "
|
|
657
|
-
value:
|
|
559
|
+
id: "contributor-count",
|
|
560
|
+
value: contributors.contributor_count,
|
|
658
561
|
});
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// Does frontmatter tag contain a title property
|
|
662
|
-
if (!fm_contains_title) {
|
|
663
|
-
// Frontmatter tags don't contain a title property - go pull the first one from the html heading tags
|
|
664
|
-
const html_heading = hdoc.getFirstHTMLHeading(
|
|
665
|
-
html_txt,
|
|
666
|
-
h_tags_to_search,
|
|
667
|
-
);
|
|
668
|
-
|
|
669
|
-
if (html_heading?.[0]?.children?.[0]?.data) {
|
|
670
|
-
// We've found a heading tag, add that as a title to the frontmatter content
|
|
671
|
-
fm_headers.push({
|
|
672
|
-
id: "title",
|
|
673
|
-
value: html_heading[0].children[0].data.trim(),
|
|
674
|
-
});
|
|
675
|
-
doc_title = html_heading[0].children[0].data.trim();
|
|
676
|
-
} else if (
|
|
677
|
-
file_path.name !== "description_ext.md" &&
|
|
678
|
-
file_path.name !== "article_ext.md"
|
|
679
|
-
) {
|
|
680
|
-
// No header tag, no frontmatter title, output a warning
|
|
681
|
-
console.info(
|
|
682
|
-
`[WARNING] No frontmatter title property, or h1, h2 or h3 header tags detected in ${file_path.path}`,
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Does frontmatter contain a description header, generate one if not
|
|
688
|
-
if (!fm_contains_description) {
|
|
689
|
-
const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ["p"]);
|
|
690
|
-
if (html_p_tag?.[0]?.children?.[0]?.data) {
|
|
691
|
-
fm_headers.push({
|
|
692
|
-
id: "description",
|
|
693
|
-
value:
|
|
694
|
-
`${doc_title}: ${html_p_tag[0].children[0].data.split(".")[0]}.`.trim(),
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Does frontmatter tag contain a reading-time property
|
|
700
|
-
if (!fm_contains_reading_time) {
|
|
701
|
-
const read_time_mins = hdoc.get_html_read_time(html_txt);
|
|
702
|
-
book_read_time += read_time_mins;
|
|
703
562
|
fm_headers.push({
|
|
704
|
-
id: "
|
|
705
|
-
value:
|
|
563
|
+
id: "last-commit",
|
|
564
|
+
value: contributors.last_commit_date,
|
|
706
565
|
});
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
// Remove the first <h1>title</h1> from the HTML as we'll add that in the document header
|
|
711
|
-
let html_h1 = h1_pattern.exec(html_txt);
|
|
712
|
-
if (html_h1?.[0])
|
|
713
|
-
html_h1 = html_h1[0].replace(/(<h1.*?>)/, "").replace(/(<\/h1>)/, "");
|
|
714
|
-
|
|
715
|
-
html_txt = html_txt.replace(h1_pattern, "");
|
|
716
|
-
|
|
717
|
-
// Get contributor data from Github, if exists
|
|
718
|
-
let contribs = [];
|
|
719
|
-
let last_commit = null;
|
|
720
|
-
if (
|
|
721
|
-
hdocbook_config.publicSource &&
|
|
722
|
-
hdocbook_config.publicSource !== "" &&
|
|
723
|
-
hdocbook_config.publicSource.includes("github.com/Hornbill-Docs")
|
|
724
|
-
) {
|
|
725
|
-
const github_paths = hdoc.get_github_api_path(
|
|
726
|
-
hdocbook_config.publicSource,
|
|
727
|
-
file_path.relativePath,
|
|
728
|
-
);
|
|
729
|
-
const contributors = await hdoc.get_github_contributors(
|
|
730
|
-
github_paths.api_path,
|
|
731
|
-
git_token,
|
|
732
|
-
hdocbook_config.publicSource,
|
|
733
|
-
);
|
|
566
|
+
contributors.success = undefined;
|
|
567
|
+
contributors.error = undefined;
|
|
568
|
+
contributors.editPath = github_paths.edit_path;
|
|
734
569
|
|
|
735
|
-
if (
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
);
|
|
739
|
-
} else {
|
|
740
|
-
last_commit = contributors.last_commit_date;
|
|
741
|
-
metadata.last_commit = contributors.last_commit_date;
|
|
742
|
-
metadata.contributor_count = contributors.contributor_count;
|
|
743
|
-
metadata.edit_url = github_paths.edit_path;
|
|
744
|
-
contribs = contributors.contributors;
|
|
745
|
-
contributors.editPath = github_paths.edit_path;
|
|
746
|
-
|
|
747
|
-
fm_headers.push({
|
|
748
|
-
id: "contributor-count",
|
|
749
|
-
value: contributors.contributor_count,
|
|
750
|
-
});
|
|
751
|
-
fm_headers.push({
|
|
752
|
-
id: "last-commit",
|
|
753
|
-
value: contributors.last_commit_date,
|
|
754
|
-
});
|
|
755
|
-
const target_file = file_path.path.replace(
|
|
570
|
+
if (is_markdown) {
|
|
571
|
+
// Write contributor metadata to a sidecar JSON file for later use
|
|
572
|
+
const target_info_file = file_path.path.replace(
|
|
756
573
|
path.extname(file_path.path),
|
|
757
574
|
"._info.json",
|
|
758
575
|
);
|
|
759
|
-
contributors.success = undefined;
|
|
760
|
-
contributors.error = undefined;
|
|
761
|
-
contributors.editPath = github_paths.edit_path;
|
|
762
576
|
try {
|
|
763
577
|
fs.writeFileSync(
|
|
764
|
-
|
|
578
|
+
target_info_file,
|
|
765
579
|
JSON.stringify(contributors, null, 2),
|
|
766
580
|
);
|
|
767
581
|
} catch (err) {
|
|
768
|
-
console.error("Error writing:",
|
|
582
|
+
console.error("Error writing:", target_info_file, "\n", err);
|
|
769
583
|
}
|
|
770
584
|
}
|
|
771
|
-
fm_headers.push({
|
|
772
|
-
id: "edit-path",
|
|
773
|
-
value: github_paths.edit_path,
|
|
774
|
-
});
|
|
775
585
|
}
|
|
586
|
+
fm_headers.push({
|
|
587
|
+
id: "edit-path",
|
|
588
|
+
value: github_paths.edit_path,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
776
591
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
592
|
+
if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
|
|
593
|
+
fm_headers.push({
|
|
594
|
+
id: "pdf-path",
|
|
595
|
+
value: file_path.relativePath.replace(
|
|
596
|
+
path.extname(file_path.relativePath),
|
|
597
|
+
".pdf",
|
|
598
|
+
),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
786
601
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}
|
|
602
|
+
// Build frontmatter comment block
|
|
603
|
+
let fm_header_str = "<!--[[FRONTMATTER\n";
|
|
604
|
+
if (fm_headers.length > 0) {
|
|
605
|
+
for (let i = 0; i < fm_headers.length; i++) {
|
|
606
|
+
fm_header_str += `${fm_headers[i].id}: ${fm_headers[i].value}\n`;
|
|
793
607
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
let pdf_header = "";
|
|
798
|
-
const inline_content = file_path.relativePath.startsWith(
|
|
799
|
-
`${hdocbook_config.docId}/_inline/`,
|
|
800
|
-
);
|
|
801
|
-
if (hdocbook_config.publicSource?.includes("github.com/Hornbill-Docs")) {
|
|
802
|
-
// Build doc header from template and frontmatter tags
|
|
803
|
-
if (!inline_content)
|
|
804
|
-
doc_header = process_doc_header(
|
|
805
|
-
fm_headers,
|
|
806
|
-
file_path.relativePath,
|
|
807
|
-
doc_header_template,
|
|
808
|
-
html_h1,
|
|
809
|
-
);
|
|
810
|
-
|
|
811
|
-
if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
|
|
812
|
-
pdf_header = process_doc_header(
|
|
813
|
-
fm_headers,
|
|
814
|
-
file_path.relativePath,
|
|
815
|
-
pdf_header_template,
|
|
816
|
-
html_h1,
|
|
817
|
-
);
|
|
818
|
-
} else {
|
|
819
|
-
// Build doc header from template and frontmatter tags
|
|
820
|
-
if (!inline_content)
|
|
821
|
-
doc_header = process_doc_header(
|
|
822
|
-
fm_headers,
|
|
823
|
-
file_path.relativePath,
|
|
824
|
-
doc_header_template_non_git,
|
|
825
|
-
html_h1,
|
|
826
|
-
);
|
|
827
|
-
|
|
828
|
-
if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
|
|
829
|
-
pdf_header = process_doc_header(
|
|
830
|
-
fm_headers,
|
|
831
|
-
file_path.relativePath,
|
|
832
|
-
pdf_header_template_non_git,
|
|
833
|
-
html_h1,
|
|
834
|
-
);
|
|
608
|
+
// For static HTML, strip the original frontmatter comment before prepending the new one
|
|
609
|
+
if (!is_markdown && existing_fm_headers) {
|
|
610
|
+
html_txt = html_txt.replace(`<!--${html_fm.fm_header}-->`, "");
|
|
835
611
|
}
|
|
612
|
+
}
|
|
613
|
+
fm_header_str += "]]-->";
|
|
836
614
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
615
|
+
let doc_header = "";
|
|
616
|
+
let pdf_header = "";
|
|
617
|
+
const inline_content = file_path.relativePath.startsWith(
|
|
618
|
+
`${hdocbook_config.docId}/_inline/`,
|
|
619
|
+
);
|
|
620
|
+
if (hdocbook_config.publicSource?.includes("github.com/Hornbill-Docs")) {
|
|
621
|
+
// Build doc header from template and frontmatter tags
|
|
622
|
+
if (!inline_content)
|
|
623
|
+
doc_header = process_doc_header(
|
|
624
|
+
fm_headers,
|
|
625
|
+
file_path.relativePath,
|
|
626
|
+
doc_header_template,
|
|
627
|
+
html_h1,
|
|
628
|
+
);
|
|
841
629
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
630
|
+
if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
|
|
631
|
+
pdf_header = process_doc_header(
|
|
632
|
+
fm_headers,
|
|
633
|
+
file_path.relativePath,
|
|
634
|
+
pdf_header_template,
|
|
635
|
+
html_h1,
|
|
846
636
|
);
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
css_templates,
|
|
855
|
-
verbose,
|
|
637
|
+
} else {
|
|
638
|
+
if (!inline_content)
|
|
639
|
+
doc_header = process_doc_header(
|
|
640
|
+
fm_headers,
|
|
641
|
+
file_path.relativePath,
|
|
642
|
+
doc_header_template_non_git,
|
|
643
|
+
html_h1,
|
|
856
644
|
);
|
|
857
|
-
}
|
|
858
|
-
if (pdf_size > 0) pdf_created++;
|
|
859
645
|
|
|
860
|
-
|
|
861
|
-
|
|
646
|
+
if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
|
|
647
|
+
pdf_header = process_doc_header(
|
|
648
|
+
fm_headers,
|
|
649
|
+
file_path.relativePath,
|
|
650
|
+
pdf_header_template_non_git,
|
|
651
|
+
html_h1,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
862
654
|
|
|
863
|
-
|
|
864
|
-
|
|
655
|
+
let pdf_size = 0;
|
|
656
|
+
if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
|
|
657
|
+
let pdf_txt = await hdoc_build_pdf.process_images(file_path, html_txt);
|
|
658
|
+
pdf_txt = `${pdf_header}\n${pdf_txt}`;
|
|
865
659
|
|
|
866
|
-
//
|
|
867
|
-
const
|
|
660
|
+
// Generate PDF file from HTML
|
|
661
|
+
const pdf_file_path = file_path.path.replace(
|
|
868
662
|
path.extname(file_path.path),
|
|
869
|
-
".
|
|
663
|
+
".pdf",
|
|
870
664
|
);
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
665
|
+
pdf_size = await hdoc_build_pdf.generate_pdf(
|
|
666
|
+
browser,
|
|
667
|
+
pdf_template_path,
|
|
668
|
+
pdf_template,
|
|
669
|
+
hdocbook_config,
|
|
670
|
+
pdf_txt,
|
|
671
|
+
pdf_file_path,
|
|
672
|
+
css_templates,
|
|
673
|
+
verbose,
|
|
874
674
|
);
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
} catch (err) {
|
|
878
|
-
console.error("Error writing:", target_file, "\n", err);
|
|
879
|
-
}
|
|
675
|
+
}
|
|
676
|
+
if (pdf_size > 0) pdf_created++;
|
|
880
677
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
bc[relative_path.replace("/index.html", "")]
|
|
884
|
-
) {
|
|
885
|
-
relative_path = relative_path.replace("/index.html", "");
|
|
886
|
-
}
|
|
678
|
+
// Wrap h2 and h3 tags, plus content, in id'd divs
|
|
679
|
+
html_txt = hdoc.wrapHContent(html_txt);
|
|
887
680
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
index_records.push({
|
|
891
|
-
relative_path: relative_path,
|
|
892
|
-
index_html: {
|
|
893
|
-
fm_props: index_data.fm_props,
|
|
894
|
-
text: section.text,
|
|
895
|
-
preview: section.preview,
|
|
896
|
-
id: section.id ? section.id : null,
|
|
897
|
-
},
|
|
898
|
-
metadata: metadata,
|
|
899
|
-
contributors: contribs,
|
|
900
|
-
pdf_size: pdf_size,
|
|
901
|
-
md5: file_path.hash,
|
|
902
|
-
lastmod: last_commit !== null ? last_commit : file_path.hb_lastmod,
|
|
903
|
-
inline: inline_content,
|
|
904
|
-
status: index_data.fm_props.status ? index_data.fm_props.status : 'release',
|
|
905
|
-
keywords: index_data.fm_props.keywords ? index_data.fm_props.keywords : '',
|
|
906
|
-
});
|
|
907
|
-
}
|
|
681
|
+
if (inline_content) html_txt = `${fm_header_str}\n${html_txt}`;
|
|
682
|
+
else html_txt = `${fm_header_str}\n${doc_header}\n${html_txt}`;
|
|
908
683
|
|
|
684
|
+
// Determine output file path (.md → .html for markdown; same path for static HTML)
|
|
685
|
+
const target_file = is_markdown
|
|
686
|
+
? file_path.path.replace(path.extname(file_path.path), ".html")
|
|
687
|
+
: file_path.path;
|
|
688
|
+
try {
|
|
689
|
+
fs.writeFileSync(target_file, html_txt);
|
|
690
|
+
} catch (err) {
|
|
691
|
+
console.error("Error writing:", target_file, "\n", err);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
let relative_path = is_markdown
|
|
695
|
+
? file_path.relativePath.replace(path.extname(file_path.path), ".html")
|
|
696
|
+
: file_path.relativePath;
|
|
697
|
+
if (
|
|
698
|
+
!bc[relative_path.replace(".html", "")] &&
|
|
699
|
+
bc[relative_path.replace("/index.html", "")]
|
|
700
|
+
) {
|
|
701
|
+
relative_path = relative_path.replace("/index.html", "");
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const index_data = hdoc_index.transform_html_for_index(html_txt);
|
|
705
|
+
for (const section of index_data.sections) {
|
|
706
|
+
index_records.push({
|
|
707
|
+
relative_path: relative_path,
|
|
708
|
+
index_html: {
|
|
709
|
+
fm_props: index_data.fm_props,
|
|
710
|
+
text: section.text,
|
|
711
|
+
preview: section.preview,
|
|
712
|
+
id: section.id ? section.id : null,
|
|
713
|
+
},
|
|
714
|
+
metadata: metadata,
|
|
715
|
+
contributors: contribs,
|
|
716
|
+
pdf_size: pdf_size,
|
|
717
|
+
md5: file_path.hash,
|
|
718
|
+
lastmod: last_commit !== null ? last_commit : file_path.hb_lastmod,
|
|
719
|
+
inline: inline_content,
|
|
720
|
+
status: index_data.fm_props.status ? index_data.fm_props.status : 'release',
|
|
721
|
+
keywords: index_data.fm_props.keywords ? index_data.fm_props.keywords : '',
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (is_markdown) {
|
|
909
726
|
// Add MD file to delete queue
|
|
910
727
|
md_files_delete.push(file_path.path);
|
|
911
|
-
|
|
912
728
|
conversion_success++;
|
|
913
729
|
return true;
|
|
914
730
|
}
|
|
915
|
-
conversion_failed++;
|
|
916
|
-
console.error("MD file does not exist:", file_path.path);
|
|
917
|
-
return false;
|
|
918
731
|
};
|
|
919
732
|
|
|
920
733
|
const tidy_code_tags = (markdown, file) => {
|
|
@@ -950,7 +763,7 @@
|
|
|
950
763
|
.replace("```", "");
|
|
951
764
|
let new_xml_string = xml_tidy;
|
|
952
765
|
try {
|
|
953
|
-
new_xml_string =
|
|
766
|
+
new_xml_string = hdoc.xml_format(xml_tidy, {
|
|
954
767
|
indentation: " ",
|
|
955
768
|
collapseContent: true,
|
|
956
769
|
lineSeparator: "\n",
|
|
@@ -1105,7 +918,7 @@
|
|
|
1105
918
|
const filename_validation_callback = (element) => {
|
|
1106
919
|
if (element.relativePath.startsWith("_inline/")) return;
|
|
1107
920
|
if (element.name.toLowerCase() === ".ds_store") return;
|
|
1108
|
-
if (element.name === "article_ext.md" || element.name === "description_ext.md" ) return;
|
|
921
|
+
if (element.name === "article_ext.md" || element.name === "description_ext.md" || element.name === "internal_ext.md" ) return;
|
|
1109
922
|
|
|
1110
923
|
const file_no_ext = element.name.replace(`.${element.extension}`, "");
|
|
1111
924
|
|
|
@@ -1290,7 +1103,7 @@
|
|
|
1290
1103
|
|
|
1291
1104
|
// Validate all filenames first
|
|
1292
1105
|
console.log("Validating book filenames meet kebab-case requirements...");
|
|
1293
|
-
|
|
1106
|
+
hdoc.scan_dir(book_path, dreeOptionsAllFiles, filename_validation_callback);
|
|
1294
1107
|
if (errors_filename.length > 0) {
|
|
1295
1108
|
console.log("\r\n-----------------------");
|
|
1296
1109
|
console.log(" Validation Output ");
|
|
@@ -1322,7 +1135,8 @@
|
|
|
1322
1135
|
// Copy files from book into _work-doc_id folder
|
|
1323
1136
|
console.log("Copying content into work folder...");
|
|
1324
1137
|
try {
|
|
1325
|
-
fs.
|
|
1138
|
+
fs.cpSync(path.join(source_path, doc_id), work_path_content, {
|
|
1139
|
+
recursive: true,
|
|
1326
1140
|
filter: file_filter,
|
|
1327
1141
|
});
|
|
1328
1142
|
} catch (e) {
|
|
@@ -1336,7 +1150,7 @@
|
|
|
1336
1150
|
// Create MD5 hash of content before build
|
|
1337
1151
|
console.log("Creating Hash...");
|
|
1338
1152
|
|
|
1339
|
-
|
|
1153
|
+
hdoc.scan_dir(work_path_content, md5DreeOptions, hash_callback);
|
|
1340
1154
|
let concat_hash = "|";
|
|
1341
1155
|
for (let i = 0; i < built_file_hashes.length; i++) {
|
|
1342
1156
|
concat_hash += `${built_file_hashes[i].path}:${built_file_hashes[i].hash}|`;
|
|
@@ -1405,7 +1219,7 @@
|
|
|
1405
1219
|
console.log("Processing content...");
|
|
1406
1220
|
|
|
1407
1221
|
// Get a list of MD files in work_path
|
|
1408
|
-
|
|
1222
|
+
hdoc.scan_dir(work_path, dreeOptions, build_file_callback);
|
|
1409
1223
|
|
|
1410
1224
|
// Create a Chromium browser instance generate PDFs and validate links with
|
|
1411
1225
|
browser = await puppeteer.launch({ headless: "shell", args: ['--no-sandbox'] });
|
|
@@ -1421,7 +1235,7 @@
|
|
|
1421
1235
|
// do whatever
|
|
1422
1236
|
await Promise.all(
|
|
1423
1237
|
chunk.map(async (file) => {
|
|
1424
|
-
await
|
|
1238
|
+
await transform_file(file);
|
|
1425
1239
|
}),
|
|
1426
1240
|
);
|
|
1427
1241
|
}
|
|
@@ -1435,7 +1249,7 @@
|
|
|
1435
1249
|
const chunk = htmlPromiseArray.slice(i, i + chunkSize);
|
|
1436
1250
|
await Promise.all(
|
|
1437
1251
|
chunk.map(async (file) => {
|
|
1438
|
-
await
|
|
1252
|
+
await transform_file(file);
|
|
1439
1253
|
}),
|
|
1440
1254
|
);
|
|
1441
1255
|
}
|