- Replace CRA with Vite as the build tool - Rename index.tsx to main.tsx (Vite convention) - Add vite.config.ts, tsconfig.app.json, tsconfig.node.json - Move index.html from public/ to project root - Replace yarn.lock with package-lock.json - Remove CRA-specific files (reportWebVitals, setupTests, react-app-env.d.ts) - Update package.json, tsconfig.json, .gitignore, and README.md - Adjust all source files for Vite compatibility
151 lines
4.4 KiB
TypeScript
151 lines
4.4 KiB
TypeScript
import ReactHtmlParser, {
|
||
convertNodeToElement,
|
||
type DomElement,
|
||
type DomNode,
|
||
type Transform,
|
||
} from '@orrisroot/react-html-parser';
|
||
import type React from 'react';
|
||
import { HashLink } from 'react-router-hash-link';
|
||
import type { MultiLang } from '../../config';
|
||
import Functions from '../../functions';
|
||
|
||
interface Props {
|
||
lang: MultiLang;
|
||
text: string;
|
||
dohtml?: boolean;
|
||
dosmiley?: boolean;
|
||
doxcode?: boolean;
|
||
doimage?: boolean;
|
||
dobr?: boolean;
|
||
}
|
||
|
||
const preConvertXCode = (text: string, doxcode: boolean): string => {
|
||
if (doxcode) {
|
||
return text.replace(/\[code\](.*)\[\/code\]/gs, (_m0, m1) => {
|
||
return '[code]' + Functions.base64Encode(m1) + '[/code]';
|
||
});
|
||
}
|
||
return text;
|
||
};
|
||
|
||
const postConvertXCode = (text: string, doxcode: boolean, doimage: boolean): string => {
|
||
if (doxcode) {
|
||
return text.replace(/\[code\](.*)\[\/code\]/gs, (_m0, m1) => {
|
||
const text = convertXCode(Functions.htmlspecialchars(Functions.base64Decode(m1)), doimage);
|
||
return '<div class="xoopsCode"><pre><code>' + text + '</code></pre></div>';
|
||
});
|
||
}
|
||
return text;
|
||
};
|
||
|
||
const convertClickable = (text: string) => {
|
||
text = text.replace(
|
||
/(^|[^\]_a-zA-Z0-9-="'/]+)((?:https?|ftp)(?::\/\/[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+[a-zA-Z0-9=]))/g,
|
||
(...matches) => {
|
||
return (
|
||
matches[1] +
|
||
'<a href="' +
|
||
matches[2] +
|
||
'" target="_blank" rel="external noopener noreferrer">' +
|
||
matches[2] +
|
||
'</a>'
|
||
);
|
||
},
|
||
);
|
||
text = text.replace(
|
||
/(^|[^\]_a-zA-Z0-9-="'/:.]+)([a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+)/g,
|
||
(...matches) => {
|
||
return matches[1] + '<a href="mailto:' + matches[2] + '">' + matches[2] + '</a>';
|
||
},
|
||
);
|
||
return text;
|
||
};
|
||
|
||
const convertXCode = (text: string, _doimage: boolean): string => {
|
||
// TODO: implement
|
||
return text;
|
||
};
|
||
|
||
const convertSmiley = (text: string) => {
|
||
// TODO: implement
|
||
return text;
|
||
};
|
||
|
||
const convertBr = (text: string): string => {
|
||
return text.replace(/(\r?\n|\r)/g, '<br />');
|
||
};
|
||
|
||
const cssConvert = (text: string): object => {
|
||
const ret: any = {};
|
||
text.split(';').forEach((line) => {
|
||
const line_ = line.trim();
|
||
if (line.length === 0) {
|
||
return;
|
||
}
|
||
const kv = line_.split(':');
|
||
const key = Functions.camelCase(kv[0].trim());
|
||
const value = kv[1].trim();
|
||
ret[key] = value;
|
||
});
|
||
return ret;
|
||
};
|
||
|
||
const xoopsTransform: Transform = (node: DomNode, index: number | string, transform?: Transform): React.ReactNode => {
|
||
if (node.type === 'tag') {
|
||
const node_ = node as DomElement;
|
||
if (node_.name === 'a') {
|
||
const url = node_.attribs?.['href'] || '/';
|
||
const download = (node_.attribs && node_.attribs['download']) || '';
|
||
const rel = (node_.attribs && node_.attribs['rel']) || '';
|
||
const klass = (node_.attribs && node_.attribs['class']) || '';
|
||
const isFile = download !== '' || /\.(zip|pdf|png|gif|jpg)$/.test(url);
|
||
const isExternal = /external/.test(rel) || /external/.test(klass) || /^(mailto|https?:?\/\/)/.test(url);
|
||
if (!isFile && !isExternal) {
|
||
const style = node_.attribs?.['style'] || '';
|
||
const title = node_.attribs?.['title'];
|
||
return (
|
||
<HashLink key={index} to={url} style={cssConvert(style)} title={title}>
|
||
{node_.children.map((value: DomNode, index: number) => {
|
||
return convertNodeToElement(value, index, transform);
|
||
})}
|
||
</HashLink>
|
||
);
|
||
}
|
||
}
|
||
if (node_.name === 'img') {
|
||
const src = (node_.attribs && node_.attribs['src']) || '';
|
||
node_.attribs['src'] = src.replace('`XOOPS_URL`', '');
|
||
return convertNodeToElement(node_, index, transform);
|
||
}
|
||
}
|
||
};
|
||
|
||
const XoopsCode: React.FC<Props> = (props: Props) => {
|
||
const { lang } = props;
|
||
let text = props.text;
|
||
const dohtml = !!props.dohtml;
|
||
const dosmiley = !!props.dosmiley;
|
||
const doxcode = !!props.doxcode;
|
||
const doimage = !!props.doimage;
|
||
const dobr = !!props.dobr;
|
||
text = preConvertXCode(text, doxcode);
|
||
if (!dohtml) {
|
||
text = Functions.htmlspecialchars(text);
|
||
text = convertClickable(text);
|
||
}
|
||
if (dosmiley) {
|
||
text = convertSmiley(text);
|
||
}
|
||
if (doxcode) {
|
||
text = convertXCode(text, doimage);
|
||
}
|
||
if (dobr) {
|
||
text = convertBr(text);
|
||
}
|
||
text = postConvertXCode(text, doxcode, doimage);
|
||
text = Functions.mlang(text, lang);
|
||
return <div>{ReactHtmlParser(text, { transform: xoopsTransform })}</div>;
|
||
};
|
||
|
||
export default XoopsCode;
|