Adding hotfixes for packages
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
46d8f8b289
commit
1b2d85eeef
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
test.ini
|
16
jsx-html/.github/workflows/ci.yml
vendored
Normal file
16
jsx-html/.github/workflows/ci.yml
vendored
Normal 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
3
jsx-html/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/dist
|
||||||
|
node_modules
|
||||||
|
tmp
|
5
jsx-html/.prettierrc.json
Normal file
5
jsx-html/.prettierrc.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
4
jsx-html/.vscode/settings.json
vendored
Normal file
4
jsx-html/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"deno.enable": true,
|
||||||
|
"deno.unstable": true
|
||||||
|
}
|
21
jsx-html/LICENSE
Normal file
21
jsx-html/LICENSE
Normal 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
103
jsx-html/README.md
Normal 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 it’s 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).
|
26
jsx-html/babel-remove-ext.js
Normal file
26
jsx-html/babel-remove-ext.js
Normal 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
6
jsx-html/constants.ts
Normal 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
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
17
jsx-html/deno.json
Normal 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
101
jsx-html/deno2nodejs.js
Normal 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
6
jsx-html/examples/00.tsx
Normal 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
62
jsx-html/examples/01.tsx
Normal 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 />`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
6
jsx-html/examples/02/02.tsx
Normal file
6
jsx-html/examples/02/02.tsx
Normal 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);
|
6
jsx-html/examples/02/tsconfig.json
Normal file
6
jsx-html/examples/02/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react",
|
||||||
|
"jsxFactory": "jsx"
|
||||||
|
}
|
||||||
|
}
|
30
jsx-html/examples/03-async.tsx
Normal file
30
jsx-html/examples/03-async.tsx
Normal 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
9
jsx-html/examples/04.tsx
Normal 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
27
jsx-html/examples/05.tsx
Normal 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
19
jsx-html/examples/06.tsx
Normal 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>',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
19
jsx-html/examples/browser/dist/index.html
vendored
Normal file
19
jsx-html/examples/browser/dist/index.html
vendored
Normal 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
502
jsx-html/examples/browser/dist/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
25
jsx-html/examples/browser/package.json
Normal file
25
jsx-html/examples/browser/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
5
jsx-html/examples/browser/src/index.tsx
Normal file
5
jsx-html/examples/browser/src/index.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { jsx, ElementNode } from 'async-jsx-html';
|
||||||
|
|
||||||
|
export function Test(): ElementNode {
|
||||||
|
return <div>Hello World</div>;
|
||||||
|
}
|
15
jsx-html/examples/browser/tsconfig.json
Normal file
15
jsx-html/examples/browser/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"module": "commonjs",
|
||||||
|
"jsx": "react",
|
||||||
|
"jsxFactory": "jsx",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"lib": [
|
||||||
|
"es6",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
33
jsx-html/examples/browser/webpack.config.js
Normal file
33
jsx-html/examples/browser/webpack.config.js
Normal 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
|
||||||
|
},
|
||||||
|
};
|
3846
jsx-html/examples/browser/yarn.lock
Normal file
3846
jsx-html/examples/browser/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
30
jsx-html/examples/node_01.js
Normal file
30
jsx-html/examples/node_01.js
Normal 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);
|
22
jsx-html/examples/node_01.jsx
Normal file
22
jsx-html/examples/node_01.jsx
Normal 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
5
jsx-html/jsx.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
declare namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
[elemName: string]: any;
|
||||||
|
}
|
||||||
|
}
|
33
jsx-html/jsx.ts
Normal file
33
jsx-html/jsx.ts
Normal 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
12
jsx-html/meta.json
Normal 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
27
jsx-html/mod.ts
Normal 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);
|
||||||
|
},
|
||||||
|
};
|
37
jsx-html/node/ComponentNode.ts
Normal file
37
jsx-html/node/ComponentNode.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
jsx-html/node/ElementNode.ts
Normal file
91
jsx-html/node/ElementNode.ts
Normal 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(' ')}`;
|
||||||
|
}
|
||||||
|
}
|
15
jsx-html/node/FragmentNode.ts
Normal file
15
jsx-html/node/FragmentNode.ts
Normal 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
29
jsx-html/node/Node.ts
Normal 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
12
jsx-html/node/TextNode.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
13
jsx-html/node/utils/htmlEncode.ts
Normal file
13
jsx-html/node/utils/htmlEncode.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export function doubleQuoteEncode(text: string): string {
|
||||||
|
return text
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function htmlEncode(text: string): string {
|
||||||
|
return doubleQuoteEncode(text
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/\//g, '/')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/'/g, '''));
|
||||||
|
}
|
31
jsx-html/node/utils/normalizeChildren.ts
Normal file
31
jsx-html/node/utils/normalizeChildren.ts
Normal 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
6
jsx-html/nodejs/constants.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export declare enum NODE_TYPE {
|
||||||
|
ELEMENT = "element",
|
||||||
|
TEXT = "text",
|
||||||
|
COMPONENT = "component",
|
||||||
|
FRAGMENT = "fragment"
|
||||||
|
}
|
12
jsx-html/nodejs/constants.js
Normal file
12
jsx-html/nodejs/constants.js
Normal 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
|
1
jsx-html/nodejs/constants.js.map
Normal file
1
jsx-html/nodejs/constants.js.map
Normal 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
5
jsx-html/nodejs/jsx.d.ts
vendored
Normal 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
19
jsx-html/nodejs/jsx.js
Normal 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
|
1
jsx-html/nodejs/jsx.js.map
Normal file
1
jsx-html/nodejs/jsx.js.map
Normal 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
9
jsx-html/nodejs/mod.d.ts
vendored
Normal 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
17
jsx-html/nodejs/mod.js
Normal 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
|
1
jsx-html/nodejs/mod.js.map
Normal file
1
jsx-html/nodejs/mod.js.map
Normal 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
11
jsx-html/nodejs/node/ComponentNode.d.ts
vendored
Normal 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[]>;
|
||||||
|
}
|
43
jsx-html/nodejs/node/ComponentNode.js
Normal file
43
jsx-html/nodejs/node/ComponentNode.js
Normal 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
|
1
jsx-html/nodejs/node/ComponentNode.js.map
Normal file
1
jsx-html/nodejs/node/ComponentNode.js.map
Normal 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
12
jsx-html/nodejs/node/ElementNode.d.ts
vendored
Normal 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;
|
||||||
|
}
|
60
jsx-html/nodejs/node/ElementNode.js
Normal file
60
jsx-html/nodejs/node/ElementNode.js
Normal 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
|
1
jsx-html/nodejs/node/ElementNode.js.map
Normal file
1
jsx-html/nodejs/node/ElementNode.js.map
Normal 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"}
|
8
jsx-html/nodejs/node/FragmentNode.d.ts
vendored
Normal file
8
jsx-html/nodejs/node/FragmentNode.d.ts
vendored
Normal 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[]>;
|
||||||
|
}
|
16
jsx-html/nodejs/node/FragmentNode.js
Normal file
16
jsx-html/nodejs/node/FragmentNode.js
Normal 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
|
1
jsx-html/nodejs/node/FragmentNode.js.map
Normal file
1
jsx-html/nodejs/node/FragmentNode.js.map
Normal 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
9
jsx-html/nodejs/node/Node.d.ts
vendored
Normal 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[]>;
|
||||||
|
}
|
38
jsx-html/nodejs/node/Node.js
Normal file
38
jsx-html/nodejs/node/Node.js
Normal 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
|
1
jsx-html/nodejs/node/Node.js.map
Normal file
1
jsx-html/nodejs/node/Node.js.map
Normal 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
7
jsx-html/nodejs/node/TextNode.d.ts
vendored
Normal 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[]>;
|
||||||
|
}
|
27
jsx-html/nodejs/node/TextNode.js
Normal file
27
jsx-html/nodejs/node/TextNode.js
Normal 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
|
1
jsx-html/nodejs/node/TextNode.js.map
Normal file
1
jsx-html/nodejs/node/TextNode.js.map
Normal 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"}
|
2
jsx-html/nodejs/node/utils/htmlEncode.d.ts
vendored
Normal file
2
jsx-html/nodejs/node/utils/htmlEncode.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export declare function doubleQuoteEncode(text: string): string;
|
||||||
|
export declare function htmlEncode(text: string): string;
|
12
jsx-html/nodejs/node/utils/htmlEncode.js
Normal file
12
jsx-html/nodejs/node/utils/htmlEncode.js
Normal 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, '"');
|
||||||
|
}
|
||||||
|
exports.doubleQuoteEncode = doubleQuoteEncode;
|
||||||
|
function htmlEncode(text) {
|
||||||
|
return doubleQuoteEncode(text.replace(/&/g, '&').replace(/\//g, '/').replace(/</g, '<').replace(/>/g, '>').replace(/'/g, '''));
|
||||||
|
}
|
||||||
|
exports.htmlEncode = htmlEncode;
|
||||||
|
//# sourceMappingURL=htmlEncode.js.map
|
1
jsx-html/nodejs/node/utils/htmlEncode.js.map
Normal file
1
jsx-html/nodejs/node/utils/htmlEncode.js.map
Normal 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"}
|
2
jsx-html/nodejs/node/utils/normalizeChildren.d.ts
vendored
Normal file
2
jsx-html/nodejs/node/utils/normalizeChildren.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { NullableChildType, ChildNodeType } from "../../types";
|
||||||
|
export declare function normalizeChildren(children: NullableChildType[]): ChildNodeType[];
|
27
jsx-html/nodejs/node/utils/normalizeChildren.js
Normal file
27
jsx-html/nodejs/node/utils/normalizeChildren.js
Normal 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
|
1
jsx-html/nodejs/node/utils/normalizeChildren.js.map
Normal file
1
jsx-html/nodejs/node/utils/normalizeChildren.js.map
Normal 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
15
jsx-html/nodejs/types.d.ts
vendored
Normal 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
3
jsx-html/nodejs/types.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//# sourceMappingURL=types.js.map
|
1
jsx-html/nodejs/types.js.map
Normal file
1
jsx-html/nodejs/types.js.map
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"types.js","sourceRoot":"","sources":["../tmp/types.ts"],"names":[],"mappings":""}
|
16
jsx-html/note.md
Normal file
16
jsx-html/note.md
Normal 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
25
jsx-html/package.json
Normal 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
23
jsx-html/types.ts
Normal 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
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
14
markdown/.vscode/launch.json
vendored
Normal 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
9
markdown/.vscode/settings.json
vendored
Normal 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
21
markdown/LICENSE
Normal 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
62
markdown/README.md
Normal 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><p></code> tag</p>
|
||||||
|
<hr />
|
||||||
|
<p>Code Block (md2html.ts)</p>
|
||||||
|
|
||||||
|
<pre><code class="lang-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))
|
||||||
|
</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
BIN
markdown/example.html
Normal file
Binary file not shown.
34
markdown/example.md
Normal file
34
markdown/example.md
Normal 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
8
markdown/md2html.ts
Normal 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
12
markdown/meta.json
Normal 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
8
markdown/mod.ts
Normal 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
520
markdown/src/block-lexer.ts
Normal 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 };
|
||||||
|
}
|
||||||
|
}
|
43
markdown/src/extend-regexp.ts
Normal file
43
markdown/src/extend-regexp.ts
Normal 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
64
markdown/src/helpers.ts
Normal 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 = {
|
||||||
|
"&": "&",
|
||||||
|
"<": "<",
|
||||||
|
">": ">",
|
||||||
|
'"': """,
|
||||||
|
// tslint:disable-next-line:quotemark
|
||||||
|
"'": "'",
|
||||||
|
};
|
||||||
|
|
||||||
|
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 "";
|
||||||
|
});
|
||||||
|
}
|
419
markdown/src/inline-lexer.ts
Normal file
419
markdown/src/inline-lexer.ts
Normal 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
196
markdown/src/interfaces.ts
Normal 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 (<br/>, <img/>, 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
154
markdown/src/marked.ts
Normal 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
247
markdown/src/parser.ts
Normal 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
179
markdown/src/renderer.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 "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";
|
import DS from "https://raw.githubusercontent.com/hibas123/dndb/master/mod.ts";
|
||||||
|
|
||||||
/// <reference path="./types/jsx.d.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 {
|
// export {
|
||||||
// React,
|
// React,
|
||||||
// jsx,
|
// jsx,
|
||||||
|
Loading…
Reference in New Issue
Block a user