@weborigami/origami 0.6.14 → 0.6.16

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.
@@ -0,0 +1,22 @@
1
+ import { args } from "@weborigami/async-tree";
2
+ import loadJsDom from "../common/loadJsDom.js";
3
+ import domNodeToObject from "./domNodeToObject.js";
4
+
5
+ /**
6
+ * Return the DOM structure for the given HTML as a plain object.
7
+ *
8
+ * @param {import("@weborigami/async-tree").Stringlike} html
9
+ */
10
+ export default async function htmlParse(html) {
11
+ html = args.stringlike(html, "Origami.htmlParse");
12
+ const { JSDOM } = await loadJsDom();
13
+ const dom = JSDOM.fragment(html);
14
+ let object = domNodeToObject(dom);
15
+ if (
16
+ (object.name === "#document" || object.name === "#document-fragment") &&
17
+ object.children.length === 1
18
+ ) {
19
+ object = object.children[0];
20
+ }
21
+ return object;
22
+ }
@@ -22,18 +22,29 @@ export default async function mdOutline(input) {
22
22
  const outline = {};
23
23
  const stack = [];
24
24
  let sectionText = "";
25
+ let sectionTextTrimmed = null;
25
26
  /** @type {any} */
26
27
  let current = outline;
27
28
  for (const token of tokens) {
28
29
  if (token.type === "heading") {
29
30
  // Current section text gets added as content for the current node.
30
- if (sectionText) {
31
- current._text = sectionText.trim();
31
+ sectionTextTrimmed = sectionText.trim();
32
+ if (sectionTextTrimmed) {
33
+ current._text = sectionTextTrimmed;
32
34
  sectionText = "";
33
35
  }
34
36
 
35
- // Pop the stack to find the right level for this heading
36
37
  const { depth, text: headingText } = token;
38
+
39
+ // Did we skip a heading level? If so, create `_skip<n>` nodes
40
+ while (stack.length < depth - 1) {
41
+ const skipNode = {};
42
+ current[`_skip${stack.length + 1}`] = skipNode;
43
+ stack.push(current);
44
+ current = skipNode;
45
+ }
46
+
47
+ // Pop the stack to find the right level for this heading
37
48
  while (stack.length >= depth) {
38
49
  current = stack.pop();
39
50
  consolidateText(current);
@@ -51,8 +62,9 @@ export default async function mdOutline(input) {
51
62
  }
52
63
 
53
64
  // Any remaining section text gets added as content for the current node.
54
- if (sectionText) {
55
- current._text = sectionText.trim();
65
+ sectionTextTrimmed = sectionText.trim();
66
+ if (sectionTextTrimmed) {
67
+ current._text = sectionTextTrimmed;
56
68
  current = stack.pop();
57
69
  if (current) {
58
70
  consolidateText(current);
@@ -4,8 +4,8 @@ export { default as basename } from "./basename.js";
4
4
  export { default as csv } from "./csv.js";
5
5
  export { default as document } from "./document.js";
6
6
  export { default as fetch } from "./fetch.js";
7
- export { default as htmlDom } from "./htmlDom.js";
8
7
  export { default as htmlEscape } from "./htmlEscape.js";
8
+ export { default as htmlParse } from "./htmlParse.js";
9
9
  export { default as format } from "./image/format.js";
10
10
  export * as image from "./image/image.js";
11
11
  export { default as resize } from "./image/resize.js";
@@ -34,5 +34,6 @@ export { default as static } from "./static.js";
34
34
  export { default as string } from "./string.js";
35
35
  export { default as tsv } from "./tsv.js";
36
36
  export { default as unpack } from "./unpack.js";
37
+ export { default as xmlParse } from "./xmlParse.js";
37
38
  export { default as yaml } from "./yaml.js";
38
39
  export { default as yamlParse } from "./yamlParse.js";
@@ -0,0 +1,33 @@
1
+ import { args } from "@weborigami/async-tree";
2
+ import loadJsDom from "../common/loadJsDom.js";
3
+ import domNodeToObject from "./domNodeToObject.js";
4
+
5
+ let parser;
6
+
7
+ /**
8
+ * Return the DOM for the given XML as a plain object.
9
+ *
10
+ * @param {import("@weborigami/async-tree").Stringlike} xml
11
+ */
12
+ export default async function xmlParse(xml) {
13
+ xml = args.stringlike(xml, "Origami.xmlParse");
14
+ const parser = await getParser();
15
+ const dom = parser.parseFromString(xml, "application/xml");
16
+ let object = domNodeToObject(dom);
17
+ if (
18
+ (object.name === "#document" || object.name === "#document-fragment") &&
19
+ object.children.length === 1
20
+ ) {
21
+ object = object.children[0];
22
+ }
23
+ return object;
24
+ }
25
+
26
+ async function getParser() {
27
+ if (!parser) {
28
+ const { JSDOM } = await loadJsDom();
29
+ const dom = new JSDOM();
30
+ parser = new dom.window.DOMParser();
31
+ }
32
+ return parser;
33
+ }
@@ -1,4 +1,9 @@
1
- import { Tree, keysFromPath, trailingSlash } from "@weborigami/async-tree";
1
+ import {
2
+ TraverseError,
3
+ Tree,
4
+ keysFromPath,
5
+ trailingSlash,
6
+ } from "@weborigami/async-tree";
2
7
  import { formatError } from "@weborigami/language";
3
8
  import { ServerResponse } from "node:http";
4
9
  import constructResponse from "./constructResponse.js";
@@ -83,7 +88,14 @@ export async function handleRequest(request, response, map) {
83
88
 
84
89
  export function keysFromUrl(url) {
85
90
  const encodedKeys = keysFromPath(url.pathname);
86
- const keys = encodedKeys.map((key) => decodeURIComponent(key));
91
+ // Decode the keys, but stop decoding if we encounter an Origami debugger command
92
+ let foundCommand = false;
93
+ const keys = encodedKeys.map((key) => {
94
+ if (key.startsWith("!")) {
95
+ foundCommand = true;
96
+ }
97
+ return foundCommand ? key : decodeURIComponent(key);
98
+ });
87
99
 
88
100
  // If the keys array is empty (the path was just a trailing slash) or if the
89
101
  // path ended with a slash, add "index.html" to the end of the keys.
@@ -99,12 +111,17 @@ export function keysFromUrl(url) {
99
111
  * https.createServer calls, letting you serve an async tree as a set of pages.
100
112
  *
101
113
  * @typedef {import("@weborigami/async-tree").Maplike} Maplike
114
+ * @param {object} options
115
+ * @param {boolean} [options.quiet] If true, suppresses logging of incoming requests.
102
116
  * @param {Maplike} maplike
103
117
  */
104
- export function requestListener(maplike) {
118
+ export function requestListener(maplike, options = {}) {
119
+ const quiet = options.quiet ?? false;
105
120
  const tree = Tree.from(maplike);
106
121
  return async function (request, response) {
107
- console.log(decodeURI(request.url));
122
+ if (!quiet) {
123
+ console.log(decodeURI(request.url));
124
+ }
108
125
  const handled = await handleRequest(request, response, tree);
109
126
  if (!handled) {
110
127
  // Not found, return a 404.
@@ -138,9 +155,16 @@ ${message}
138
155
  </body>
139
156
  </html>
140
157
  `;
141
- response.writeHead(500, { "Content-Type": "text/html" });
158
+ response.writeHead(500, {
159
+ "Content-Type": "text/html",
160
+ "x-error-details": encodeURIComponent(message),
161
+ });
142
162
  response.end(html, "utf-8");
143
- console.error(message);
163
+
164
+ // Don't log traverse errors for requests like favicon.ico, com.chrome.devtools.json, etc.
165
+ if (!(error instanceof TraverseError)) {
166
+ console.error(message);
167
+ }
144
168
  }
145
169
 
146
170
  // Asynchronous tree router as Express middleware.
@@ -1,14 +0,0 @@
1
- import { args } from "@weborigami/async-tree";
2
- import loadJsDom from "../common/loadJsDom.js";
3
-
4
- /**
5
- * Return the DOM for the given HTML string.
6
- *
7
- * @param {import("@weborigami/async-tree").Stringlike} html
8
- */
9
- export default async function htmlDom(html) {
10
- html = args.stringlike(html, "Origami.htmlDom");
11
- const { JSDOM } = await loadJsDom();
12
- const dom = JSDOM.fragment(html);
13
- return dom;
14
- }