feat(sdk, plugins): jsx support

This commit is contained in:
Artemy 2024-05-13 16:35:36 +03:00
parent bdf625bb1f
commit 3cee45b591
11 changed files with 58 additions and 15 deletions

View File

@ -0,0 +1,13 @@
import { Engine } from '@txtdot/sdk';
import { JSX } from '@txtdot/sdk';
const Habr = new Engine('Habr', 'Habr parser', ['*']);
Habr.route('*path', async (input, ro) => {
return {
content: <div>Test</div>,
};
});
export default Habr;

View File

@ -1,5 +1,6 @@
import StackOverflow from './stackoverflow';
import Readability from './readability';
import SearX from './searx';
import Habr from './habr';
export { StackOverflow, Readability, SearX };
export { StackOverflow, Readability, SearX, Habr };

View File

@ -18,7 +18,7 @@ Readability.route('*path', async (input, ro) => {
}
return {
document: parseHTML(parsed.content).document,
content: parsed.content,
textContent: parsed.textContent,
title: parsed.title,
lang: parsed.lang,

View File

@ -46,7 +46,7 @@ async function search(
const textContent = articles_parsed.map((a) => a.text).join('');
return {
document: parseHTML(content).document,
content: content,
textContent,
title: `${search} - Searx - Page ${page}`,
lang: document.documentElement.lang,

View File

@ -16,9 +16,7 @@ async function questions(
const answers = allAnswers.map((a) => postParser(a));
return {
document: parseHTML(
`${question}<hr>${answers.length} answers <hr>${answers.join('<hr>')}`
).document,
content: `${question}<hr>${answers.length} answers <hr>${answers.join('<hr>')}`,
textContent: `${ro.q.id}/${ro.q.slug}\nText output not supported`, // TODO
title,
lang: document.documentElement.lang,

View File

@ -27,8 +27,7 @@ async function users(
.join('<br/>');
return {
document: parseHTML(`${userInfo}<hr><h3>Top Posts</h3>${topPosts}`)
.document,
content: `${userInfo}<hr><h3>Top Posts</h3>${topPosts}`,
textContent: `${ro.q.id}/${ro.q.slug}\n`, // TODO
title: document.querySelector('title')?.textContent || '',
lang: document.documentElement.lang,

View File

@ -13,13 +13,13 @@
/* Language and Environment */
"target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
"jsx": "react" /* Specify what JSX code is generated. */,
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
"reactNamespace": "JSX" /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */,
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */

24
packages/sdk/src/jsx.ts Normal file
View File

@ -0,0 +1,24 @@
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace JSX {
export type Element = string;
export interface IntrinsicElements {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[elemName: string]: any;
}
}
export function createElement(
name: string,
props: { [id: string]: string },
...content: string[]
) {
props = props || {};
const propsstr = Object.keys(props)
.map((key) => {
const value = props[key];
if (key === 'className') return `class=${value}`;
else return `${key}=${value}`;
})
.join(' ');
return `<${name} ${propsstr}>${content.join('')}</${name}>`;
}

View File

@ -18,6 +18,8 @@ import {
handlerSchema,
} from './types/handler';
import * as JSX from './jsx';
export {
Engine,
EngineParseError,
@ -32,4 +34,5 @@ export {
HandlerOutput,
Route,
handlerSchema,
JSX,
};

View File

@ -33,7 +33,7 @@ export interface HandlerOutput {
}
export interface EngineOutput {
document: Document;
content: string;
textContent?: string;
title?: string;
lang?: string;

View File

@ -9,6 +9,7 @@ import replaceHref from './utils/replace-href';
import { Engine } from '@txtdot/sdk';
import { HandlerInput, HandlerOutput } from '@txtdot/sdk';
import config from './config';
import { parseHTML } from 'linkedom';
interface IEngineId {
[key: string]: number;
@ -51,6 +52,7 @@ export class Distributor {
}
const engine = this.getFallbackEngine(urlObj.hostname, engineName);
const output = await engine.handle(
new HandlerInput(
await decodeStream(data, parseEncodingName(mime)),
@ -58,22 +60,25 @@ export class Distributor {
)
);
const dom = parseHTML(output.content);
// post-process
// TODO: generate dom in handler and not parse here twice
replaceHref(
output.document,
dom.document,
requestUrl,
new URL(remoteUrl),
engineName,
redirectPath
);
const purify = DOMPurify();
const content = purify.sanitize(output.document.toString());
const purify = DOMPurify(dom);
const content = purify.sanitize(output.content);
return {
content,
textContent: output.textContent || output.document.textContent || '',
textContent:
output.textContent || dom.document.documentElement.textContent || '',
title: output.title,
lang: output.lang,
};