Skip to content

Commit 9f8f193

Browse files
committed
Initial Fork Changes for bbcode-compiler-react
We're now reacting baybeeee!
1 parent ed138e9 commit 9f8f193

25 files changed

+865
-852
lines changed

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ module.exports = {
3434
],
3535

3636
rules: {
37+
'no-use-before-define': 'off',
3738
semi: 'off',
3839
'comma-dangle': 'off',
3940
'no-undef': 'off',

README.md

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
1-
# BBCode Compiler
1+
# BBCode Compiler - React
22

3-
This is a fast BBCode parser and HTML generator with TypeScript support.
3+
A fast BBCode parser and React generator with TypeScript support. Forked from [Trinovantes/bbcode-compiler](https://github.com/Trinovantes/bbcode-compiler).
44

5-
**Note:** This package is only available in ESM format.
5+
**Note:** This package is a [Pure ESM package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
66

77
## Usage
88

99
```ts
10-
import { generateHtml } from 'bbcode-compiler'
10+
import { generateReact } from 'bbcode-compiler-react'
1111

12-
// <strong>Hello World</strong>
13-
const html = generateHtml('[b]Hello World[/b]')
12+
// React: <b>Hello World</b>
13+
const react = generateReact('[b]Hello World[/b]')
1414
```
1515

1616
## Extending With Custom Tags
1717

18-
```ts
19-
import { generateHtml, getTagImmediateText, htmlTransforms, getWidthHeightAttr } from 'bbcode-compiler'
18+
```tsx
19+
import { generateReact, defaultTransforms, getWidthHeightAttr } from 'bbcode-compiler-react'
2020

21-
const customTransforms: typeof htmlTransforms = [
21+
const customTransforms: typeof defaultTransforms = [
2222
// Default tags included with this package
23-
...htmlTransforms,
23+
...defaultTransforms,
2424

2525
// You can override a default tag by including it after the original in the transforms array
2626
{
2727
name: 'b',
28-
start: () => '<b>',
29-
end: () => '</b>',
28+
component({ tagNode, children }) {
29+
return <b>
30+
{children}
31+
</b>
32+
}
3033
},
3134

3235
// Create new tag
@@ -35,8 +38,8 @@ const customTransforms: typeof htmlTransforms = [
3538
{
3639
name: 'youtube',
3740
skipChildren: true, // Do not actually render the "https://www.youtube.com/watch?v=dQw4w9WgXcQ" text
38-
start: (tagNode) => {
39-
const src = getTagImmediateText(tagNode)
41+
component({ tagNode, children }) {
42+
const src = tagNode.getTagImmediateText()
4043
if (!src) {
4144
return false
4245
}
@@ -49,17 +52,15 @@ const customTransforms: typeof htmlTransforms = [
4952
const videoId = matches[1]
5053
const { width, height } = getWidthHeightAttr(tagNode)
5154

52-
return `
53-
<iframe
54-
width="${width ?? 560}"
55-
height="${height ?? 315}"
56-
src="https://www.youtube.com/embed/${videoId}"
57-
title="YouTube Video Player"
58-
frameborder="0"
59-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
60-
allowfullscreen
61-
></iframe>
62-
`
55+
return <iframe
56+
width={width ?? 560}
57+
height={height ?? 315}
58+
src={`https://www.youtube.com/embed/${videoId}`}
59+
title="YouTube Video Player"
60+
frameBorder="0"
61+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
62+
allowFullScreen
63+
/>
6364
},
6465
},
6566
]
@@ -69,9 +70,9 @@ const customTransforms: typeof htmlTransforms = [
6970
// height="315"
7071
// src="https://www.youtube.com/embed/dQw4w9WgXcQ"
7172
// title="YouTube Video Player"
72-
// frameborder="0"
73+
// frameBorder="0"
7374
// allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
74-
// allowfullscreen
75+
// allowFullScreen
7576
// ></iframe>
76-
const html = generateHtml('[youtube]https://www.youtube.com/watch?v=dQw4w9WgXcQ[/youtube]', customTransforms)
77+
const html = generateReact('[youtube]https://www.youtube.com/watch?v=dQw4w9WgXcQ[/youtube]', customTransforms)
7778
```

package.json

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"name": "bbcode-compiler",
3-
"version": "0.1.5",
4-
"description": "Parses BBCode and generates HTML ",
2+
"name": "bbcode-compiler-react",
3+
"version": "1.0.0",
4+
"description": "Compiles BBCode into React components",
55
"exports": "./dist/index.js",
66
"types": "./dist/index.d.ts",
77
"files": [
@@ -13,13 +13,9 @@
1313
"sideEffects": false,
1414
"repository": {
1515
"type": "git",
16-
"url": "https://github.com/Trinovantes/bbcode-compiler"
17-
},
18-
"author": {
19-
"name": "Stephen",
20-
"email": "hello@stephenli.ca",
21-
"url": "https://www.stephenli.ca"
16+
"url": "https://github.com/BellCubeDev/bbcode-compiler-react"
2217
},
18+
"author": "BellCube",
2319
"license": "MIT",
2420
"private": false,
2521
"scripts": {
@@ -28,8 +24,8 @@
2824
"prepublishOnly": "rm -rf ./dist && yarn build",
2925
"lint": "tsc --noemit && eslint --ext .ts,.js,.vue --ignore-path .gitignore .",
3026
"test": "vitest",
31-
"benchmark": " node --loader ts-node/esm --experimental-specifier-resolution=node tests/benchmarks/benchmark.ts",
32-
"profile": " node --loader ts-node/esm --experimental-specifier-resolution=node --prof --no-logfile-per-isolate tests/benchmarks/profile.ts && node --prof-process v8.log > v8.txt"
27+
"benchmark": " yarn build && node --loader ts-node/esm --experimental-specifier-resolution=node tests/benchmarks/benchmark.ts",
28+
"profile": " yarn build && node --loader ts-node/esm --experimental-specifier-resolution=node --prof --no-logfile-per-isolate tests/benchmarks/profile.ts && node --prof-process v8.log > v8.txt"
3329
},
3430
"devDependencies": {
3531
"@bbob/html": "^3.0.0",
@@ -38,9 +34,12 @@
3834
"@types/benchmark": "^2.1.1",
3935
"@types/markdown-it": "^13.0.1",
4036
"@types/node": "^20.7.0",
37+
"@types/react": "^18.3.2",
38+
"@types/react-dom": "^18.3.0",
4139
"@typescript-eslint/eslint-plugin": "^7.5.0",
4240
"@typescript-eslint/parser": "^7.5.0",
4341
"bbcode": "^0.1.5",
42+
"bbcode-compiler": "^0.1.5",
4443
"bbcode-parser": "^1.0.10",
4544
"bbcodejs": "^0.0.4",
4645
"benchmark": "^2.1.4",
@@ -53,10 +52,14 @@
5352
"eslint-plugin-promise": "^6.0.0",
5453
"eslint-plugin-vue": "^9.9.0",
5554
"markdown-it": "^14.0.0",
55+
"react-dom": "^18.3.1",
5656
"ts-bbcode-parser": "^1.0.4",
5757
"ts-node": "^10.8.1",
5858
"typescript": "^5.0.2",
5959
"vitest": "^1.2.1",
6060
"ya-bbcode": "^4.0.0"
61+
},
62+
"dependencies": {
63+
"react": "^18.3.1"
6164
}
6265
}

src/generateHtml.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/generateReact.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Generator } from './generator/Generator.js'
2+
import { defaultTransforms } from './generator/transforms/defaultTransforms.js'
3+
import { Lexer } from './lexer/Lexer.js'
4+
import { Parser } from './parser/Parser.js'
5+
6+
export function generateReact(
7+
/** BBCode to parse */
8+
input: string,
9+
/** A list of transforms
10+
*
11+
* @see defaultTransforms
12+
*/
13+
transforms = defaultTransforms,
14+
/** If true, throws an error when a tag is not in the transforms list. This is the default behavior.
15+
*
16+
* If false, will only throw a warning and render the tag as plain text.
17+
*/
18+
errorIfNoTransform = true,
19+
): React.ReactElement {
20+
const lexer = new Lexer()
21+
const tokens = lexer.tokenize(input)
22+
23+
const parser = new Parser(transforms)
24+
const root = parser.parse(input, tokens)
25+
26+
const generator = new Generator(transforms, errorIfNoTransform)
27+
return generator.generate(root)
28+
}

src/generator/Generator.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.

src/generator/Generator.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from 'react';
2+
import { AstNode, AstNodeType, RootNode } from '../parser/AstNode.js'
3+
import { defaultTransforms } from './transforms/defaultTransforms.js'
4+
import type { Transform } from './transforms/Transform.js'
5+
6+
export class Generator {
7+
transforms: ReadonlyMap<string, Transform>
8+
9+
constructor(transforms = defaultTransforms, public errorIfNoTransform = true) {
10+
this.transforms = new Map(transforms.map((transform) => [transform.name, transform]))
11+
}
12+
13+
private joinAdjacentStrings(children: Array<React.ReactNode>): Array<React.ReactNode> {
14+
return children.reduce((acc: Array<React.ReactNode>, child) => {
15+
if (typeof child === 'string' && typeof acc[acc.length - 1]! === 'string') {
16+
acc[acc.length - 1]! += child
17+
} else {
18+
acc.push(child)
19+
}
20+
return acc
21+
}, [] as Array<React.ReactNode>)
22+
}
23+
24+
private generateForNode(this: Generator, node: AstNode, i: number): [number, React.ReactNode] {
25+
switch (node.nodeType) {
26+
case AstNodeType.LinebreakNode: {
27+
return [i + 1, <br key={i} />]
28+
} case AstNodeType.TextNode: {
29+
return [i, node.str]
30+
} case AstNodeType.TagNode: {
31+
const tagName = node.tagName
32+
const transform = this.transforms.get(tagName)
33+
if (!transform) {
34+
if (this.errorIfNoTransform) {
35+
throw new Error(`Unrecognized bbcode ${node.tagName}`)
36+
} else {
37+
console.warn(`Unrecognized bbcode ${node.tagName}`)
38+
}
39+
return [i, node.ogEndTag]
40+
}
41+
42+
const renderedChildren = node.children.map((child, j) => {
43+
const [newI, renderedChild] = this.generateForNode(child, i)
44+
i = newI
45+
return renderedChild
46+
})
47+
48+
const { component: Component } = transform
49+
const renderedTag = <Component tagNode={node} key={i}>
50+
{this.joinAdjacentStrings(renderedChildren)}
51+
</Component>
52+
53+
if ((renderedTag as any) === false) {
54+
return [i, node.ogStartTag + renderedChildren + node.ogEndTag]
55+
}
56+
57+
return [i + 1, renderedTag]
58+
} default: {
59+
const renderedChildren = node.children.map((child, j) => {
60+
const [newI, renderedChild] = this.generateForNode(child, i)
61+
i = newI
62+
return renderedChild
63+
});
64+
65+
return [i + 1, this.joinAdjacentStrings(renderedChildren)]
66+
}
67+
}
68+
}
69+
70+
public generate(root: RootNode): React.ReactElement {
71+
return <>
72+
{this.generateForNode(root, 0)[1]}
73+
</>
74+
}
75+
}

0 commit comments

Comments
 (0)