/** @jsx h */
/** @jsxFrag Fragment */
import { Fragment, h, render, useEffect, useState } from "./deps/preact.tsx";
import { useKaTeX } from "./useKaTeX.ts";
import type { KatexOptions } from "./deps/katex.ts";
import { version } from "./deps/katex.ts";
import { PopupContainer, style as popupStyle } from "./PopupContainer.tsx";
import { useCaretPosition } from "./useCaretPosition.ts";
import { useLayout } from "./useLayout.ts";
import {
cursor as cursorDOM,
editor,
getCharDOM,
} from "./deps/scrapbox-std.ts";
/** katex-previewerの有効/無効を切り替える函数を受け取るcallback
*
* @param enable previewerを有効にする
* @param disable previewerを無効にする
* @return (もし返すなら)useEffectに渡す後始末函数
*/
export type Controller = (
enable: () => void,
disable: () => void,
) => (void | (() => void));
/** `mount`に渡す初期化options*/
export interface MountOptions {
/** 文法エラー箇所などの詳細なエラー内容を表示したいときは`true`を渡す
*
* @default false
*/
throwOnError?: boolean;
/** katex-previewerの有効/無効を切り替える函数を受け取るcallback */
controller?: Controller;
}
/** katex-previewerを初期化する
*
* @param options 初期化options
*/
export const mount = (options?: MountOptions): void => {
const { throwOnError = false, controller = () => {} } = options ?? {};
const app = document.createElement("div");
app.dataset.userscriptName = "katex-previewer";
editor()!.append(app);
const shadowRoot = app.attachShadow({ mode: "open" });
render(
,
shadowRoot,
);
};
interface Props extends KatexOptions {
controller: Controller;
}
const App = (props: Props) => {
const { ref, error, setFormula } = useKaTeX("", props); // 数式rendering用hook
const [open, setOpen] = useState(false); // popupの開閉
const [enable, setEnable] = useState(true); // 有効無効切り替え
const [cursor, setCursor] = useState({
top: 0,
left: 0,
}); // cursorの位置
const { line, char } = useCaretPosition();
const layout = useLayout();
// .formula内にcursorが来たらpreviewを開始する
useEffect(() => {
// 無効のときは何もしない
if (!enable) return;
// 編集画面以外では起動しない
if (layout !== "page") {
setOpen(false);
return;
}
const charDOM = getCharDOM(line, char);
if (!charDOM) {
setOpen(false);
return;
}
// TODO: wanna replace `getFormulaDOM()`
const formulaDOM = charDOM.closest(".cursor-line span.formula");
if (!formulaDOM) {
setOpen(false);
return;
}
setOpen(true);
setFormula((formulaDOM.textContent ?? "").slice(3, -1));
// popupを出すy座標は、[$ ]の上端に合わせる
const { top: formulaTop } = formulaDOM.getBoundingClientRect();
const { top, left } = editor()?.getBoundingClientRect?.() ??
{ top: 0, left: 0 };
const cursorLeft = cursorDOM()?.getBoundingClientRect?.()?.left ?? 0;
setCursor({
top: formulaTop - top,
left: cursorLeft - left,
});
}, [line, char, layout, enable]);
useEffect(
() => props.controller(() => setEnable(true), () => setEnable(false)),
[
props.controller,
],
);
return (
<>
{error && {error}}
>
);
};