712 lines
30 KiB
JavaScript
712 lines
30 KiB
JavaScript
"use strict";
|
||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
(function () {
|
||
"use strict";
|
||
let ParameterType;
|
||
(function (ParameterType) {
|
||
ParameterType["String"] = "string";
|
||
ParameterType["Number"] = "number";
|
||
ParameterType["Boolean"] = "boolean";
|
||
ParameterType["Enum"] = "enum";
|
||
})(ParameterType || (ParameterType = {}));
|
||
let MethodType;
|
||
(function (MethodType) {
|
||
MethodType["Mod"] = "mod";
|
||
MethodType["Surgery"] = "surgery";
|
||
MethodType["Comment"] = "comment";
|
||
MethodType["CustomMethod"] = "custom_method";
|
||
MethodType["Ink"] = "ink";
|
||
MethodType["Variable"] = "variable";
|
||
MethodType["Condition"] = "condition";
|
||
MethodType["CustomClass"] = "custom_class";
|
||
})(MethodType || (MethodType = {}));
|
||
const enumTypeMap = {
|
||
Characters: ["Logan", "Green", "Blue", "Yellow", "Purple", "Orange"],
|
||
Size: ["Small", "Medium", "Large"],
|
||
Shape: ["Circle", "Square", "Triangle"],
|
||
};
|
||
class NumericRange {
|
||
constructor(min, max, isInteger = false) {
|
||
this.min = min;
|
||
this.max = max;
|
||
this.isInteger = isInteger;
|
||
}
|
||
}
|
||
class Parameter {
|
||
constructor(name, type, description, enumType, numericRange) {
|
||
this.name = name;
|
||
this.type = type;
|
||
this.description = description;
|
||
this.enumType = enumType;
|
||
this.numericRange = numericRange;
|
||
}
|
||
}
|
||
class Method {
|
||
constructor(name, type, shortDescription, description) {
|
||
this.name = name;
|
||
this.type = type;
|
||
this.shortDescription = shortDescription;
|
||
this.description = description;
|
||
}
|
||
}
|
||
class Mod extends Method {
|
||
constructor(name, shortDescription, description) {
|
||
super(name, MethodType.Mod, shortDescription, description);
|
||
}
|
||
}
|
||
class Surgery extends Method {
|
||
constructor(name, shortDescription, description) {
|
||
super(name, MethodType.Surgery, shortDescription, description);
|
||
}
|
||
}
|
||
class CommentMethod extends Method {
|
||
constructor(name, shortDescription, description, parameters) {
|
||
super(name, MethodType.Comment, shortDescription, description);
|
||
this.parameters = parameters;
|
||
}
|
||
}
|
||
class InkMethod extends Method {
|
||
constructor(name, shortDescription, description, parameters) {
|
||
super(name, MethodType.Ink, shortDescription, description);
|
||
this.parameters = parameters;
|
||
}
|
||
}
|
||
class CustomMethods extends Method {
|
||
constructor(name, shortDescription, description, parameters) {
|
||
super(name, MethodType.CustomMethod, shortDescription, description);
|
||
this.parameters = parameters;
|
||
}
|
||
}
|
||
class VariableMethod extends Method {
|
||
constructor(name, shortDescription, description, parameters) {
|
||
super(name, MethodType.Variable, shortDescription, description);
|
||
this.parameters = parameters;
|
||
}
|
||
}
|
||
class ConditionMethod extends Method {
|
||
constructor(name, shortDescription, description, parameters) {
|
||
super(name, MethodType.Condition, shortDescription, description);
|
||
this.parameters = parameters;
|
||
}
|
||
}
|
||
class CustomClassMethod extends Method {
|
||
constructor(name, shortDescription, description) {
|
||
super(name, MethodType.CustomClass, shortDescription, description);
|
||
}
|
||
}
|
||
function editDistance(a, b, caseSensitive = true) {
|
||
if (a.length === 0)
|
||
return b.length;
|
||
if (b.length === 0)
|
||
return a.length;
|
||
const matrix = [];
|
||
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) {
|
||
var _a;
|
||
return (_a = method.parameters) !== null && _a !== void 0 ? _a : [];
|
||
}
|
||
// 全局策略:字符串写法、数字/布尔默认的常量/表达式状态
|
||
const strategies = {
|
||
string: "prefix",
|
||
number: "constant",
|
||
boolean: "constant",
|
||
};
|
||
// ---------------------------------------------------------------------
|
||
// 外置数据 hydrate(script/custommethod-data.js)
|
||
// ---------------------------------------------------------------------
|
||
function hydrateMethod(raw) {
|
||
var _a, _b, _c, _d;
|
||
const shortDescription = (_b = (_a = raw.shortDescription) !== null && _a !== void 0 ? _a : raw.description) !== null && _b !== void 0 ? _b : "";
|
||
const description = (_d = (_c = raw.description) !== null && _c !== void 0 ? _c : raw.shortDescription) !== null && _d !== void 0 ? _d : "";
|
||
const parameters = Array.isArray(raw.parameters)
|
||
? raw.parameters.map((item) => {
|
||
var _a, _b;
|
||
return new Parameter(item.name, (_a = item.type) !== null && _a !== void 0 ? _a : ParameterType.String, (_b = item.description) !== null && _b !== void 0 ? _b : "", 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, shortDescription, description);
|
||
case MethodType.Surgery: return new Surgery(raw.name, shortDescription, description);
|
||
case MethodType.Comment: return new CommentMethod(raw.name, shortDescription, description, parameters);
|
||
case MethodType.Ink: return new InkMethod(raw.name, shortDescription, description, parameters);
|
||
case MethodType.Variable: return new VariableMethod(raw.name, shortDescription, description, parameters);
|
||
case MethodType.Condition: return new ConditionMethod(raw.name, shortDescription, description, parameters);
|
||
case MethodType.CustomClass: return new CustomClassMethod(raw.name, shortDescription, description);
|
||
default: return new CustomMethods(raw.name, shortDescription, description, parameters);
|
||
}
|
||
}
|
||
let methods = [];
|
||
function loadExternalData() {
|
||
const data = window.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();
|
||
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) {
|
||
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;
|
||
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(tag, className) {
|
||
const node = document.createElement(tag);
|
||
if (className)
|
||
node.className = className;
|
||
return node;
|
||
}
|
||
function resolveEnumOptions(parameter) {
|
||
if (parameter.enumType && enumTypeMap[parameter.enumType])
|
||
return enumTypeMap[parameter.enumType];
|
||
if (enumTypeMap[parameter.description])
|
||
return enumTypeMap[parameter.description];
|
||
if (enumTypeMap[parameter.name])
|
||
return enumTypeMap[parameter.name];
|
||
return [];
|
||
}
|
||
// 布尔可用 true/false,或布尔变量 b0~b9
|
||
const BOOLEAN_OPTIONS = ["true", "false"];
|
||
for (let i = 0; i < 10; i++) {
|
||
BOOLEAN_OPTIONS.push("b" + i);
|
||
}
|
||
function makeCombo(parameter, options, onChange) {
|
||
const combo = el("span", "cm-combo");
|
||
const input = el("input", "cm-input cm-combo-input");
|
||
input.type = "text";
|
||
input.placeholder = parameter.name;
|
||
input.autocomplete = "off";
|
||
const list = el("ul", "cm-combo-list");
|
||
list.hidden = true;
|
||
function populate() {
|
||
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 makeModeToggle(initial, onChange) {
|
||
const button = el("button", "cm-mode");
|
||
button.type = "button";
|
||
let mode = initial;
|
||
const render = () => {
|
||
button.textContent = mode === "expression" ? "表达式" : "常量";
|
||
button.title = "切换 常量 / 表达式";
|
||
button.classList.toggle("is-expression", mode === "expression");
|
||
};
|
||
render();
|
||
button.addEventListener("click", (event) => {
|
||
event.preventDefault();
|
||
mode = mode === "expression" ? "constant" : "expression";
|
||
render();
|
||
onChange();
|
||
});
|
||
return { el: button, get: () => mode };
|
||
}
|
||
function stringValue(value) {
|
||
if (value.length === 0)
|
||
return "<文本>";
|
||
return strategies.string === "quote" ? `"${value}"` : `str:${value}`;
|
||
}
|
||
function makeField(parameter, onChange) {
|
||
switch (parameter.type) {
|
||
case ParameterType.Boolean: {
|
||
const wrap = el("span", "cm-field");
|
||
const checkbox = el("input", "cm-input cm-check");
|
||
checkbox.type = "checkbox";
|
||
checkbox.addEventListener("change", onChange);
|
||
const combo = makeCombo(parameter, BOOLEAN_OPTIONS, onChange);
|
||
const comboInput = combo.querySelector("input");
|
||
let mode = strategies.boolean;
|
||
const toggle = makeModeToggle(strategies.boolean, () => {
|
||
mode = toggle.get();
|
||
applyMode();
|
||
onChange();
|
||
});
|
||
function applyMode() {
|
||
const expression = mode === "expression";
|
||
checkbox.hidden = expression;
|
||
combo.hidden = !expression;
|
||
}
|
||
applyMode();
|
||
wrap.appendChild(toggle.el);
|
||
wrap.appendChild(checkbox);
|
||
wrap.appendChild(combo);
|
||
return {
|
||
el: wrap,
|
||
read: () => {
|
||
if (mode === "constant") {
|
||
return { text: checkbox.checked ? "true" : "false", valid: true, invalid: false };
|
||
}
|
||
const value = comboInput.value.trim();
|
||
return value.length > 0
|
||
? { text: value, valid: true, invalid: false }
|
||
: { text: "<布尔>", valid: false, invalid: true };
|
||
},
|
||
};
|
||
}
|
||
case ParameterType.Number: {
|
||
const wrap = el("span", "cm-field");
|
||
const input = el("input", "cm-input");
|
||
input.type = "text";
|
||
input.inputMode = "decimal";
|
||
input.placeholder = parameter.name;
|
||
input.addEventListener("input", onChange);
|
||
const mode = makeModeToggle(strategies.number, onChange);
|
||
wrap.appendChild(mode.el);
|
||
wrap.appendChild(input);
|
||
return {
|
||
el: wrap,
|
||
read: () => {
|
||
const value = input.value.trim();
|
||
if (value.length === 0)
|
||
return { text: "<数字>", valid: false, invalid: true };
|
||
if (mode.get() === "expression")
|
||
return { text: value, valid: true, invalid: false };
|
||
const number = Number(value);
|
||
let valid = isFinite(number);
|
||
const range = parameter.numericRange;
|
||
if (valid && range) {
|
||
if (range.isInteger && !Number.isInteger(number))
|
||
valid = false;
|
||
if (range.min !== undefined && number < range.min)
|
||
valid = false;
|
||
if (range.max !== undefined && number > range.max)
|
||
valid = false;
|
||
}
|
||
return { text: value, valid, invalid: !valid };
|
||
},
|
||
};
|
||
}
|
||
case ParameterType.Enum: {
|
||
const wrap = el("span", "cm-field cm-field-string");
|
||
const open = el("span", "cm-lit");
|
||
const close = el("span", "cm-lit");
|
||
open.textContent = strategies.string === "quote" ? "\"" : "str:";
|
||
close.textContent = strategies.string === "quote" ? "\"" : "";
|
||
const combo = makeCombo(parameter, resolveEnumOptions(parameter), onChange);
|
||
const input = combo.querySelector("input");
|
||
wrap.appendChild(open);
|
||
wrap.appendChild(combo);
|
||
wrap.appendChild(close);
|
||
return {
|
||
el: wrap,
|
||
read: () => {
|
||
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: stringValue(value), valid, invalid: !valid };
|
||
},
|
||
};
|
||
}
|
||
default: {
|
||
const wrap = el("span", "cm-field cm-field-string");
|
||
const open = el("span", "cm-lit");
|
||
const close = el("span", "cm-lit");
|
||
open.textContent = strategies.string === "quote" ? "\"" : "str:";
|
||
close.textContent = strategies.string === "quote" ? "\"" : "";
|
||
const input = el("input", "cm-input");
|
||
input.type = "text";
|
||
input.placeholder = parameter.name;
|
||
input.addEventListener("input", onChange);
|
||
wrap.appendChild(open);
|
||
wrap.appendChild(input);
|
||
wrap.appendChild(close);
|
||
return {
|
||
el: wrap,
|
||
read: () => {
|
||
const value = input.value.trim();
|
||
return { text: stringValue(value), valid: value.length > 0, invalid: value.length === 0 };
|
||
},
|
||
};
|
||
}
|
||
}
|
||
}
|
||
function methodSkeleton(method, hasParams) {
|
||
switch (method.type) {
|
||
case MethodType.Comment: return { prefix: `()=>${method.name}(`, suffix: ")" };
|
||
case MethodType.Ink: return { prefix: `DOTHIS{${method.name}(`, suffix: ")}" };
|
||
case MethodType.Variable: return { prefix: `${method.name} = `, suffix: "" };
|
||
case MethodType.Mod: return { prefix: `"mods": [..., "`, suffix: `"]` };
|
||
case MethodType.CustomClass: return { prefix: `"customClass": "`, suffix: `"` };
|
||
case MethodType.Condition:
|
||
return hasParams ? { prefix: `${method.name}(`, suffix: ")" } : { prefix: method.name, suffix: "" };
|
||
case MethodType.Surgery: return { prefix: method.name, suffix: "" };
|
||
default:
|
||
return hasParams ? { prefix: `${method.name}(`, suffix: ")" } : { prefix: method.name, suffix: "" };
|
||
}
|
||
}
|
||
function formatMethod(method, states) {
|
||
const args = states.map((state) => state.text).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} = ${states.length ? states[0].text : ""}`;
|
||
case MethodType.Mod: return `"mods": [..., "${method.name}"]`;
|
||
case MethodType.CustomClass: return `"customClass": "${method.name}"`;
|
||
case MethodType.Condition: return states.length ? `${method.name}(${args})` : method.name;
|
||
case MethodType.Surgery: return method.name;
|
||
default: return states.length ? `${method.name}(${args})` : method.name;
|
||
}
|
||
}
|
||
function copyText(text) {
|
||
return __awaiter(this, void 0, void 0, function* () {
|
||
try {
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
yield navigator.clipboard.writeText(text);
|
||
return true;
|
||
}
|
||
}
|
||
catch (error) {
|
||
// fall through
|
||
}
|
||
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) {
|
||
if (container.dataset.cmReady === "1")
|
||
return;
|
||
container.dataset.cmReady = "1";
|
||
let currentMethod = null;
|
||
let query = container.querySelector(".cm-query");
|
||
let suggest = container.querySelector(".cm-suggest");
|
||
let selected = container.querySelector(".cm-selected");
|
||
let nameEl = container.querySelector(".cm-name");
|
||
let shortEl = container.querySelector(".cm-short");
|
||
let call = container.querySelector(".cm-call");
|
||
let preview = container.querySelector(".cm-preview");
|
||
let desc = container.querySelector(".cm-desc");
|
||
let options = container.querySelector(".cm-options");
|
||
if (!query || !suggest || !selected || !nameEl || !shortEl || !call || !preview || !desc) {
|
||
container.textContent = "";
|
||
const searchBar = el("div", "cm-search");
|
||
query = el("input", "cm-query");
|
||
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");
|
||
shortEl = el("span", "cm-short");
|
||
head.appendChild(nameEl);
|
||
head.appendChild(shortEl);
|
||
call = el("div", "cm-call");
|
||
preview = el("code", "cm-preview");
|
||
desc = el("div", "cm-desc");
|
||
selected.appendChild(head);
|
||
selected.appendChild(call);
|
||
selected.appendChild(preview);
|
||
selected.appendChild(desc);
|
||
container.appendChild(searchBar);
|
||
container.appendChild(selected);
|
||
}
|
||
// 搜索框下的策略选项
|
||
options = container.querySelector(".cm-options");
|
||
if (!options) {
|
||
options = el("div", "cm-options");
|
||
query.parentElement.insertAdjacentElement("afterend", options);
|
||
}
|
||
if (!options.querySelector(".cm-option-select")) {
|
||
const optionsBox = options;
|
||
function addOption(label, key, values, current) {
|
||
const wrap = el("label", "cm-option");
|
||
const text = el("span", "cm-option-label");
|
||
text.textContent = label;
|
||
const select = el("select", "cm-option-select");
|
||
for (const [value, caption] of values) {
|
||
const opt = el("option");
|
||
opt.value = value;
|
||
opt.textContent = caption;
|
||
select.appendChild(opt);
|
||
}
|
||
select.value = current;
|
||
select.addEventListener("change", () => {
|
||
strategies[key] = select.value;
|
||
if (currentMethod) {
|
||
selectMethod(currentMethod);
|
||
}
|
||
});
|
||
wrap.appendChild(text);
|
||
wrap.appendChild(select);
|
||
optionsBox.appendChild(wrap);
|
||
}
|
||
addOption("字符串", "string", [["prefix", "str: 前缀"], ["quote", "引号 \"…\""]], strategies.string);
|
||
}
|
||
const queryEl = query;
|
||
const suggestEl = suggest;
|
||
const selectedEl = selected;
|
||
const nameNode = nameEl;
|
||
const shortNode = shortEl;
|
||
const callEl = call;
|
||
const previewEl = preview;
|
||
const descNode = desc;
|
||
const copyBtn = el("button", "cm-copy");
|
||
copyBtn.type = "button";
|
||
copyBtn.textContent = "复制";
|
||
copyBtn.hidden = true;
|
||
copyBtn.title = "复制生成的代码";
|
||
selectedEl.appendChild(copyBtn);
|
||
copyBtn.addEventListener("click", () => {
|
||
var _a;
|
||
copyText((_a = previewEl.textContent) !== null && _a !== void 0 ? _a : "").then((ok) => {
|
||
copyBtn.textContent = ok ? "已复制" : "复制失败";
|
||
window.setTimeout(() => { copyBtn.textContent = "复制"; }, 1200);
|
||
});
|
||
});
|
||
function clearSuggest() {
|
||
suggestEl.textContent = "";
|
||
}
|
||
function selectMethod(method) {
|
||
currentMethod = method;
|
||
clearSuggest();
|
||
queryEl.value = method.name;
|
||
selectedEl.hidden = false;
|
||
nameNode.textContent = method.name;
|
||
shortNode.textContent = method.shortDescription || "";
|
||
descNode.textContent = method.description || "";
|
||
callEl.textContent = "";
|
||
previewEl.textContent = "";
|
||
copyBtn.hidden = true;
|
||
const parameters = parametersOf(method);
|
||
const fields = [];
|
||
function updatePreview() {
|
||
const states = fields.map((field) => field.read());
|
||
fields.forEach((field, i) => field.el.classList.toggle("cm-invalid", states[i].invalid));
|
||
previewEl.textContent = formatMethod(method, states);
|
||
copyBtn.hidden = !states.every((state) => state.valid);
|
||
}
|
||
const skeleton = methodSkeleton(method, parameters.length > 0);
|
||
const code = el("code", "cm-code");
|
||
const prefix = el("span", "cm-lit");
|
||
prefix.textContent = skeleton.prefix;
|
||
code.appendChild(prefix);
|
||
parameters.forEach((parameter, index) => {
|
||
if (index > 0) {
|
||
const sep = el("span", "cm-lit");
|
||
sep.textContent = ", ";
|
||
code.appendChild(sep);
|
||
}
|
||
const field = makeField(parameter, updatePreview);
|
||
field.el.title = parameter.description;
|
||
code.appendChild(field.el);
|
||
fields.push(field);
|
||
});
|
||
if (skeleton.suffix) {
|
||
const suffix = el("span", "cm-lit");
|
||
suffix.textContent = skeleton.suffix;
|
||
code.appendChild(suffix);
|
||
}
|
||
callEl.appendChild(code);
|
||
if (parameters.length === 0) {
|
||
const empty = el("span", "cm-empty");
|
||
empty.textContent = "(无参数)";
|
||
callEl.appendChild(empty);
|
||
}
|
||
updatePreview();
|
||
}
|
||
function updateSuggestions() {
|
||
clearSuggest();
|
||
for (const method of searchMethods(queryEl.value)) {
|
||
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.shortDescription || 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() {
|
||
document.querySelectorAll(".custommethods-search").forEach((container) => renderContainer(container));
|
||
}
|
||
function register() {
|
||
const docsify = window.$docsify;
|
||
if (docsify) {
|
||
docsify.plugins = [].concat(docsify.plugins, (hook) => {
|
||
hook.doneEach(() => initAll());
|
||
});
|
||
}
|
||
else if (document.readyState === "loading") {
|
||
document.addEventListener("DOMContentLoaded", () => initAll());
|
||
}
|
||
else {
|
||
initAll();
|
||
}
|
||
}
|
||
window.CustomMethodSearch = {
|
||
search: searchMethods,
|
||
editDistance,
|
||
renderAll: initAll,
|
||
strategies,
|
||
getMethods: () => methods,
|
||
setMethods: (list) => { 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();
|
||
}
|
||
})();
|