/// /// /// /** @jsx h */ /** @jsxFrag Fragment */ import { Fragment, h, render } from "../preact/mod.tsx"; import { useCallback, useState, useMemo, useEffect, useRef } from "../preact/hooks.ts"; import { useSelection } from "./useSelection.ts"; import { useSearch } from "./useSearch.ts"; import { selections } from "../scrapbox-userscript-std/dom.ts"; export interface Options { /** 表示する最大候補数 * * @default 5 */ limit?: number; } const App = ({ limit }: Options) => { const { text, range } = useSelection(); const ref = useRef(null); // 座標計算用 const style = useMemo(() => { // 一行だけ選択している時のみ表示する if (text === "" || text.includes("\n") || !ref.current) { return { display: "none" }; } // 座標を取得する const root = ref.current.parentNode; if (!(root instanceof ShadowRoot)) { throw Error(`The parent of "div.container" must be ShadowRoot`); } /** 基準座標 */ const parentRect = root.host?.parentElement?.getBoundingClientRect?.(); /** 選択範囲の座標 */ const rect = selections()?.lastElementChild?.getBoundingClientRect?.(); if (!rect || !parentRect) return { display: "none" }; return { top: `${rect.bottom - parentRect.top}px`, left: `${(rect.left - parentRect.left)}px`, }; }, [text, range]); // 選択範囲のサイズ変更でも再計算するために、rangeを依存配列に加えている const candidates = useSearch(text, limit ?? 5); return (<>
{candidates.map((title) => (
{title}
))}
); }; const app = document.createElement("div"); const shadowRoot = app.attachShadow({ mode: "open" }); document.body.append(app); render(, shadowRoot);