662 lines
26 KiB
TypeScript
662 lines
26 KiB
TypeScript
(function () {
|
|
"use strict";
|
|
|
|
enum ParameterType {
|
|
String = "string",
|
|
Number = "number",
|
|
Boolean = "boolean",
|
|
Enum = "enum",
|
|
}
|
|
enum MethodType {
|
|
Mod = "mod",
|
|
Surgery = "surgery",
|
|
Comment = "comment",
|
|
CustomMethod = "custom_method",
|
|
Ink = "ink",
|
|
Variable = "variable",
|
|
Condition = "condition",
|
|
CustomClass = "custom_class",
|
|
}
|
|
|
|
const enumTypeMap: Record<string, string[]> = {
|
|
Characters: ["Logan", "Green", "Blue", "Yellow", "Purple", "Orange"],
|
|
Size: ["Small", "Medium", "Large"],
|
|
Shape: ["Circle", "Square", "Triangle"],
|
|
};
|
|
class NumericRange {
|
|
min: number;
|
|
max: number;
|
|
isInteger: boolean;
|
|
public constructor(min: number, max: number, isInteger: boolean = false) {
|
|
this.min = min;
|
|
this.max = max;
|
|
this.isInteger = isInteger;
|
|
}
|
|
}
|
|
class Parameter {
|
|
name: string;
|
|
type: ParameterType;
|
|
enumType?: string;
|
|
numericRange?: NumericRange;
|
|
description: string;
|
|
public constructor(name: string, type: ParameterType, description: string, enumType?: string, numericRange?: NumericRange) {
|
|
this.name = name;
|
|
this.type = type;
|
|
this.description = description;
|
|
this.enumType = enumType;
|
|
this.numericRange = numericRange;
|
|
}
|
|
}
|
|
class Method {
|
|
name: string;
|
|
type: MethodType;
|
|
description: string;
|
|
public constructor(name: string, type: MethodType, description: string) {
|
|
this.name = name;
|
|
this.type = type;
|
|
this.description = description;
|
|
}
|
|
}
|
|
class Mod extends Method {
|
|
public constructor(name: string, description: string) {
|
|
super(name, MethodType.Mod, description);
|
|
}
|
|
}
|
|
class Surgery extends Method {
|
|
public constructor(name: string, description: string) {
|
|
super(name, MethodType.Surgery, description);
|
|
}
|
|
}
|
|
class CommentMethod extends Method {
|
|
parameters: Parameter[];
|
|
public constructor(name: string, description: string, parameters: Parameter[]) {
|
|
super(name, MethodType.Comment, description);
|
|
this.parameters = parameters;
|
|
}
|
|
}
|
|
class InkMethod extends Method {
|
|
parameters: Parameter[];
|
|
public constructor(name: string, description: string, parameters: Parameter[]) {
|
|
super(name, MethodType.Ink, description);
|
|
this.parameters = parameters;
|
|
}
|
|
}
|
|
class CustomMethods extends Method {
|
|
parameters: Parameter[];
|
|
public constructor(name: string, description: string, parameters: Parameter[]) {
|
|
super(name, MethodType.CustomMethod, description);
|
|
this.parameters = parameters;
|
|
}
|
|
}
|
|
class VariableMethod extends Method {
|
|
parameters: Parameter[];
|
|
public constructor(name: string, description: string, parameters: Parameter[]) {
|
|
super(name, MethodType.Variable, description);
|
|
this.parameters = parameters;
|
|
}
|
|
}
|
|
class ConditionMethod extends Method {
|
|
parameters: Parameter[];
|
|
public constructor(name: string, description: string, parameters: Parameter[]) {
|
|
super(name, MethodType.Condition, description);
|
|
this.parameters = parameters;
|
|
}
|
|
}
|
|
class CustomClassMethod extends Method {
|
|
public constructor(name: string, description: string) {
|
|
super(name, MethodType.CustomClass, description);
|
|
}
|
|
}
|
|
|
|
function editDistance(a: string, b: string, caseSensitive: boolean = true): number {
|
|
if (a.length === 0) return b.length;
|
|
if (b.length === 0) return a.length;
|
|
const matrix: number[][] = [];
|
|
for (let i = 0; i <= b.length; i++) {
|
|
matrix[i] = [i];
|
|
}
|
|
for (let j = 0; j <= a.length; j++) {
|
|
matrix[0][j] = j;
|
|
}
|
|
for (let i = 1; i <= b.length; i++) {
|
|
for (let j = 1; j <= a.length; j++) {
|
|
const ac = a.charAt(j - 1);
|
|
const bc = b.charAt(i - 1);
|
|
if (caseSensitive ? bc === ac : bc.toLowerCase() === ac.toLowerCase()) {
|
|
matrix[i][j] = matrix[i - 1][j - 1];
|
|
} else {
|
|
matrix[i][j] = Math.min(
|
|
matrix[i - 1][j - 1] + 1,
|
|
Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return matrix[b.length][a.length];
|
|
}
|
|
|
|
function parametersOf(method: Method): Parameter[] {
|
|
return (method as CommentMethod | InkMethod | CustomMethods | VariableMethod | ConditionMethod).parameters ?? [];
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// 数据:可直接在此编辑,或通过 window.CustomMethodSearch.setMethods(...) 覆盖
|
|
// ---------------------------------------------------------------------
|
|
let methods: Method[] = [
|
|
new Mod("wobblyLines", "让单发拍子的轨道抖动"),
|
|
new Mod("invisibleHeart", "隐藏轨道上的心脏"),
|
|
new CustomMethods("ChangeCharacter", "修改某条轨道对应的人物", [
|
|
new Parameter("character", ParameterType.Enum, "Characters"),
|
|
new Parameter("row", ParameterType.Number, "轨道编号", undefined, new NumericRange(0, 15, true)),
|
|
]),
|
|
new CustomMethods("DisableRowReflections", "开关某个房间的地面反射", [
|
|
new Parameter("room", ParameterType.Number, "房间编号", undefined, new NumericRange(0, 3, true)),
|
|
]),
|
|
new CustomMethods("SetRowLength", "设置轨道长度", [
|
|
new Parameter("row", ParameterType.Number, "轨道编号", undefined, new NumericRange(0, 15, true)),
|
|
new Parameter("length", ParameterType.Number, "轨道长度", undefined, new NumericRange(1, 7, true)),
|
|
]),
|
|
new CommentMethod("trueCameraMove", "移动真实摄像头", [
|
|
new Parameter("x", ParameterType.Number, "目标 X"),
|
|
new Parameter("y", ParameterType.Number, "目标 Y"),
|
|
new Parameter("angle", ParameterType.Number, "角度"),
|
|
new Parameter("duration", ParameterType.Number, "时长"),
|
|
new Parameter("ease", ParameterType.Enum, "缓动类型", "EaseType"),
|
|
]),
|
|
new CustomMethods("DemoAllTypes", "演示四种参数控件", [
|
|
new Parameter("text", ParameterType.String, "文本"),
|
|
new Parameter("count", ParameterType.Number, "数字"),
|
|
new Parameter("enabled", ParameterType.Boolean, "开关"),
|
|
new Parameter("character", ParameterType.Enum, "角色", "Characters"),
|
|
new Parameter("color", ParameterType.Enum, "颜色"),
|
|
]),
|
|
];
|
|
|
|
// 合并外置数据(script/custommethod-data.js),同名以内置为准
|
|
function hydrateMethod(raw: any): Method {
|
|
const parameters: Parameter[] = Array.isArray(raw.parameters)
|
|
? raw.parameters.map((item: any) => new Parameter(
|
|
item.name,
|
|
item.type ?? ParameterType.String,
|
|
item.description ?? "",
|
|
item.enumType,
|
|
item.numericRange
|
|
? new NumericRange(item.numericRange.min, item.numericRange.max, item.numericRange.isInteger)
|
|
: undefined,
|
|
))
|
|
: [];
|
|
switch (raw.type) {
|
|
case MethodType.Mod: return new Mod(raw.name, raw.description ?? "");
|
|
case MethodType.Surgery: return new Surgery(raw.name, raw.description ?? "");
|
|
case MethodType.Comment: return new CommentMethod(raw.name, raw.description ?? "", parameters);
|
|
case MethodType.Ink: return new InkMethod(raw.name, raw.description ?? "", parameters);
|
|
case MethodType.Variable: return new VariableMethod(raw.name, raw.description ?? "", parameters);
|
|
case MethodType.Condition: return new ConditionMethod(raw.name, raw.description ?? "", parameters);
|
|
case MethodType.CustomClass: return new CustomClassMethod(raw.name, raw.description ?? "");
|
|
default: return new CustomMethods(raw.name, raw.description ?? "", parameters);
|
|
}
|
|
}
|
|
|
|
function loadExternalData(): void {
|
|
const data = (window as any).CustomMethodSearchData;
|
|
if (!data) return;
|
|
if (data.enumTypes) {
|
|
for (const key of Object.keys(data.enumTypes)) {
|
|
enumTypeMap[key] = data.enumTypes[key];
|
|
}
|
|
}
|
|
if (Array.isArray(data.methods)) {
|
|
const index = new Map<string, number>();
|
|
methods.forEach((method, i) => index.set(method.name.toLowerCase(), i));
|
|
for (const raw of data.methods) {
|
|
if (!raw || !raw.name) continue;
|
|
const name = String(raw.name).toLowerCase();
|
|
const hydrated = hydrateMethod(raw);
|
|
if (index.has(name)) {
|
|
methods[index.get(name)!] = hydrated;
|
|
} else {
|
|
index.set(name, methods.length);
|
|
methods.push(hydrated);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
loadExternalData();
|
|
|
|
// ---------------------------------------------------------------------
|
|
// 匹配
|
|
// ---------------------------------------------------------------------
|
|
function searchMethods(query: string): Method[] {
|
|
const q = query.trim();
|
|
if (!q) return [];
|
|
const ql = q.toLowerCase();
|
|
const maxDistance = Math.max(2, Math.floor(q.length / 2));
|
|
return methods
|
|
.map((method) => {
|
|
const name = method.name;
|
|
const lower = name.toLowerCase();
|
|
let score: number;
|
|
if (name === q) {
|
|
score = 0;
|
|
} else if (lower.startsWith(ql)) {
|
|
score = 1;
|
|
} else if (lower.indexOf(ql) >= 0) {
|
|
score = 2;
|
|
} else {
|
|
score = 3 + editDistance(name, q, false);
|
|
}
|
|
return { method, score };
|
|
})
|
|
.filter((item) => item.score <= 2 || item.score <= 3 + maxDistance)
|
|
.sort((a, b) => a.score - b.score || a.method.name.localeCompare(b.method.name))
|
|
.slice(0, 8)
|
|
.map((item) => item.method);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// DOM 构建
|
|
// ---------------------------------------------------------------------
|
|
function el<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string): HTMLElementTagNameMap[K] {
|
|
const node = document.createElement(tag);
|
|
if (className) node.className = className;
|
|
return node;
|
|
}
|
|
|
|
function resolveEnumOptions(parameter: Parameter): string[] {
|
|
if (parameter.enumType && enumTypeMap[parameter.enumType]) {
|
|
return enumTypeMap[parameter.enumType];
|
|
}
|
|
// 兼容把枚举类型名写在 description 的情况
|
|
if (enumTypeMap[parameter.description]) {
|
|
return enumTypeMap[parameter.description];
|
|
}
|
|
if (enumTypeMap[parameter.name]) {
|
|
return enumTypeMap[parameter.name];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
// 布尔值可用 true/false,也可用布尔变量 b0~b9
|
|
const BOOLEAN_OPTIONS: string[] = ["true", "false"];
|
|
for (let i = 0; i < 10; i++) {
|
|
BOOLEAN_OPTIONS.push("b" + i);
|
|
}
|
|
|
|
function isNumberVariable(value: string): boolean {
|
|
// 数字位置允许的变量:i0~i9(整型)、f0~f9(浮点)、b0~b9(布尔)
|
|
return /^[ifb][0-9]$/.test(value);
|
|
}
|
|
|
|
function makeCombo(parameter: Parameter, options: string[], onChange: () => void): HTMLElement {
|
|
const combo = el("span", "cm-combo");
|
|
const input = el("input", "cm-input cm-combo-input") as HTMLInputElement;
|
|
input.type = "text";
|
|
input.placeholder = parameter.name;
|
|
input.autocomplete = "off";
|
|
const list = el("ul", "cm-combo-list");
|
|
list.hidden = true;
|
|
|
|
function populate(): void {
|
|
list.textContent = "";
|
|
if (options.length === 0) {
|
|
list.hidden = true;
|
|
return;
|
|
}
|
|
const query = input.value.trim();
|
|
const ql = query.toLowerCase();
|
|
const maxDistance = Math.max(1, Math.floor(query.length / 2));
|
|
const shown = (query
|
|
? options.filter((option) =>
|
|
option.toLowerCase().indexOf(ql) >= 0 ||
|
|
editDistance(option, query, false) <= maxDistance)
|
|
: options
|
|
).slice(0, 10);
|
|
if (shown.length === 0) {
|
|
list.hidden = true;
|
|
return;
|
|
}
|
|
for (const option of shown) {
|
|
const item = el("li", "cm-combo-item");
|
|
item.textContent = option;
|
|
item.title = option;
|
|
item.addEventListener("mousedown", (event) => {
|
|
event.preventDefault();
|
|
input.value = option;
|
|
list.hidden = true;
|
|
onChange();
|
|
});
|
|
list.appendChild(item);
|
|
}
|
|
list.hidden = false;
|
|
}
|
|
|
|
input.addEventListener("input", () => {
|
|
populate();
|
|
onChange();
|
|
});
|
|
input.addEventListener("focus", populate);
|
|
input.addEventListener("blur", () => {
|
|
window.setTimeout(() => { list.hidden = true; }, 120);
|
|
});
|
|
|
|
combo.appendChild(input);
|
|
combo.appendChild(list);
|
|
return combo;
|
|
}
|
|
|
|
function makeControl(parameter: Parameter, onChange: () => void): HTMLElement {
|
|
switch (parameter.type) {
|
|
case ParameterType.Boolean: {
|
|
return makeCombo(parameter, BOOLEAN_OPTIONS, onChange);
|
|
}
|
|
case ParameterType.Enum: {
|
|
// 可输入的下拉框:输入时用编辑距离筛选候选项
|
|
return makeCombo(parameter, resolveEnumOptions(parameter), onChange);
|
|
}
|
|
case ParameterType.Number: {
|
|
// 普通输入框,合法性由 readParameter 校验(非数字标红)
|
|
const number = el("input", "cm-input") as HTMLInputElement;
|
|
number.type = "text";
|
|
number.inputMode = "decimal";
|
|
number.placeholder = parameter.name;
|
|
number.addEventListener("input", onChange);
|
|
return number;
|
|
}
|
|
default: {
|
|
const text = el("input", "cm-input") as HTMLInputElement;
|
|
text.type = "text";
|
|
text.placeholder = parameter.name;
|
|
text.addEventListener("input", onChange);
|
|
return text;
|
|
}
|
|
}
|
|
}
|
|
|
|
interface ParameterState {
|
|
text: string;
|
|
valid: boolean;
|
|
invalid: boolean;
|
|
control: HTMLElement;
|
|
}
|
|
|
|
function readParameter(parameter: Parameter, control: HTMLElement): ParameterState {
|
|
switch (parameter.type) {
|
|
case ParameterType.Boolean: {
|
|
const input = control.querySelector("input") as HTMLInputElement;
|
|
const value = input.value.trim();
|
|
const valid = value === "true" || value === "false" || /^b[0-9]$/.test(value);
|
|
return { text: value || "<布尔>", valid, invalid: value.length > 0 && !valid, control };
|
|
}
|
|
case ParameterType.Enum: {
|
|
const input = control.querySelector("input") as HTMLInputElement;
|
|
const value = input.value.trim();
|
|
const options = resolveEnumOptions(parameter);
|
|
const valid = options.length > 0
|
|
? options.some((option) => option.toLowerCase() === value.toLowerCase())
|
|
: value.length > 0;
|
|
return { text: value || "<选项>", valid, invalid: value.length > 0 && !valid, control };
|
|
}
|
|
case ParameterType.Number: {
|
|
const input = control as HTMLInputElement;
|
|
const value = input.value.trim();
|
|
if (value.length === 0) {
|
|
return { text: "<数字>", valid: false, invalid: false, control };
|
|
}
|
|
if (isNumberVariable(value)) {
|
|
return { text: value, valid: true, invalid: false, control };
|
|
}
|
|
const number = Number(value);
|
|
let valid = isFinite(number);
|
|
const range = parameter.numericRange;
|
|
if (valid && range) {
|
|
if (range.isInteger && !Number.isInteger(number)) valid = false;
|
|
if (number < range.min || number > range.max) valid = false;
|
|
}
|
|
return { text: value, valid, invalid: !valid, control };
|
|
}
|
|
default: {
|
|
const input = control as HTMLInputElement;
|
|
const value = input.value.trim();
|
|
return { text: value ? `str:${value}` : "<文本>", valid: value.length > 0, invalid: false, control };
|
|
}
|
|
}
|
|
}
|
|
|
|
function stripStringPrefix(text: string): string {
|
|
return text.startsWith("str:") ? text.slice(4) : text;
|
|
}
|
|
|
|
function formatMethod(method: Method, states: ParameterState[]): string {
|
|
const values = states.map((state) => state.text);
|
|
const args = values.join(", ");
|
|
switch (method.type) {
|
|
case MethodType.Comment:
|
|
return `()=>${method.name}(${args})`;
|
|
case MethodType.Ink:
|
|
return `DOTHIS{${method.name}(${args})}`;
|
|
case MethodType.Variable:
|
|
return `${method.name} = ${values.length ? stripStringPrefix(values[0]) : ""}`;
|
|
case MethodType.Mod:
|
|
return `"mods": [..., "${method.name}"]`;
|
|
case MethodType.CustomClass:
|
|
return `"customClass": "${method.name}"`;
|
|
case MethodType.Condition:
|
|
return values.length ? `${method.name}(${args})` : method.name;
|
|
case MethodType.Surgery:
|
|
return method.name;
|
|
default:
|
|
return values.length ? `${method.name}(${args})` : method.name;
|
|
}
|
|
}
|
|
|
|
async function copyText(text: string): Promise<boolean> {
|
|
try {
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
await navigator.clipboard.writeText(text);
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
// fallthrough to execCommand
|
|
}
|
|
try {
|
|
const area = document.createElement("textarea");
|
|
area.value = text;
|
|
area.style.position = "fixed";
|
|
area.style.opacity = "0";
|
|
document.body.appendChild(area);
|
|
area.select();
|
|
const ok = document.execCommand("copy");
|
|
document.body.removeChild(area);
|
|
return ok;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function renderContainer(container: HTMLElement): void {
|
|
if (container.dataset.cmReady === "1") return;
|
|
container.dataset.cmReady = "1";
|
|
|
|
// 优先使用 custommethod.md 里写好的骨架;缺失时再动态生成
|
|
let query = container.querySelector<HTMLInputElement>(".cm-query");
|
|
let suggest = container.querySelector<HTMLUListElement>(".cm-suggest");
|
|
let selected = container.querySelector<HTMLElement>(".cm-selected");
|
|
let nameEl = container.querySelector<HTMLElement>(".cm-name");
|
|
let descEl = container.querySelector<HTMLElement>(".cm-desc");
|
|
let row = container.querySelector<HTMLElement>(".cm-row");
|
|
let preview = container.querySelector<HTMLElement>(".cm-preview");
|
|
|
|
if (!query || !suggest || !selected || !nameEl || !descEl || !row || !preview) {
|
|
container.textContent = "";
|
|
const searchBar = el("div", "cm-search");
|
|
query = el("input", "cm-query") as HTMLInputElement;
|
|
query.type = "text";
|
|
query.placeholder = "输入自定义方法名,如 ChangeCharacter";
|
|
query.autocomplete = "off";
|
|
suggest = el("ul", "cm-suggest");
|
|
searchBar.appendChild(query);
|
|
searchBar.appendChild(suggest);
|
|
selected = el("div", "cm-selected");
|
|
selected.hidden = true;
|
|
const head = el("div", "cm-head");
|
|
nameEl = el("span", "cm-name");
|
|
descEl = el("span", "cm-desc");
|
|
head.appendChild(nameEl);
|
|
head.appendChild(descEl);
|
|
row = el("div", "cm-row");
|
|
preview = el("code", "cm-preview");
|
|
selected.appendChild(head);
|
|
selected.appendChild(row);
|
|
selected.appendChild(preview);
|
|
container.appendChild(searchBar);
|
|
container.appendChild(selected);
|
|
}
|
|
|
|
const queryEl = query;
|
|
const suggestEl = suggest;
|
|
const selectedEl = selected;
|
|
const nameNode = nameEl;
|
|
const descNode = descEl;
|
|
const rowEl = row;
|
|
const previewEl = preview;
|
|
|
|
const copyBtn = el("button", "cm-copy") as HTMLButtonElement;
|
|
copyBtn.type = "button";
|
|
copyBtn.textContent = "复制";
|
|
copyBtn.hidden = true;
|
|
copyBtn.title = "复制生成的调用代码";
|
|
selectedEl.appendChild(copyBtn);
|
|
copyBtn.addEventListener("click", () => {
|
|
copyText(previewEl.textContent).then((ok) => {
|
|
copyBtn.textContent = ok ? "已复制" : "复制失败";
|
|
window.setTimeout(() => { copyBtn.textContent = "复制"; }, 1200);
|
|
});
|
|
});
|
|
|
|
function clearSuggest(): void {
|
|
suggestEl.textContent = "";
|
|
}
|
|
|
|
function selectMethod(method: Method): void {
|
|
clearSuggest();
|
|
queryEl.value = method.name;
|
|
selectedEl.hidden = false;
|
|
nameNode.textContent = method.name;
|
|
descNode.textContent = method.description;
|
|
rowEl.textContent = "";
|
|
previewEl.textContent = "";
|
|
copyBtn.hidden = true;
|
|
|
|
const parameters = parametersOf(method);
|
|
const controls: { parameter: Parameter; control: HTMLElement }[] = [];
|
|
|
|
function updatePreview(): void {
|
|
const states = controls.map((item) => readParameter(item.parameter, item.control));
|
|
states.forEach((state) => state.control.classList.toggle("cm-invalid", state.invalid));
|
|
previewEl.textContent = formatMethod(method, states);
|
|
copyBtn.hidden = !states.every((state) => state.valid);
|
|
}
|
|
|
|
if (parameters.length === 0) {
|
|
const empty = el("span", "cm-empty");
|
|
empty.textContent = "该方法没有参数";
|
|
rowEl.appendChild(empty);
|
|
updatePreview();
|
|
return;
|
|
}
|
|
|
|
for (const parameter of parameters) {
|
|
const field = el("label", "cm-param");
|
|
field.title = parameter.description;
|
|
const label = el("span", "cm-param-name");
|
|
label.textContent = parameter.name;
|
|
const control = makeControl(parameter, updatePreview);
|
|
field.appendChild(label);
|
|
field.appendChild(control);
|
|
rowEl.appendChild(field);
|
|
controls.push({ parameter, control });
|
|
}
|
|
updatePreview();
|
|
}
|
|
|
|
function updateSuggestions(): void {
|
|
clearSuggest();
|
|
const matches = searchMethods(queryEl.value);
|
|
for (const method of matches) {
|
|
const item = el("li", "cm-suggest-item");
|
|
const nameSpan = el("span", "cm-suggest-name");
|
|
nameSpan.textContent = method.name;
|
|
const descSpan = el("span", "cm-suggest-desc");
|
|
descSpan.textContent = method.description;
|
|
item.appendChild(nameSpan);
|
|
item.appendChild(descSpan);
|
|
item.addEventListener("mousedown", (event) => {
|
|
event.preventDefault();
|
|
selectMethod(method);
|
|
});
|
|
suggestEl.appendChild(item);
|
|
}
|
|
}
|
|
|
|
queryEl.addEventListener("input", () => {
|
|
updateSuggestions();
|
|
selectedEl.hidden = true;
|
|
});
|
|
queryEl.addEventListener("focus", updateSuggestions);
|
|
queryEl.addEventListener("blur", () => {
|
|
window.setTimeout(clearSuggest, 120);
|
|
});
|
|
|
|
if (queryEl.value) {
|
|
updateSuggestions();
|
|
}
|
|
}
|
|
|
|
function initAll(): void {
|
|
const containers = document.querySelectorAll<HTMLElement>(".custommethods-search");
|
|
containers.forEach((container) => renderContainer(container));
|
|
}
|
|
|
|
function register(): void {
|
|
const docsify = (window as any).$docsify;
|
|
if (docsify) {
|
|
docsify.plugins = ([] as any[]).concat(docsify.plugins, (hook: any) => {
|
|
hook.doneEach(() => initAll());
|
|
});
|
|
} else if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", () => initAll());
|
|
} else {
|
|
initAll();
|
|
}
|
|
}
|
|
|
|
(window as any).CustomMethodSearch = {
|
|
search: searchMethods,
|
|
editDistance,
|
|
renderAll: initAll,
|
|
getMethods: () => methods,
|
|
setMethods: (list: Method[]) => {
|
|
methods = list;
|
|
initAll();
|
|
},
|
|
Parameter,
|
|
ParameterType,
|
|
Method,
|
|
MethodType,
|
|
Mod,
|
|
Surgery,
|
|
CommentMethod,
|
|
InkMethod,
|
|
CustomMethods,
|
|
VariableMethod,
|
|
ConditionMethod,
|
|
CustomClassMethod,
|
|
};
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", register);
|
|
} else {
|
|
register();
|
|
}
|
|
})();
|