@weborigami/origami 0.0.48 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/exports/buildExports.js +2 -2
  2. package/exports/exports.js +29 -13
  3. package/index.ts +0 -3
  4. package/package.json +10 -10
  5. package/src/builtins/@addNextPrevious.js +58 -0
  6. package/src/builtins/{@arrows.js → @arrowsMap.js} +7 -7
  7. package/src/builtins/@changes.js +46 -0
  8. package/src/builtins/@clean.js +19 -0
  9. package/src/builtins/@constructor.js +17 -0
  10. package/src/builtins/@crawl.js +2 -2
  11. package/src/builtins/@debug.js +17 -19
  12. package/src/builtins/@deepMap.js +19 -0
  13. package/src/builtins/@deepMapFn.js +25 -0
  14. package/src/builtins/{@mergeDeep.js → @deepMerge.js} +7 -7
  15. package/src/builtins/@deepTake.js +21 -0
  16. package/src/builtins/@deepTakeFn.js +22 -0
  17. package/src/builtins/{@valuesDeep.js → @deepValues.js} +6 -5
  18. package/src/builtins/@document.js +1 -2
  19. package/src/builtins/@files.js +14 -1
  20. package/src/builtins/@group.js +20 -0
  21. package/src/builtins/@groupFn.js +30 -0
  22. package/src/builtins/@if.js +2 -1
  23. package/src/builtins/@image/format.js +10 -31
  24. package/src/builtins/@image/formatFn.js +15 -0
  25. package/src/builtins/@image/resize.js +7 -28
  26. package/src/builtins/@image/resizeFn.js +14 -0
  27. package/src/builtins/@inline.js +8 -2
  28. package/src/builtins/@invoke.js +1 -1
  29. package/src/builtins/@json.js +5 -1
  30. package/src/builtins/@jsonParse.js +9 -0
  31. package/src/builtins/@map.js +10 -170
  32. package/src/builtins/@mapFn.js +143 -0
  33. package/src/builtins/@mdHtml.js +2 -0
  34. package/src/builtins/@mdTree.js +69 -0
  35. package/src/builtins/@naturalOrder.js +1 -0
  36. package/src/builtins/@ori.js +1 -1
  37. package/src/builtins/@paginate.js +18 -0
  38. package/src/builtins/@paginateFn.js +61 -0
  39. package/src/builtins/@perf.js +1 -1
  40. package/src/builtins/@redirect.js +10 -1
  41. package/src/builtins/@regexParse.js +5 -0
  42. package/src/builtins/@regexParseFn.js +9 -0
  43. package/src/builtins/@rss.js +8 -4
  44. package/src/builtins/@sitemap.js +4 -4
  45. package/src/builtins/@slug.js +15 -0
  46. package/src/builtins/@sort.js +10 -7
  47. package/src/builtins/@sortFn.js +58 -0
  48. package/src/builtins/@take.js +3 -17
  49. package/src/builtins/@takeFn.js +21 -0
  50. package/src/builtins/@tree.js +2 -14
  51. package/src/builtins/@yaml.js +4 -0
  52. package/src/builtins/@yamlParse.js +10 -0
  53. package/src/builtins/map.d.ts +6 -7
  54. package/src/common/ExplorableSiteTransform.js +16 -10
  55. package/src/common/ShuffleTransform.js +3 -3
  56. package/src/common/{arrowFunctionsMap.js → arrowsMapFn.js} +3 -3
  57. package/src/common/documentObject.js +18 -9
  58. package/src/common/serialize.js +1 -10
  59. package/src/common/utilities.js +5 -2
  60. package/src/misc/OriCommandTransform.js +2 -7
  61. package/src/misc/explore.ori +7 -7
  62. package/src/server/constructResponse.js +3 -9
  63. package/src/server/server.js +5 -2
  64. package/src/builtins/@apply.js +0 -6
  65. package/src/builtins/@groupBy.js +0 -37
  66. package/src/builtins/@isAsyncTree.js +0 -17
  67. package/src/builtins/@mapDeep.js +0 -22
  68. package/src/builtins/@new.js +0 -6
  69. package/src/builtins/@parse/json.js +0 -6
  70. package/src/builtins/@parse/yaml.js +0 -8
  71. package/src/builtins/@sortBy.js +0 -37
  72. package/src/builtins/@with.js +0 -22
