project補完テスト
2024-07-22
これで問題なさそう
21:28:27 ようやくバグを潰せた
動作はいい感じ
例えばfoo bar bazは文字列長11なので3文字まで許容される
するとfooがどこにも含まれていない文字列もmatchしてしまう
https://gyazo.com/8d566511b5eb742952174ebef458a977
code:main.ts
import { mount } from "./App.tsx";
mount();
code:App.tsx
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="dom" />
/** @jsx h */
/** @jsxFrag Fragment */
import { Fragment, h, render } from "../preact/mod.tsx";
import {
useCallback,
useMemo,
useEffect,
useState,
} from "../preact/hooks.ts";
import { Asearch, MatchResult } from "../deno-asearch/mod.ts";
import { getMaxDistance } from "./distance.ts";
import { makeSource } from "./makeSource.ts";
export const mount = () => {
const app = document.createElement("div");
const shadowRoot = app.attachShadow({ mode: "open" });
document.body.append(app);
remove = () => app.remove();
render(<App />, shadowRoot);
};
let remove: () => void;
const App = () => {
const data, setData = useState<{ name: string, displayName: string }[]>([]); useEffect(() => {
(async () => {
for await (const candidates of makeSource()) {
}
})();
}, []);
const candidates = useMemo(
() => {
const pattern = pattern_.trim().replace(/^\//, "").replace(/\/$/, "");
if (pattern.length === 0) return [];
const match = Asearch(${pattern} ).match;
const maxDistance = getMaxDistance[
Math.max(...pattern.split(/\s+/).map((word) => word.trim().length))
];
return data.flatMap(
({ name, displayName }) => {
const result1 = match(name, maxDistance);
const result2 = match(displayName, maxDistance);
if (!result1.found) {
if (!result2.found) return [];
return candidate: displayName, distance: result2.distance };
}
if (!result2.found) return candidate: name, distance: result1.distance };
return result1.distance > result2.distance
? candidate: displayName, distance: result2.distance }
: candidate: name, distance: result1.distance };
}
)
// 1. 編集距離 2. 文字列超 3. 辞書順序 が小さい順に並び替える
.sort((a, b) => {
const diff = a.distance - b.distance;
if (diff !== 0) return diff;
const lenDiff = a.candidate.length - b.candidate.length;
if (lenDiff !== 0) return lenDiff;
return a.candidate.localeCompare(b.candidate);
});
},
);
const handlePattern = useCallback(
(e: h.JSX.TargetedEvent<HTMLInputElement>) =>
setPattern(e.currentTarget.value),
[],
);
return (
<>
<style>
{`
:host {
position: fixed;
top: 60px;
left: 50%;
transform: translate(-50%, 0);
padding: 5px;
border: 1px solid lime;
border-radius: 5px;
font-size: 14px;
background-color: var(--page-bg);
color: var(--page-text-color);
}
input {
min-width: 40%;
}
button {
position: absolute;
top: 0px;
right: 0px;
}
`}
</style>
<button onClick={remove}>x</button>
<p>
<label>
pattern: <input type="text" value={pattern_} onInput={handlePattern} />
</label>
</p>
<p>
{candidates.length > 0 ? Matched ${candidates.length} words : "No matched"}
<br />
<ul>
{candidates.map(({ candidate }) => (<li key={candidate}>{candidate}</li>))}
</ul>
</p>
</>
);
};
code:distance.ts
export const getMaxDistance = [
0, // 空文字のとき
0, 0,
1, 1,
2, 2, 2, 2,
3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
];
データを作る
code:makeSource.ts
import { listProjects } from "../scrapbox-userscript-std/rest.ts";
export async function* makeSource(): AsyncGenerator<{ name: string, displayName: string }[]> {
const watchList = JSON.parse(localStorage.getItem("projectsLastAccessed") ?? "{}");
const chunk = 50;
const yielded = new Set<string>();
for (let i = 0; i <= Math.floor(ids.length / chunk); i++) {
const res = await listProjects(ids.slice(i * chunk, (i + 1) * chunk));
if (!res.ok) continue;
yield res.value.projects.flatMap((project) => {
if (yielded.has(project.name)) return [];
yielded.add(project.name);
});
}
}