hdoc-tools 0.50.1 → 0.52.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-serve.js CHANGED
@@ -3,16 +3,8 @@
3
3
  const fs = require("node:fs");
4
4
  const path = require("node:path");
5
5
  const hdoc = require(path.join(__dirname, "hdoc-module.js"));
6
- const stream = require("node:stream");
7
-
8
- const mdfm = require("markdown-it-front-matter");
9
-
10
- const { execSync } = require("child_process");
11
-
12
- const mermaid_theme_path = path.resolve(
13
- __dirname,
14
- "templates",
15
- "mermaid-theme.yaml",
6
+ const { create_content_handler } = require(
7
+ path.join(__dirname, "hdoc-content-routes.js"),
16
8
  );
17
9
 
18
10
  let port = 3000;
@@ -97,310 +89,38 @@
97
89
  process.exit(1);
98
90
  }
99
91
 
100
- app.get("/_books/library.json", (req, res) => {
101
- const library = {
102
- books: [
103
- {
104
- docId: hdocbook_config.docId,
105
- title: hdocbook_config.title,
106
- nav_inline: nav_inline,
107
- },
108
- ],
109
- };
110
- res.setHeader("Content-Type", "application/json");
111
- res.send(JSON.stringify(library, null, 3));
112
- });
113
-
114
- async function transform_markdown_and_send_html(req, res, file_path) {
115
- if (!fs.existsSync(file_path)) return false;
116
-
117
- let md_txt = hdoc.expand_variables(
118
- fs.readFileSync(file_path).toString(),
119
- docId,
120
- );
121
-
122
- const includes_processed = await hdoc.process_includes(
123
- file_path,
124
- md_txt,
125
- global_source_path,
126
- );
127
- md_txt = includes_processed.body;
128
- const md = require("markdown-it")({
129
- html: true,
130
- linkify: true,
131
- typographer: true,
132
- highlight: function (str, lang) {
133
- if (lang === "mermaid") {
134
- try {
135
- const tmpInput = hdoc.tmp_file_sync({ postfix: ".mmd" });
136
- const tmpOutput = hdoc.tmp_file_sync({ postfix: ".svg" });
137
-
138
- if (!str.startsWith('---')) {
139
- str = '---\n' + fs.readFileSync(mermaid_theme_path, {encoding: 'utf-8'}) + `\n---\n${str}`;
140
- }
141
-
142
- fs.writeFileSync(tmpInput.name, str);
143
-
144
- let cmd = `${__dirname}/node_modules/.bin/mmdc`;
145
-
146
- if (process.platform === "win32") {
147
- cmd = `"${cmd}"`;
148
- }
149
- cmd = `${cmd} -i "${tmpInput.name}" -o "${tmpOutput.name}" --quiet --backgroundColor transparent`;
150
- console.log(`Generating Inline Mermaid SVG found in: ${file_path}`);
151
- execSync(cmd);
152
-
153
- if (!fs.existsSync(tmpOutput.name)) {
154
- throw new Error("mmdc did not generate output");
155
- }
156
-
157
- const svg = fs.readFileSync(tmpOutput.name, "utf8");
158
-
159
- tmpInput.removeCallback();
160
- tmpOutput.removeCallback();
161
-
162
- return `<div class="text-body">${svg}</div>`;
163
- } catch (err) {
164
- return `<div class="alert alert-danger mb-0 text-break"
165
- role="alert"
166
- data-bs-toggle="collapse"
167
- data-bs-target="#alertContent"
168
- aria-expanded="false"
169
- aria-controls="alertContent">
170
- <div>
171
- <strong>Error!</strong> Mermaid was unable to generate the SVG expected here. Click for more details
172
- </div>
173
- <div class="collapse mt-3" id="alertContent">
174
- <p class="mb-0 text-break">
175
- ${err.message}
176
- </p>
177
- </div>
178
- </div>`;
179
- }
180
- }
181
- }
182
- });
183
- md.linkify.set({
184
- fuzzyEmail: false,
185
- fuzzyLink: false,
186
- fuzzyIP: false,
187
- });
188
-
189
- let frontmatter_content = "";
190
- md.use(mdfm, (fm) => {
191
- frontmatter_content = fm;
192
- });
193
-
194
- const tips = require(`${__dirname}/custom_modules/tips.js`);
195
- md.use(tips, { links: true });
196
-
197
- const html_txt = md.render(md_txt.toString());
198
-
199
- if (frontmatter_content.length) {
200
- const obj = hdoc.parse_yaml(frontmatter_content);
201
- const buff = Buffer.from(JSON.stringify(obj), "utf-8");
202
- const base64 = buff.toString("base64");
203
- res.setHeader("X-frontmatter", base64);
204
- }
205
-
206
- res.setHeader("Content-Type", "text/html");
207
- res.send(html_txt);
208
- return true;
209
- }
210
-
211
-
212
- function send_content_file(req, res, file_path, redirected = false) {
213
- let content_txt = hdoc.expand_variables(
214
- fs.readFileSync(file_path).toString(),
215
- docId,
216
- );
217
- if (redirected)
218
- content_txt = `Redirected from ${redirected}\n\n${content_txt}`;
219
-
220
- const contentType = hdoc.content_type_for_ext(path.extname(file_path));
221
-
222
- if (path.extname(file_path) === ".md") {
223
- res.setHeader("Content-Disposition", "inline");
224
- }
225
-
226
- res.setHeader("Content-Type", contentType);
227
-
228
- res.send(content_txt);
229
- }
230
-
231
- function send_file(req, res, file_path) {
232
- // Need to set the content type here??
233
- const contentType = hdoc.content_type_for_ext(path.extname(file_path));
234
- res.setHeader("Content-Type", contentType);
235
-
236
- const r = fs.createReadStream(file_path);
237
- const ps = new stream.PassThrough();
238
- stream.pipeline(r, ps, (err) => {
239
- if (err) {
240
- console.error(err); // No such file or any other kind of error
241
- return res.sendStatus(400).send("Unexpected error");
242
- }
243
- });
244
- ps.pipe(res);
245
- }
246
-
247
- function send_content_resource_404(req, res) {
248
- res.setHeader("Content-Type", "text/html");
249
- res.status(404).send("Content resource not found");
250
- }
251
-
252
- // 1. If we request a file with a .html file extension, and that file DOES NOT exist,
253
- // we will look for the same file but with a .md extension. If we find that
254
- // corresponding markdown file, we will transform that markdown file to HTML and
255
- // return the HTML content
256
- //
257
- // 2. If we request a file, without any file extension then we will look for that file
258
- // with a .md extension, and if that file exists, we will transform that markdown
259
- // file to HTML and return that file.
260
- //
261
- // For all other requests, we are going to look on the filesystem. If we request
262
- // a specific file with its extension (including .md files), then we will simply
263
- // return the file verbatim as a static file.
264
- //
265
- // If we request a file without an extension and that file does not exist, we will
266
- // assume that is a folder, will append index.html and look for that file, if present
267
- // we will send it, if not present we will look for index.md, and if thats present
268
- // we will transform to HTML and return that
269
- //
270
- // Anything else in this handler will return a 404 error
271
-
272
- app.get("/_books/*", (req, res) => {
273
- let url = req.url.replace("/_books/", "/");
274
-
275
- console.log("URL Requested:", url);
276
-
277
- // Process redirect
278
- if (
279
- hdocbook_project.redirects &&
280
- Array.isArray(hdocbook_project.redirects) &&
281
- hdocbook_project.redirects.length > 0
282
- ) {
283
- const source_url = url.indexOf("/") === 0 ? url : `/${url}`;
284
- for (const redir of hdocbook_project.redirects) {
285
- redir.url =
286
- redir.url.indexOf("/") === 0 ? redir.url : `/${redir.url}`;
287
- if (
288
- redir.url === source_url &&
289
- redir.location &&
290
- redir.location !== ""
291
- ) {
292
- url = `${redir.location}`;
293
- console.log(`Redirecting to ${url}`);
294
- }
295
- }
296
- }
297
-
298
- const file_path = path.join(source_path, url);
299
-
300
- if (path.extname(file_path) === ".html") {
301
- // 1a. check for html files, and send/transform as required
302
- if (fs.existsSync(file_path)) {
303
- // HTML file exists on disk, just return it verbatim
304
- res.setHeader("Content-Type", "text/html");
305
- send_file(req, res, file_path);
306
- return true;
307
- }
308
- if (fs.existsSync(file_path.replace(".html", ".md"))) {
309
- if (
310
- transform_markdown_and_send_html(
311
- req,
312
- res,
313
- file_path.replace(".html", ".md"),
314
- )
315
- ) {
316
- return;
317
- }
318
- }
319
- } else if (path.extname(file_path) === ".md") {
320
- // If the markdown file exists, just send to caller as is
321
- if (fs.existsSync(file_path)) {
322
- send_content_file(req, res, file_path);
323
- return true;
324
- }
325
- } else if (path.extname(file_path).length === 0) {
326
- // 2. If we request a file, without any file extension
327
- if (fs.existsSync(`${file_path}.md`)) {
328
- if (transform_markdown_and_send_html(req, res, `${file_path}.md`)) {
329
- return;
330
- }
331
- } else if (fs.existsSync(path.join(`${file_path}index.md`))) {
332
- if (
333
- transform_markdown_and_send_html(
334
- req,
335
- res,
336
- path.join(file_path, "index.md"),
337
- )
338
- ) {
339
- return;
340
- }
341
- } else if (fs.existsSync(path.join(`${file_path}index.html`))) {
342
- res.setHeader("Content-Type", "text/html");
343
- send_content_file(req, res, path.join(`${file_path}index.html`));
344
- return;
345
- } else if (fs.existsSync(`${file_path}/index.md`)) {
346
- if (
347
- transform_markdown_and_send_html(req, res, `${file_path}/index.md`)
348
- ) {
349
- return;
350
- }
351
- } else if (fs.existsSync(path.join(`${file_path}/index.html`))) {
352
- res.setHeader("Content-Type", "text/html");
353
- send_content_file(req, res, path.join(`${file_path}/index.html`));
354
- return;
355
- } else if (fs.existsSync(path.join(`${file_path}.html`))) {
356
- res.setHeader("Content-Type", "text/html");
357
- send_content_file(req, res, path.join(`${file_path}.html`));
358
- return;
359
- } else if (fs.existsSync(path.join(`${file_path}.htm`))) {
360
- res.setHeader("Content-Type", "text/html");
361
- send_content_file(req, res, path.join(`${file_path}.htm`));
362
- return;
363
- }
364
- } else if (fs.existsSync(file_path)) {
365
- if (
366
- file_path.endsWith("hdocbook.json") ||
367
- file_path.endsWith("hdocbook_project.json")
368
- ) {
369
- try {
370
- // Read & parse file
371
- JSON.parse(fs.readFileSync(file_path));
372
- } catch (e) {
373
- console.error(`Error parsing hdocbook.json: ${e}`);
374
- }
375
- }
376
- send_file(req, res, file_path);
377
- return;
378
- }
379
-
380
- // Return a 404 error here
381
- send_content_resource_404(req, res);
92
+ // Mount the shared /_books/* content + render routes. The very same
93
+ // handler is reused by `hdoc edit`, so live preview matches published
94
+ // output. The returned helpers are reused by the SPA catch-all below.
95
+ const content = create_content_handler({
96
+ source_path,
97
+ docId,
98
+ hdocbook_config,
99
+ hdocbook_project,
100
+ nav_inline,
382
101
  });
102
+ content.register(app);
383
103
 
384
104
  // Catch all
385
- app.get("/*", (req, res) => {
105
+ app.get("/{*splat}", (req, res) => {
386
106
 
387
107
  const ui_file_path = path.join(ui_path, req.url);
388
108
 
389
109
  // To support the SPA application behavior, if there is no file extension present, then
390
110
  // we simply return the /index.html file content to the client
391
111
  if (path.extname(ui_file_path).length === 0) {
392
- send_content_file(req, res, path.join(ui_path, "index.html"));
112
+ content.send_content_file(req, res, path.join(ui_path, "index.html"));
393
113
  return;
394
114
  }
395
115
 
396
116
  // If the file exists, send it.
397
117
  if (fs.existsSync(ui_file_path)) {
398
- send_content_file(req, res, ui_file_path);
118
+ content.send_content_file(req, res, ui_file_path);
399
119
  return;
400
120
  }
401
121
 
402
122
  // All else fails, we have not file to return, so return a 404 error here
403
- send_content_resource_404(req, res);
123
+ content.send_content_resource_404(req, res);
404
124
  });
405
125
 
406
126
  const server = app.listen(port, "0.0.0.0", () => {
package/hdoc-validate.js CHANGED
@@ -28,6 +28,7 @@ const { error } = require("node:console");
28
28
  let _on_int_net_cached = null; // null = not yet checked; cached after first DNS lookup
29
29
  const exclude_h1_count = {};
30
30
  const exclude_spellcheck_output = [];
31
+ let global_spellcheck = [];
31
32
 
32
33
  const excludeLink = (url) => {
33
34
  if (exclude_links[url]) return true;
@@ -87,13 +88,13 @@ const { error } = require("node:console");
87
88
  error_message = `${markdown_paths.relativePath}:${key}:${link_location.column} - ${error_message}`;
88
89
  else
89
90
  error_message = `${markdown_paths.relativePath}:${key} - ${error_message}`;
90
- if (!excludes[source_path]) {
91
+ if (!excludes[source_path] && !global_spellcheck.includes(spelling.toLowerCase())) {
91
92
  errors[sourceFile.relativePath].push(
92
93
  `${error_message} ${spelling} should be ${translate_output[key][i][spelling].details}`,
93
94
  );
94
95
  spelling_errors[spelling] = true;
95
96
  } else if (
96
- !excludes[source_path].includes(spelling.toLowerCase())
97
+ !excludes[source_path].includes(spelling.toLowerCase()) && !global_spellcheck.includes(spelling.toLowerCase())
97
98
  ) {
98
99
  errors[sourceFile.relativePath].push(
99
100
  `${error_message} ${spelling} should be ${translate_output[key][i][spelling].details}`,
@@ -1000,6 +1001,8 @@ const { error } = require("node:console");
1000
1001
  }
1001
1002
  }
1002
1003
 
1004
+ if (hdocbook_project.validation && Array.isArray(hdocbook_project.validation.spellcheckDictionary)) global_spellcheck = hdocbook_project.validation.spellcheckDictionary.map((w) => String(w).toLowerCase());
1005
+
1003
1006
  // Check navigation spellings & paths exist
1004
1007
  const nav_errors = await checkNavigation(
1005
1008
  source_path,
package/hdoc.js CHANGED
@@ -127,6 +127,7 @@
127
127
  // Default source path to working directory
128
128
  let source_path = process.cwd();
129
129
  let ui_path = path.join(__dirname, "ui");
130
+ let editor_path = path.join(__dirname, "editor", "dist");
130
131
  let git_token = "";
131
132
  let command = ""; // Our command to run
132
133
  let build_version = "";
@@ -210,6 +211,9 @@
210
211
  if (command.toLowerCase() === "serve") {
211
212
  const server = require(path.join(__dirname, "hdoc-serve.js"));
212
213
  server.run(ui_path, source_path);
214
+ } else if (command.toLowerCase() === "edit") {
215
+ const editor = require(path.join(__dirname, "hdoc-edit.js"));
216
+ editor.run(editor_path, source_path);
213
217
  } else if (command.toLowerCase() === "build") {
214
218
  const builder = require(path.join(__dirname, "hdoc-build.js"));
215
219
  await builder.run(