@@ -0,0 +1,18 @@
1
+ import getTreeArgument from "../misc/getTreeArgument.js";
2
+ import paginateFn from "./@paginateFn.js";
3
+
4
+ /**
5
+ * Return a new grouping of the treelike's values into chunks of the specified
6
+ * size.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
10
+ *
11
+ * @this {AsyncTree|null}
12
+ * @param {Treelike} [treelike]
13
+ * @param {number} [size=10]
14
+ */
15
+ export default async function paginate(treelike, size = 10) {
16
+ const tree = await getTreeArgument(this, arguments, treelike, "@count");
17
+ return paginateFn.call(this, size)(tree);
18
+ }
@@ -0,0 +1,61 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import { Scope } from "@weborigami/language";
3
+
4
+ /**
5
+ * Return a new grouping of the treelike's values into "pages" of the specified
6
+ * size.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
10
+ *
11
+ * @this {AsyncTree|null}
12
+ * @param {number} [size=10]
13
+ */
14
+ export default function paginateFn(size = 10) {
15
+ const scope = this;
16
+ /**
17
+ * @param {Treelike} [treelike]
18
+ */
19
+ return async function (treelike) {
20
+ const tree = Tree.from(treelike);
21
+ const keys = Array.from(await tree.keys());
22
+ const pageCount = Math.ceil(keys.length / size);
23
+
24
+ const result = {
25
+ async get(pageKey) {
26
+ // Note: page numbers are 1-based.
27
+ const pageNumber = Number(pageKey);
28
+ if (Number.isNaN(pageNumber)) {
29
+ return undefined;
30
+ }
31
+ const nextPage = pageNumber + 1 <= pageCount ? pageNumber + 1 : null;
32
+ const previousPage = pageNumber - 1 >= 1 ? pageNumber - 1 : null;
33
+ const items = {};
34
+ for (
35
+ let index = (pageNumber - 1) * size;
36
+ index < Math.min(keys.length, pageNumber * size);
37
+ index++
38
+ ) {
39
+ const key = keys[index];
40
+ items[key] = await tree.get(keys[index]);
41
+ }
42
+
43
+ return {
44
+ items,
45
+ nextPage,
46
+ pageCount,
47
+ pageNumber,
48
+ previousPage,
49
+ };
50
+ },
51
+
52
+ async keys() {
53
+ // Return an array from 1..totalPages
54
+ return Array.from({ length: pageCount }, (_, index) => index + 1);
55
+ },
56
+ };
57
+
58
+ const scoped = Scope.treeWithScope(result, scope);
59
+ return scoped;
60
+ };
61
+ }
@@ -7,7 +7,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
7
7
  * @this {import("@weborigami/types").AsyncTree|null}
8
8
  * @param {Function} fn
9
9
  */
