Adding hotfixes for packages
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Fabian Stamm 2020-10-14 02:56:11 +02:00
parent 46d8f8b289
commit 1b2d85eeef
95 changed files with 12467 additions and 2 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
test.ini

16
jsx-html/.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: ci
on:
push:
branches:
- master
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: denolib/setup-deno@master
- run: deno --version
- run: deno test examples/01.tsx
- run: deno test examples/03-async.tsx

3
jsx-html/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/dist
node_modules
tmp

View File

@ -0,0 +1,5 @@
{
"trailingComma": "all",
"tabWidth": 4,
"singleQuote": true
}

4
jsx-html/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"deno.enable": true,
"deno.unstable": true
}

21
jsx-html/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020 Alexandre Piel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

103
jsx-html/README.md Normal file
View File

@ -0,0 +1,103 @@
# jsx-html
`jsx-html` render JSX template to HTML asynchronously. Compatible with Deno, NodeJs and can also run in browser.
Try with runkit: https://runkit.com/apiel/jsx-html-example
## NodeJs
```sh
yarn add async-jsx-html
# or
npm install async-jsx-html
```
```tsx
import { React } from 'async-jsx-html';
const View = () => <div>Hello</div>;
// render return a Promise
(<View />).render().then((html: string) => console.log(html));
```
## Deno
```tsx
/// <reference path="https://raw.githubusercontent.com/apiel/jsx-html/master/jsx.d.ts" />
import { React } from 'https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts';
const View = () => <div>Hello</div>;
// render return a Promise
(<View />).render().then((html: string) => console.log(html));
```
```sh
deno run https://raw.githubusercontent.com/apiel/jsx-html/master/examples/00.tsx
```
## TsConfig
As you would do with React, you need to import `React` from `jsx-html` for the transpiler. If you are not feeling confortable with using `React` as import since it is not React, you can import `jsx` from `jsx-html` but you would have to update your tsconfig file: https://github.com/denoland/deno/issues/3572
```json
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "jsx"
}
}
```
```tsx
/// <reference path="https://raw.githubusercontent.com/apiel/jsx-html/master/jsx.d.ts" />
import { jsx } from 'https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts';
const View = () => <div>Hello</div>;
(<View />).render().then(console.log);
```
> **Note:** prefer using sermver tags version instead of master to avoid conflict with caching, e.g:
> `import { jsx } from 'https://raw.githubusercontent.com/apiel/jsx-html/1.0.0/mod.ts';`.
## Async component
Unlike React, components can be asynchrone, so you can fetch for data without to handle states.
```tsx
import { React } from 'https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts';
const Data = async () => {
const res = await fetch('http://example.com/some/api');
const content = new Uint8Array(await res.arrayBuffer());
return <div>{content}</div>;
};
const View = () => (
<div>
<Data />
</div>
);
(<View />).render().then(console.log);
```
# InnerHTML
The Element property innerHTML sets the HTML or XML markup contained within the property.
In general, setting HTML from code is risky because its easy to inadvertently expose your users to a cross-site scripting (XSS) attack.
```tsx
/// <reference path="https://raw.githubusercontent.com/apiel/jsx-html/master/jsx.d.ts" />
import { jsx } from 'https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts';
const View = () => <div innerHTML="<b>hello</b> world" />;
(<View />).render().then(console.log); // will output <div><b>hello</b> world</div>
```
## Browser
`jsx-html` can also be used directly in browser. Find an example with webpack [here](https://github.com/apiel/jsx-html/tree/master/examples/browser).

View File

@ -0,0 +1,26 @@
'use strict';
var regExp = /\.(ts|tsx|js|jsx)$/i;
module.exports = function () {
return {
visitor: {
ImportDeclaration: function ImportDeclaration(path) {
var source = path.node.source;
if (!source.value.match(regExp)) {
return;
}
source.value = source.value.replace(regExp, '');
},
ExportDeclaration: function ExportDeclaration(path) {
var source = path.node.source;
if (source) {
if (!source.value.match(regExp)) {
return;
}
source.value = source.value.replace(regExp, '');
}
},
},
};
};

6
jsx-html/constants.ts Normal file
View File

@ -0,0 +1,6 @@
export enum NODE_TYPE {
ELEMENT = 'element',
TEXT = 'text',
COMPONENT = 'component',
FRAGMENT = 'fragment',
};

3496
jsx-html/deno.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

17
jsx-html/deno.json Normal file
View File

@ -0,0 +1,17 @@
{
"cmds": {
"start": ["den 00", "den 01", "den 02", "den 03", "den 04", "den 05", "den 06"],
"test": ["den 01:test", "den 03:test", "den 05:test", "den 06:test"],
"00": "deno run examples/00.tsx",
"01": "deno run examples/01.tsx",
"01:test": "deno test examples/01.tsx",
"02": "deno run -c examples/02/tsconfig.json examples/02/02.tsx",
"03": "deno run examples/03-async.tsx",
"03:test": "deno test examples/03-async.tsx",
"04": "deno run examples/04.tsx",
"05": "deno run examples/05.tsx",
"05:test": "deno test examples/05.tsx",
"06": "deno run examples/06.tsx",
"06:test": "deno test examples/06.tsx"
}
}

101
jsx-html/deno2nodejs.js Normal file
View File

@ -0,0 +1,101 @@
const fs = require('fs');
const { parse } = require('@babel/parser');
const { default: generator } = require('@babel/generator');
const { default: traverse } = require('@babel/traverse');
const { resolve, extname, join, dirname } = require('path');
const { tmpdir } = require('os');
const { readdir } = require('fs').promises;
const { cwd } = require('process');
const exts = ['.ts'];
const excludes = [
'/node_modules/',
'/examples/',
'/dist/',
'/jsx.d.ts',
'/mod.d.ts',
'/deno.d.ts',
];
const regExpRemoveExts = /\.(ts|tsx|js|jsx)$/i;
const tsconfig = {
compilerOptions: {
types: ['node'],
module: 'commonjs',
declaration: true,
removeComments: true,
emitDecoratorMetadata: true,
experimentalDecorators: true,
allowSyntheticDefaultImports: true,
target: 'es6',
sourceMap: true,
outDir: join(cwd(), 'nodejs'),
baseUrl: './',
},
};
// const distFolder = join(tmpdir(), `deno2nodejs-${+new Date()}`);
const distFolder = join(cwd(), `tmp`);
console.log('distFolder:', distFolder);
fs.mkdirSync(distFolder);
fs.writeFileSync(join(distFolder, 'tsconfig.json'), JSON.stringify(tsconfig));
async function getFiles(dir) {
const dirents = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(
dirents.map((dirent) => {
const res = resolve(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
}),
);
return Array.prototype.concat(...files);
}
getFiles('./').then((files) => {
const tsFiles = files.filter(
(f) =>
exts.includes(extname(f)) &&
!excludes.some((val) => f.includes(val)),
);
tsFiles.forEach((file) => {
const code = deno2nodejs(file);
const dist = join(distFolder, file.substr(cwd().length));
const ensureDir = dirname(dist);
fs.mkdirSync(ensureDir, { recursive: true });
fs.writeFileSync(dist, code);
// console.log({ file, dist, ensureDir });
});
});
// we might want to execute `tsc -p ./tmp/tsconfig.json` in here
function deno2nodejs(file) {
const source = fs.readFileSync(file).toString();
const ast = parse(source, {
sourceType: 'module',
plugins: ['typescript', 'classProperties'],
});
traverse(ast, {
ImportDeclaration: function ImportDeclaration(path) {
var source = path.node.source;
if (!source.value.match(regExpRemoveExts)) {
return;
}
source.value = source.value.replace(regExpRemoveExts, '');
},
ExportDeclaration: function ExportDeclaration(path) {
var source = path.node.source;
if (source) {
if (!source.value.match(regExpRemoveExts)) {
return;
}
source.value = source.value.replace(regExpRemoveExts, '');
}
},
});
const { code } = generator(ast);
return code;
}

6
jsx-html/examples/00.tsx Normal file
View File

@ -0,0 +1,6 @@
/// <reference path="https://raw.githubusercontent.com/apiel/jsx-html/latest/jsx.d.ts" />
import { React } from 'https://raw.githubusercontent.com/apiel/jsx-html/latest/mod.ts';
const View = () => <div>Hello</div>;
console.log((<View />).render());

62
jsx-html/examples/01.tsx Normal file
View File

@ -0,0 +1,62 @@
/// <reference path="../jsx.d.ts" />
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { React, Fragment } from '../mod.ts';
const Title = () => <h1>title</h1>;
const Value = ({ val }: { val: string }) => <p>value: {val}</p>;
const Numeric = ({ num }: { num: number }) => <p>num: {num}</p>;
const View = () => (
<div class="deno">
<Title />
<p onclick={() => 'lol'} valid checked={true} select="">
land
</p>
<br />
<hr />
<Fragment>
<Value val="hello" />
<Numeric num={23} />
</Fragment>
</div>
);
if (import.meta.main) {
(<View />).render().then(console.log);
} else {
// Run test
Deno.test('render title', async() => {
assertEquals(await (<Title />).render(), '<h1>title</h1>');
});
Deno.test('render value', async() => {
const val = 'hello';
assertEquals(await (<Value val={val} />).render(), `<p>value: ${val}</p>`);
});
Deno.test('render numeric', async() => {
const num = 123;
assertEquals(await (<Numeric num={num} />).render(), `<p>num: ${num}</p>`);
});
Deno.test('render view', async() => {
assertEquals(
await (<View />).render(),
'<div class="deno"><h1>title</h1><p valid checked select>land</p><br /><hr /><p>value: hello</p><p>num: 23</p></div>',
);
});
Deno.test('render empty', async ()=>{
assertEquals(
await (<div/>).render(),
`<div></div>`
)
assertEquals(
await (<img/>).render(),
`<img />`
)
})
}

View File

@ -0,0 +1,6 @@
/// <reference path="../../jsx.d.ts" />
import { jsx } from '../../mod.ts';
const View = () => <div>Hello</div>;
(<View />).render().then(console.log);

View File

@ -0,0 +1,6 @@
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "jsx"
}
}

View File

@ -0,0 +1,30 @@
/// <reference path="../jsx.d.ts" />
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { delay } from 'https://deno.land/std/async/delay.ts';
import { React } from '../mod.ts';
const Title = async () => {
await delay(100);
return <h1>title{ await delay(100) }</h1>;
};
const View = () => (
<div>
<Title />
</div>
);
if (import.meta.main) {
(<View />).render().then(console.log);
} else {
// Run test
Deno.test('render title', async () => {
assertEquals(await (<Title />).render(), '<h1>title</h1>');
});
Deno.test('render view', async () => {
assertEquals(await (<View />).render(), '<div><h1>title</h1></div>');
});
}

9
jsx-html/examples/04.tsx Normal file
View File

@ -0,0 +1,9 @@
/// <reference path="../jsx.d.ts" />
import { React } from '../mod.ts';
const View = () => <div>Hello</div>;
if (import.meta.main) {
(<View />).render().then(console.log);
}

