import ReactHtmlParser, {
convertNodeToElement,
type DomElement,
type DomNode,
type Transform,
} from '@orrisroot/react-html-parser';
import type { CSSProperties } from 'react';
import { useLocation } from 'react-router';
import type { MultiLang } from '../../config';
import Functions from '../../functions';
import HashLink from './HashLink';
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 `
`;
});
}
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] +
'' +
matches[2] +
''
);
}
);
text = text.replace(
/(^|[^\]_a-zA-Z0-9-="'/:.]+)([a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+)/g,
(...matches) => {
return `${matches[1]}${matches[2]}`;
}
);
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, '
');
};
const cssConvert = (text: string): CSSProperties => {
const ret: Record = {};
text.split(';').forEach((line) => {
const line_ = line.trim();
if (line_.length === 0) {
return;
}
const kv = line_.split(':');
if (kv.length < 2) {
return;
}
ret[Functions.camelCase(kv[0].trim())] = kv.slice(1).join(':').trim();
});
return ret as CSSProperties;
};
/**
* Stored content links are written relative to the page that contains them
* (`node4.html`, `manual.html#SECTION1`, ...), so they have to be resolved the
* way a browser resolves an `href`. react-router would instead resolve them
* against the whole current pathname, turning `/manual_ja/node3.html` +
* `node4.html` into `/manual_ja/node3.html/node4.html`.
*/
const resolveUrl = (href: string, basePath: string): string => {
const url = new URL(href, window.location.origin + basePath);
return url.pathname + url.search + url.hash;
};
const makeTransform = (basePath: string): Transform => {
const transform: Transform = (node: DomNode, idx: number | string) => {
if (node.type !== 'tag') {
return undefined;
}
const element = node as DomElement;
if (element.name !== 'a') {
return undefined;
}
const url = element.attribs?.href;
const isExternal = !url || /^(mailto|https?:?\/\/)/.test(url);
if (isExternal) {
return undefined;
}
const style = element.attribs?.style || '';
const title = element.attribs?.title || '';
return (
{element.children.map((child, index) => convertNodeToElement(child, index, transform))}
);
};
return transform;
};
const XoopsCode = (props: Props) => {
const { lang } = props;
const { pathname } = useLocation();
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 {ReactHtmlParser(text, { transform: makeTransform(pathname) })}
;
};
export default XoopsCode;