lake-html 0.0.2 → 1.0.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/README.md +105 -2
- package/dist/index.d.mts +21 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.js +185 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +166 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +2 -3
- package/dist/index.cjs +0 -33
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -6
package/README.md
CHANGED
|
@@ -6,10 +6,113 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
A lightweight, zero-dependency utility to convert Lake Markup Language (LML) strings into standard, semantic HTML.
|
|
10
|
+
|
|
11
|
+
Designed for [Lake](https://lakejs.org/), this library handles the parsing of custom `<lake-box>` tags and transforms them into clean HTML elements like images, code blocks, embeds, and more.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
* **Lightweight**: Zero external dependencies.
|
|
16
|
+
|
|
17
|
+
* **Isomorphic**: Works perfectly in Node.js and Browsers.
|
|
18
|
+
|
|
19
|
+
* **Safe**: Auto-encodes HTML entities to prevent XSS.
|
|
20
|
+
|
|
21
|
+
* **Extensible**: Easily override default renderers or add custom box types.
|
|
22
|
+
|
|
23
|
+
* **TypeScript**: Written in TypeScript with complete type definitions.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install lake-html
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Basic Usage
|
|
32
|
+
|
|
33
|
+
Import the `toHTML` function and pass your [LML](https://lakejs.org/guide/content-format) string.
|
|
10
34
|
|
|
11
35
|
``` js
|
|
12
36
|
import { toHTML } from 'lake-html';
|
|
13
37
|
|
|
14
|
-
const
|
|
38
|
+
const content = `
|
|
39
|
+
<p>Hello World</p>
|
|
40
|
+
<lake-box name="image" value="eyJ1cmwiOi..."></lake-box>
|
|
41
|
+
<p>End of content</p>
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const html = toHTML(content);
|
|
45
|
+
|
|
46
|
+
console.log(html);
|
|
47
|
+
// Output:
|
|
48
|
+
// <p>Hello World</p>
|
|
49
|
+
// <img src="..." border="0" />
|
|
50
|
+
// <p>End of content</p>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Customization
|
|
54
|
+
|
|
55
|
+
You can customize how specific boxes are rendered or add support for new box types by passing a `rules` object as the second argument.
|
|
56
|
+
|
|
57
|
+
### Overriding an existing renderer
|
|
58
|
+
|
|
59
|
+
For example, if you want to wrap all images in a `<figure>` tag:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { toHTML, getDefaultBoxRenderers } from 'lake-html';
|
|
63
|
+
|
|
64
|
+
const rules = getDefaultBoxRenderers();
|
|
65
|
+
|
|
66
|
+
// Override the 'image' renderer
|
|
67
|
+
rules.image = (boxValue, encode) => {
|
|
68
|
+
return {
|
|
69
|
+
tagName: 'figure',
|
|
70
|
+
attributes: { class: 'custom-image' },
|
|
71
|
+
innerHTML: `<img src="${encode(boxValue.url)}" alt="${encode(boxValue.caption)}" />`
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const html = toHTML(content, rules);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Adding a new box type
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
import { toHTML, getDefaultBoxRenderers } from 'lake-html';
|
|
82
|
+
|
|
83
|
+
const rules = getDefaultBoxRenderers();
|
|
84
|
+
|
|
85
|
+
// Add a custom 'card' box
|
|
86
|
+
rules.card = (boxValue, encode) => {
|
|
87
|
+
return `<div class="card">
|
|
88
|
+
<h3>${encode(boxValue.title)}</h3>
|
|
89
|
+
<p>${encode(boxValue.summary)}</p>
|
|
90
|
+
</div>`;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const html = toHTML(content, rules);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## API Reference
|
|
97
|
+
|
|
98
|
+
### toHTML(value: string, rules?: BoxRenderers): string
|
|
99
|
+
|
|
100
|
+
The main conversion function.
|
|
101
|
+
|
|
102
|
+
* `value`: The input LML string.
|
|
103
|
+
|
|
104
|
+
* `rules`: (Optional) An object to override default renderers.
|
|
105
|
+
|
|
106
|
+
### getDefaultBoxRenderers()
|
|
107
|
+
|
|
108
|
+
Returns the default map of box renderers. Useful when you want to extend the defaults rather than replace them entirely.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Build the library
|
|
114
|
+
pnpm build
|
|
115
|
+
|
|
116
|
+
# Run tests
|
|
117
|
+
pnpm test
|
|
15
118
|
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type AttributeMap = Record<string, string>;
|
|
2
|
+
interface BoxNode {
|
|
3
|
+
tagName: string;
|
|
4
|
+
attributes?: AttributeMap;
|
|
5
|
+
isVoid?: boolean;
|
|
6
|
+
innerHTML?: string;
|
|
7
|
+
}
|
|
8
|
+
type EntityEncoder = (value: string) => string;
|
|
9
|
+
type BoxRenderer = (boxValue: AttributeMap, encode: EntityEncoder) => BoxNode | string;
|
|
10
|
+
type BoxRenderers = Record<string, BoxRenderer>;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the default configuration for rendering various Lake attributes.
|
|
13
|
+
*/
|
|
14
|
+
declare function getDefaultBoxRenderers(): BoxRenderers;
|
|
15
|
+
/**
|
|
16
|
+
* Main function to convert Lake Markup Language (LML) to standard HTML.
|
|
17
|
+
* It processes custom <lake-box> tags and removes internal anchors.
|
|
18
|
+
*/
|
|
19
|
+
declare function toHTML(value: string, rules?: BoxRenderers): string;
|
|
20
|
+
|
|
21
|
+
export { getDefaultBoxRenderers, toHTML };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
+
type AttributeMap = Record<string, string>;
|
|
2
|
+
interface BoxNode {
|
|
3
|
+
tagName: string;
|
|
4
|
+
attributes?: AttributeMap;
|
|
5
|
+
isVoid?: boolean;
|
|
6
|
+
innerHTML?: string;
|
|
7
|
+
}
|
|
8
|
+
type EntityEncoder = (value: string) => string;
|
|
9
|
+
type BoxRenderer = (boxValue: AttributeMap, encode: EntityEncoder) => BoxNode | string;
|
|
10
|
+
type BoxRenderers = Record<string, BoxRenderer>;
|
|
1
11
|
/**
|
|
2
|
-
*
|
|
12
|
+
* Returns the default configuration for rendering various Lake attributes.
|
|
3
13
|
*/
|
|
4
|
-
declare function
|
|
14
|
+
declare function getDefaultBoxRenderers(): BoxRenderers;
|
|
15
|
+
/**
|
|
16
|
+
* Main function to convert Lake Markup Language (LML) to standard HTML.
|
|
17
|
+
* It processes custom <lake-box> tags and removes internal anchors.
|
|
18
|
+
*/
|
|
19
|
+
declare function toHTML(value: string, rules?: BoxRenderers): string;
|
|
5
20
|
|
|
6
|
-
export { toHTML };
|
|
21
|
+
export { getDefaultBoxRenderers, toHTML };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
22
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
var __copyProps = (to, from, except, desc) => {
|
|
28
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
29
|
+
for (let key of __getOwnPropNames(from))
|
|
30
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
31
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
32
|
+
}
|
|
33
|
+
return to;
|
|
34
|
+
};
|
|
35
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
|
+
|
|
1
37
|
// src/index.ts
|
|
2
|
-
|
|
3
|
-
|
|
38
|
+
var index_exports = {};
|
|
39
|
+
__export(index_exports, {
|
|
40
|
+
getDefaultBoxRenderers: () => getDefaultBoxRenderers,
|
|
41
|
+
toHTML: () => toHTML
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(index_exports);
|
|
44
|
+
function extractIdFromUrl(url) {
|
|
45
|
+
const result = /[\w\-]+$/.exec(url);
|
|
46
|
+
return result ? result[0] : "";
|
|
47
|
+
}
|
|
48
|
+
function getDefaultBoxRenderers() {
|
|
49
|
+
return {
|
|
50
|
+
hr: () => '<div class="lake-box-block lake-hr"><hr /></div>',
|
|
51
|
+
image: (boxValue) => ({
|
|
52
|
+
tagName: "img",
|
|
53
|
+
attributes: __spreadProps(__spreadValues(__spreadValues(__spreadValues({
|
|
54
|
+
src: boxValue.url
|
|
55
|
+
}, boxValue.width && { width: boxValue.width }), boxValue.height && { height: boxValue.height }), boxValue.caption && { alt: boxValue.caption }), {
|
|
56
|
+
border: "0"
|
|
57
|
+
}),
|
|
58
|
+
isVoid: true
|
|
59
|
+
}),
|
|
60
|
+
file: (boxValue, encode) => ({
|
|
61
|
+
tagName: "a",
|
|
62
|
+
attributes: {
|
|
63
|
+
href: boxValue.url,
|
|
64
|
+
target: "_blank"
|
|
65
|
+
},
|
|
66
|
+
innerHTML: encode(boxValue.name)
|
|
67
|
+
}),
|
|
68
|
+
codeBlock: (boxValue, encode) => ({
|
|
69
|
+
tagName: "pre",
|
|
70
|
+
attributes: {
|
|
71
|
+
class: `lang-${boxValue.lang}`
|
|
72
|
+
},
|
|
73
|
+
innerHTML: `<code>${encode(boxValue.code)}</code>`
|
|
74
|
+
}),
|
|
75
|
+
emoji: (boxValue) => ({
|
|
76
|
+
tagName: "img",
|
|
77
|
+
attributes: {
|
|
78
|
+
src: boxValue.url,
|
|
79
|
+
width: "32",
|
|
80
|
+
height: "32",
|
|
81
|
+
border: "0"
|
|
82
|
+
},
|
|
83
|
+
isVoid: true
|
|
84
|
+
}),
|
|
85
|
+
equation: (boxValue) => ({
|
|
86
|
+
tagName: "code",
|
|
87
|
+
innerHTML: boxValue.code
|
|
88
|
+
}),
|
|
89
|
+
video: (boxValue) => ({
|
|
90
|
+
tagName: "iframe",
|
|
91
|
+
attributes: __spreadProps(__spreadValues({}, boxValue.url && { src: `https://www.youtube.com/embed/${extractIdFromUrl(boxValue.url)}` }), {
|
|
92
|
+
title: "YouTube video player",
|
|
93
|
+
frameborder: "0",
|
|
94
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
|
|
95
|
+
referrerpolicy: "strict-origin-when-cross-origin",
|
|
96
|
+
allowfullscreen: "true",
|
|
97
|
+
style: "width: 560px; height: 315px;"
|
|
98
|
+
})
|
|
99
|
+
}),
|
|
100
|
+
twitter: (boxValue) => ({
|
|
101
|
+
tagName: "iframe",
|
|
102
|
+
attributes: __spreadProps(__spreadValues({}, boxValue.url && { src: `https://platform.twitter.com/embed/Tweet.html?id=${extractIdFromUrl(boxValue.url)}` }), {
|
|
103
|
+
title: "Twitter tweet",
|
|
104
|
+
scrolling: "no",
|
|
105
|
+
frameborder: "0",
|
|
106
|
+
allowtransparency: "true",
|
|
107
|
+
allowfullscreen: "true",
|
|
108
|
+
style: "width: 550px; height: 300px;"
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
var htmlEntityMap = /* @__PURE__ */ new Map([
|
|
114
|
+
["&", "&"],
|
|
115
|
+
["<", "<"],
|
|
116
|
+
[">", ">"],
|
|
117
|
+
['"', """],
|
|
118
|
+
["\xA0", " "]
|
|
119
|
+
]);
|
|
120
|
+
function encodeHTMLEntities(value) {
|
|
121
|
+
return value.replace(/[&<>"\xA0]/g, (match) => htmlEntityMap.get(match));
|
|
4
122
|
}
|
|
5
|
-
|
|
123
|
+
function decodeBase64(value) {
|
|
124
|
+
const binaryString = atob(value);
|
|
125
|
+
const byteArray = new Uint8Array(binaryString.length);
|
|
126
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
127
|
+
byteArray[i] = binaryString.charCodeAt(i);
|
|
128
|
+
}
|
|
129
|
+
const decoder = new TextDecoder();
|
|
130
|
+
return decoder.decode(byteArray);
|
|
131
|
+
}
|
|
132
|
+
function parseAttributes(tag) {
|
|
133
|
+
const attributes = {};
|
|
134
|
+
const reg = /\s+(?:([\w\-:]+)=([^\s"'<>]+)|([\w\-:"]+)="([^"]*)"|([\w\-:"]+)='([^']*)')(?=[\s/>])/g;
|
|
135
|
+
let match;
|
|
136
|
+
while (match = reg.exec(tag)) {
|
|
137
|
+
const key = (match[1] || match[3] || match[5]).toLowerCase();
|
|
138
|
+
const value = match[1] ? match[2] : match[3] ? match[4] : match[6];
|
|
139
|
+
attributes[key] = value;
|
|
140
|
+
}
|
|
141
|
+
return attributes;
|
|
142
|
+
}
|
|
143
|
+
function serializeAttributes(attrs) {
|
|
144
|
+
const result = [];
|
|
145
|
+
for (const key of Object.keys(attrs)) {
|
|
146
|
+
const value = String(attrs[key]);
|
|
147
|
+
result.push(`${key}="${encodeHTMLEntities(value)}"`);
|
|
148
|
+
}
|
|
149
|
+
return result.join(" ");
|
|
150
|
+
}
|
|
151
|
+
function toHTML(value, rules) {
|
|
152
|
+
const config = rules != null ? rules : getDefaultBoxRenderers();
|
|
153
|
+
const combinedRegex = /(<lake-box[^>]+>)[\s\S]*?(?:<\/lake-box>|$)|(<anchor\s*\/>)|(<focus\s*\/>)/gi;
|
|
154
|
+
return value.replace(combinedRegex, (match, boxOpen) => {
|
|
155
|
+
var _a;
|
|
156
|
+
if (boxOpen) {
|
|
157
|
+
const attributes = parseAttributes(boxOpen);
|
|
158
|
+
const render = config[attributes.name];
|
|
159
|
+
if (render) {
|
|
160
|
+
try {
|
|
161
|
+
const decodedValue = attributes.value ? JSON.parse(decodeBase64(attributes.value)) : {};
|
|
162
|
+
const result = render(decodedValue, encodeHTMLEntities);
|
|
163
|
+
if (typeof result === "string") {
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
let html = `<${result.tagName}`;
|
|
167
|
+
if (result.attributes) {
|
|
168
|
+
html += ` ${serializeAttributes(result.attributes)}`;
|
|
169
|
+
}
|
|
170
|
+
if (result.isVoid === true) {
|
|
171
|
+
html += " />";
|
|
172
|
+
} else {
|
|
173
|
+
html += `>${(_a = result.innerHTML) != null ? _a : ""}</${result.tagName}>`;
|
|
174
|
+
}
|
|
175
|
+
return html;
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.error("Failed to parse lake-box value:", e);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return "";
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
185
|
+
0 && (module.exports = {
|
|
186
|
+
getDefaultBoxRenderers,
|
|
6
187
|
toHTML
|
|
7
|
-
};
|
|
188
|
+
});
|
|
8
189
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Define a generic type for HTML attributes (key-value pairs)\ntype AttributeMap = Record<string, string>;\n\n// Structure representing the output HTML node\ninterface BoxNode {\n tagName: string;\n attributes?: AttributeMap;\n isVoid?: boolean; // True for self-closing tags like <img />, <hr />\n innerHTML?: string;\n}\n\n// Function signature for encoding HTML entities\ntype EntityEncoder = (value: string) => string;\n\n// Function signature for encoding HTML entities\ntype BoxRenderer = (boxValue: AttributeMap, encode: EntityEncoder) => BoxNode | string;\n\n// Registry of renderers for different box types\ntype BoxRenderers = Record<string, BoxRenderer>;\n\n/**\n * Extracts the ID from a URL.\n * Useful for extracting YouTube or Twitter IDs from clean URLs.\n */\nfunction extractIdFromUrl(url: string): string {\n const result = /[\\w\\-]+$/.exec(url);\n return result ? result[0] : '';\n}\n\n/**\n * Returns the default configuration for rendering various Lake attributes.\n */\nexport function getDefaultBoxRenderers(): BoxRenderers {\n return {\n hr: () => '<div class=\"lake-box-block lake-hr\"><hr /></div>',\n\n image: boxValue => ({\n tagName: 'img',\n attributes: {\n src: boxValue.url,\n ...(boxValue.width && { width: boxValue.width }),\n ...(boxValue.height && { height: boxValue.height }),\n ...(boxValue.caption && { alt: boxValue.caption }),\n border: '0',\n },\n isVoid: true,\n }),\n\n file: (boxValue, encode) => ({\n tagName: 'a',\n attributes: {\n href: boxValue.url,\n target: '_blank',\n },\n innerHTML: encode(boxValue.name),\n }),\n\n codeBlock: (boxValue, encode) => ({\n tagName: 'pre',\n attributes: {\n class: `lang-${boxValue.lang}`,\n },\n innerHTML: `<code>${encode(boxValue.code)}</code>`,\n }),\n\n emoji: boxValue => ({\n tagName: 'img',\n attributes: {\n src: boxValue.url,\n width: '32',\n height: '32',\n border: '0',\n },\n isVoid: true,\n }),\n\n equation: boxValue => ({\n tagName: 'code',\n innerHTML: boxValue.code,\n }),\n\n video: boxValue => ({\n tagName: 'iframe',\n attributes: {\n ...(boxValue.url && { src: `https://www.youtube.com/embed/${extractIdFromUrl(boxValue.url)}` }),\n title: 'YouTube video player',\n frameborder: '0',\n allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share',\n referrerpolicy: 'strict-origin-when-cross-origin',\n allowfullscreen: 'true',\n style: 'width: 560px; height: 315px;',\n },\n }),\n\n twitter: boxValue => ({\n tagName: 'iframe',\n attributes: {\n ...(boxValue.url && { src: `https://platform.twitter.com/embed/Tweet.html?id=${extractIdFromUrl(boxValue.url)}` }),\n title: 'Twitter tweet',\n scrolling: 'no',\n frameborder: '0',\n allowtransparency: 'true',\n allowfullscreen: 'true',\n style: 'width: 550px; height: 300px;',\n },\n }),\n };\n}\n\n// Map for reserved HTML characters\nconst htmlEntityMap = new Map([\n ['&', '&'],\n ['<', '<'],\n ['>', '>'],\n ['\"', '"'],\n ['\\xA0', ' '],\n]);\n\n/**\n * Escapes reserved HTML characters to prevent XSS and rendering issues.\n */\nfunction encodeHTMLEntities(value: string): string {\n return value.replace(/[&<>\"\\xA0]/g, match => htmlEntityMap.get(match)!);\n}\n\n/**\n * Decodes a Base64 encoded string with UTF-8 support.\n */\nfunction decodeBase64(value: string): string {\n const binaryString = atob(value);\n const byteArray = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n byteArray[i] = binaryString.charCodeAt(i);\n }\n const decoder = new TextDecoder();\n return decoder.decode(byteArray);\n}\n\n/**\n * Parses HTML tag attributes from a string into a key-value object.\n */\nfunction parseAttributes(tag: string): AttributeMap {\n const attributes: AttributeMap = {};\n // Regex breakdown:\n // Group 1/2: Key=Value (unquoted)\n // Group 3/4: Key=\"Value\" (double quoted)\n // Group 5/6: Key='Value' (single quoted)\n const reg = /\\s+(?:([\\w\\-:]+)=([^\\s\"'<>]+)|([\\w\\-:\"]+)=\"([^\"]*)\"|([\\w\\-:\"]+)='([^']*)')(?=[\\s/>])/g;\n let match: RegExpExecArray | null;\n while ((match = reg.exec(tag))) {\n const key = (match[1] || match[3] || match[5]).toLowerCase();\n const value = match[1] ? match[2] : (match[3] ? match[4] : match[6]);\n attributes[key] = value;\n }\n return attributes;\n}\n\n/**\n * Serializes an attribute map into an HTML attribute string.\n */\nfunction serializeAttributes(attrs: AttributeMap): string {\n const result: string[] = [];\n for (const key of Object.keys(attrs)) {\n const value = String(attrs[key]);\n result.push(`${key}=\"${encodeHTMLEntities(value)}\"`);\n }\n return result.join(' ');\n}\n\n/**\n * Main function to convert Lake Markup Language (LML) to standard HTML.\n * It processes custom <lake-box> tags and removes internal anchors.\n */\nexport function toHTML(value: string, rules?: BoxRenderers): string {\n const config = rules ?? getDefaultBoxRenderers();\n // Regex to match <lake-box>, <anchor>, and <focus> tags\n const combinedRegex = /(<lake-box[^>]+>)[\\s\\S]*?(?:<\\/lake-box>|$)|(<anchor\\s*\\/>)|(<focus\\s*\\/>)/gi;\n return value.replace(combinedRegex, (match, boxOpen) => {\n // Handle lake-box conversion\n if (boxOpen) {\n const attributes = parseAttributes(boxOpen);\n const render = config[attributes.name];\n if (render) {\n try {\n const decodedValue = attributes.value ? JSON.parse(decodeBase64(attributes.value)) : {};\n const result = render(decodedValue, encodeHTMLEntities);\n // If renderer returns a raw string, return it directly\n if (typeof result === 'string') {\n return result;\n }\n // Otherwise, build the HTML tag from BoxNode\n let html = `<${result.tagName}`;\n if (result.attributes) {\n html += ` ${serializeAttributes(result.attributes)}`;\n }\n if (result.isVoid === true) {\n html += ' />';\n } else {\n html += `>${result.innerHTML ?? ''}</${result.tagName}>`;\n }\n return html;\n } catch (e) {\n console.error('Failed to parse lake-box value:', e);\n }\n }\n }\n // Remove internal selection markers (<anchor /> and <focus />)\n return '';\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
function extractIdFromUrl(url) {
|
|
23
|
+
const result = /[\w\-]+$/.exec(url);
|
|
24
|
+
return result ? result[0] : "";
|
|
25
|
+
}
|
|
26
|
+
function getDefaultBoxRenderers() {
|
|
27
|
+
return {
|
|
28
|
+
hr: () => '<div class="lake-box-block lake-hr"><hr /></div>',
|
|
29
|
+
image: (boxValue) => ({
|
|
30
|
+
tagName: "img",
|
|
31
|
+
attributes: __spreadProps(__spreadValues(__spreadValues(__spreadValues({
|
|
32
|
+
src: boxValue.url
|
|
33
|
+
}, boxValue.width && { width: boxValue.width }), boxValue.height && { height: boxValue.height }), boxValue.caption && { alt: boxValue.caption }), {
|
|
34
|
+
border: "0"
|
|
35
|
+
}),
|
|
36
|
+
isVoid: true
|
|
37
|
+
}),
|
|
38
|
+
file: (boxValue, encode) => ({
|
|
39
|
+
tagName: "a",
|
|
40
|
+
attributes: {
|
|
41
|
+
href: boxValue.url,
|
|
42
|
+
target: "_blank"
|
|
43
|
+
},
|
|
44
|
+
innerHTML: encode(boxValue.name)
|
|
45
|
+
}),
|
|
46
|
+
codeBlock: (boxValue, encode) => ({
|
|
47
|
+
tagName: "pre",
|
|
48
|
+
attributes: {
|
|
49
|
+
class: `lang-${boxValue.lang}`
|
|
50
|
+
},
|
|
51
|
+
innerHTML: `<code>${encode(boxValue.code)}</code>`
|
|
52
|
+
}),
|
|
53
|
+
emoji: (boxValue) => ({
|
|
54
|
+
tagName: "img",
|
|
55
|
+
attributes: {
|
|
56
|
+
src: boxValue.url,
|
|
57
|
+
width: "32",
|
|
58
|
+
height: "32",
|
|
59
|
+
border: "0"
|
|
60
|
+
},
|
|
61
|
+
isVoid: true
|
|
62
|
+
}),
|
|
63
|
+
equation: (boxValue) => ({
|
|
64
|
+
tagName: "code",
|
|
65
|
+
innerHTML: boxValue.code
|
|
66
|
+
}),
|
|
67
|
+
video: (boxValue) => ({
|
|
68
|
+
tagName: "iframe",
|
|
69
|
+
attributes: __spreadProps(__spreadValues({}, boxValue.url && { src: `https://www.youtube.com/embed/${extractIdFromUrl(boxValue.url)}` }), {
|
|
70
|
+
title: "YouTube video player",
|
|
71
|
+
frameborder: "0",
|
|
72
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
|
|
73
|
+
referrerpolicy: "strict-origin-when-cross-origin",
|
|
74
|
+
allowfullscreen: "true",
|
|
75
|
+
style: "width: 560px; height: 315px;"
|
|
76
|
+
})
|
|
77
|
+
}),
|
|
78
|
+
twitter: (boxValue) => ({
|
|
79
|
+
tagName: "iframe",
|
|
80
|
+
attributes: __spreadProps(__spreadValues({}, boxValue.url && { src: `https://platform.twitter.com/embed/Tweet.html?id=${extractIdFromUrl(boxValue.url)}` }), {
|
|
81
|
+
title: "Twitter tweet",
|
|
82
|
+
scrolling: "no",
|
|
83
|
+
frameborder: "0",
|
|
84
|
+
allowtransparency: "true",
|
|
85
|
+
allowfullscreen: "true",
|
|
86
|
+
style: "width: 550px; height: 300px;"
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
var htmlEntityMap = /* @__PURE__ */ new Map([
|
|
92
|
+
["&", "&"],
|
|
93
|
+
["<", "<"],
|
|
94
|
+
[">", ">"],
|
|
95
|
+
['"', """],
|
|
96
|
+
["\xA0", " "]
|
|
97
|
+
]);
|
|
98
|
+
function encodeHTMLEntities(value) {
|
|
99
|
+
return value.replace(/[&<>"\xA0]/g, (match) => htmlEntityMap.get(match));
|
|
100
|
+
}
|
|
101
|
+
function decodeBase64(value) {
|
|
102
|
+
const binaryString = atob(value);
|
|
103
|
+
const byteArray = new Uint8Array(binaryString.length);
|
|
104
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
105
|
+
byteArray[i] = binaryString.charCodeAt(i);
|
|
106
|
+
}
|
|
107
|
+
const decoder = new TextDecoder();
|
|
108
|
+
return decoder.decode(byteArray);
|
|
109
|
+
}
|
|
110
|
+
function parseAttributes(tag) {
|
|
111
|
+
const attributes = {};
|
|
112
|
+
const reg = /\s+(?:([\w\-:]+)=([^\s"'<>]+)|([\w\-:"]+)="([^"]*)"|([\w\-:"]+)='([^']*)')(?=[\s/>])/g;
|
|
113
|
+
let match;
|
|
114
|
+
while (match = reg.exec(tag)) {
|
|
115
|
+
const key = (match[1] || match[3] || match[5]).toLowerCase();
|
|
116
|
+
const value = match[1] ? match[2] : match[3] ? match[4] : match[6];
|
|
117
|
+
attributes[key] = value;
|
|
118
|
+
}
|
|
119
|
+
return attributes;
|
|
120
|
+
}
|
|
121
|
+
function serializeAttributes(attrs) {
|
|
122
|
+
const result = [];
|
|
123
|
+
for (const key of Object.keys(attrs)) {
|
|
124
|
+
const value = String(attrs[key]);
|
|
125
|
+
result.push(`${key}="${encodeHTMLEntities(value)}"`);
|
|
126
|
+
}
|
|
127
|
+
return result.join(" ");
|
|
128
|
+
}
|
|
129
|
+
function toHTML(value, rules) {
|
|
130
|
+
const config = rules != null ? rules : getDefaultBoxRenderers();
|
|
131
|
+
const combinedRegex = /(<lake-box[^>]+>)[\s\S]*?(?:<\/lake-box>|$)|(<anchor\s*\/>)|(<focus\s*\/>)/gi;
|
|
132
|
+
return value.replace(combinedRegex, (match, boxOpen) => {
|
|
133
|
+
var _a;
|
|
134
|
+
if (boxOpen) {
|
|
135
|
+
const attributes = parseAttributes(boxOpen);
|
|
136
|
+
const render = config[attributes.name];
|
|
137
|
+
if (render) {
|
|
138
|
+
try {
|
|
139
|
+
const decodedValue = attributes.value ? JSON.parse(decodeBase64(attributes.value)) : {};
|
|
140
|
+
const result = render(decodedValue, encodeHTMLEntities);
|
|
141
|
+
if (typeof result === "string") {
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
let html = `<${result.tagName}`;
|
|
145
|
+
if (result.attributes) {
|
|
146
|
+
html += ` ${serializeAttributes(result.attributes)}`;
|
|
147
|
+
}
|
|
148
|
+
if (result.isVoid === true) {
|
|
149
|
+
html += " />";
|
|
150
|
+
} else {
|
|
151
|
+
html += `>${(_a = result.innerHTML) != null ? _a : ""}</${result.tagName}>`;
|
|
152
|
+
}
|
|
153
|
+
return html;
|
|
154
|
+
} catch (e) {
|
|
155
|
+
console.error("Failed to parse lake-box value:", e);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return "";
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
export {
|
|
163
|
+
getDefaultBoxRenderers,
|
|
164
|
+
toHTML
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Define a generic type for HTML attributes (key-value pairs)\ntype AttributeMap = Record<string, string>;\n\n// Structure representing the output HTML node\ninterface BoxNode {\n tagName: string;\n attributes?: AttributeMap;\n isVoid?: boolean; // True for self-closing tags like <img />, <hr />\n innerHTML?: string;\n}\n\n// Function signature for encoding HTML entities\ntype EntityEncoder = (value: string) => string;\n\n// Function signature for encoding HTML entities\ntype BoxRenderer = (boxValue: AttributeMap, encode: EntityEncoder) => BoxNode | string;\n\n// Registry of renderers for different box types\ntype BoxRenderers = Record<string, BoxRenderer>;\n\n/**\n * Extracts the ID from a URL.\n * Useful for extracting YouTube or Twitter IDs from clean URLs.\n */\nfunction extractIdFromUrl(url: string): string {\n const result = /[\\w\\-]+$/.exec(url);\n return result ? result[0] : '';\n}\n\n/**\n * Returns the default configuration for rendering various Lake attributes.\n */\nexport function getDefaultBoxRenderers(): BoxRenderers {\n return {\n hr: () => '<div class=\"lake-box-block lake-hr\"><hr /></div>',\n\n image: boxValue => ({\n tagName: 'img',\n attributes: {\n src: boxValue.url,\n ...(boxValue.width && { width: boxValue.width }),\n ...(boxValue.height && { height: boxValue.height }),\n ...(boxValue.caption && { alt: boxValue.caption }),\n border: '0',\n },\n isVoid: true,\n }),\n\n file: (boxValue, encode) => ({\n tagName: 'a',\n attributes: {\n href: boxValue.url,\n target: '_blank',\n },\n innerHTML: encode(boxValue.name),\n }),\n\n codeBlock: (boxValue, encode) => ({\n tagName: 'pre',\n attributes: {\n class: `lang-${boxValue.lang}`,\n },\n innerHTML: `<code>${encode(boxValue.code)}</code>`,\n }),\n\n emoji: boxValue => ({\n tagName: 'img',\n attributes: {\n src: boxValue.url,\n width: '32',\n height: '32',\n border: '0',\n },\n isVoid: true,\n }),\n\n equation: boxValue => ({\n tagName: 'code',\n innerHTML: boxValue.code,\n }),\n\n video: boxValue => ({\n tagName: 'iframe',\n attributes: {\n ...(boxValue.url && { src: `https://www.youtube.com/embed/${extractIdFromUrl(boxValue.url)}` }),\n title: 'YouTube video player',\n frameborder: '0',\n allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share',\n referrerpolicy: 'strict-origin-when-cross-origin',\n allowfullscreen: 'true',\n style: 'width: 560px; height: 315px;',\n },\n }),\n\n twitter: boxValue => ({\n tagName: 'iframe',\n attributes: {\n ...(boxValue.url && { src: `https://platform.twitter.com/embed/Tweet.html?id=${extractIdFromUrl(boxValue.url)}` }),\n title: 'Twitter tweet',\n scrolling: 'no',\n frameborder: '0',\n allowtransparency: 'true',\n allowfullscreen: 'true',\n style: 'width: 550px; height: 300px;',\n },\n }),\n };\n}\n\n// Map for reserved HTML characters\nconst htmlEntityMap = new Map([\n ['&', '&'],\n ['<', '<'],\n ['>', '>'],\n ['\"', '"'],\n ['\\xA0', ' '],\n]);\n\n/**\n * Escapes reserved HTML characters to prevent XSS and rendering issues.\n */\nfunction encodeHTMLEntities(value: string): string {\n return value.replace(/[&<>\"\\xA0]/g, match => htmlEntityMap.get(match)!);\n}\n\n/**\n * Decodes a Base64 encoded string with UTF-8 support.\n */\nfunction decodeBase64(value: string): string {\n const binaryString = atob(value);\n const byteArray = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n byteArray[i] = binaryString.charCodeAt(i);\n }\n const decoder = new TextDecoder();\n return decoder.decode(byteArray);\n}\n\n/**\n * Parses HTML tag attributes from a string into a key-value object.\n */\nfunction parseAttributes(tag: string): AttributeMap {\n const attributes: AttributeMap = {};\n // Regex breakdown:\n // Group 1/2: Key=Value (unquoted)\n // Group 3/4: Key=\"Value\" (double quoted)\n // Group 5/6: Key='Value' (single quoted)\n const reg = /\\s+(?:([\\w\\-:]+)=([^\\s\"'<>]+)|([\\w\\-:\"]+)=\"([^\"]*)\"|([\\w\\-:\"]+)='([^']*)')(?=[\\s/>])/g;\n let match: RegExpExecArray | null;\n while ((match = reg.exec(tag))) {\n const key = (match[1] || match[3] || match[5]).toLowerCase();\n const value = match[1] ? match[2] : (match[3] ? match[4] : match[6]);\n attributes[key] = value;\n }\n return attributes;\n}\n\n/**\n * Serializes an attribute map into an HTML attribute string.\n */\nfunction serializeAttributes(attrs: AttributeMap): string {\n const result: string[] = [];\n for (const key of Object.keys(attrs)) {\n const value = String(attrs[key]);\n result.push(`${key}=\"${encodeHTMLEntities(value)}\"`);\n }\n return result.join(' ');\n}\n\n/**\n * Main function to convert Lake Markup Language (LML) to standard HTML.\n * It processes custom <lake-box> tags and removes internal anchors.\n */\nexport function toHTML(value: string, rules?: BoxRenderers): string {\n const config = rules ?? getDefaultBoxRenderers();\n // Regex to match <lake-box>, <anchor>, and <focus> tags\n const combinedRegex = /(<lake-box[^>]+>)[\\s\\S]*?(?:<\\/lake-box>|$)|(<anchor\\s*\\/>)|(<focus\\s*\\/>)/gi;\n return value.replace(combinedRegex, (match, boxOpen) => {\n // Handle lake-box conversion\n if (boxOpen) {\n const attributes = parseAttributes(boxOpen);\n const render = config[attributes.name];\n if (render) {\n try {\n const decodedValue = attributes.value ? JSON.parse(decodeBase64(attributes.value)) : {};\n const result = render(decodedValue, encodeHTMLEntities);\n // If renderer returns a raw string, return it directly\n if (typeof result === 'string') {\n return result;\n }\n // Otherwise, build the HTML tag from BoxNode\n let html = `<${result.tagName}`;\n if (result.attributes) {\n html += ` ${serializeAttributes(result.attributes)}`;\n }\n if (result.isVoid === true) {\n html += ' />';\n } else {\n html += `>${result.innerHTML ?? ''}</${result.tagName}>`;\n }\n return html;\n } catch (e) {\n console.error('Failed to parse lake-box value:', e);\n }\n }\n }\n // Remove internal selection markers (<anchor /> and <focus />)\n return '';\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lake-html",
|
|
3
|
-
"
|
|
4
|
-
"version": "0.0.2",
|
|
3
|
+
"version": "1.0.0",
|
|
5
4
|
"description": "A lightweight utility to convert Lake Markup Language (LML) to HTML",
|
|
6
5
|
"author": "Luo Longhao <luolonghao@gmail.com>",
|
|
7
6
|
"license": "MIT",
|
|
@@ -42,7 +41,7 @@
|
|
|
42
41
|
"semver": "^7.7.3",
|
|
43
42
|
"tsup": "^8.5.1",
|
|
44
43
|
"typescript": "^5.9.3",
|
|
45
|
-
"vitest": "^4.0.
|
|
44
|
+
"vitest": "^4.0.18"
|
|
46
45
|
},
|
|
47
46
|
"scripts": {
|
|
48
47
|
"build": "tsup",
|
package/dist/index.cjs
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
toHTML: () => toHTML
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(index_exports);
|
|
26
|
-
function toHTML(value) {
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
30
|
-
0 && (module.exports = {
|
|
31
|
-
toHTML
|
|
32
|
-
});
|
|
33
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Converts LML string to HTML string.\n */\nexport function toHTML(value: string): string {\n return value;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,SAAS,OAAO,OAAuB;AAC5C,SAAO;AACT;","names":[]}
|