Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F995738
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/package.json b/package.json
index e94f28f..55ffcc8 100644
--- a/package.json
+++ b/package.json
@@ -1,60 +1,60 @@
{
"name": "@sealcode/jdd",
- "version": "0.6.0-alpha",
+ "version": "0.6.0-alpha1",
"description": "JSON-Driven Documents",
"main": "lib/index.js",
"type": "module",
"scripts": {
"pretest": "npm run build",
"test": "node test.cjs",
"prebuild": "npm run lint",
"build": "tsc",
"typecheck": "tsc --noemit",
"prepare": "npm run build",
"lint": "eslint src",
"preinstrument": "npm run build && rm -fr .xunit coverage lib-instrumented",
"instrument": "npx nyc instrument --exclude \"\" lib lib-instrumented",
"pretest-reports": "npm run instrument",
"test-reports": "npx nyc --exclude \"\" ./node_modules/.bin/mocha --recursive --timeout=10000 --require source-map-support/register --reporter xunit --reporter-option output=.xunit 'lib-instrumented/**/*.test.js' && nyc report --reporter clover --exclude \"\"",
"precover-html": "rm -rf coverate/lcov-report",
"cover-html": "npm run test-reports && nyc report --reporter lcov --exclude \"\" && xdg-open coverage/lcov-report/index.html"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/mocha": "^10.0.1",
"@types/prettier": "^2.7.0",
"@types/slug": "^5.0.9",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"assert": "^2.0.0",
"eslint": "^8.38.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-with-tsc-error": "^0.0.8",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
},
"types": "./@types/index.d.ts",
"dependencies": {
"@sealcode/file-manager": "^1.0.2",
"@sealcode/ts-predicates": "^0.5.3",
"escape-goat": "^4.0.0",
"hyphenopoly": "^5.3.0",
"koa-responsive-image-router": "^0.2.29",
"locreq": "^3.0.0",
"marked": "^12.0.0",
"mri": "^1.2.0",
"prettier": "^2.7.1",
"slug": "^9.1.0",
"tempstream": "^0.4.1",
"uuid": "^9.0.1"
},
"peerDependencies": {
"sealious": "^0.19.18"
}
}
diff --git a/src/component-arguments/enum.test.ts b/src/component-arguments/enum.test.ts
new file mode 100644
index 0000000..4b800be
--- /dev/null
+++ b/src/component-arguments/enum.test.ts
@@ -0,0 +1,11 @@
+import assert from "assert";
+import { Enum } from "./enum.js";
+
+describe("enum argument", () => {
+ it("uses only the example values if provided when running getExampleValues", () => {
+ const e = new Enum(["a", "b"]).setExampleValues(["a"]);
+ for (let i = 0; i <= 100; i++) {
+ assert.deepStrictEqual(e.getExampleValue(), "a");
+ }
+ });
+});
diff --git a/src/jdd.test.ts b/src/jdd.test.ts
new file mode 100644
index 0000000..30e99c9
--- /dev/null
+++ b/src/jdd.test.ts
@@ -0,0 +1,39 @@
+import assert from "assert";
+import { Markdown } from "./components/markdown.js";
+import { JDD } from "./jdd.js";
+import { Registry } from "./registry.js";
+import { simplestContext } from "./test-utils/simplest-context.js";
+
+describe("jdd", () => {
+ it("properly extracts headings from multiple components", () => {
+ const registry = new Registry();
+ registry.add("markdown", Markdown);
+ const jdd = JDD.fromParsed(registry, simplestContext(), [
+ {
+ component_name: "markdown",
+ args: {
+ markdown: `
+# heading one
+
+## heading two
+
+### heading three`,
+ },
+ },
+ {
+ component_name: "markdown",
+ args: {
+ markdown: `
+# heading four
+`,
+ },
+ },
+ ]);
+ assert.deepEqual(jdd.getHeadings(), [
+ { text: "heading one", level: 1, slug: "heading-one" },
+ { text: "heading two", level: 2, slug: "heading-two" },
+ { text: "heading three", level: 3, slug: "heading-three" },
+ { text: "heading four", level: 1, slug: "heading-four" },
+ ]);
+ });
+});
diff --git a/src/jdd.ts b/src/jdd.ts
new file mode 100644
index 0000000..43018db
--- /dev/null
+++ b/src/jdd.ts
@@ -0,0 +1,201 @@
+import { tempstream } from "tempstream";
+
+import { JDDContext } from "./jdd-context.js";
+import { Registry } from "./registry.js";
+import { EarlyAsset, JDDHeading } from "./component.js";
+import { hasField } from "@sealcode/ts-predicates";
+import {
+ JDDocumentContainer,
+ RawJDDocument,
+ documentContainerFromParsed,
+ documentContainerFromStorage,
+} from "./document.js";
+import { documentToParsed } from "./document.js";
+
+export class JDD {
+ constructor(
+ public registry: Registry,
+ public jdd_context: JDDContext,
+ public parsed: JDDocumentContainer<"parsed">
+ ) {}
+
+ static async fromStorage(
+ registry: Registry,
+ jdd_context: JDDContext,
+ document: RawJDDocument
+ ) {
+ const parsed = await documentToParsed(
+ registry,
+ jdd_context,
+ documentContainerFromStorage(document)
+ );
+ return new JDD(registry, jdd_context, parsed);
+ }
+
+ static fromParsed(
+ registry: Registry,
+ jdd_context: JDDContext,
+ parsed: RawJDDocument
+ ) {
+ return new JDD(
+ registry,
+ jdd_context,
+ documentContainerFromParsed(parsed)
+ );
+ }
+
+ render(
+ make_component_classes = (index: number) => [
+ "jdd-component",
+ `component-number-${index}`,
+ ]
+ ) {
+ return tempstream`${this.parsed.value.map(
+ ({ component_name, args }, index) => {
+ const component = this.registry.get(component_name);
+ if (!component) {
+ console.warn(
+ "Component not found in the registry: " + component_name
+ );
+ return "";
+ }
+ for (const arg_name in component?.getArguments()) {
+ if (!Object.prototype.hasOwnProperty.call(args, arg_name)) {
+ args[arg_name] = component
+ ?.getArguments()
+ [arg_name]?.getEmptyValue(this.jdd_context);
+ }
+ }
+ return component.toHTML({
+ args,
+ classes: make_component_classes(index),
+ jdd_context: this.jdd_context,
+ index,
+ });
+ }
+ )}`;
+ }
+
+ renderEarlyScript(asset: EarlyAsset): string {
+ if (hasField("url", asset)) {
+ return /* HTML */ `<script
+ async
+ src="${asset.url}"
+ onLoad="document.dispatchEvent(new Event('loaded-${asset.identity}'))"
+ ${(asset.integrity &&
+ `integrity="${asset.integrity}" crossorigin="anonymous"`) ||
+ ""}
+ ></script>`;
+ } else {
+ return /* HTML */ `<script><${asset.content}/script>`;
+ }
+ }
+
+ renderEarlyStyle(asset: EarlyAsset): string {
+ if (hasField("url", asset)) {
+ const integrity =
+ (asset.integrity &&
+ `integrity="${asset.integrity}" crossorigin="anonymous"`) ||
+ "";
+ // see https://web.dev/articles/defer-non-critical-css
+ return /* HTML */ `<link
+ rel="preload"
+ href="${asset.url}"
+ as="style"
+ onload="this.onload=null;this.rel='stylesheet'"
+ ${integrity}
+ />
+ <noscript
+ ><link rel="stylesheet" href="${asset.url}" ${integrity}
+ /></noscript>`;
+ } else {
+ return /* HTML */ `<style>
+ ${asset.content}
+ </style>`;
+ }
+ }
+
+ async renderEarlyAssets(
+ registry: Registry,
+ document_container: JDDocumentContainer<"parsed">,
+ context: JDDContext
+ ) {
+ const early_assets = (
+ await Promise.all(
+ document_container.value.map(
+ async ({ component_name, args }) => {
+ const component = registry.get(component_name);
+ if (!component) {
+ console.warn(
+ "Component not found in the registry: " +
+ component_name
+ );
+ return [];
+ }
+ for (const arg_name in component?.getArguments()) {
+ if (
+ !Object.prototype.hasOwnProperty.call(
+ args,
+ arg_name
+ )
+ ) {
+ args[arg_name] = component
+ ?.getArguments()
+ [arg_name]?.getEmptyValue(context);
+ }
+ }
+ return await component.getEarlyAssets(args, context);
+ }
+ )
+ )
+ ).flat();
+ const deduplicated_assets: Record<string, EarlyAsset> = {};
+ for (const asset of early_assets) {
+ deduplicated_assets[asset.identity] = asset;
+ }
+ return Object.values(deduplicated_assets)
+ .map((asset) => {
+ if (asset.type == "script") {
+ return this.renderEarlyScript(asset);
+ } else if (asset.type == "style") {
+ this.renderEarlyStyle(asset);
+ }
+ })
+ .join(" ");
+ }
+
+ countWords(registry: Registry): number {
+ return this.parsed.value.reduce((acc, { component_name, args }) => {
+ const component = registry.get(component_name);
+ if (!component) {
+ console.warn(
+ "Component not found in the registry: " + component_name
+ );
+ return acc + 0;
+ }
+
+ return acc + component.countWords(args);
+ }, 0);
+ }
+
+ getHeadings(): JDDHeading[] {
+ return this.parsed.value
+ .map(({ component_name, args }) => {
+ const component = this.registry.get(component_name);
+ if (!component) {
+ return [];
+ }
+ return component.getHeadings(this.jdd_context, args);
+ })
+ .flat();
+ }
+
+ static async renderFromStorage(
+ registry: Registry,
+ document: RawJDDocument,
+ jdd_context: JDDContext
+ ) {
+ const jdd = await JDD.fromStorage(registry, jdd_context, document);
+ return jdd.render();
+ }
+}
diff --git a/src/test-utils/simplest-context.ts b/src/test-utils/simplest-context.ts
new file mode 100644
index 0000000..6ca7e8b
--- /dev/null
+++ b/src/test-utils/simplest-context.ts
@@ -0,0 +1,8 @@
+import { FileManager } from "@sealcode/file-manager";
+import { makeSimpleEnglishJDDContext } from "../jdd-context.js";
+
+export function simplestContext() {
+ return makeSimpleEnglishJDDContext(
+ new FileManager("/tmp", "/uploaded_files")
+ );
+}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Dec 23, 14:28 (17 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
556267
Default Alt Text
(9 KB)
Attached To
Mode
R130 jdd
Attached
Detach File
Event Timeline
Log In to Comment