10
- export default async function perf(fn, count = 10000) {
10
+ export default async function perf(fn, count = 1) {
11
11
  assertScopeIsDefined(this, "perf");
12
12
  const start = performance.now();
13
13
  for (let i = 0; i < count; i++) {
@@ -1,8 +1,17 @@
1
1
  export default function redirect(url, options = { permanent: false }) {
2
- return new Response("ok", {
2
+ const response = new Response("ok", {
3
3
  headers: {
4
4
  Location: url,
5
5
  },
6
6
  status: options.permanent ? 301 : 307,
7
7
  });
8
+ /** @type {any} */ (response).pack = () => `<!DOCTYPE html>
9
+ <html>
10
+ <head>
11
+ <meta charset="utf-8" />
12
+ <meta http-equiv="refresh" content="0;url=${url}" />
13
+ </head>
14
+ </html>
15
+ `;
16
+ return response;
8
17
  }
@@ -0,0 +1,5 @@
1
+ import regexParser from "./@regexParseFn.js";
2
+
3
+ export default function regexParse(text, regex) {
4
+ return regexParser(regex)(text);
5
+ }
@@ -0,0 +1,9 @@
1
+ const parsers = {};
2
+
3
+ export default function regexParseFn(text) {
4
+ if (!parsers[text]) {
5
+ const regexp = new RegExp(text);
6
+ parsers[text] = (input) => input.match(regexp)?.groups;
7
+ }
8
+ return parsers[text];
9
+ }
@@ -24,7 +24,7 @@ export default async function rss(jsonFeedTree) {
24
24
  <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
25
25
  <channel>
26
26
  <atom:link href="${rssUrl}" rel="self" type="application/rss+xml"/>
27
- <title>${title}</title>
27
+ <title><![CDATA[${title}]]></title>
28
28
  <link>${home_page_url}</link>
29
29
  <description>${description}</description>
30
30
  ${itemsRss}</channel>
@@ -33,15 +33,19 @@ ${itemsRss}</channel>
33
33
 
34
34
  function itemRss(jsonFeedItem) {
35
35
  const { content_html, date_published, id, title, url } = jsonFeedItem;
36
- // RSS wants dates in RFC-822.
37
- const date = date_published?.toUTCString() ?? null;
36
+ // RSS wants dates in RFC-822, essentially the same as ISO 8601.
37
+ const date =
38
+ date_published instanceof Date
39
+ ? date_published.toUTCString()
40
+ : date_published;
38
41
  const dateElement = date ? ` <pubDate>${date}</pubDate>\n` : "";
39
42
  const guidElement = id ? ` <guid>${id}</guid>\n` : "";
40
43
  const contentElement = content_html
41
44
  ? ` <description><![CDATA[${content_html}]]></description>\n`
42
45
  : "";
46
+ const titleElement = `<![CDATA[${title}]]>`;
43
47
  return ` <item>
44
- ${dateElement} <title>${title}</title>
48
+ ${dateElement} <title>${titleElement}</title>
45
49
  <link>${url}</link>
46
50
  ${guidElement}${contentElement} </item>
47
51
  `;
@@ -3,13 +3,13 @@ import builtins from "./@builtins.js";
3
3
  import paths from "./@paths.js";
4
4
  import fileTypeOrigami from "./ori_handler.js";
5
5
 
6
- const templateText = `=\`<?xml version="1.0" encoding="UTF-8"?>
6
+ const templateText = `(urls) => \`<?xml version="1.0" encoding="UTF-8"?>
7
7
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
8
- \${ @map(=\`
8
+ \${ @map(urls, (url) => \`
9
9
  <url>
10
- <loc>\${ _ }</loc>
10
+ <loc>\${ url }</loc>
11
11
  </url>
12
- \`)(_) }
12
+ \`) }
13
13
  </urlset>
14
14
  \`
15
15
  `;
@@ -0,0 +1,15 @@
1
+ export default function slug(filename) {
2
+ let slug = filename.toLowerCase();
3
+
4
+ // Convert spaces to dashes
5
+ slug = slug.replace(/\s+/g, "-");
6
+
7
+ // Remove special characters except dashes, letters, numbers, and periods.
8
+ slug = slug.replace(/[^\w\-\.]/g, "");
9
+
10
+ // Trim leading or trailing dashes.
11
+ slug = slug.replace(/^-+/, "");
12
+ slug = slug.replace(/-+$/, "");
13
+
14
+ return slug;
15
+ }
@@ -1,20 +1,23 @@
1
- import { sortNatural } from "@weborigami/async-tree";
2
1
  import getTreeArgument from "../misc/getTreeArgument.js";
2
+ import sortFn from "./@sortFn.js";
3
3
 
4
4
  /**
5
- * Return a new tree with the original's keys sorted in natural sort order.
5
+ * Return a new tree with the original's keys sorted.
6
6
  *
7
7
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
8
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
9
- * @typedef {import("../../index.ts").Invocable} Invocable
9
+ * @typedef {import("@weborigami/async-tree").ValueKeyFn} ValueKeyFn
10
+ * @typedef {{ compare?: (a: any, b: any) => number, sortKey?: ValueKeyFn }}
11
+ * SortOptions
10
12
  *
11
13
  * @this {AsyncTree|null}
12
14
  * @param {Treelike} [treelike]
15
+ * @param {ValueKeyFn|SortOptions} [options]
13
16
  */
14
- export default async function sort(treelike) {
17
+ export default async function sortBuiltin(treelike, options) {
15
18
  const tree = await getTreeArgument(this, arguments, treelike, "@sort");
16
- return sortNatural()(tree);
19
+ return sortFn.call(this, options)(tree);
17
20
  }
18
21
 
19
- sort.usage = `@sort <tree>\tReturn a new tree with the original's keys sorted`;
20
- sort.documentation = "https://weborigami.org/cli/builtins.html#@sort";
22
+ sortBuiltin.usage = `@sort <tree>, [options]\tReturn a new tree with the original's keys sorted`;
23
+ sortBuiltin.documentation = "https://weborigami.org/builtins/@sort.html";
@@ -0,0 +1,58 @@
1
+ import { sortFn } from "@weborigami/async-tree";
2
+ import { Scope } from "@weborigami/language";
3
+ import { toFunction } from "../common/utilities.js";
4
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
5
+
6
+ /**
7
+ * Return a transform function that sorts a tree's keys using a comparison
8
+ * function.
9
+ *
10
+ * If the `options` include a `sortKey` function, that will be invoked for each
11
+ * key in the tree to produce a sort key. If no `sortKey` function is provided,
12
+ * the original keys will be used as sort keys.
13
+ *
14
+ * If the `options` include a `compare` function, that will be used to compare
15
+ * sort keys.
16
+ *
17
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
18
+ * @typedef {import("@weborigami/async-tree").ValueKeyFn} ValueKeyFn
19
+ * @typedef {{ compare?: (a: any, b: any) => number, sortKey?: ValueKeyFn }}
20
+ * SortOptions
21
+ *
22
+ * @this {AsyncTree|null}
23
+ * @param {ValueKeyFn|SortOptions} [options]
24
+ */
25
+ export default function sortFnBuiltin(options) {
26
+ assertScopeIsDefined(this);
27
+ const scope = this;
28
+
29
+ if (typeof options === "function") {
30
+ // Take the function as the `sortKey` option
31
+ options = { sortKey: options };
32
+ }
33
+
34
+ const compare = options?.compare;
35
+ let extendedSortKeyFn;
36
+ if (options?.sortKey) {
37
+ const originalSortKey = toFunction(options?.sortKey);
38
+ extendedSortKeyFn = async (key, tree) => {
39
+ const value = await tree.get(key);
40
+ const sortKey = await originalSortKey.call(scope, value, key);
41
+ return sortKey;
42
+ };
43
+ }
44
+
45
+ const fn = sortFn({
46
+ compare,
47
+ sortKey: extendedSortKeyFn,
48
+ });
49
+
50
+ return (treelike) => {
51
+ const sorted = fn(treelike);
52
+ const scoped = Scope.treeWithScope(sorted, scope);
53
+ return scoped;
54
+ };
55
+ }
56
+
57
+ sortFnBuiltin.usage = `@sortFn [sortFn]\tReturn a function that sorts a tree`;
58
+ sortFnBuiltin.documentation = "https://weborigami.org/builtins/@sortFn.html";
@@ -1,33 +1,19 @@
1
- import { Scope } from "@weborigami/language";
2
1
  import getTreeArgument from "../misc/getTreeArgument.js";
2
+ import takeFn from "./@takeFn.js";
3
3
 
4
4
  /**
5
5
  * Given a tree, take the first n items from it.
6
6
  *
7
7
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
8
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
9
+ *
9
10
  * @this {AsyncTree|null}
10
11
  * @param {Treelike} treelike
11
12
  * @param {number} n
12
13
  */
13
14
  export default async function take(treelike, n) {
14
15
  const tree = await getTreeArgument(this, arguments, treelike, "@take");
15
-
16
- /** @type {AsyncTree} */
17
- let takeTree = {
18
- async keys() {
19
- const keys = Array.from(await tree.keys());
20
- return keys.slice(0, n);
21
- },
22
-
23
- async get(key) {
24
- return tree.get(key);
25
- },
26
- };
27
-
28
- takeTree = Scope.treeWithScope(takeTree, this);
29
-
30
- return takeTree;
16
+ return takeFn.call(this, n)(tree);
31
17
  }
32
18
 
33
19
  take.usage = `@take tree, n\tReturn the first n items from tree`;
@@ -0,0 +1,21 @@
1
+ import { takeFn } from "@weborigami/async-tree";
2
+ import { Scope } from "@weborigami/language";
3
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
4
+
5
+ /**
6
+ * Limit the number of keys to the indicated count.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ *
10
+ * @this {AsyncTree|null}
11
+ * @param {number} count
12
+ */
13
+ export default function takeFnBuiltin(count) {
14
+ assertScopeIsDefined(this, "takeFn");
15
+ const scope = this;
16
+ return (treelike) => {
17
+ const taken = takeFn(count)(treelike);
18
+ const scoped = Scope.treeWithScope(taken, scope);
19
+ return scoped;
20
+ };
21
+ }
@@ -1,16 +1,4 @@
1
- import getTreeArgument from "../misc/getTreeArgument.js";
2
-
3
1
  /**
4
- * Cast the indicated treelike to a tree.
5
- *
6
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
8
- * @this {AsyncTree|null}
9
- * @param {Treelike} [treelike]
2
+ * Tree utilities functions
10
3
  */
11
- export default async function tree(treelike) {
12
- return getTreeArgument(this, arguments, treelike, "@tree");
13
- }
14
-
15
- tree.usage = `@tree <treelike>\tConvert JSON, YAML, function, or plain object to a tree`;
16
- tree.documentation = "https://weborigami.org/cli/builtins.html#tree";
4
+ export { Tree as default } from "@weborigami/async-tree";
@@ -1,4 +1,5 @@
1
1
  /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
2
+ import { isUnpackable } from "@weborigami/async-tree";
2
3
  import YAML from "yaml";
3
4
  import * as serialize from "../common/serialize.js";
4
5
  import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
@@ -21,6 +22,9 @@ export default async function toYaml(obj) {
21
22
  if (obj === undefined) {
22
23
  return undefined;
23
24
  }
25
+ if (isUnpackable(obj)) {
26
+ obj = await obj.unpack();
27
+ }
24
28
  const value = await serialize.toJsonValue(obj);
25
29
  return YAML.stringify(value);
26
30
  }
@@ -0,0 +1,10 @@
1
+ import * as serialize from "../common/serialize.js";
2
+ import { toString } from "../common/utilities.js";
3
+
4
+ export default async function yamlParse(input) {
5
+ const text = toString(input);
6
+ return text ? serialize.parseYaml(text) : undefined;
7
+ }
8
+
9
+ yamlParse.usage = `@yamlParse <text>\tParse text as YAML`;
10
+ yamlParse.documentation = "https://weborigami.org/builtins/@yamlParse.html";
@@ -1,17 +1,16 @@
1
- import { KeyFn, Treelike, ValueKeyFn } from "@weborigami/async-tree";
1
+ import { KeyFn, Treelike, TreeTransform, ValueKeyFn } from "@weborigami/async-tree";
2
2
  import { AsyncTree } from "@weborigami/types";
3
- import { TreelikeTransform } from "../../index.ts";
4
3
 
5
4
  type TreeMapOptions = {
6
5
  deep?: boolean;
7
6
  description?: string;
8
- extensions?: string;
7
+ extension?: string;
9
8
  inverseKey?: KeyFn;
10
9
  key?: ValueKeyFn;
10
+ needsSourceValue?: boolean;
11
11
  value?: ValueKeyFn;
12
12
  };
13
13
 
14
- export default function treeMap(value: ValueKeyFn): TreelikeTransform;
15
- export default function treeMap(options: TreeMapOptions): TreelikeTransform;
16
- export default function treeMap(treelike: Treelike, value: ValueKeyFn): AsyncTree;
17
- export default function treeMap(treelike: Treelike, options: TreeMapOptions): AsyncTree;
14
+ export default function treeMap(options: ValueKeyFn | TreeMapOptions): TreeTransform;
15
+ export default function treeMap(treelike: Treelike, options: ValueKeyFn | TreeMapOptions): AsyncTree;
16
+ export default function treeMap(param1: Treelike | ValueKeyFn | TreeMapOptions, param2?: ValueKeyFn | TreeMapOptions): AsyncTree | TreeTransform;
@@ -1,7 +1,7 @@
1
1
  import { Tree, keysJson } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
3
  import index from "../builtins/@index.js";
4
- import { transformObject } from "../common/utilities.js";
4
+ import { isTransformApplied, transformObject } from "../common/utilities.js";
5
5
 
6
6
  /**
7
7
  * Wraps a tree (typically a SiteTree) to turn a standard site into an
@@ -33,10 +33,10 @@ export default function ExplorableSiteTransform(Base) {
33
33
 
34
34
  // Ask the tree if it has the key.
35
35
  let value = await super.get(key);
36
+ const scope = Scope.getScope(this);
36
37
 
37
38
  if (value === undefined) {
38
39
  // The tree doesn't have the key; try the defaults.
39
- const scope = Scope.getScope(this);
40
40
  if (scope) {
41
41
  if (key === "index.html") {
42
42
  value = await index.call(scope, this);
@@ -46,15 +46,19 @@ export default function ExplorableSiteTransform(Base) {
46
46
  }
47
47
  }
48
48
 
49
- // Ensure this transform is applied to any explorable result. This lets
50
- // the user browse into data and explorable trees of types other than the
51
- // current class.
52
49
  if (Tree.isAsyncTree(value)) {
53
- value = transformObject(ExplorableSiteTransform, value);
54
- }
50
+ // Ensure this transform is applied to any tree result so the user
51
+ // browse into data and trees of classes other than the current class.
52
+ if (!isTransformApplied(ExplorableSiteTransform, value)) {
53
+ value = transformObject(ExplorableSiteTransform, value);
54
+ }
55
55
 
56
- if (value?.unpack) {
57
- // If the value isn't a tree, but has a tree attached via a `unpack`
56
+ if (key.endsWith?.("/")) {
57
+ // Instead of return the tree directly, return an index for it.
58
+ value = await index.call(scope, value);
59
+ }
60
+ } else if (value?.unpack) {
61
+ // If the value isn't a tree, but has a tree attached via an `unpack`
58
62
  // method, wrap the unpack method to add this transform.
59
63
  const original = value.unpack.bind(value);
60
64
  value.unpack = async () => {
@@ -68,7 +72,9 @@ export default function ExplorableSiteTransform(Base) {
68
72
  const scope = Scope.getScope(this);
69
73
  tree = Scope.treeWithScope(tree, scope);
70
74
  }
71
- tree = transformObject(ExplorableSiteTransform, tree);
75
+ if (!isTransformApplied(ExplorableSiteTransform, tree)) {
76
+ tree = transformObject(ExplorableSiteTransform, tree);
77
+ }
72
78
  return tree;
73
79
  };
74
80
  }
@@ -19,10 +19,10 @@ export default function ShuffleTransform(Base) {
19
19
  * Performs a Fisher-Yates shuffle. From http://sedition.com/perl/javascript-fy.html
20
20
  */
21
21
  function shuffle(array) {
22
- var i = array.length;
22
+ let i = array.length;
23
23
  while (--i >= 0) {
24
- var j = Math.floor(Math.random() * (i + 1));
25
- var temp = array[i];
24
+ const j = Math.floor(Math.random() * (i + 1));
25
+ const temp = array[i];
26
26
  array[i] = array[j];
27
27
  array[j] = temp;
28
28
  }
@@ -1,9 +1,9 @@
1
- import { cachedKeyFunctions, map } from "@weborigami/async-tree";
1
+ import { cachedKeyFunctions, mapFn } from "@weborigami/async-tree";
2
2
  import { toFunction } from "./utilities.js";
3
3
 
4
- export default function arrowFunctionsMap() {
4
+ export default function arrowsMapFn() {
5
5
  const deep = true;
6
- return map({
6
+ return mapFn({
7
7
  deep,
8
8
  description: "arrowFunctions",
9
9
  value: valueFn,
@@ -1,5 +1,5 @@
1
- import { isPlainObject } from "@weborigami/async-tree";
2
- import txtHandler from "../builtins/txt_handler.js";
1
+ import { isPlainObject, isUnpackable } from "@weborigami/async-tree";
2
+ // import txtHandler from "../builtins/txt_handler.js";
3
3
 
4
4
  /**
5
5
  * In Origami, a text document object is any object with a `@text` property and
@@ -12,9 +12,15 @@ import txtHandler from "../builtins/txt_handler.js";
12
12
  * @param {StringLike|PlainObject} input
13
13
  * @param {any} [data]
14
14
  */
15
- export default function documentObject(input, data) {
15
+ export default async function documentObject(input, data) {
16
16
  let text;
17
17
  let inputData;
18
+
19
+ if (isUnpackable(input)) {
20
+ // Unpack the input first, might already be a document object.
21
+ input = await input.unpack();
22
+ }
23
+
18
24
  if (isPlainObject(input)) {
19
25
  text = input["@text"];
20
26
  inputData = input;
@@ -22,12 +28,15 @@ export default function documentObject(input, data) {
22
28
  text = String(input);
23
29
  inputData = null;
24
30
  }
25
- const base = {
26
- pack() {
27
- return txtHandler.pack(this);
28
- },
29
- };
30
- const result = Object.create(base);
31
+ // TODO: Either restore this code, or move responsibility for packing a
32
+ // document to HandleExtensionsTransform set().
33
+ // const base = {
34
+ // pack() {
35
+ // return txtHandler.pack(this);
36
+ // },
37
+ // };
38
+ // const result = Object.create(base);
39
+ const result = {};
31
40
  Object.assign(result, inputData, data, { "@text": text });
32
41
  return result;
33
42
  }
@@ -5,12 +5,7 @@
5
5
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
6
6
  */
7
7
 
8
- import {
9
- Tree,
10
- isPlainObject,
11
- isStringLike,
12
- isUnpackable,
13
- } from "@weborigami/async-tree";
8
+ import { Tree, isPlainObject, isStringLike } from "@weborigami/async-tree";
14
9
  import { OrigamiTree } from "@weborigami/language";
15
10
  import * as YAMLModule from "yaml";
16
11
  import yamlOrigamiTag from "../misc/yamlOrigamiTag.js";
@@ -145,10 +140,6 @@ export async function toValue(input, jsonValuesOnly = false) {
145
140
  } else {
146
141
  return input;
147
142
  }
148
- } else if (isUnpackable(input)) {
149
- // Unpack first, then convert to JSON value.
150
- const unpacked = await input.unpack();
151
- return toValue(unpacked);
152
143
  } else if (isStringLike(input) && !(input instanceof Array)) {
153
144
  return String(input);
154
145
  } else if (Tree.isTreelike(input)) {
@@ -45,10 +45,13 @@ export const keySymbol = Symbol("key");
45
45
  * @param {string} resultExtension
46
46
  */
47
47
  export function replaceExtension(key, sourceExtension, resultExtension) {
48
- if (!key.endsWith(sourceExtension)) {
48
+ if (!key) {
49
+ return undefined;
50
+ } else if (!key.endsWith(sourceExtension)) {
49
51
  return key;
52
+ } else {
53
+ return key.slice(0, -sourceExtension.length) + resultExtension;
50
54
  }
51
- return key.slice(0, -sourceExtension.length) + resultExtension;
52
55
  }
53
56
 
54
57
  /**
@@ -1,8 +1,8 @@
1
1
  /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
2
- import { ObjectTree, Tree } from "@weborigami/async-tree";
2
+ import { ObjectTree } from "@weborigami/async-tree";
3
3
  import { Scope } from "@weborigami/language";
4
4
  import ori from "../builtins/@ori.js";
5
- import { keySymbol, transformObject } from "../common/utilities.js";
5
+ import { keySymbol } from "../common/utilities.js";
6
6
 
7
7
  /**
8
8
  * Add support for commands prefixed with `!`.
@@ -34,11 +34,6 @@ export default function OriCommandTransform(Base) {
34
34
  const extendedScope = new Scope(ambientsTree, Scope.getScope(this));
35
35
  const source = key.slice(1).trim();
36
36
  value = await ori.call(extendedScope, source, { formatResult: false });
37
-
38
- // Ensure this transform is applied to any subtree.
39
- if (Tree.isAsyncTree(value)) {
40
- value = transformObject(OriCommandTransform, value);
41
- }
42
37
  }
43
38
 
44
39
  return value;
@@ -1,4 +1,4 @@
1
- =`<!DOCTYPE html>
1
+ (scope) => `<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8" />
@@ -16,16 +16,16 @@
16
16
  <button id="buttonSVG">SVG</button>
17
17
  <button id="buttonYAML">YAML</button>
18
18
  </div>
19
- {{ @map(=`
19
+ ${ @map(scope, (scopeTree) => `
20
20
  <ul>
21
- <h2>${ _/name }</h2>
22
- {{ @map(=`
21
+ <h2>${ scopeTree/name }</h2>
22
+ ${ @map(scopeTree/keys, (key) => `
23
23
  <li>
24
- <a href="./!@explore/${ _ }" target="frame">${ _ }</a>
24
+ <a href="./!@explore/${ key }" target="frame">${ key }</a>
25
25
  </li>
26
- `)(_/keys) }}
26
+ `) }
27
27
  </ul>
28
- `)(_) }}
28
+ `) }
29
29
  </nav>
30
30
  <iframe id="frame" name="frame"></iframe>
31
31
  </body>