防撤回软件提交
This commit is contained in:
@@ -0,0 +1,857 @@
|
||||
const semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
||||
const validateAndParse = (version) => {
|
||||
if (typeof version !== "string") {
|
||||
throw new TypeError("Invalid argument expected string");
|
||||
}
|
||||
const match = version.match(semver);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
|
||||
}
|
||||
match.shift();
|
||||
return match;
|
||||
};
|
||||
const isWildcard = (s) => s === "*" || s === "x" || s === "X";
|
||||
const tryParse = (v) => {
|
||||
const n = parseInt(v, 10);
|
||||
return isNaN(n) ? v : n;
|
||||
};
|
||||
const forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
|
||||
const compareStrings = (a, b) => {
|
||||
if (isWildcard(a) || isWildcard(b))
|
||||
return 0;
|
||||
const [ap, bp] = forceType(tryParse(a), tryParse(b));
|
||||
if (ap > bp)
|
||||
return 1;
|
||||
if (ap < bp)
|
||||
return -1;
|
||||
return 0;
|
||||
};
|
||||
const compareSegments = (a, b) => {
|
||||
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
||||
const r = compareStrings(a[i] || "0", b[i] || "0");
|
||||
if (r !== 0)
|
||||
return r;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const compareVersions = (v1, v2) => {
|
||||
const n1 = validateAndParse(v1);
|
||||
const n2 = validateAndParse(v2);
|
||||
const p1 = n1.pop();
|
||||
const p2 = n2.pop();
|
||||
const r = compareSegments(n1, n2);
|
||||
if (r !== 0)
|
||||
return r;
|
||||
if (p1 && p2) {
|
||||
return compareSegments(p1.split("."), p2.split("."));
|
||||
} else if (p1 || p2) {
|
||||
return p1 ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const compare = (v1, v2, operator) => {
|
||||
assertValidOperator(operator);
|
||||
const res = compareVersions(v1, v2);
|
||||
return operatorResMap[operator].includes(res);
|
||||
};
|
||||
const operatorResMap = {
|
||||
">": [1],
|
||||
">=": [0, 1],
|
||||
"=": [0],
|
||||
"<=": [-1, 0],
|
||||
"<": [-1],
|
||||
"!=": [-1, 1]
|
||||
};
|
||||
const allowedOperators = Object.keys(operatorResMap);
|
||||
const assertValidOperator = (op) => {
|
||||
if (typeof op !== "string") {
|
||||
throw new TypeError(`Invalid operator type, expected string but got ${typeof op}`);
|
||||
}
|
||||
if (allowedOperators.indexOf(op) === -1) {
|
||||
throw new Error(`Invalid operator, expected one of ${allowedOperators.join("|")}`);
|
||||
}
|
||||
};
|
||||
class Node {
|
||||
value;
|
||||
next;
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
class Queue {
|
||||
#head;
|
||||
#tail;
|
||||
#size;
|
||||
constructor() {
|
||||
this.clear();
|
||||
}
|
||||
enqueue(value) {
|
||||
const node = new Node(value);
|
||||
if (this.#head) {
|
||||
this.#tail.next = node;
|
||||
this.#tail = node;
|
||||
} else {
|
||||
this.#head = node;
|
||||
this.#tail = node;
|
||||
}
|
||||
this.#size++;
|
||||
}
|
||||
dequeue() {
|
||||
const current = this.#head;
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
this.#head = this.#head.next;
|
||||
this.#size--;
|
||||
return current.value;
|
||||
}
|
||||
clear() {
|
||||
this.#head = void 0;
|
||||
this.#tail = void 0;
|
||||
this.#size = 0;
|
||||
}
|
||||
get size() {
|
||||
return this.#size;
|
||||
}
|
||||
*[Symbol.iterator]() {
|
||||
let current = this.#head;
|
||||
while (current) {
|
||||
yield current.value;
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
const AsyncResource = {
|
||||
bind(fn, _type, thisArg) {
|
||||
return fn.bind(thisArg);
|
||||
}
|
||||
};
|
||||
function pLimit(concurrency) {
|
||||
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
||||
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
||||
}
|
||||
const queue = new Queue();
|
||||
let activeCount = 0;
|
||||
const next = () => {
|
||||
activeCount--;
|
||||
if (queue.size > 0) {
|
||||
queue.dequeue()();
|
||||
}
|
||||
};
|
||||
const run = async (function_, resolve, arguments_) => {
|
||||
activeCount++;
|
||||
const result = (async () => function_(...arguments_))();
|
||||
resolve(result);
|
||||
try {
|
||||
await result;
|
||||
} catch {
|
||||
}
|
||||
next();
|
||||
};
|
||||
const enqueue = (function_, resolve, arguments_) => {
|
||||
queue.enqueue(
|
||||
AsyncResource.bind(run.bind(void 0, function_, resolve, arguments_))
|
||||
);
|
||||
(async () => {
|
||||
await Promise.resolve();
|
||||
if (activeCount < concurrency && queue.size > 0) {
|
||||
queue.dequeue()();
|
||||
}
|
||||
})();
|
||||
};
|
||||
const generator = (function_, ...arguments_) => new Promise((resolve) => {
|
||||
enqueue(function_, resolve, arguments_);
|
||||
});
|
||||
Object.defineProperties(generator, {
|
||||
activeCount: {
|
||||
get: () => activeCount
|
||||
},
|
||||
pendingCount: {
|
||||
get: () => queue.size
|
||||
},
|
||||
clearQueue: {
|
||||
value() {
|
||||
queue.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
return generator;
|
||||
}
|
||||
const hostReg = /^https?:\/\/[^/]+/;
|
||||
const originMirrors = ["https://mirror.ghproxy.com/", "https://ghproxy.net/", "https://github.moeyy.xyz/"];
|
||||
const thisSlug = "list-viewer";
|
||||
let config;
|
||||
async function initConfig() {
|
||||
const defaultConfig = {
|
||||
inactivePlugins: [],
|
||||
debug: false,
|
||||
useMirror: true,
|
||||
mirrors: {
|
||||
downloadUrl: ["https://cdn.jsdelivr.net/gh"]
|
||||
// rawUrl: []
|
||||
}
|
||||
};
|
||||
config = await LiteLoader.api.config.get(thisSlug, defaultConfig);
|
||||
const save = debounce((obj) => {
|
||||
LiteLoader.api.config.set(thisSlug, obj);
|
||||
}, 1e3);
|
||||
config = deepWatch(config, () => {
|
||||
save(config);
|
||||
});
|
||||
}
|
||||
function getRandomItem(arr) {
|
||||
return arr ? arr[Math.floor(Math.random() * arr.length)] : void 0;
|
||||
}
|
||||
function useMirror(url2, mirror, removeHost = true) {
|
||||
if (/\/gh$/.test(mirror) && mirror.includes("jsdelivr")) {
|
||||
return mirror + url2.replace(hostReg, "").replace("/raw/", "@").replace(/@v(?!v)/, "@vv");
|
||||
} else {
|
||||
return mirror + (removeHost ? url2.replace(hostReg, "") : url2);
|
||||
}
|
||||
}
|
||||
function localFetch(path, plugin = "list-viewer") {
|
||||
return fetch(`local:///${LiteLoader.plugins[plugin].path.plugin.replace(":\\", "://").replaceAll("\\", "/")}/${path.startsWith("/") ? path.slice(1) : path}`);
|
||||
}
|
||||
function fetchWithTimeout(url2, options, timeout = 3e3) {
|
||||
config.debug && console.log("fetchWithTimeout", url2);
|
||||
return new Promise((resolve, reject) => {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => {
|
||||
controller.abort();
|
||||
reject(new Error("请求超时"));
|
||||
}, timeout);
|
||||
fetch(url2, { ...options, signal: controller.signal }).then((response) => {
|
||||
clearTimeout(timeoutId);
|
||||
resolve(response);
|
||||
}).catch((error) => {
|
||||
clearTimeout(timeoutId);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
function debounce(func, delay) {
|
||||
let timeoutId;
|
||||
return function(...args) {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
func(args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
function deepWatch(obj, callback) {
|
||||
const observer = new Proxy(obj, {
|
||||
set(target, key, value, receiver) {
|
||||
const oldValue = target[key];
|
||||
if (oldValue !== value) {
|
||||
callback();
|
||||
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
||||
deepWatch(value, callback);
|
||||
}
|
||||
}
|
||||
return Reflect.set(target, key, value, receiver);
|
||||
}
|
||||
});
|
||||
return observer;
|
||||
}
|
||||
const listUrl = {
|
||||
repo: "LiteLoaderQQNT/Plugin-List",
|
||||
branch: "v4",
|
||||
file: "plugins.json"
|
||||
};
|
||||
const defaultIcon = "local://root/src/setting/static/default.png";
|
||||
const domParser = new DOMParser();
|
||||
const typeMap = {
|
||||
extension: "扩展",
|
||||
theme: "主题",
|
||||
framework: "框架"
|
||||
};
|
||||
let pluginList;
|
||||
let currentItem;
|
||||
let currentManifest;
|
||||
let showDialog;
|
||||
let filterInput;
|
||||
function onSettingWindowCreated(view) {
|
||||
initConfig();
|
||||
localFetch("/assets/view.html").then((e) => e.text()).then(async (res) => {
|
||||
const doms = domParser.parseFromString(res, "text/html");
|
||||
filterInput = doms.querySelector("#list-search");
|
||||
const refreshBtn = doms.querySelector(".refresh-btn");
|
||||
const totalEl = doms.querySelector(".total-text");
|
||||
const dialogInstall = doms.querySelector(".list-dialog-install");
|
||||
const dialogInstallClose = doms.querySelector(".list-dialog-install-btn-close");
|
||||
let resFunc;
|
||||
dialogInstallClose.addEventListener("click", () => {
|
||||
dialogInstall.close();
|
||||
resFunc();
|
||||
});
|
||||
const useRepo = doms.querySelector(".list-dialog-btn-repo");
|
||||
useRepo.addEventListener("click", () => {
|
||||
dialogInstall.close();
|
||||
resFunc(false);
|
||||
});
|
||||
const useRelease = doms.querySelector(".list-dialog-btn-release");
|
||||
useRelease.addEventListener("click", () => {
|
||||
dialogInstall.close();
|
||||
resFunc(true);
|
||||
});
|
||||
let dialogResolve;
|
||||
const dialog = doms.querySelector(".list-dialog");
|
||||
const dialogClose = doms.querySelector(".list-dialog-btn-close");
|
||||
dialogClose.addEventListener("click", () => {
|
||||
dialog.close();
|
||||
dialogResolve();
|
||||
});
|
||||
const dialogTitle = doms.querySelector(".list-dialog-title");
|
||||
const dialogContent = doms.querySelector(".list-dialog-content");
|
||||
const dialogConfirm = doms.querySelector(".list-dialog-btn-confirm");
|
||||
dialogConfirm.addEventListener("click", () => {
|
||||
dialog.close();
|
||||
dialogResolve(true);
|
||||
});
|
||||
const dialogCancel = doms.querySelector(".list-dialog-btn-cancel");
|
||||
dialogCancel.addEventListener("click", () => {
|
||||
dialog.close();
|
||||
dialogResolve(false);
|
||||
});
|
||||
showDialog = (option) => {
|
||||
let dialogInput;
|
||||
dialogTitle.innerText = option.title;
|
||||
if (option.type === "confirm" || option.type === "message") {
|
||||
if (option.message) {
|
||||
dialogContent.innerText = option.message;
|
||||
}
|
||||
if (option.content) {
|
||||
if (typeof option.content === "string") {
|
||||
dialogContent.innerHTML = option.content;
|
||||
} else {
|
||||
dialogContent.replaceChildren(option.content);
|
||||
}
|
||||
}
|
||||
if (option.type === "message") {
|
||||
dialogCancel.style.display = "none";
|
||||
}
|
||||
} else if (option.type === "prompt") {
|
||||
dialogInput = option.textarea ? document.createElement("textarea") : document.createElement("input");
|
||||
dialogInput.placeholder = option.placeholder ?? "请输入内容";
|
||||
dialogInput.value = option.default ?? "";
|
||||
dialogInput.style.width = "100%";
|
||||
dialogInput.style.background = "var(--background_02)";
|
||||
dialogInput.style.color = "var(--bg_white_light)";
|
||||
dialogInput.style.marginTop = "10px";
|
||||
dialogContent.replaceChildren(option.message || "", dialogInput);
|
||||
}
|
||||
dialogConfirm.innerText = option.confirm || "确定";
|
||||
dialogCancel.innerText = option.cancel || "取消";
|
||||
return new Promise((resolve) => {
|
||||
dialog.showModal();
|
||||
dialogResolve = (bool) => {
|
||||
if (option.type === "prompt") {
|
||||
resolve(bool ? dialogInput.value : "");
|
||||
} else {
|
||||
resolve(bool);
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
const mirrorSwitch = doms.querySelector(".mirror-switch");
|
||||
mirrorSwitch.toggleAttribute("is-active", config.useMirror);
|
||||
mirrorSwitch.onclick = () => {
|
||||
const isActive = mirrorSwitch.hasAttribute("is-active");
|
||||
mirrorSwitch.toggleAttribute("is-active", !isActive);
|
||||
config.useMirror = !isActive;
|
||||
};
|
||||
const mirrorAddBtn = doms.querySelector(".mirror-add-btn");
|
||||
mirrorAddBtn.onclick = () => {
|
||||
showDialog({
|
||||
title: "添加镜像",
|
||||
type: "prompt",
|
||||
placeholder: "请输入镜像地址,每行一个",
|
||||
message: `请输入镜像地址,每行一个,如果代理方式是完整url在地址后面,比如
|
||||
https://mirror/https://github.com/xx/xx,则需要写
|
||||
https://mirror/https://github.com/
|
||||
如果代理方式是 path在地址后面,比如
|
||||
https://mirror/xx/xx,则需要写https://mirror
|
||||
jsdelivr镜像直接按默认那个写就行
|
||||
内置三个镜像'https://mirror.ghproxy.com', 'https://ghproxy.net', 'https://github.moeyy.xyz'
|
||||
使用时默认优先使用第一个,如果没有响应才会使用其他镜像`,
|
||||
textarea: true,
|
||||
default: config.mirrors.downloadUrl.join("\n")
|
||||
}).then((res2) => {
|
||||
if (res2) {
|
||||
config.mirrors.downloadUrl = res2.split("\n");
|
||||
}
|
||||
});
|
||||
};
|
||||
doms.body.childNodes.forEach((dom) => {
|
||||
view.appendChild(dom);
|
||||
});
|
||||
const showInstallDialog = () => new Promise((resolve) => {
|
||||
dialogInstall.showModal();
|
||||
resFunc = resolve;
|
||||
});
|
||||
createItemComponent(await localFetch("/assets/list-item.html").then((e) => e.text()), showInstallDialog);
|
||||
const pluginListDom = view.querySelector("#plugin-list");
|
||||
const getList1 = (noCache = false) => {
|
||||
refreshBtn.setAttribute("is-disabled", "");
|
||||
getList(noCache).then(async (list) => {
|
||||
pluginList = list;
|
||||
totalEl.innerText = list.length.toString();
|
||||
const promArr = [];
|
||||
const limit = pLimit(3);
|
||||
list.forEach((plugin, i) => {
|
||||
const dom = document.createElement("plugin-item");
|
||||
pluginListDom.appendChild(dom);
|
||||
promArr.push(
|
||||
limit(async () => {
|
||||
const manifest = await getManifest(plugin, noCache);
|
||||
dom.dataset.index = i + "";
|
||||
config.debug && console.log(plugin, manifest);
|
||||
updateElProp(dom, manifest, plugin.repo);
|
||||
})
|
||||
);
|
||||
});
|
||||
return Promise.all(promArr);
|
||||
}).finally(() => {
|
||||
refreshBtn.removeAttribute("is-disabled");
|
||||
});
|
||||
};
|
||||
refreshBtn.addEventListener("click", () => {
|
||||
pluginListDom.replaceChildren();
|
||||
getList1(true);
|
||||
});
|
||||
getList1();
|
||||
}).catch(console.error);
|
||||
}
|
||||
function createItemComponent(innerHtml, showInstallDialog) {
|
||||
class PluginListClass extends HTMLElement {
|
||||
titleEl;
|
||||
descriptionEl;
|
||||
versionEl;
|
||||
authorsEl;
|
||||
manipulateEl;
|
||||
iconEl;
|
||||
updateBtnEl;
|
||||
installBtnEl;
|
||||
uninstallBtnEl;
|
||||
detailBtnEl;
|
||||
retryBtnEl;
|
||||
typeEl;
|
||||
dependenciesItemsEl;
|
||||
platformsEl;
|
||||
manifest = null;
|
||||
#initPromise;
|
||||
#initPromiseResolve;
|
||||
#initialized = false;
|
||||
constructor() {
|
||||
super();
|
||||
const shadow = this.attachShadow({ mode: "open" });
|
||||
shadow.innerHTML = innerHtml;
|
||||
this.#initPromise = new Promise((resolve) => {
|
||||
this.#initPromiseResolve = resolve;
|
||||
if (this.#initialized)
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
connectedCallback() {
|
||||
this.titleEl = this.shadowRoot.querySelector(".title");
|
||||
this.descriptionEl = this.shadowRoot.querySelector(".description");
|
||||
this.versionEl = this.shadowRoot.querySelector(".version");
|
||||
this.authorsEl = this.shadowRoot.querySelector(".authors");
|
||||
this.manipulateEl = this.shadowRoot.querySelector(".manipulate");
|
||||
this.iconEl = this.shadowRoot.querySelector(".icon");
|
||||
this.typeEl = this.shadowRoot.querySelector(".type");
|
||||
this.dependenciesItemsEl = this.shadowRoot.querySelector(".dependencies>.items");
|
||||
this.platformsEl = this.shadowRoot.querySelector(".platforms");
|
||||
this.updateBtnEl = this.shadowRoot.querySelector(".update");
|
||||
const installEvent = async (update = false) => {
|
||||
currentItem = pluginList[Number(this.dataset.index)];
|
||||
currentManifest = this.manifest;
|
||||
showInstallDialog().then((res) => {
|
||||
if (res !== void 0) {
|
||||
if (update) {
|
||||
this.updateBtnEl.setAttribute("is-disabled", "");
|
||||
this.updateBtnEl.innerText = "安装中...";
|
||||
} else {
|
||||
this.installBtnEl.setAttribute("is-disabled", "");
|
||||
this.installBtnEl.innerText = "安装中...";
|
||||
}
|
||||
install(res).then((res2) => {
|
||||
if (res2.success) {
|
||||
this.dataset.installed = "1";
|
||||
this.dataset.inactive = "1";
|
||||
if (update)
|
||||
delete this.dataset.update;
|
||||
config.inactivePlugins.push(this.manifest.slug);
|
||||
this.updateOpenDirEvent();
|
||||
} else {
|
||||
showDialog({ title: "安装失败", message: res2.message, type: "message" });
|
||||
}
|
||||
}).catch((e) => {
|
||||
showDialog({ title: "安装失败", message: e.message, type: "message" });
|
||||
}).finally(() => {
|
||||
if (update) {
|
||||
this.updateBtnEl.removeAttribute("is-disabled");
|
||||
this.updateBtnEl.innerText = "更新";
|
||||
} else {
|
||||
this.installBtnEl.removeAttribute("is-disabled");
|
||||
this.installBtnEl.innerText = "安装";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
this.updateBtnEl.addEventListener("click", () => installEvent(true));
|
||||
this.installBtnEl = this.shadowRoot.querySelector(".install");
|
||||
this.installBtnEl.addEventListener("click", () => installEvent());
|
||||
this.uninstallBtnEl = this.shadowRoot.querySelector(".uninstall");
|
||||
this.uninstallBtnEl.addEventListener("click", async () => {
|
||||
config.debug && console.log("uninstall", this.manifest.name);
|
||||
currentItem = pluginList[Number(this.dataset.index)];
|
||||
currentManifest = this.manifest;
|
||||
showDialog({ title: "卸载", message: `确定要卸载插件 ${this.manifest.name} 吗?`, type: "confirm" }).then((e) => {
|
||||
if (e) {
|
||||
this.uninstallBtnEl.innerText = "卸载中...";
|
||||
this.uninstallBtnEl.setAttribute("is-disabled", "");
|
||||
uninstall().then((res) => {
|
||||
if (res.success) {
|
||||
this.uninstallBtnEl.innerText = "卸载";
|
||||
this.uninstallBtnEl.removeAttribute("is-disabled");
|
||||
delete this.dataset.installed;
|
||||
if (config.inactivePlugins.includes(this.manifest.slug)) {
|
||||
this.dataset.inactive = "0";
|
||||
config.inactivePlugins = config.inactivePlugins.filter((e2) => e2 !== this.manifest.slug);
|
||||
}
|
||||
this.updateOpenDirEvent();
|
||||
} else {
|
||||
showDialog({ title: "卸载失败", message: res.message, type: "message" });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
this.retryBtnEl = this.shadowRoot.querySelector(".retry");
|
||||
this.retryBtnEl.addEventListener("click", async () => {
|
||||
this.retryBtnEl.innerText = "重试中...";
|
||||
this.retryBtnEl.setAttribute("is-disabled", "");
|
||||
const manifest = await getManifest(pluginList[Number(this.dataset.index)]);
|
||||
this.manifest = manifest;
|
||||
updateElProp(this, manifest, this.dataset.failed);
|
||||
this.retryBtnEl.innerText = "重试";
|
||||
this.retryBtnEl.removeAttribute("is-disabled");
|
||||
});
|
||||
this.detailBtnEl = this.shadowRoot.querySelector(".detail");
|
||||
this.detailBtnEl.addEventListener("click", async () => {
|
||||
LiteLoader.api.openExternal(`https://github.com/${pluginList[Number(this.dataset.index)].repo}/tree/${pluginList[Number(this.dataset.index)].branch}`);
|
||||
});
|
||||
filterInput.addEventListener("input", () => this.updateHidden());
|
||||
this.updateHidden();
|
||||
this.updateOpenDirEvent();
|
||||
this.#initialized = true;
|
||||
this.#initPromiseResolve?.();
|
||||
}
|
||||
static get observedAttributes() {
|
||||
return ["data-name", "data-version", "data-description", "data-authors", "data-icon", "data-failed", "data-type", "data-dependencies", "data-platforms"];
|
||||
}
|
||||
attributeChangedCallback(name, _oldValue, newValue) {
|
||||
this.#initPromise.then(() => {
|
||||
try {
|
||||
switch (name) {
|
||||
case "data-failed":
|
||||
if (newValue) {
|
||||
this.titleEl.innerText = newValue;
|
||||
this.titleEl.style.color = "red";
|
||||
this.descriptionEl.innerText = "获取失败";
|
||||
} else {
|
||||
this.titleEl.style.removeProperty("color");
|
||||
}
|
||||
break;
|
||||
case "data-name":
|
||||
this.titleEl.innerText = newValue || "插件名";
|
||||
this.titleEl.title = newValue || "";
|
||||
break;
|
||||
case "data-version":
|
||||
this.versionEl.innerText = newValue || "版本";
|
||||
this.versionEl.title = newValue || "";
|
||||
break;
|
||||
case "data-description":
|
||||
this.descriptionEl.innerText = newValue || "插件描述";
|
||||
this.descriptionEl.title = newValue || "";
|
||||
break;
|
||||
case "data-authors": {
|
||||
const arr = newValue === "1" ? this.manifest.authors : [];
|
||||
this.authorsEl.append(
|
||||
...arr.map((author) => {
|
||||
const a = document.createElement("a");
|
||||
a.title = author.link;
|
||||
a.innerText = author.name;
|
||||
a.onclick = () => LiteLoader.api.openExternal(author.link);
|
||||
return a;
|
||||
}).reduce((p, v, i) => {
|
||||
p[i * 2] = v;
|
||||
if (i)
|
||||
p[i * 2 - 1] = " | ";
|
||||
return p;
|
||||
}, [])
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "data-type":
|
||||
this.typeEl.innerText = typeMap[newValue + ""] || "unknown";
|
||||
break;
|
||||
case "data-platforms":
|
||||
this.platformsEl.title = this.platformsEl.innerText = newValue || "";
|
||||
break;
|
||||
case "data-dependencies": {
|
||||
const arr = newValue === "1" ? this.manifest.dependencies : [];
|
||||
this.dependenciesItemsEl.append(
|
||||
...arr.map((e) => {
|
||||
const a = document.createElement("a");
|
||||
a.title = e;
|
||||
a.innerText = e;
|
||||
a.onclick = () => {
|
||||
const item = document.getElementById(`item-${e}`);
|
||||
if (item) {
|
||||
item.scrollIntoView?.();
|
||||
item.classList.add("highlight-item");
|
||||
setTimeout(() => {
|
||||
item.classList.remove("highlight-item");
|
||||
}, 2e3);
|
||||
}
|
||||
};
|
||||
return a;
|
||||
}).reduce((p, v, i) => {
|
||||
p[i * 2] = v;
|
||||
if (i)
|
||||
p[i * 2 - 1] = " | ";
|
||||
return p;
|
||||
}, [])
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "data-icon": {
|
||||
const [src, src1] = (newValue || "").split(",");
|
||||
this.iconEl.src = src || defaultIcon;
|
||||
let num = 0;
|
||||
this.iconEl.addEventListener("error", () => {
|
||||
if (src1 && num < 3) {
|
||||
const iconPath = this.manifest.icon.replace(/^\.?\//, "");
|
||||
switch (num) {
|
||||
case 0:
|
||||
this.iconEl.src = src.replace(iconPath, `src/${iconPath}`);
|
||||
break;
|
||||
case 1:
|
||||
this.iconEl.src = src1;
|
||||
break;
|
||||
case 2:
|
||||
this.iconEl.src = src1.replace(iconPath, `src/${iconPath}`);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.iconEl.src = defaultIcon;
|
||||
}
|
||||
num++;
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(this.dataset.name || this.dataset.failed, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
updateOpenDirEvent() {
|
||||
if (this.manifest && LiteLoader.plugins[this.manifest.slug] && this.dataset.installed === "1") {
|
||||
this.titleEl.title = "点击打开插件所在目录";
|
||||
this.titleEl.addEventListener("click", () => {
|
||||
LiteLoader.api.openPath(LiteLoader.plugins[this.manifest.slug].path.plugin);
|
||||
});
|
||||
this.titleEl.style.cursor = "pointer";
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 过滤用,判断是否应该隐藏
|
||||
*/
|
||||
updateHidden() {
|
||||
try {
|
||||
const authors = this.dataset.authors === "1" ? this.manifest.authors : [];
|
||||
const str = (this.dataset.name || "") + (this.dataset.version || "") + (this.dataset.description || "") + (this.dataset.version || "") + authors.map((e) => e.name).join("");
|
||||
if (!filterInput.value || str.toLowerCase().includes(filterInput.value.toLowerCase())) {
|
||||
this.hidden = false;
|
||||
} else {
|
||||
this.hidden = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(this.manifest?.slug || this.dataset.name, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define("plugin-item", PluginListClass);
|
||||
return new PluginListClass();
|
||||
}
|
||||
function updateElProp(el, manifest, repo) {
|
||||
if (manifest) {
|
||||
el.id = `item-${manifest.slug}`;
|
||||
el.dataset.name = manifest.name;
|
||||
el.manifest = manifest;
|
||||
el.updateOpenDirEvent();
|
||||
el.dataset.description = manifest.description;
|
||||
el.dataset.lower4 = Number(manifest.manifest_version) >= 4 ? "" : "1";
|
||||
el.dataset.authors = manifest.authors ? "1" : "";
|
||||
el.dataset.platforms = manifest.platform.join("|");
|
||||
el.dataset.installed = LiteLoader.plugins[manifest.slug] ? "1" : "";
|
||||
el.dataset.slug = manifest.slug;
|
||||
el.dataset.icon = getIconUrls(pluginList[Number(el.dataset.index)], manifest).toString();
|
||||
el.dataset.defaultIcon = defaultIcon;
|
||||
el.dataset.type = manifest.type;
|
||||
el.dataset.dependencies = manifest.dependencies ? "1" : "";
|
||||
delete el.dataset.failed;
|
||||
if (LiteLoader.plugins[manifest.slug]) {
|
||||
el.dataset.version = LiteLoader.plugins[manifest.slug].manifest.version;
|
||||
config.debug && console.log(manifest.slug, LiteLoader.plugins[manifest.slug], manifest);
|
||||
el.dataset.update = compare(manifest.version, LiteLoader.plugins[manifest.slug]?.manifest?.version ?? manifest.version, ">") ? "1" : "";
|
||||
el.shadowRoot.querySelector(".newer-version").innerText = `-> ${manifest.version}`;
|
||||
} else {
|
||||
el.dataset.version = manifest.version;
|
||||
}
|
||||
if (config.inactivePlugins.includes(manifest.slug)) {
|
||||
if (!LiteLoader.plugins[manifest.slug]) {
|
||||
el.dataset.inactive = "1";
|
||||
} else {
|
||||
config.inactivePlugins = config.inactivePlugins.filter((v) => v !== manifest.slug);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
el.dataset.failed = repo;
|
||||
}
|
||||
}
|
||||
async function getList(noCache = false, again = false) {
|
||||
let url;
|
||||
if (config.useMirror) {
|
||||
const m = getGithubMirror(!again);
|
||||
url = useMirror(`https://github.com/${listUrl.repo}/raw/${listUrl.branch}/${listUrl.file}`, m || getRandomItem(originMirrors), !!m);
|
||||
} else {
|
||||
url = `https://github.com/${listUrl.repo}/raw/${listUrl.branch}/${listUrl.file}`;
|
||||
}
|
||||
return await fetchWithTimeout(url, {
|
||||
cache: noCache ? "no-cache" : "default"
|
||||
}).then((res) => res.status === 200 ? res.json() : null).catch((err) => {
|
||||
if (again) {
|
||||
console.error(`getList ${url}`, err);
|
||||
return null;
|
||||
} else {
|
||||
console.warn(`getList ${url}`, err);
|
||||
return getList(noCache, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
async function getManifest(item, noCache = false, again = false) {
|
||||
let url;
|
||||
let m = getGithubMirror(!again);
|
||||
if (config.useMirror) {
|
||||
url = useMirror(`https://github.com/${item.repo}/raw/${item.branch}/manifest.json`, m || getRandomItem(originMirrors), !!m);
|
||||
} else {
|
||||
url = `https://github.com/${item.repo}/raw/${item.branch}/manifest.json`;
|
||||
}
|
||||
return await fetchWithTimeout(url, {
|
||||
cache: noCache ? "no-cache" : "default"
|
||||
}).then((res) => {
|
||||
if (res.status === 200) {
|
||||
return res.json();
|
||||
} else {
|
||||
m = getGithubMirror(!again);
|
||||
if (config.useMirror) {
|
||||
url = useMirror(`https://github.com/${item.repo}/raw/${item.branch}/package.json`, m || getRandomItem(originMirrors), !!m);
|
||||
} else {
|
||||
url = `https://github.com/${item.repo}/raw/${item.branch}/package.json`;
|
||||
}
|
||||
return fetchWithTimeout(url, {
|
||||
cache: noCache ? "no-cache" : "default"
|
||||
}).then(async (res1) => {
|
||||
if (res1.status === 200) {
|
||||
const pkg = await res1.json();
|
||||
const obj = pkg.liteloader_manifest;
|
||||
if (obj) {
|
||||
obj.version = pkg.version;
|
||||
obj.description = pkg.description;
|
||||
obj.authors = typeof pkg.author === "string" ? [{ name: pkg.author, link: `https://github.com/${pkg.author}` }] : [pkg.author];
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
if (again) {
|
||||
console.error(`getManifest ${url}`, err);
|
||||
return null;
|
||||
} else {
|
||||
console.warn(`getManifest ${url}`, err);
|
||||
return getManifest(item, noCache, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
async function install(release = false) {
|
||||
let url;
|
||||
config.debug && console.log("install", currentItem);
|
||||
if (release) {
|
||||
url = await getLatestReleaseUrl(currentItem);
|
||||
if (!url) {
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
message: "获取release包失败"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
url = getArchiveUrl(currentItem);
|
||||
}
|
||||
if (config.useMirror) {
|
||||
const m = getGithubMirror();
|
||||
return ListViewer.getPkg(currentManifest.slug, useMirror(url, m || getRandomItem(originMirrors), !!m));
|
||||
} else {
|
||||
return ListViewer.getPkg(currentManifest.slug, url);
|
||||
}
|
||||
}
|
||||
function getIconUrls(item, manifest) {
|
||||
if (manifest.icon) {
|
||||
const iconPath = manifest.icon.replace(/^\.?\//, "");
|
||||
const m = getGithubMirror(true);
|
||||
const m1 = getGithubMirror();
|
||||
if (config.useMirror) {
|
||||
return [
|
||||
useMirror(`https://github.com/${item.repo}/raw/${item.branch}/${iconPath}`, m || getRandomItem(originMirrors), !!m),
|
||||
useMirror(`https://github.com/${item.repo}/raw/${item.branch}/${iconPath}`, m1 || getRandomItem(originMirrors), !!m1)
|
||||
];
|
||||
} else {
|
||||
return [`https://github.com/${item.repo}/raw/${item.branch}/${iconPath}`];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
function uninstall() {
|
||||
return ListViewer.removePkg(currentManifest.slug);
|
||||
}
|
||||
function getGithubMirror(first = false) {
|
||||
if (first) {
|
||||
return config.mirrors.downloadUrl[0];
|
||||
}
|
||||
return getRandomItem(config.mirrors.downloadUrl.splice(1));
|
||||
}
|
||||
function getArchiveUrl(item) {
|
||||
return `https://github.com/${item.repo}/archive/refs/heads/${item.branch}.zip`;
|
||||
}
|
||||
async function getLatestReleaseUrl(item) {
|
||||
const url = `https://api.github.com/repos/${item.repo}/releases/latest`;
|
||||
const body = await fetchWithTimeout(url).then((e) => e.json()).catch((err) => {
|
||||
throw new Error(`${err.message}
|
||||
${url}`);
|
||||
});
|
||||
const zipFile = body.assets.find((asset) => asset.name.endsWith(".zip"));
|
||||
return zipFile.browser_download_url;
|
||||
}
|
||||
export {
|
||||
onSettingWindowCreated
|
||||
};
|
||||
Reference in New Issue
Block a user