Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 8561fcb

Browse filesBrowse files
authored
Deep formatter in playground (#2099)
1 parent 96bb203 commit 8561fcb
Copy full SHA for 8561fcb

10 files changed

+375
-50
lines changed

‎eslint.config.mjs

Copy file name to clipboardExpand all lines: eslint.config.mjs
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export default tseslint.config(
112112
},
113113

114114
{
115-
files: ['__tests__/**/*', 'perf/*'],
115+
files: ['__tests__/**/*', 'website/**/*.test.ts', 'perf/*'],
116116
languageOptions: {
117117
globals: pluginJest.environments.globals.globals,
118118
},

‎jest.config.mjs

Copy file name to clipboardExpand all lines: jest.config.mjs
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const config = {
55
transform: {
66
'^.+\\.(js|ts)$': '<rootDir>/resources/jestPreprocessor.js',
77
},
8-
testRegex: '/__tests__/.*\\.(ts|js)$',
8+
testRegex: ['/__tests__/.*\\.(ts|js)$', '/website/.*\\.test\\.(ts|js)$'],
99
testPathIgnorePatterns: ['/__tests__/ts-utils.ts'],
1010
};
1111

‎package-lock.json

Copy file name to clipboardExpand all lines: package-lock.json
+14Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

Copy file name to clipboardExpand all lines: package.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"@codemirror/theme-one-dark": "^6.1.2",
9090
"@codemirror/view": "^6.36.5",
9191
"@eslint/js": "^9.20.0",
92+
"@jdeniau/immutable-devtools": "^0.2.0",
9293
"@jest/globals": "^29.7.0",
9394
"@rollup/plugin-buble": "1.0.3",
9495
"@rollup/plugin-commonjs": "28.0.2",

‎website/src/repl/FormatterOutput.tsx

Copy file name to clipboard
+4-20Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { toHTML } from 'jsonml-html';
22
import { useEffect, useRef, type JSX } from 'react';
3+
import { Element, JsonMLElementList } from '../worker/jsonml-types';
34

45
/**
56
* immutable-devtools is a console custom formatter.
@@ -8,36 +9,19 @@ import { useEffect, useRef, type JSX } from 'react';
89
* The `jsonml-html` package can convert jsonml to HTML.
910
*/
1011
type Props = {
11-
output: {
12-
header: Array<unknown>;
13-
body?: Array<unknown>;
14-
};
12+
output: JsonMLElementList | Element;
1513
};
1614

1715
export default function FormatterOutput({ output }: Props): JSX.Element {
1816
const header = useRef<HTMLDivElement>(null);
19-
const body = useRef<HTMLDivElement>(null);
2017

21-
const htmlHeader = toHTML(output.header);
18+
const htmlHeader = toHTML(output);
2219

2320
useEffect(() => {
2421
if (header.current && htmlHeader) {
2522
header.current.replaceChildren(htmlHeader);
2623
}
2724
}, [htmlHeader]);
2825

29-
const htmlBody = output.body ? toHTML(output.body) : null;
30-
31-
useEffect(() => {
32-
if (body.current) {
33-
body.current.replaceChildren(htmlBody ?? '');
34-
}
35-
}, [htmlBody]);
36-
37-
return (
38-
<>
39-
<div ref={header}></div>
40-
<div ref={body}></div>
41-
</>
42-
);
26+
return <div ref={header}></div>;
4327
}

‎website/src/repl/Repl.tsx

Copy file name to clipboardExpand all lines: website/src/repl/Repl.tsx
+10-6Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ import React, { useEffect, useRef, useState, type JSX } from 'react';
44
import { Editor } from './Editor';
55
import FormatterOutput from './FormatterOutput';
66
import './repl.css';
7+
import { Element, JsonMLElementList } from '../worker/jsonml-types';
78

89
type Props = { defaultValue: string; onRun?: (code: string) => void };
910

1011
function Repl({ defaultValue, onRun }: Props): JSX.Element {
1112
const [code, setCode] = useState<string>(defaultValue);
12-
const [output, setOutput] = useState<{
13-
header: Array<unknown>;
14-
body?: Array<unknown>;
15-
}>({ header: [] });
13+
const [output, setOutput] = useState<JsonMLElementList | Element>([]);
1614
const workerRef = useRef<Worker | null>(null);
1715

1816
useEffect(() => {
@@ -41,9 +39,15 @@ function Repl({ defaultValue, onRun }: Props): JSX.Element {
4139
workerRef.current.postMessage(code);
4240
workerRef.current.onmessage = (event) => {
4341
if (event.data.error) {
44-
setOutput({ header: ['div', 'Error: ' + event.data.error] });
42+
setOutput(['div', 'Error: ' + event.data.error]);
4543
} else {
46-
setOutput(event.data.output);
44+
const { output } = event.data;
45+
46+
if (typeof output === 'object' && !Array.isArray(output)) {
47+
setOutput(['div', { object: output }]);
48+
} else {
49+
setOutput(output);
50+
}
4751
}
4852
};
4953
}
+104Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { describe, it, expect } from '@jest/globals';
2+
import { Element, explodeElement } from './jsonml-types';
3+
4+
describe('explodeElement', () => {
5+
it('should explode an element', () => {
6+
expect(explodeElement(['div'])).toEqual({
7+
tagName: 'div',
8+
attributes: undefined,
9+
children: [],
10+
});
11+
});
12+
13+
it('should explode an element with attributes', () => {
14+
expect(explodeElement(['div', { id: 'test' }])).toEqual({
15+
tagName: 'div',
16+
attributes: { id: 'test' },
17+
children: [],
18+
});
19+
});
20+
21+
it('should explode an element with children', () => {
22+
expect(explodeElement(['div', { id: 'test' }, 'Hello'])).toEqual({
23+
tagName: 'div',
24+
attributes: { id: 'test' },
25+
children: ['Hello'],
26+
});
27+
});
28+
29+
it('should explode an element with multiple children', () => {
30+
expect(explodeElement(['div', { id: 'test' }, 'Hello', 'World'])).toEqual({
31+
tagName: 'div',
32+
attributes: { id: 'test' },
33+
children: ['Hello', 'World'],
34+
});
35+
});
36+
37+
it('should explode an element without attributes with multiple children', () => {
38+
expect(explodeElement(['div', 'Hello', 'World'])).toEqual({
39+
tagName: 'div',
40+
attributes: undefined,
41+
children: ['Hello', 'World'],
42+
});
43+
});
44+
45+
it('should explode an element with a nested element', () => {
46+
expect(explodeElement(['div', { id: 'test' }, ['span', 'Hello']])).toEqual({
47+
tagName: 'div',
48+
attributes: { id: 'test' },
49+
children: [['span', 'Hello']],
50+
});
51+
});
52+
53+
it('should explode an element with a nested element with attributes', () => {
54+
expect(
55+
explodeElement([
56+
'div',
57+
{ id: 'test' },
58+
['span', { class: 'test' }, 'Hello'],
59+
])
60+
).toEqual({
61+
tagName: 'div',
62+
attributes: { id: 'test' },
63+
children: [['span', { class: 'test' }, 'Hello']],
64+
});
65+
});
66+
67+
it('should explode an element with a nested element with multiple children', () => {
68+
expect(
69+
explodeElement([
70+
'div',
71+
{ id: 'test' },
72+
['span', 'Hello'],
73+
['span', { id: 'world' }, 'World'],
74+
])
75+
).toEqual({
76+
tagName: 'div',
77+
attributes: { id: 'test' },
78+
children: [
79+
['span', 'Hello'],
80+
['span', { id: 'world' }, 'World'],
81+
],
82+
});
83+
});
84+
85+
it('should handle immutable list jsonml', () => {
86+
const spanElement: Element = [
87+
'span',
88+
{ style: 'color: light-dark( #881391, #D48CE6)' },
89+
'0: ',
90+
];
91+
const objectElement: Element = ['object', { object: ['a'] }];
92+
93+
const element: Element = ['li', spanElement, objectElement];
94+
95+
expect(explodeElement(element)).toEqual({
96+
tagName: 'li',
97+
attributes: undefined,
98+
children: [
99+
['span', { style: 'color: light-dark( #881391, #D48CE6)' }, '0: '],
100+
['object', { object: ['a'] }],
101+
],
102+
});
103+
});
104+
});

‎website/src/worker/jsonml-types.ts

Copy file name to clipboardExpand all lines: website/src/worker/jsonml-types.ts
+51-4Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,67 @@
1010
// Basic types
1111
type TagName = string;
1212
type AttributeName = string;
13-
type AttributeValue = string | number | boolean | null;
13+
type AttributeValue = string | number | boolean | null | object;
1414

1515
// Attributes
1616
// type Attribute = [AttributeName, AttributeValue];
1717
// type AttributeList = Attribute[];
1818
export type Attributes = Record<AttributeName, AttributeValue>;
1919

20+
type ElementWithAttributes =
21+
| [TagName, Attributes, ...Element[]] // [tag-name, attributes, element-list]
22+
| [TagName, Attributes]; // [tag-name, attributes]
23+
2024
// Elements
2125
export type Element =
22-
| [TagName, Attributes, ...Element[]] // [tag-name, attributes, element-list]
23-
| [TagName, Attributes] // [tag-name, attributes]
26+
| ElementWithAttributes
2427
| [TagName, ...Element[]] // [tag-name, element-list]
2528
| [TagName] // [tag-name]
2629
| string; // string
2730

2831
// Element list is just a list of elements
29-
export type JsonMLElementList = Element[];
32+
export type JsonMLElementList = Array<Element | JsonMLElementList>;
33+
34+
export function isElement(maybeElement: unknown): maybeElement is Element {
35+
return (
36+
typeof maybeElement === 'string' ||
37+
(Array.isArray(maybeElement) &&
38+
maybeElement.length >= 1 &&
39+
typeof maybeElement[0] === 'string')
40+
);
41+
}
42+
43+
function hasAttributes(
44+
maybeElementWithAttributes: Element
45+
): maybeElementWithAttributes is ElementWithAttributes {
46+
return (
47+
Array.isArray(maybeElementWithAttributes) &&
48+
typeof maybeElementWithAttributes[1] === 'object' &&
49+
!Array.isArray(maybeElementWithAttributes[1])
50+
);
51+
}
52+
53+
type ExplodedElement = {
54+
tagName: TagName;
55+
attributes?: Attributes;
56+
children: Element[];
57+
};
58+
59+
export function explodeElement(element: Element): ExplodedElement {
60+
if (typeof element === 'string') {
61+
return { tagName: element, children: [] };
62+
}
63+
64+
if (hasAttributes(element)) {
65+
const [tagName, attributes, ...children] = element;
66+
67+
return { tagName, attributes, children };
68+
}
69+
70+
const [tagName, attributes, ...children] = element;
71+
72+
return {
73+
tagName,
74+
children: [attributes, ...children].filter(isElement),
75+
};
76+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.