27
jsx-html/examples/05.tsx Normal file
View File

@ -0,0 +1,27 @@
/// <reference path="../jsx.d.ts" />
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { React } from '../mod.ts';
const View = () => {
const techs = ['NodeJS', 'React Native', 'Next'];
return (
<ul>
{techs.map((tech: any) => (
<li>{tech}</li>
))}
</ul>
);
};
if (import.meta.main) {
(<View />).render().then(console.log);
} else {
Deno.test('render with array', async () => {
assertEquals(
await (<View />).render(),
'<ul><li>NodeJS</li><li>React Native</li><li>Next</li></ul>',
);
});
}

19
jsx-html/examples/06.tsx Normal file
View File

@ -0,0 +1,19 @@
/// <reference path="../jsx.d.ts" />
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { React } from '../mod.ts';
const View = () => {
return <div innerHTML="<b>hello</b> world" />;
};
if (import.meta.main) {
(<View />).render().then(console.log);
} else {
Deno.test('render with array', async () => {
assertEquals(
await (<View />).render(),
'<div><b>hello</b> world</div>',
);
});
}

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<script src="index.js"></script>
<div id="div-container" />
<script>
demo.Test().render('#div-container').then((html) => {
document.getElementById('div-container').innerHTML = html;
});
</script>
</body>
</html>

502
jsx-html/examples/browser/dist/index.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,25 @@
{
"name": "jsx-to-html-testing",
"version": "1.0.0",
"description": "jsx-to-html-testing",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch",
"start": "webpack-dev-server --open",
"build": "webpack"
},
"keywords": [],
"author": "Alex",
"license": "ISC",
"devDependencies": {
"ts-loader": "^8.0.1",
"typescript": "^3.9.7",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"async-jsx-html": "^1.2.1"
}
}

View File

@ -0,0 +1,5 @@
import { jsx, ElementNode } from 'async-jsx-html';
export function Test(): ElementNode {
return <div>Hello World</div>;
}

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"outDir": "./dist/",
"module": "commonjs",
"jsx": "react",
"jsxFactory": "jsx",
"esModuleInterop": true,
"sourceMap": true,
"allowJs": true,
"lib": [
"es6",
"dom"
]
}
}

View File

@ -0,0 +1,33 @@
const path = require('path');
const MODULE_NAME = 'demo';
module.exports = {
mode: 'development',
entry: {
index: './src/index.tsx'
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: 'umd',
globalObject: 'this',
// libraryExport: 'default',
library: MODULE_NAME
},
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
let {
React,
Fragment
} = require('../nodejs/mod');
const Title = () => /*#__PURE__*/React.createElement("h1", null, "title");
const Value = ({
val
}) => /*#__PURE__*/React.createElement("p", null, "value: ", val);
const Numeric = ({
num
}) => /*#__PURE__*/React.createElement("p", null, "num: ", num);
const View = () => /*#__PURE__*/React.createElement("div", {
class: "deno"
}, /*#__PURE__*/React.createElement(Title, null), /*#__PURE__*/React.createElement("p", {
onclick: () => 'lol',
valid: true,
checked: true,
select: ""
}, "land"), /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement("hr", null), /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(Value, {
val: "hello"
}), /*#__PURE__*/React.createElement(Numeric, {
num: 23
})));
/*#__PURE__*/
React.createElement(View, null).render().then(console.log);

View File

@ -0,0 +1,22 @@
let { React, Fragment } = require('../nodejs/mod');
const Title = () => <h1>title</h1>;
const Value = ({ val }) => <p>value: {val}</p>;
const Numeric = ({ num }) => <p>num: {num}</p>;
const View = () => (
<div class="deno">
<Title />
<p onclick={() => 'lol'} valid checked={true} select="">
land
</p>
<br />
<hr />
<Fragment>
<Value val="hello" />
<Numeric num={23} />
</Fragment>
</div>
);
(<View />).render().then(console.log);

5
jsx-html/jsx.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}

33
jsx-html/jsx.ts Normal file
View File

@ -0,0 +1,33 @@
import type {
NodePropsType,
ComponentFunctionType,
NullableChildType,
ChildType,
} from './types.ts';
import { ElementNode } from './node/ElementNode.ts';
import { ComponentNode } from './node/ComponentNode.ts';
export const jsx = <P extends NodePropsType = NodePropsType>(
element: string | ComponentFunctionType,
props: P | null,
...children: NullableChildType[]
) => {
const nodeProps = props || {};
if (typeof element === 'string') {
return new ElementNode(element, nodeProps, children);
}
if (typeof element === 'function') {
return new ComponentNode(element, nodeProps, children);
}
throw new TypeError(`Expected jsx element to be a string or a function`);
};
export const Fragment = (
props: NodePropsType,
children: ChildType,
): NullableChildType => {
return children;
};

12
jsx-html/meta.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "jsx-html",
"version": "0.0.1",
"description": "",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"files": [
"**/*.ts",
"**/*.js",
"README.md"
]
}

27
jsx-html/mod.ts Normal file
View File

@ -0,0 +1,27 @@
import { jsx, Fragment } from './jsx.ts';
import type {
ComponentFunctionType,
NodePropsType,
NullableChildType,
} from './types.ts';
export { ElementNode } from './node/ElementNode.ts';
export { ComponentNode } from './node/ComponentNode.ts';
export type {
jsx,
Fragment,
ComponentFunctionType,
NullableChildType,
NodePropsType,
};
export const React = {
Fragment,
createElement<P extends NodePropsType = NodePropsType>(
element: string | ComponentFunctionType,
props: P | null,
...children: NullableChildType[]
) {
return jsx(element, props, ...children);
},
};

View File

@ -0,0 +1,37 @@
import type {
NodePropsType,
ComponentFunctionType,
NullableChildType,
} from '../types.ts';
import { NODE_TYPE } from '../constants.ts';
import { FragmentNode } from './FragmentNode.ts';
import { Node } from './Node.ts';
import { normalizeChildren } from './utils/normalizeChildren.ts';
export class ComponentNode extends Node {
type = NODE_TYPE.COMPONENT;
constructor(
public component: ComponentFunctionType,
public props: NodePropsType,
children: NullableChildType[],
) {
super(children);
}
async render(): Promise<string | any[]> {
return [].concat((await this.renderComponent()) as any).join('');
}
async renderComponent() {
const child = await this.component(this.props, this.children);
const children = normalizeChildren(
Array.isArray(child) ? child : [child],
);
if (children.length === 1) {
return children[0].render();
} else if (children.length > 1) {
return new FragmentNode(children).render();
}
}
}

View File

@ -0,0 +1,91 @@
import { NODE_TYPE } from '../constants.ts';
import type { NodePropsType, NullableChildType } from '../types.ts';
import { Node } from './Node.ts';
import { doubleQuoteEncode } from './utils/htmlEncode.ts';
const ELEMENT_PROP = {
INNER_HTML: 'innerHTML',
};
// List taken from http://w3c.github.io/html-reference/syntax.html
const VOID_ELEMENTS = new Set<string>([
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr',
]);
export class ElementNode extends Node {
type = NODE_TYPE.ELEMENT;
constructor(
public name: string,
public props: NodePropsType,
children: NullableChildType[],
) {
super(children);
}
async render(): Promise<string | any[]> {
const renderedProps = this.propsToHTML();
const renderedChildren =
typeof this.props[ELEMENT_PROP.INNER_HTML] === 'string'
? this.props[ELEMENT_PROP.INNER_HTML]
: (await this.renderChildren()).join('');
return renderedChildren || !VOID_ELEMENTS.has(this.name)
? `<${this.name}${renderedProps}>${renderedChildren || ''}</${
this.name
}>`
: `<${this.name}${renderedProps} />`;
}
private getValidProps() {
const props = this.props;
return Object.keys(this.props).filter((key) => {
if (key === ELEMENT_PROP.INNER_HTML) {
return false;
}
const val = props[key];
return (
typeof val === 'string' ||
typeof val === 'number' ||
val === true
);
});
}
private propsToHTML(): string {
const keys = this.getValidProps();
if (!keys.length) {
return '';
}
const props = this.props;
const pairs = keys.map((key) => {
if (!/^[a-zA-Z0-9-:\._]+$/.test(key)) {
throw new Error(`Invalid attribute name format ${key}`);
}
const val = props[key];
// https://html.spec.whatwg.org/multipage/dom.html#attributes
return val === true || val === ''
? key
: `${key}="${doubleQuoteEncode(val.toString())}"`;
});
return ` ${pairs.join(' ')}`;
}
}

View File

@ -0,0 +1,15 @@
import { NODE_TYPE } from '../constants.ts';
import type { ChildNodeType } from '../types.ts';
import { Node } from './Node.ts';
export class FragmentNode extends Node {
type = NODE_TYPE.FRAGMENT;
constructor(children: ChildNodeType[]) {
super(children);
}
render() {
return this.renderChildren();
}
}

29
jsx-html/node/Node.ts Normal file
View File

@ -0,0 +1,29 @@
import type { NODE_TYPE } from '../constants.ts';
import type { NullableChildType } from '../types.ts';
import { normalizeChildren } from './utils/normalizeChildren.ts';
export abstract class Node {
abstract type: NODE_TYPE;
constructor(public children: NullableChildType[]) {}
abstract async render(): Promise<string | any[]>;
async renderChildren() {
const result: string[] = [];
const children = normalizeChildren(this.children);
for (const child of children) {
const renderedChild = await child.render();
if (renderedChild) {
if (Array.isArray(renderedChild)) {
renderedChild.forEach(
(subchild) => subchild && result.push(subchild),
);
} else {
result.push(renderedChild);
}
}
}
return result;
}
}

12
jsx-html/node/TextNode.ts Normal file
View File

@ -0,0 +1,12 @@
import { NODE_TYPE } from '../constants.ts';
import { htmlEncode } from './utils/htmlEncode.ts';
export class TextNode {
type = NODE_TYPE.TEXT;
constructor(public text: string) {}
async render(): Promise<string | any[]> {
return htmlEncode(this.text);
}
}

View File

@ -0,0 +1,13 @@
export function doubleQuoteEncode(text: string): string {
return text
.replace(/"/g, '&quot;')
}
export function htmlEncode(text: string): string {
return doubleQuoteEncode(text
.replace(/&/g, '&amp;')
.replace(/\//g, '&#x2F;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/'/g, '&#39;'));
}

View File

@ -0,0 +1,31 @@
import type { NullableChildType, ChildNodeType } from '../../types.ts';
import { TextNode } from '../TextNode.ts';
import { NODE_TYPE } from '../../constants.ts';
export function normalizeChildren(
children: NullableChildType[],
): ChildNodeType[] {
const result: any[] = [];
for (const child of children) {
if (child && typeof child !== 'boolean') {
if (typeof child === 'string' || typeof child === 'number') {
result.push(new TextNode(`${child}`));
} else if (Array.isArray(child)) {
normalizeChildren(child).forEach((normalized) =>
result.push(normalized),
);
} else if (
child.type === NODE_TYPE.ELEMENT ||
child.type === NODE_TYPE.TEXT ||
child.type === NODE_TYPE.COMPONENT
) {
result.push(child);
} else {
throw new TypeError(`Unrecognized node type: ${typeof child}`);
}
}
}
return result;
}

6
jsx-html/nodejs/constants.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export declare enum NODE_TYPE {
ELEMENT = "element",
TEXT = "text",
COMPONENT = "component",
FRAGMENT = "fragment"
}

View File

@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NODE_TYPE = void 0;
var NODE_TYPE;
(function (NODE_TYPE) {
NODE_TYPE["ELEMENT"] = "element";
NODE_TYPE["TEXT"] = "text";
NODE_TYPE["COMPONENT"] = "component";
NODE_TYPE["FRAGMENT"] = "fragment";
})(NODE_TYPE = exports.NODE_TYPE || (exports.NODE_TYPE = {}));
;
//# sourceMappingURL=constants.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../tmp/constants.ts"],"names":[],"mappings":";;;AAAA,IAAY,SAKX;AALD,WAAY,SAAS;IACnB,gCAAmB,CAAA;IACnB,0BAAa,CAAA;IACb,oCAAuB,CAAA;IACvB,kCAAqB,CAAA;AACvB,CAAC,EALW,SAAS,GAAT,iBAAS,KAAT,iBAAS,QAKpB;AACD,CAAC"}

5
jsx-html/nodejs/jsx.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { NodePropsType, ComponentFunctionType, NullableChildType, ChildType } from "./types";
import { ElementNode } from "./node/ElementNode";
import { ComponentNode } from "./node/ComponentNode";
export declare const jsx: <P extends NodePropsType = NodePropsType>(element: string | ComponentFunctionType, props: P, ...children: NullableChildType[]) => ElementNode | ComponentNode;
export declare const Fragment: (props: NodePropsType, children: ChildType) => NullableChildType;

19
jsx-html/nodejs/jsx.js Normal file
View File

@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Fragment = exports.jsx = void 0;
const ElementNode_1 = require("./node/ElementNode");
const ComponentNode_1 = require("./node/ComponentNode");
exports.jsx = (element, props, ...children) => {
const nodeProps = props || {};
if (typeof element === 'string') {
return new ElementNode_1.ElementNode(element, nodeProps, children);
}
if (typeof element === 'function') {
return new ComponentNode_1.ComponentNode(element, nodeProps, children);
}
throw new TypeError(`Expected jsx element to be a string or a function`);
};
exports.Fragment = (props, children) => {
return children;
};
//# sourceMappingURL=jsx.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"jsx.js","sourceRoot":"","sources":["../tmp/jsx.ts"],"names":[],"mappings":";;;AACA,oDAAiD;AACjD,wDAAqD;AACxC,QAAA,GAAG,GAAG,CAA0C,OAAuC,EAAE,KAAe,EAAE,GAAG,QAA6B,EAAE,EAAE;IACzJ,MAAM,SAAS,GAAG,KAAK,IAAI,EAAE,CAAC;IAE9B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE;QAC/B,OAAO,IAAI,yBAAW,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;KACtD;IAED,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE;QACjC,OAAO,IAAI,6BAAa,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;KACxD;IAED,MAAM,IAAI,SAAS,CAAC,mDAAmD,CAAC,CAAC;AAC3E,CAAC,CAAC;AACW,QAAA,QAAQ,GAAG,CAAC,KAAoB,EAAE,QAAmB,EAAqB,EAAE;IACvF,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC"}

9
jsx-html/nodejs/mod.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { jsx, Fragment } from "./jsx";
import { ComponentFunctionType, NodePropsType, NullableChildType } from "./types";
export { ElementNode } from "./node/ElementNode";
export { ComponentNode } from "./node/ComponentNode";
export { jsx, Fragment, ComponentFunctionType, NullableChildType, NodePropsType };
export declare const React: {
Fragment: (props: NodePropsType, children: import("./types").ChildType) => NullableChildType;
createElement<P extends NodePropsType = NodePropsType>(element: string | ComponentFunctionType, props: P, ...children: NullableChildType[]): import("./node/ElementNode").ElementNode | import("./node/ComponentNode").ComponentNode;
};

17
jsx-html/nodejs/mod.js Normal file
View File

@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.React = exports.Fragment = exports.jsx = void 0;
const jsx_1 = require("./jsx");
Object.defineProperty(exports, "jsx", { enumerable: true, get: function () { return jsx_1.jsx; } });
Object.defineProperty(exports, "Fragment", { enumerable: true, get: function () { return jsx_1.Fragment; } });
var ElementNode_1 = require("./node/ElementNode");
Object.defineProperty(exports, "ElementNode", { enumerable: true, get: function () { return ElementNode_1.ElementNode; } });
var ComponentNode_1 = require("./node/ComponentNode");
Object.defineProperty(exports, "ComponentNode", { enumerable: true, get: function () { return ComponentNode_1.ComponentNode; } });
exports.React = {
Fragment: jsx_1.Fragment,
createElement(element, props, ...children) {
return jsx_1.jsx(element, props, ...children);
}
};
//# sourceMappingURL=mod.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"mod.js","sourceRoot":"","sources":["../tmp/mod.ts"],"names":[],"mappings":";;;AAAA,+BAAsC;AAI7B,oFAJA,SAAG,OAIA;AAAE,yFAJA,cAAQ,OAIA;AAFtB,kDAAiD;AAAxC,0GAAA,WAAW,OAAA;AACpB,sDAAqD;AAA5C,8GAAA,aAAa,OAAA;AAET,QAAA,KAAK,GAAG;IACnB,QAAQ,EAAR,cAAQ;IAER,aAAa,CAA0C,OAAuC,EAAE,KAAe,EAAE,GAAG,QAA6B;QAC/I,OAAO,SAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,CAAC;IAC1C,CAAC;CAEF,CAAC"}

11
jsx-html/nodejs/node/ComponentNode.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import { NodePropsType, ComponentFunctionType, NullableChildType } from "../types";
import { NODE_TYPE } from "../constants";
import { Node } from "./Node";
export declare class ComponentNode extends Node {
component: ComponentFunctionType;
props: NodePropsType;
type: NODE_TYPE;
constructor(component: ComponentFunctionType, props: NodePropsType, children: NullableChildType[]);
render(): Promise<string | any[]>;
renderComponent(): Promise<string | any[]>;
}

View File

@ -0,0 +1,43 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComponentNode = void 0;
const constants_1 = require("../constants");
const FragmentNode_1 = require("./FragmentNode");
const Node_1 = require("./Node");
const normalizeChildren_1 = require("./utils/normalizeChildren");
class ComponentNode extends Node_1.Node {
constructor(component, props, children) {
super(children);
this.component = component;
this.props = props;
this.type = constants_1.NODE_TYPE.COMPONENT;
}
render() {
return __awaiter(this, void 0, void 0, function* () {
return [].concat(yield this.renderComponent()).join('');
});
}
renderComponent() {
return __awaiter(this, void 0, void 0, function* () {
const child = yield this.component(this.props, this.children);
const children = normalizeChildren_1.normalizeChildren(Array.isArray(child) ? child : [child]);
if (children.length === 1) {
return children[0].render();
}
else if (children.length > 1) {
return new FragmentNode_1.FragmentNode(children).render();
}
});
}
}
exports.ComponentNode = ComponentNode;
//# sourceMappingURL=ComponentNode.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"ComponentNode.js","sourceRoot":"","sources":["../../tmp/node/ComponentNode.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,4CAAyC;AACzC,iDAA8C;AAC9C,iCAA8B;AAC9B,iEAA8D;AAC9D,MAAa,aAAc,SAAQ,WAAI;IAGrC,YAAmB,SAAgC,EAAS,KAAoB,EAAE,QAA6B;QAC7G,KAAK,CAAC,QAAQ,CAAC,CAAC;QADC,cAAS,GAAT,SAAS,CAAuB;QAAS,UAAK,GAAL,KAAK,CAAe;QAFhF,SAAI,GAAG,qBAAS,CAAC,SAAS,CAAC;IAI3B,CAAC;IAEK,MAAM;;YACV,OAAO,EAAE,CAAC,MAAM,CAAE,MAAM,IAAI,CAAC,eAAe,EAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC;KAAA;IAEK,eAAe;;YACnB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,QAAQ,GAAG,qCAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YAE3E,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;gBACzB,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aAC7B;iBAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;gBAC9B,OAAO,IAAI,2BAAY,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC;aAC5C;QACH,CAAC;KAAA;CAEF;AAtBD,sCAsBC"}

12
jsx-html/nodejs/node/ElementNode.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
import { NODE_TYPE } from "../constants";
import { NodePropsType, NullableChildType } from "../types";
import { Node } from "./Node";
export declare class ElementNode extends Node {
name: string;
props: NodePropsType;
type: NODE_TYPE;
constructor(name: string, props: NodePropsType, children: NullableChildType[]);
render(): Promise<string | any[]>;
private getValidProps;
private propsToHTML;
}

View File

@ -0,0 +1,60 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ElementNode = void 0;
const constants_1 = require("../constants");
const Node_1 = require("./Node");
const htmlEncode_1 = require("./utils/htmlEncode");
const ELEMENT_PROP = {
INNER_HTML: 'innerHTML'
};
class ElementNode extends Node_1.Node {
constructor(name, props, children) {
super(children);
this.name = name;
this.props = props;
this.type = constants_1.NODE_TYPE.ELEMENT;
}
render() {
return __awaiter(this, void 0, void 0, function* () {
const renderedProps = this.propsToHTML();
const renderedChildren = typeof this.props[ELEMENT_PROP.INNER_HTML] === 'string' ? this.props[ELEMENT_PROP.INNER_HTML] : (yield this.renderChildren()).join('');
return renderedChildren ? `<${this.name}${renderedProps}>${renderedChildren}</${this.name}>` : `<${this.name}${renderedProps} />`;
});
}
getValidProps() {
const props = this.props;
return Object.keys(this.props).filter(key => {
if (key === ELEMENT_PROP.INNER_HTML) {
return false;
}
const val = props[key];
return typeof val === 'string' || typeof val === 'number' || val === true;
});
}
propsToHTML() {
const keys = this.getValidProps();
if (!keys.length) {
return '';
}
const props = this.props;
const pairs = keys.map(key => {
if (!/^[a-zA-Z0-9-:\._]+$/.test(key)) {
throw new Error(`Invalid attribute name format ${key}`);
}
const val = props[key];
return val === true || val === '' ? key : `${key}="${htmlEncode_1.doubleQuoteEncode(val.toString())}"`;
});
return ` ${pairs.join(' ')}`;
}
}
exports.ElementNode = ElementNode;
//# sourceMappingURL=ElementNode.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"ElementNode.js","sourceRoot":"","sources":["../../tmp/node/ElementNode.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAyC;AAEzC,iCAA8B;AAC9B,mDAAuD;AACvD,MAAM,YAAY,GAAG;IACnB,UAAU,EAAE,WAAW;CACxB,CAAC;AACF,MAAa,WAAY,SAAQ,WAAI;IAGnC,YAAmB,IAAY,EAAS,KAAoB,EAAE,QAA6B;QACzF,KAAK,CAAC,QAAQ,CAAC,CAAC;QADC,SAAI,GAAJ,IAAI,CAAQ;QAAS,UAAK,GAAL,KAAK,CAAe;QAF5D,SAAI,GAAG,qBAAS,CAAC,OAAO,CAAC;IAIzB,CAAC;IAEK,MAAM;;YACV,MAAM,aAAa,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACzC,MAAM,gBAAgB,GAAG,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAChK,OAAO,gBAAgB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,aAAa,IAAI,gBAAgB,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,aAAa,KAAK,CAAC;QACpI,CAAC;KAAA;IAEO,aAAa;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YAC1C,IAAI,GAAG,KAAK,YAAY,CAAC,UAAU,EAAE;gBACnC,OAAO,KAAK,CAAC;aACd;YAED,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,WAAW;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAElC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAChB,OAAO,EAAE,CAAC;SACX;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YAC3B,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;gBACpC,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;aACzD;YAED,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;YAEvB,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,8BAAiB,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC;QAC5F,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/B,CAAC;CAEF;AA7CD,kCA6CC"}

View File

@ -0,0 +1,8 @@
import { NODE_TYPE } from "../constants";
import { ChildNodeType } from "../types";
import { Node } from "./Node";
export declare class FragmentNode extends Node {
type: NODE_TYPE;
constructor(children: ChildNodeType[]);
render(): Promise<string[]>;
}

View File

@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FragmentNode = void 0;
const constants_1 = require("../constants");
const Node_1 = require("./Node");
class FragmentNode extends Node_1.Node {
constructor(children) {
super(children);
this.type = constants_1.NODE_TYPE.FRAGMENT;
}
render() {
return this.renderChildren();
}
}
exports.FragmentNode = FragmentNode;
//# sourceMappingURL=FragmentNode.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"FragmentNode.js","sourceRoot":"","sources":["../../tmp/node/FragmentNode.ts"],"names":[],"mappings":";;;AAAA,4CAAyC;AAEzC,iCAA8B;AAC9B,MAAa,YAAa,SAAQ,WAAI;IAGpC,YAAY,QAAyB;QACnC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAHlB,SAAI,GAAG,qBAAS,CAAC,QAAQ,CAAC;IAI1B,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;CAEF;AAXD,oCAWC"}

9
jsx-html/nodejs/node/Node.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { NODE_TYPE } from "../constants";
import { NullableChildType } from "../types";
export declare abstract class Node {
children: NullableChildType[];
abstract type: NODE_TYPE;
constructor(children: NullableChildType[]);
abstract render(): Promise<string | any[]>;
renderChildren(): Promise<string[]>;
}

View File

@ -0,0 +1,38 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Node = void 0;
const normalizeChildren_1 = require("./utils/normalizeChildren");
class Node {
constructor(children) {
this.children = children;
}
renderChildren() {
return __awaiter(this, void 0, void 0, function* () {
const result = [];
const children = normalizeChildren_1.normalizeChildren(this.children);
for (const child of children) {
const renderedChild = yield child.render();
if (renderedChild) {
if (Array.isArray(renderedChild)) {
renderedChild.forEach(subchild => subchild && result.push(subchild));
}
else {
result.push(renderedChild);
}
}
}
return result;
});
}
}
exports.Node = Node;
//# sourceMappingURL=Node.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Node.js","sourceRoot":"","sources":["../../tmp/node/Node.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,iEAA8D;AAC9D,MAAsB,IAAI;IAGxB,YAAmB,QAA6B;QAA7B,aAAQ,GAAR,QAAQ,CAAqB;IAAG,CAAC;IAI9C,cAAc;;YAClB,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,qCAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAElD,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE;gBAC5B,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;gBAE3C,IAAI,aAAa,EAAE;oBACjB,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE;wBAChC,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;qBACtE;yBAAM;wBACL,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;qBAC5B;iBACF;aACF;YAED,OAAO,MAAM,CAAC;QAChB,CAAC;KAAA;CAEF;AA1BD,oBA0BC"}

7
jsx-html/nodejs/node/TextNode.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import { NODE_TYPE } from "../constants";
export declare class TextNode {
text: string;
type: NODE_TYPE;
constructor(text: string);
render(): Promise<string | any[]>;
}

View File

@ -0,0 +1,27 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextNode = void 0;
const constants_1 = require("../constants");
const htmlEncode_1 = require("./utils/htmlEncode");
class TextNode {
constructor(text) {
this.text = text;
this.type = constants_1.NODE_TYPE.TEXT;
}
render() {
return __awaiter(this, void 0, void 0, function* () {
return htmlEncode_1.htmlEncode(this.text);
});
}
}
exports.TextNode = TextNode;
//# sourceMappingURL=TextNode.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"TextNode.js","sourceRoot":"","sources":["../../tmp/node/TextNode.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAyC;AACzC,mDAAgD;AAChD,MAAa,QAAQ;IAGnB,YAAmB,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QAF/B,SAAI,GAAG,qBAAS,CAAC,IAAI,CAAC;IAEY,CAAC;IAE7B,MAAM;;YACV,OAAO,uBAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;KAAA;CAEF;AATD,4BASC"}

View File

@ -0,0 +1,2 @@
export declare function doubleQuoteEncode(text: string): string;
export declare function htmlEncode(text: string): string;

View File

@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.htmlEncode = exports.doubleQuoteEncode = void 0;
function doubleQuoteEncode(text) {
return text.replace(/"/g, '&quot;');
}
exports.doubleQuoteEncode = doubleQuoteEncode;
function htmlEncode(text) {
return doubleQuoteEncode(text.replace(/&/g, '&amp;').replace(/\//g, '&#x2F;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/'/g, '&#39;'));
}
exports.htmlEncode = htmlEncode;
//# sourceMappingURL=htmlEncode.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"htmlEncode.js","sourceRoot":"","sources":["../../../tmp/node/utils/htmlEncode.ts"],"names":[],"mappings":";;;AAAA,SAAgB,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACtC,CAAC;AAFD,8CAEC;AACD,SAAgB,UAAU,CAAC,IAAY;IACrC,OAAO,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AACpJ,CAAC;AAFD,gCAEC"}

View File

@ -0,0 +1,2 @@
import { NullableChildType, ChildNodeType } from "../../types";
export declare function normalizeChildren(children: NullableChildType[]): ChildNodeType[];

View File

@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeChildren = void 0;
const TextNode_1 = require("../TextNode");
const constants_1 = require("../../constants");
function normalizeChildren(children) {
const result = [];
for (const child of children) {
if (child && typeof child !== 'boolean') {
if (typeof child === 'string' || typeof child === 'number') {
result.push(new TextNode_1.TextNode(`${child}`));
}
else if (Array.isArray(child)) {
normalizeChildren(child).forEach(result.push);
}
else if (child.type === constants_1.NODE_TYPE.ELEMENT || child.type === constants_1.NODE_TYPE.TEXT || child.type === constants_1.NODE_TYPE.COMPONENT) {
result.push(child);
}
else {
throw new TypeError(`Unrecognized node type: ${typeof child}`);
}
}
}
return result;
}
exports.normalizeChildren = normalizeChildren;
//# sourceMappingURL=normalizeChildren.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"normalizeChildren.js","sourceRoot":"","sources":["../../../tmp/node/utils/normalizeChildren.ts"],"names":[],"mappings":";;;AACA,0CAAuC;AACvC,+CAA4C;AAC5C,SAAgB,iBAAiB,CAAC,QAA6B;IAC7D,MAAM,MAAM,GAAU,EAAE,CAAC;IAEzB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE;QAC5B,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE;YACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;gBAC1D,MAAM,CAAC,IAAI,CAAC,IAAI,mBAAQ,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;aACvC;iBAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBAC/B,iBAAiB,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;aAC/C;iBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAS,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAS,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAS,CAAC,SAAS,EAAE;gBAClH,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aACpB;iBAAM;gBACL,MAAM,IAAI,SAAS,CAAC,2BAA2B,OAAO,KAAK,EAAE,CAAC,CAAC;aAChE;SACF;KACF;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAlBD,8CAkBC"}

15
jsx-html/nodejs/types.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
import { ElementNode } from "./node/ElementNode";
import { TextNode } from "./node/TextNode";
import { ComponentNode } from "./node/ComponentNode";
import { FragmentNode } from "./node/FragmentNode";
export declare type NodePropsType = {
[key: string]: any;
};
declare type Primitive = string | boolean | number;
declare type NullablePrimitive = Primitive | null | void;
export declare type ChildNodeType = ElementNode | TextNode | ComponentNode;
export declare type NodeType = ChildNodeType | FragmentNode;
export declare type ChildType = ChildNodeType | Primitive;
export declare type NullableChildType = ChildType | ChildNodeType | NullablePrimitive;
export declare type ComponentFunctionType = (props: NodePropsType, child?: NullableChildType[]) => NullableChildType | Promise<NullableChildType>;
export {};

3
jsx-html/nodejs/types.js Normal file
View File

@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../tmp/types.ts"],"names":[],"mappings":""}

16
jsx-html/note.md Normal file
View File

@ -0,0 +1,16 @@
deno test examples/01.tsx
deno run examples/00.tsx
deno run examples/01.tsx
deno run -c examples/02/tsconfig.json examples/02/02.tsx
https://deno.land/manual/contributing/style_guide
- would be great to make it as well node compatible:
tsc examples/01.tsx --jsx react --outDir dist && node dist/examples/01.js
git tag --delete latest
git push --delete origin latest
git tag latest
git push --tags

25
jsx-html/package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "async-jsx-html",
"version": "1.2.1",
"main": "nodejs/mod.js",
"types": "nodejs/mod.d.ts",
"repository": "git@github.com:apiel/jsx-html.git",
"author": "Alexandre Piel <alexandre.piel@gmail.com>",
"license": "MIT",
"scripts": {
"clean": "rm -rf nodejs/ && rm -rf tmp/",
"build": "yarn clean && node deno2nodejs.js && tsc -p ./tmp/tsconfig.json",
"transpile": "babel --no-babelrc --plugins @babel/plugin-transform-react-jsx ./examples/node_01.jsx -o ./examples/node_01.js",
"start": "yarn transpile && node ./examples/node_01.js"
},
"devDependencies": {
"@babel/cli": "^7.10.3",
"@babel/core": "^7.10.3",
"@babel/generator": "^7.10.3",
"@babel/parser": "^7.10.3",
"@babel/plugin-transform-react-jsx": "^7.10.3",
"@babel/traverse": "^7.10.3",
"@types/node": "^14.0.13",
"typescript": "^3.9.5"
}
}

23
jsx-html/types.ts Normal file
View File

@ -0,0 +1,23 @@
import type { ElementNode } from './node/ElementNode.ts';
import type { TextNode } from './node/TextNode.ts';
import type { ComponentNode } from './node/ComponentNode.ts';
import type { FragmentNode } from './node/FragmentNode.ts';
export type NodePropsType = {
[key: string]: any;
};
type Primitive = string | boolean | number;
type NullablePrimitive = Primitive | null | void;
export type ChildNodeType = ElementNode | TextNode | ComponentNode;
export type NodeType = ChildNodeType | FragmentNode;
export type ChildType = ChildNodeType | Primitive;
export type NullableChildType = ChildType | ChildNodeType | NullablePrimitive;
export type ComponentFunctionType = (
props: NodePropsType,
child?: NullableChildType[],
) => NullableChildType | Promise<NullableChildType>;

1267
jsx-html/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

14
markdown/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Deno",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "deno",
"runtimeArgs": ["run", "--inspect", "-A", "app.ts"],
"port": 9229
}
]
}

9
markdown/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"deno.enable": true,
"[typescript]": {
"editor.defaultFormatter": "axetroy.vscode-deno"
},
"[typescriptreact]": {
"editor.defaultFormatter": "axetroy.vscode-deno"
}
}

21
markdown/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Eivind Furuberg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

62
markdown/README.md Normal file
View File

@ -0,0 +1,62 @@
# markdown
Deno Markdown module forked from https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831
### Example usage
Simple md2html.ts script:
```typescript
import { Marked } from "./mod.ts";
const decoder = new TextDecoder("utf-8");
const filename = Deno.args[0];
const markdown = decoder.decode(await Deno.readFile(filename));
const markup = Marked.parse(markdown);
console.log(markup.content);
console.log(JSON.stringify(markup.meta))
```
Now running:
```bash
deno run --allow-read md2html.ts example.md > example.html
```
Will output:
```html
<h1 id="hello-world">Hello World</h1>
<h2 id="this-an-example-for-md2html-ts-">
This an example for <code>md2html.ts</code>
</h2>
<p>A small paragraph that will become a <code>&lt;p&gt;</code> tag</p>
<hr />
<p>Code Block (md2html.ts)</p>
<pre><code class="lang-typescript">import { Marked } from &quot;./mod.ts&quot;;
const decoder = new TextDecoder("utf-8");
const filename = Deno.args[0];
const markdown = decoder.decode(await Deno.readFile(filename));
const markup = Marked.parse(markdown);
console.log(markup.content);
console.log(JSON.stringify(markup.meta))
</code></pre>
<p>
This module is forked from
<a
href="https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831"
>ts-stack/markdown</a
>
</p>
<p>Made for Deno <img src="https://deno.land/logo.svg" alt="deno-logo" /></p>
{"title":"Hello world!","subtitle":"Front-matter is supported!","boolean":true,"list-example":["this","is",{"a":"list"}]}
```
---
### Notes
I had to do some changes to the source code to make the compiler happy, mostly fixes for things that were uninitialized and possibly null or undefined

BIN
markdown/example.html Normal file

Binary file not shown.

34
markdown/example.md Normal file
View File

@ -0,0 +1,34 @@
---
title : Hello world!
subtitle : Front-matter is supported!
boolean: true
list-example:
- this
- is
- a: list
---
# Hello World
## This an example for `md2html.ts`
A small paragraph that will become a `<p>` tag
---
Code Block (md2html.ts)
```typescript
import { Marked } from "./mod.ts";
const decoder = new TextDecoder("utf-8");
const filename = Deno.args[0];
const markdown = decoder.decode(await Deno.readFile(filename));
const markup = Marked.parse(markdown);
console.log(markup.content);
console.log(JSON.stringify(markup.meta));
```
This module is forked from [ts-stack/markdown](https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831)
Made for Deno
![deno-logo](https://deno.land/logo.svg)

8
markdown/md2html.ts Normal file
View File

@ -0,0 +1,8 @@
import { Marked } from "./mod.ts";
const decoder = new TextDecoder("utf-8");
const filename = Deno.args[0];
const markdown = decoder.decode(await Deno.readFile(filename));
const markup = Marked.parse(markdown);
console.log(markup.content);
console.log(JSON.stringify(markup.meta))

12
markdown/meta.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "markdown",
"version": "0.0.1",
"description": "",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"files": [
"**/*.ts",
"**/*.js",
"README.md"
]
}

8
markdown/mod.ts Normal file
View File

@ -0,0 +1,8 @@
export * from "./src/block-lexer.ts";
export * from "./src/helpers.ts";
export * from "./src/inline-lexer.ts";
export * from "./src/interfaces.ts";
export * from "./src/marked.ts";
export * from "./src/parser.ts";
export * from "./src/renderer.ts";
export * from "./src/extend-regexp.ts";

520
markdown/src/block-lexer.ts Normal file
View File

@ -0,0 +1,520 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { ExtendRegexp } from "./extend-regexp.ts";
import {
Align,
LexerReturns,
Links,
MarkedOptions,
RulesBlockBase,
RulesBlockGfm,
RulesBlockTables,
Token,
TokenType,
Obj
} from "./interfaces.ts";
import { Marked } from "./marked.ts";
import { load } from "https://deno.land/std/encoding/_yaml/loader/loader.ts";
export class BlockLexer<T extends typeof BlockLexer> {
static simpleRules: RegExp[] = [];
protected static rulesBase: RulesBlockBase;
/**
* GFM Block Grammar.
*/
protected static rulesGfm: RulesBlockGfm;
/**
* GFM + Tables Block Grammar.
*/
protected static rulesTables: RulesBlockTables;
protected rules!: RulesBlockBase | RulesBlockGfm | RulesBlockTables;
protected options: MarkedOptions;
protected links: Links = {};
protected tokens: Token[] = [];
protected frontmatter: Obj = {};
protected hasRulesGfm!: boolean;
protected hasRulesTables!: boolean;
constructor(protected staticThis: typeof BlockLexer, options?: object) {
this.options = options || Marked.options;
this.setRules();
}
/**
* Accepts Markdown text and returns object with tokens and links.
*
* @param src String of markdown source to be compiled.
* @param options Hash of options.
*/
static lex(
src: string,
options?: MarkedOptions,
top?: boolean,
isBlockQuote?: boolean,
): LexerReturns {
const lexer = new this(this, options);
return lexer.getTokens(src, top, isBlockQuote);
}
protected static getRulesBase(): RulesBlockBase {
if (this.rulesBase) {
return this.rulesBase;
}
const base: RulesBlockBase = {
newline: /^\n+/,
code: /^( {4}[^\n]+\n*)+/,
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
html:
/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
paragraph:
/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
text: /^[^\n]+/,
bullet: /(?:[*+-]|\d+\.)/,
item: /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,
};
base.item = new ExtendRegexp(base.item, "gm").setGroup(/bull/g, base.bullet)
.getRegexp();
base.list = new ExtendRegexp(base.list)
.setGroup(/bull/g, base.bullet)
.setGroup("hr", "\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")
.setGroup("def", "\\n+(?=" + base.def.source + ")")
.getRegexp();
const tag = "(?!(?:" +
"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code" +
"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo" +
"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";
base.html = new ExtendRegexp(base.html)
.setGroup("comment", /<!--[\s\S]*?-->/)
.setGroup("closed", /<(tag)[\s\S]+?<\/\1>/)
.setGroup("closing", /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
.setGroup(/tag/g, tag)
.getRegexp();
base.paragraph = new ExtendRegexp(base.paragraph)
.setGroup("hr", base.hr)
.setGroup("heading", base.heading)
.setGroup("lheading", base.lheading)
.setGroup("blockquote", base.blockquote)
.setGroup("tag", "<" + tag)
.setGroup("def", base.def)
.getRegexp();
return (this.rulesBase = base);
}
protected static getRulesGfm(): RulesBlockGfm {
if (this.rulesGfm) {
return this.rulesGfm;
}
const base = this.getRulesBase();
const gfm: RulesBlockGfm = {
...base,
...{
fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
paragraph: /^/,
heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/,
},
};
const group1 = gfm.fences.source.replace("\\1", "\\2");
const group2 = base.list.source.replace("\\1", "\\3");
gfm.paragraph = new ExtendRegexp(base.paragraph).setGroup(
"(?!",
`(?!${group1}|${group2}|`,
).getRegexp();
return (this.rulesGfm = gfm);
}
protected static getRulesTable(): RulesBlockTables {
if (this.rulesTables) {
return this.rulesTables;
}
return (this.rulesTables = {
...this.getRulesGfm(),
...{
nptable:
/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/,
},
});
}
protected setRules() {
if (this.options.gfm) {
if (this.options.tables) {
this.rules = this.staticThis.getRulesTable();
} else {
this.rules = this.staticThis.getRulesGfm();
}
} else {
this.rules = this.staticThis.getRulesBase();
}
this.hasRulesGfm = (this.rules as RulesBlockGfm).fences !== undefined;
this.hasRulesTables = (this.rules as RulesBlockTables).table !== undefined;
}
/**
* Lexing.
*/
protected getTokens(
src: string,
top?: boolean,
isBlockQuote?: boolean,
): LexerReturns {
let nextPart = src;
let execArr, fmArr: RegExpExecArray | null;
mainLoop:
while (nextPart) {
// newline
if ((execArr = this.rules.newline.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
if (execArr[0].length > 1) {
this.tokens.push({ type: TokenType.space });
}
}
// code
if ((execArr = this.rules.code.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
const code = execArr[0].replace(/^ {4}/gm, "");
this.tokens.push({
type: TokenType.code,
text: !this.options.pedantic ? code.replace(/\n+$/, "") : code,
});
continue;
}
// fences code (gfm)
if (
this.hasRulesGfm &&
(execArr = (this.rules as RulesBlockGfm).fences.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({
type: TokenType.code,
lang: execArr[2],
text: execArr[3] || "",
});
continue;
}
// heading
if ((execArr = this.rules.heading.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({
type: TokenType.heading,
depth: execArr[1].length,
text: execArr[2],
});
continue;
}
// table no leading pipe (gfm)
if (
top && this.hasRulesTables &&
(execArr = (this.rules as RulesBlockTables).nptable.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
const item: Token = {
type: TokenType.table,
header: execArr[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),
align: execArr[2].replace(/^ *|\| *$/g, "").split(
/ *\| */,
) as Align[],
cells: [],
};
if (!item.align) throw ReferenceError;
for (let i = 0; i < item.align.length; i++) {
if (/^ *-+: *$/.test(item.align[i])) {
item.align[i] = "right";
} else if (/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = "center";
} else if (/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = "left";
} else {
item.align[i] = "";
}
}
const td: string[] = execArr[3].replace(/\n$/, "").split("\n");
if (!item.cells) throw ReferenceError;
for (let i = 0; i < td.length; i++) {
item.cells[i] = td[i].split(/ *\| */);
}
this.tokens.push(item);
continue;
}
// lheading
if ((execArr = this.rules.lheading.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({
type: TokenType.heading,
depth: execArr[2] === "=" ? 1 : 2,
text: execArr[1],
});
continue;
}
// hr
if ((execArr = this.rules.hr.exec(nextPart))) {
// Checks if the previous string contains a content.
if ((this.tokens.length == 0) || (this.tokens.every(object => object.type == TokenType.space))) {
// Grabs front-matter data and parse it into Javascript object.
if (fmArr = /^(?:\-\-\-)(.*?)(?:\-\-\-|\.\.\.)/s.exec(nextPart)) {
nextPart = nextPart.substring(fmArr[0].length);
this.frontmatter = <Obj> load(fmArr[1]);
}
continue;
} else {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({ type: TokenType.hr });
continue;
}
}
// blockquote
if ((execArr = this.rules.blockquote.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({ type: TokenType.blockquoteStart });
const str = execArr[0].replace(/^ *> ?/gm, "");
// Pass `top` to keep the current
// "toplevel" state. This is exactly
// how markdown.pl works.
this.getTokens(str);
this.tokens.push({ type: TokenType.blockquoteEnd });
continue;
}
// list
if ((execArr = this.rules.list.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
const bull: string = execArr[2];
this.tokens.push(
{ type: TokenType.listStart, ordered: bull.length > 1 },
);
// Get each top-level item.
const str = execArr[0].match(this.rules.item) || "";
const length = str.length;
let next = false;
let space: number;
let blockBullet: string;
let loose: boolean;
for (let i = 0; i < length; i++) {
let item = str[i];
// Remove the list item's bullet so it is seen as the next token.
space = item.length;
item = item.replace(/^ *([*+-]|\d+\.) +/, "");
// Outdent whatever the list item contains. Hacky.
if (item.indexOf("\n ") !== -1) {
space -= item.length;
item = !this.options.pedantic
? item.replace(new RegExp("^ {1," + space + "}", "gm"), "")
: item.replace(/^ {1,4}/gm, "");
}
// Determine whether the next list item belongs here.
// Backpedal if it does not belong in this list.
if (this.options.smartLists && i !== length - 1) {
const bb = this.staticThis.getRulesBase().bullet.exec(str[i + 1]);
blockBullet = bb ? bb[0] : "";
if (
bull !== blockBullet &&
!(bull.length > 1 && blockBullet.length > 1)
) {
nextPart = (str.slice(i + 1) as string[]).join("\n") + nextPart;
i = length - 1;
}
}
// Determine whether item is loose or not.
// Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
// for discount behavior.
loose = next || /\n\n(?!\s*$)/.test(item);
if (i !== length - 1) {
next = item.charAt(item.length - 1) === "\n";
if (!loose) {
loose = next;
}
}
this.tokens.push(
{
type: loose ? TokenType.looseItemStart : TokenType.listItemStart,
},
);
// Recurse.
this.getTokens(item, false, isBlockQuote);
this.tokens.push({ type: TokenType.listItemEnd });
}
this.tokens.push({ type: TokenType.listEnd });
continue;
}
// html
if ((execArr = this.rules.html.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
const attr = execArr[1];
const isPre = attr === "pre" || attr === "script" || attr === "style";
this.tokens.push({
type: this.options.sanitize ? TokenType.paragraph : TokenType.html,
pre: !this.options.sanitizer && isPre,
text: execArr[0],
});
continue;
}
// def
if (top && (execArr = this.rules.def.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.links[execArr[1].toLowerCase()] = {
href: execArr[2],
title: execArr[3],
};
continue;
}
// table (gfm)
if (
top && this.hasRulesTables &&
(execArr = (this.rules as RulesBlockTables).table.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
const item: Token = {
type: TokenType.table,
header: execArr[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),
align: execArr[2].replace(/^ *|\| *$/g, "").split(
/ *\| */,
) as Align[],
cells: [],
};
if (!item.align) throw ReferenceError;
for (let i = 0; i < item.align.length; i++) {
if (/^ *-+: *$/.test(item.align[i])) {
item.align[i] = "right";
} else if (/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = "center";
} else if (/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = "left";
} else {
item.align[i] = "";
}
}
const td = execArr[3].replace(/(?: *\| *)?\n$/, "").split("\n");
if (!item.cells) throw ReferenceError;
for (let i = 0; i < td.length; i++) {
item.cells[i] = td[i].replace(/^ *\| *| *\| *$/g, "").split(/ *\| */);
}
this.tokens.push(item);
continue;
}
// simple rules
if (this.staticThis.simpleRules.length) {
const simpleRules = this.staticThis.simpleRules;
for (let i = 0; i < simpleRules.length; i++) {
if ((execArr = simpleRules[i].exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
const type = "simpleRule" + (i + 1);
this.tokens.push({ type, execArr });
continue mainLoop;
}
}
}
// top-level paragraph
if (top && (execArr = this.rules.paragraph.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
if (execArr[1].slice(-1) === "\n") {
this.tokens.push({
type: TokenType.paragraph,
text: execArr[1].slice(0, -1),
});
} else {
this.tokens.push({
type: this.tokens.length > 0 ? TokenType.paragraph : TokenType.text,
text: execArr[1],
});
}
continue;
}
// text
// Top-level should never reach here.
if ((execArr = this.rules.text.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({ type: TokenType.text, text: execArr[0] });
continue;
}
if (nextPart) {
throw new Error(
"Infinite loop on byte: " + nextPart.charCodeAt(0) +
`, near text '${nextPart.slice(0, 30)}...'`,
);
}
}
return { tokens: this.tokens, links: this.links, meta: this.frontmatter };
}
}

View File

@ -0,0 +1,43 @@
/*
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
export class ExtendRegexp {
private source: string;
private flags: string;
constructor(regex: RegExp, flags: string = "") {
this.source = regex.source;
this.flags = flags;
}
/**
* Extend regular expression.
*
* @param groupName Regular expression for search a group name.
* @param groupRegexp Regular expression of named group.
*/
setGroup(groupName: RegExp | string, groupRegexp: RegExp | string): this {
let newRegexp: string = typeof groupRegexp == "string"
? groupRegexp
: groupRegexp.source;
newRegexp = newRegexp.replace(/(^|[^\[])\^/g, "$1");
// Extend regexp.
this.source = this.source.replace(groupName, newRegexp);
return this;
}
/**
* Returns a result of extending a regular expression.
*/
getRegexp(): RegExp {
return new RegExp(this.source, this.flags);
}
}

64
markdown/src/helpers.ts Normal file
View File

@ -0,0 +1,64 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import type { Replacements } from "./interfaces.ts";
const escapeTest = /[&<>"']/;
const escapeReplace = /[&<>"']/g;
const replacements: Replacements = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
// tslint:disable-next-line:quotemark
"'": "&#39;",
};
const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
export function escape(html: string, encode?: boolean) {
if (encode) {
if (escapeTest.test(html)) {
return html.replace(escapeReplace, (ch: string) => replacements[ch]);
}
} else {
if (escapeTestNoEncode.test(html)) {
return html.replace(
escapeReplaceNoEncode,
(ch: string) => replacements[ch]
);
}
}
return html;
}
export function unescape(html: string) {
// Explicitly match decimal, hex, and named HTML entities
return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi, function (
_,
n
) {
n = n.toLowerCase();
if (n === "colon") {
return ":";
}
if (n.charAt(0) === "#") {
return n.charAt(1) === "x"
? String.fromCharCode(parseInt(n.substring(2), 16))
: String.fromCharCode(+n.substring(1));
}
return "";
});
}

View File

@ -0,0 +1,419 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { ExtendRegexp } from "./extend-regexp.ts";
import type {
Link,
Links,
MarkedOptions,
RulesInlineBase,
RulesInlineBreaks,
RulesInlineCallback,
RulesInlineGfm,
RulesInlinePedantic,
} from "./interfaces.ts";
import { Marked } from "./marked.ts";
import { Renderer } from "./renderer.ts";
/**
* Inline Lexer & Compiler.
*/
export class InlineLexer {
protected static rulesBase: RulesInlineBase;
/**
* Pedantic Inline Grammar.
*/
protected static rulesPedantic: RulesInlinePedantic;
/**
* GFM Inline Grammar
*/
protected static rulesGfm: RulesInlineGfm;
/**
* GFM + Line Breaks Inline Grammar.
*/
protected static rulesBreaks: RulesInlineBreaks;
protected rules!:
| RulesInlineBase
| RulesInlinePedantic
| RulesInlineGfm
| RulesInlineBreaks;
protected renderer: Renderer;
protected inLink!: boolean;
protected hasRulesGfm!: boolean;
protected ruleCallbacks!: RulesInlineCallback[];
constructor(
protected staticThis: typeof InlineLexer,
protected links: Links,
protected options: MarkedOptions = Marked.options,
renderer?: Renderer
) {
this.renderer =
renderer || this.options.renderer || new Renderer(this.options);
if (!this.links) {
throw new Error(`InlineLexer requires 'links' parameter.`);
}
this.setRules();
}
/**
* Static Lexing/Compiling Method.
*/
static output(src: string, links: Links, options: MarkedOptions): string {
const inlineLexer = new this(this, links, options);
return inlineLexer.output(src);
}
protected static getRulesBase(): RulesInlineBase {
if (this.rulesBase) {
return this.rulesBase;
}
/**
* Inline-Level Grammar.
*/
const base: RulesInlineBase = {
escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
autolink: /^<([^ <>]+(@|:\/)[^ <>]+)>/,
tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^<'">])*?>/,
link: /^!?\[(inside)\]\(href\)/,
reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
code: /^(`+)([\s\S]*?[^`])\1(?!`)/,
br: /^ {2,}\n(?!\s*$)/,
text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/,
_inside: /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/,
_href: /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/,
};
base.link = new ExtendRegexp(base.link)
.setGroup("inside", base._inside)
.setGroup("href", base._href)
.getRegexp();
base.reflink = new ExtendRegexp(base.reflink)
.setGroup("inside", base._inside)
.getRegexp();
return (this.rulesBase = base);
}
protected static getRulesPedantic(): RulesInlinePedantic {
if (this.rulesPedantic) {
return this.rulesPedantic;
}
return (this.rulesPedantic = {
...this.getRulesBase(),
...{
strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
},
});
}
protected static getRulesGfm(): RulesInlineGfm {
if (this.rulesGfm) {
return this.rulesGfm;
}
const base = this.getRulesBase();
const escape = new ExtendRegexp(base.escape)
.setGroup("])", "~|])")
.getRegexp();
const text = new ExtendRegexp(base.text)
.setGroup("]|", "~]|")
.setGroup("|", "|https?://|")
.getRegexp();
return (this.rulesGfm = {
...base,
...{
escape,
url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
del: /^~~(?=\S)([\s\S]*?\S)~~/,
text,
},
});
}
protected static getRulesBreaks(): RulesInlineBreaks {
if (this.rulesBreaks) {
return this.rulesBreaks;
}
const inline = this.getRulesGfm();
const gfm = this.getRulesGfm();
return (this.rulesBreaks = {
...gfm,
...{
br: new ExtendRegexp(inline.br).setGroup("{2,}", "*").getRegexp(),
text: new ExtendRegexp(gfm.text).setGroup("{2,}", "*").getRegexp(),
},
});
}
protected setRules() {
if (this.options.gfm) {
if (this.options.breaks) {
this.rules = this.staticThis.getRulesBreaks();
} else {
this.rules = this.staticThis.getRulesGfm();
}
} else if (this.options.pedantic) {
this.rules = this.staticThis.getRulesPedantic();
} else {
this.rules = this.staticThis.getRulesBase();
}
this.hasRulesGfm = (this.rules as RulesInlineGfm).url !== undefined;
}
/**
* Lexing/Compiling.
*/
output(nextPart: string): string {
nextPart = nextPart;
let execArr: RegExpExecArray | null;
let out = "";
while (nextPart) {
// escape
if ((execArr = this.rules.escape.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
out += execArr[1];
continue;
}
// autolink
if ((execArr = this.rules.autolink.exec(nextPart))) {
let text: string;
let href: string;
nextPart = nextPart.substring(execArr[0].length);
if (!this.options.escape) throw ReferenceError;
if (execArr[2] === "@") {
text = this.options.escape(
execArr[1].charAt(6) === ":"
? this.mangle(execArr[1].substring(7))
: this.mangle(execArr[1])
);
href = this.mangle("mailto:") + text;
} else {
text = this.options.escape(execArr[1]);
href = text;
}
out += this.renderer.link(href, "", text);
continue;
}
// url (gfm)
if (
!this.inLink &&
this.hasRulesGfm &&
(execArr = (this.rules as RulesInlineGfm).url.exec(nextPart))
) {
if (!this.options.escape) throw ReferenceError;
let text: string;
let href: string;
nextPart = nextPart.substring(execArr[0].length);
text = this.options.escape(execArr[1]);
href = text;
out += this.renderer.link(href, "", text);
continue;
}
// tag
if ((execArr = this.rules.tag.exec(nextPart))) {
if (!this.inLink && /^<a /i.test(execArr[0])) {
this.inLink = true;
} else if (this.inLink && /^<\/a>/i.test(execArr[0])) {
this.inLink = false;
}
nextPart = nextPart.substring(execArr[0].length);
if (!this.options.escape) throw ReferenceError;
out += this.options.sanitize
? this.options.sanitizer
? this.options.sanitizer(execArr[0])
: this.options.escape(execArr[0])
: execArr[0];
continue;
}
// link
if ((execArr = this.rules.link.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.inLink = true;
out += this.outputLink(execArr, {
href: execArr[2],
title: execArr[3],
});
this.inLink = false;
continue;
}
// reflink, nolink
if (
(execArr = this.rules.reflink.exec(nextPart)) ||
(execArr = this.rules.nolink.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
const keyLink = (execArr[2] || execArr[1]).replace(/\s+/g, " ");
const link = this.links[keyLink.toLowerCase()];
if (!link || !link.href) {
out += execArr[0].charAt(0);
nextPart = execArr[0].substring(1) + nextPart;
continue;
}
this.inLink = true;
out += this.outputLink(execArr, link);
this.inLink = false;
continue;
}
// strong
if ((execArr = this.rules.strong.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.strong(this.output(execArr[2] || execArr[1]));
continue;
}
// em
if ((execArr = this.rules.em.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.em(this.output(execArr[2] || execArr[1]));
continue;
}
// code
if ((execArr = this.rules.code.exec(nextPart))) {
if (!this.options.escape) throw ReferenceError;
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.codespan(
this.options.escape(execArr[2].trim(), true)
);
continue;
}
// br
if ((execArr = this.rules.br.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.br();
continue;
}
// del (gfm)
if (
this.hasRulesGfm &&
(execArr = (this.rules as RulesInlineGfm).del.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.del(this.output(execArr[1]));
continue;
}
// text
if ((execArr = this.rules.text.exec(nextPart))) {
if (!this.options.escape) throw ReferenceError;
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.text(
this.options.escape(this.smartypants(execArr[0]))
);
continue;
}
if (nextPart) {
throw new Error("Infinite loop on byte: " + nextPart.charCodeAt(0));
}
}
return out;
}
/**
* Compile Link.
*/
protected outputLink(execArr: RegExpExecArray, link: Link) {
if (!this.options.escape) throw ReferenceError;
const href = this.options.escape(link.href);
const title = link.title ? this.options.escape(link.title) : null;
return execArr[0].charAt(0) !== "!"
? this.renderer.link(href, title || "", this.output(execArr[1]))
: this.renderer.image(href, title || "", this.options.escape(execArr[1]));
}
/**
* Smartypants Transformations.
*/
protected smartypants(text: string) {
if (!this.options.smartypants) {
return text;
}
return (
text
// em-dashes
.replace(/---/g, "\u2014")
// en-dashes
.replace(/--/g, "\u2013")
// opening singles
.replace(/(^|[-\u2014/(\[{"\s])'/g, "$1\u2018")
// closing singles & apostrophes
.replace(/'/g, "\u2019")
// opening doubles
.replace(/(^|[-\u2014/(\[{\u2018\s])"/g, "$1\u201c")
// closing doubles
.replace(/"/g, "\u201d")
// ellipses
.replace(/\.{3}/g, "\u2026")
);
}
/**
* Mangle Links.
*/
protected mangle(text: string) {
if (!this.options.mangle) {
return text;
}
let out = "";
const length = text.length;
for (let i = 0; i < length; i++) {
let str: string = "";
if (Math.random() > 0.5) {
str = "x" + text.charCodeAt(i).toString(16);
}
out += "&#" + str + ";";
}
return out;
}
}

196
markdown/src/interfaces.ts Normal file
View File

@ -0,0 +1,196 @@
/**
* @license
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { escape, unescape } from "./helpers.ts";
import type { Renderer } from "./renderer.ts";
export interface Obj {
[key: string]: any;
}
export interface RulesBlockBase {
newline: RegExp;
code: RegExp;
hr: RegExp;
heading: RegExp;
lheading: RegExp;
blockquote: RegExp;
list: RegExp;
html: RegExp;
def: RegExp;
paragraph: RegExp;
text: RegExp;
bullet: RegExp;
/**
* List item (<li>).
*/
item: RegExp;
}
export interface RulesBlockGfm extends RulesBlockBase {
fences: RegExp;
}
export interface RulesBlockTables extends RulesBlockGfm {
nptable: RegExp;
table: RegExp;
}
export interface Link {
href: string;
title: string;
}
export interface Links {
[key: string]: Link;
}
export enum TokenType {
space = 1,
text,
paragraph,
heading,
listStart,
listEnd,
looseItemStart,
looseItemEnd,
listItemStart,
listItemEnd,
blockquoteStart,
blockquoteEnd,
code,
table,
html,
hr,
}
export type Align = "center" | "left" | "right" | "";
export interface Token {
type: number | string;
text?: string;
lang?: string;
depth?: number;
header?: string[];
align?: Align[];
cells?: string[][];
ordered?: boolean;
pre?: boolean;
escaped?: boolean;
execArr?: RegExpExecArray;
/**
* Used for debugging. Identifies the line number in the resulting HTML file.
*/
line?: number;
}
export interface RulesInlineBase {
escape: RegExp;
autolink: RegExp;
tag: RegExp;
link: RegExp;
reflink: RegExp;
nolink: RegExp;
strong: RegExp;
em: RegExp;
code: RegExp;
br: RegExp;
text: RegExp;
_inside: RegExp;
_href: RegExp;
}
export interface RulesInlinePedantic extends RulesInlineBase {}
/**
* GFM Inline Grammar
*/
export interface RulesInlineGfm extends RulesInlineBase {
url: RegExp;
del: RegExp;
}
export interface RulesInlineBreaks extends RulesInlineGfm {}
export class MarkedOptions {
gfm?: boolean = true;
tables?: boolean = true;
breaks?: boolean = false;
pedantic?: boolean = false;
sanitize?: boolean = false;
sanitizer?: (text: string) => string;
mangle?: boolean = false;
smartLists?: boolean = false;
silent?: boolean = false;
/**
* @param code The section of code to pass to the highlighter.
* @param lang The programming language specified in the code block.
*/
highlight?: (code: string, lang?: string) => string;
langPrefix?: string = "lang-";
smartypants?: boolean = false;
headerPrefix?: string = "";
/**
* An object containing functions to render tokens to HTML. Default: `new Renderer()`
*/
renderer?: Renderer;
/**
* Self-close the tags for void elements (&lt;br/&gt;, &lt;img/&gt;, etc.)
* with a "/" as required by XHTML.
*/
xhtml?: boolean = false;
/**
* The function that will be using to escape HTML entities.
* By default using inner helper.
*/
escape?: (html: string, encode?: boolean) => string = escape;
/**
* The function that will be using to unescape HTML entities.
* By default using inner helper.
*/
unescape?: (html: string) => string = unescape;
/**
* If set to `true`, an inline text will not be taken in paragraph.
*
* ```ts
* // isNoP == false
* Marked.parse('some text'); // returns '<p>some text</p>'
*
* Marked.setOptions({isNoP: true});
*
* Marked.parse('some text'); // returns 'some text'
* ```
*/
isNoP?: boolean;
}
export interface LexerReturns {
tokens: Token[];
links: Links;
meta: Obj;
}
export interface Parsed {
content: string;
meta: Obj;
}
export interface DebugReturns extends LexerReturns {
result: string;
}
export interface Replacements {
[key: string]: string;
}
export interface RulesInlineCallback {
regexp?: RegExp;
condition(): RegExp;
tokenize(execArr: RegExpExecArray): void;
}
export type SimpleRenderer = (execArr?: RegExpExecArray) => string;

154
markdown/src/marked.ts Normal file
View File

@ -0,0 +1,154 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { BlockLexer } from "./block-lexer.ts";
import {
DebugReturns,
LexerReturns,
Links,
MarkedOptions,
SimpleRenderer,
Token,
TokenType,
Parsed
} from "./interfaces.ts";
import { Parser } from "./parser.ts";
export class Marked {
static options = new MarkedOptions();
protected static simpleRenderers: SimpleRenderer[] = [];
protected static parsed: Parsed = {
content: "",
meta: {},
};
/**
* Merges the default options with options that will be set.
*
* @param options Hash of options.
*/
static setOptions(options: MarkedOptions) {
Object.assign(this.options, options);
return this;
}
/**
* Setting simple block rule.
*/
static setBlockRule(regexp: RegExp, renderer: SimpleRenderer = () => "") {
BlockLexer.simpleRules.push(regexp);
this.simpleRenderers.push(renderer);
return this;
}
/**
* Accepts Markdown text and returns an object containing HTML and metadata.
*
* @param src String of markdown source to be compiled.
* @param options Hash of options. They replace, but do not merge with the default options.
* If you want the merging, you can to do this via `Marked.setOptions()`.
*/
static parse(src: string, options: MarkedOptions = this.options): Parsed {
try {
const { tokens, links, meta } = this.callBlockLexer(src, options);
this.parsed.content = this.callParser(tokens, links, options);
this.parsed.meta = meta;
return this.parsed;
} catch (e) {
this.parsed.content = this.callMe(e);
return this.parsed;
}
}
/**
* Accepts Markdown text and returns object with text in HTML format,
* tokens and links from `BlockLexer.parser()`.
*
* @param src String of markdown source to be compiled.
* @param options Hash of options. They replace, but do not merge with the default options.
* If you want the merging, you can to do this via `Marked.setOptions()`.
*/
static debug(
src: string,
options: MarkedOptions = this.options,
): DebugReturns {
const { tokens, links, meta } = this.callBlockLexer(src, options);
let origin = tokens.slice();
const parser = new Parser(options);
parser.simpleRenderers = this.simpleRenderers;
const result = parser.debug(links, tokens);
/**
* Translates a token type into a readable form,
* and moves `line` field to a first place in a token object.
*/
origin = origin.map((token) => {
token.type = (TokenType as any)[token.type] || token.type;
const line = token.line;
delete token.line;
if (line) {
return { ...{ line }, ...token };
} else {
return token;
}
});
return { tokens: origin, links, meta, result};
}
protected static callBlockLexer(
src: string = "",
options?: MarkedOptions,
): LexerReturns {
if (typeof src != "string") {
throw new Error(
`Expected that the 'src' parameter would have a 'string' type, got '${typeof src}'`,
);
}
// Preprocessing.
src = src
.replace(/\r\n|\r/g, "\n")
.replace(/\t/g, " ")
.replace(/\u00a0/g, " ")
.replace(/\u2424/g, "\n")
.replace(/^ +$/gm, "");
return BlockLexer.lex(src, options, true);
}
protected static callParser(
tokens: Token[],
links: Links,
options?: MarkedOptions,
): string {
if (this.simpleRenderers.length) {
const parser = new Parser(options);
parser.simpleRenderers = this.simpleRenderers;
return parser.parse(links, tokens);
} else {
return Parser.parse(tokens, links, options);
}
}
protected static callMe(err: Error) {
err.message +=
"\nPlease report this to https://github.com/ts-stack/markdown";
if (this.options.silent && this.options.escape) {
return "<p>An error occured:</p><pre>" +
this.options.escape(err.message + "", true) + "</pre>";
}
throw err;
}
}

247
markdown/src/parser.ts Normal file
View File

@ -0,0 +1,247 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { InlineLexer } from "./inline-lexer.ts";
import {
Links,
MarkedOptions,
SimpleRenderer,
Token,
TokenType,
} from "./interfaces.ts";
import { Marked } from "./marked.ts";
import { Renderer } from "./renderer.ts";
/**
* Parsing & Compiling.
*/
export class Parser {
simpleRenderers: SimpleRenderer[] = [];
protected tokens: Token[];
protected token: Token | undefined;
protected inlineLexer!: InlineLexer;
protected options: MarkedOptions;
protected renderer: Renderer;
protected line: number = 0;
constructor(options?: MarkedOptions) {
this.tokens = [];
this.token = undefined;
this.options = options || Marked.options;
this.renderer = this.options.renderer || new Renderer(this.options);
}
static parse(tokens: Token[], links: Links, options?: MarkedOptions): string {
const parser = new this(options);
return parser.parse(links, tokens);
}
parse(links: Links, tokens: Token[]) {
this.inlineLexer = new InlineLexer(
InlineLexer,
links,
this.options,
this.renderer,
);
this.tokens = tokens.reverse();
let out = "";
while (this.next()) {
out += this.tok();
}
return out;
}
debug(links: Links, tokens: Token[]) {
this.inlineLexer = new InlineLexer(
InlineLexer,
links,
this.options,
this.renderer,
);
this.tokens = tokens.reverse();
let out = "";
while (this.next()) {
const outToken: string = this.tok() || "";
if (!this.token) throw ReferenceError;
this.token.line = this.line += outToken.split("\n").length - 1;
out += outToken;
}
return out;
}
protected next() {
return (this.token = this.tokens.pop());
}
protected getNextElement() {
return this.tokens[this.tokens.length - 1];
}
protected parseText() {
if (!this.token) throw ReferenceError;
let body = this.token.text;
let nextElement: Token;
while (
(nextElement = this.getNextElement()) &&
nextElement.type == TokenType.text
) {
body += "\n" + this.next()?.text;
}
return this.inlineLexer.output(body || "");
}
protected tok() {
if (!this.token) throw ReferenceError;
switch (this.token.type) {
case TokenType.space: {
return "";
}
case TokenType.paragraph: {
return this.renderer.paragraph(
this.inlineLexer.output(this.token.text || ""),
);
}
case TokenType.text: {
if (this.options.isNoP) {
return this.parseText();
} else {
return this.renderer.paragraph(this.parseText());
}
}
case TokenType.heading: {
return this.renderer.heading(
this.inlineLexer.output(this.token.text || ""),
this.token.depth || 0,
this.token.text || "",
);
}
case TokenType.listStart: {
let body = "";
const ordered = this.token.ordered;
while (this.next()?.type != TokenType.listEnd) {
body += this.tok();
}
return this.renderer.list(body, ordered);
}
case TokenType.listItemStart: {
let body = "";
while (this.next()?.type != TokenType.listItemEnd) {
body += this.token.type == (TokenType.text as any)
? this.parseText()
: this.tok();
}
return this.renderer.listitem(body);
}
case TokenType.looseItemStart: {
let body = "";
while (this.next()?.type != TokenType.listItemEnd) {
body += this.tok();
}
return this.renderer.listitem(body);
}
case TokenType.code: {
return this.renderer.code(
this.token.text || "",
this.token.lang,
this.token.escaped,
);
}
case TokenType.table: {
let header = "";
let body = "";
let cell;
if (
!this.token || !this.token.header || !this.token.align ||
!this.token.cells
) {
throw ReferenceError;
}
// header
cell = "";
for (let i = 0; i < this.token.header.length; i++) {
const flags = { header: true, align: this.token.align[i] };
const out = this.inlineLexer.output(this.token.header[i]);
cell += this.renderer.tablecell(out, flags);
}
header += this.renderer.tablerow(cell);
for (const row of this.token.cells) {
cell = "";
for (let j = 0; j < row.length; j++) {
cell += this.renderer.tablecell(this.inlineLexer.output(row[j]), {
header: false,
align: this.token.align[j],
});
}
body += this.renderer.tablerow(cell);
}
return this.renderer.table(header, body);
}
case TokenType.blockquoteStart: {
let body = "";
while (this.next()?.type != TokenType.blockquoteEnd) {
body += this.tok();
}
return this.renderer.blockquote(body);
}
case TokenType.hr: {
return this.renderer.hr();
}
case TokenType.html: {
const html = !this.token.pre && !this.options.pedantic
? this.inlineLexer.output(this.token.text || "")
: this.token.text;
return this.renderer.html(html || "");
}
default: {
if (this.simpleRenderers.length) {
for (let i = 0; i < this.simpleRenderers.length; i++) {
if (this.token.type == "simpleRule" + (i + 1)) {
return this.simpleRenderers[i].call(
this.renderer,
this.token.execArr,
);
}
}
}
const errMsg = `Token with "${this.token.type}" type was not found.`;
if (this.options.silent) {
console.log(errMsg);
} else {
throw new Error(errMsg);
}
}
}
}
}

179
markdown/src/renderer.ts Normal file
View File

@ -0,0 +1,179 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import type { Align, MarkedOptions } from "./interfaces.ts";
import { Marked } from "./marked.ts";
export class Renderer {
protected options: MarkedOptions;
constructor(options?: MarkedOptions) {
this.options = options || Marked.options;
}
code(code: string, lang?: string, escaped?: boolean): string {
if (this.options.highlight) {
const out = this.options.highlight(code, lang);
if (out != null && out !== code) {
escaped = true;
code = out;
}
}
if (!this.options.escape) throw ReferenceError;
if (!lang) {
return (
"\n<pre><code>" +
(escaped ? code : this.options.escape(code, true)) +
"\n</code></pre>\n"
);
}
return (
'\n<pre><code class="' +
this.options.langPrefix +
this.options.escape(lang, true) +
'">' +
(escaped ? code : this.options.escape(code, true)) +
"\n</code></pre>\n"
);
}
blockquote(quote: string): string {
return "<blockquote>\n" + quote + "</blockquote>\n";
}
html(html: string): string {
return html;
}
heading(text: string, level: number, raw: string): string {
const id: string =
this.options.headerPrefix + raw.toLowerCase().replace(/[^\w]+/g, "-");
return `<h${level} id="${id}">${text}</h${level}>\n`;
}
hr(): string {
return this.options.xhtml ? "<hr/>\n" : "<hr>\n";
}
list(body: string, ordered?: boolean): string {
const type = ordered ? "ol" : "ul";
return `\n<${type}>\n${body}</${type}>\n`;
}
listitem(text: string): string {
return "<li>" + text + "</li>\n";
}
paragraph(text: string): string {
return "<p>" + text + "</p>\n";
}
table(header: string, body: string): string {
return `
<table>
<thead>
${header}</thead>
<tbody>
${body}</tbody>
</table>
`;
}
tablerow(content: string): string {
return "<tr>\n" + content + "</tr>\n";
}
tablecell(
content: string,
flags: { header?: boolean; align?: Align }
): string {
const type = flags.header ? "th" : "td";
const tag = flags.align
? "<" + type + ' style="text-align:' + flags.align + '">'
: "<" + type + ">";
return tag + content + "</" + type + ">\n";
}
// *** Inline level renderer methods. ***
strong(text: string): string {
return "<strong>" + text + "</strong>";
}
em(text: string): string {
return "<em>" + text + "</em>";
}
codespan(text: string): string {
return "<code>" + text + "</code>";
}
br(): string {
return this.options.xhtml ? "<br/>" : "<br>";
}
del(text: string): string {
return "<del>" + text + "</del>";
}
link(href: string, title: string, text: string): string {
if (this.options.sanitize) {
let prot: string;
if (!this.options.unescape) throw ReferenceError;
try {
prot = decodeURIComponent(this.options.unescape(href))
.replace(/[^\w:]/g, "")
.toLowerCase();
} catch (e) {
return text;
}
if (
prot.indexOf("javascript:") === 0 ||
prot.indexOf("vbscript:") === 0 ||
prot.indexOf("data:") === 0
) {
return text;
}
}
let out = '<a href="' + href + '"';
if (title) {
out += ' title="' + title + '"';
}
out += ">" + text + "</a>";
return out;
}
image(href: string, title: string, text: string): string {
let out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += this.options.xhtml ? "/>" : ">";
return out;
}
text(text: string): string {
return text;
}
}

View File

@ -18,12 +18,19 @@ export { default as Prism } from "https://cdn.skypack.dev/prismjs";
// export { Marked } from "https://deno.land/x/markdown/mod.ts";
export { Marked } from "../../markdown/mod.ts";
// export { Marked } from "../../markdown/mod.ts";
export { Marked } from "https://deno.hibas123.de/raw/markdown/mod.ts";
import DS from "https://raw.githubusercontent.com/hibas123/dndb/master/mod.ts";
/// <reference path="./types/jsx.d.ts" />
export { React, jsx, Fragment } from "../../jsx-html/mod.ts";
// export { React, jsx, Fragment } from "../../jsx-html/mod.ts";
export {
React,
jsx,
Fragment,
} from "https://deno.hibas123.de/raw/jsx-html/mod.ts";
// export {
// React,
// jsx,