防撤回软件提交

This commit is contained in:
wangxinfei
2025-05-19 17:39:49 +08:00
parent 8020113d3b
commit 7bf0e11589
404 changed files with 69545 additions and 0 deletions
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1717043909505" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1498" fill="currentColor" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M910.222222 853.333333v56.888889h-284.444444v-56.888889h284.444444zM457.102222 72.419556c24.007111 0 47.104 4.778667 68.664889 14.222222 20.992 9.216 39.594667 21.845333 55.296 37.489778 15.644444 15.701333 28.273778 34.304 37.489778 55.352888 9.443556 21.504 14.222222 44.885333 14.222222 69.518223 0 17.976889-2.503111 35.043556-7.395555 51.143111h82.261333c25.713778 0 48.867556 9.784889 66.901333 28.273778 17.806222 18.261333 27.192889 41.358222 27.192889 66.673777V512h-56.888889V395.093333a36.693333 36.693333 0 0 0-7.054222-22.243555l-3.982222-4.721778a34.816 34.816 0 0 0-26.168889-11.093333H548.636444l22.357334-73.443556c3.242667-10.695111 4.892444-22.357333 4.892444-34.588444 0-16.725333-3.185778-32.426667-9.443555-46.705778a123.904 123.904 0 0 0-25.6-37.944889 122.88 122.88 0 0 0-37.944889-25.6 113.379556 113.379556 0 0 0-45.795556-9.443556c-16.099556 0-31.516444 3.185778-45.738666 9.443556a122.88 122.88 0 0 0-37.944889 25.6 123.050667 123.050667 0 0 0-25.656889 37.944889 115.484444 115.484444 0 0 0-9.386667 46.705778c0 11.889778 1.649778 23.210667 5.063111 33.735111l23.779556 74.296889H208.782222a36.238222 36.238222 0 0 0-26.794666 11.264 36.295111 36.295111 0 0 0-11.377778 26.794666v78.620445c0.568889 10.24 2.275556 14.165333 2.730666 15.018666 0.341333-0.227556 2.56-0.625778 5.916445-2.048a521.102222 521.102222 0 0 0 20.081778-9.443555l2.048-0.967111c1.706667-0.796444 4.551111-1.991111 8.988444-3.470222a134.200889 134.200889 0 0 1 106.609778-0.568889c16.497778 6.997333 31.061333 16.839111 43.406222 29.127111a136.305778 136.305778 0 0 1 39.822222 96.995555c0 18.944-3.584 36.864-10.695111 53.191111a137.216 137.216 0 0 1-72.533333 72.021334 135.964444 135.964444 0 0 1-53.532445 10.581333c-18.318222 0-36.465778-4.152889-54.044444-12.401778l-2.389333-0.398222-6.712889-3.015111c-1.706667-0.796444-5.176889-2.389333-10.865778-5.745778-8.704-5.12-12.288-5.290667-12.686222-5.290666 0.398222 0.284444-3.299556 3.128889-6.087111 12.913777v83.000889a34.702222 34.702222 0 0 0 11.150222 26.112 36.977778 36.977778 0 0 0 26.965333 11.093334l360.106667-0.056889v56.888889H208.782222c-22.186667 0-42.666667-7.168-59.619555-20.935112l-7.054223-6.257777A91.306667 91.306667 0 0 1 113.777778 803.214222V713.386667l0.967111-4.096c4.949333-20.707556 14.734222-36.807111 29.184-47.900445a53.418667 53.418667 0 0 1 32.824889-10.979555c13.084444 0 26.567111 4.266667 41.244444 13.027555 2.958222 1.706667 4.721778 2.56 5.632 3.015111l3.527111 0.568889 4.778667 2.446222c10.638222 5.233778 20.935111 7.793778 31.516444 7.793778a78.677333 78.677333 0 0 0 56.718223-23.096889 76.003556 76.003556 0 0 0 23.153777-55.808 78.506667 78.506667 0 0 0-23.153777-56.718222 78.734222 78.734222 0 0 0-56.718223-23.210667c-10.638222 0-20.878222 2.275556-31.288888 6.826667l-1.536 0.682667-1.592889 0.568889a26.396444 26.396444 0 0 0-4.209778 1.592889 426.666667 426.666667 0 0 1-22.869333 10.695111 74.524444 74.524444 0 0 1-36.579556 6.371555 54.272 54.272 0 0 1-39.310222-24.746666c-7.395556-11.093333-11.377778-25.429333-12.231111-45.056L113.777778 473.713778V395.093333c0-25.486222 9.671111-48.696889 27.989333-67.015111a93.184 93.184 0 0 1 67.015111-27.932444h80.497778a165.546667 165.546667 0 0 1-7.793778-51.143111c0-24.632889 4.778667-48.014222 14.222222-69.575111 9.216-20.992 21.788444-39.594667 37.489778-55.296 15.644444-15.644444 34.247111-28.273778 55.296-37.489778a169.528889 169.528889 0 0 1 68.608-14.222222zM910.222222 711.111111v56.888889h-284.444444v-56.888889h284.444444z m0-142.222222v56.888889h-284.444444v-56.888889h284.444444z" p-id="1499"></path></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,141 @@
<style>
img.icon {
width: 60px;
border-radius: 10px;
position: relative;
}
img.icon::after {
content: "";
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
background: url(local://root/src/setting/static/default.png) no-repeat center;
background-size: 100%;
}
:host(.highlight-item) .plugin-item-info {
animation: highlightAnimation 2s ease-out 1;
}
/* 动画关键帧 */
@keyframes highlightAnimation {
0% {
background-color: orange;
box-shadow: 0 0 10px 10px orange;
}
100% {
background-color: transparent;
box-shadow: 0 0 1px 1px transparent;
}
}
.authors {
display: inline-flex;
gap: 10px;
justify-content: space-between;
}
.authors a {
color: #81adef;
cursor: pointer;
}
.manipulate {
display: flex;
flex-direction: column;
gap: 10px;
}
.dependencies a {
color: green;
cursor: pointer;
}
.failed,
.uninstall,
.update,
.inactive-tag,
.newer-version,
.dependencies,
.lower4 {
display: none;
}
:host([data-failed]) .failed {
display: unset;
}
:host([data-failed]) .success {
display: none;
}
:host([data-installed='1']) .uninstall {
display: unset;
}
:host([data-installed='1']) .install {
display: none;
}
:host([data-update='1']) .update {
display: unset;
}
:host([data-update='1']) .newer-version {
display: inline;
color: skyblue;
padding-left: 5px;
}
:host([data-inactive='1']) .inactive-tag {
display: inline;
}
:host([data-dependencies='1']) .dependencies {
display: inline;
}
:host([data-lower4='1']) .lower4 {
display: inline;
color: var(--feedback_warning);
padding-left: 5px;
}
</style>
<setting-item data-direction="row">
<div class="plugin-item-info">
<div style="display: flex; gap: 10px; margin-bottom: 10px">
<img class="icon" src="local://root/src/setting/static/default.png" />
<div>
<setting-text>
<span class="title" title="插件名">插件名</span>
<span class="inactive-tag">(未激活)</span>
<span class="lower4" title="manifest版本小于4">(&lt;4)</span>
</setting-text>
<setting-text data-type="secondary">
<span class="description" title="插件描述">插件描述</span>
</setting-text>
</div>
</div>
<setting-text data-type="secondary">
<div style="display: flex;gap: 5px 30px;flex-wrap: wrap;">
<span>版本:<span class="version">版本</span><span class="newer-version"></span>
</span>
<span>开发:<span class="authors"></span></span>
<span>类型:<span class="type"></span></span>
<span>平台:<span class="platforms"></span></span>
<span class="dependencies">依赖:<span class="items"></span></span>
</div>
</setting-text>
</div>
<div class="manipulate">
<setting-button class="success update" data-type="primary">更新</setting-button>
<setting-button class="success install" data-type="primary">安装</setting-button>
<setting-button class="success uninstall" data-type="secondary">卸载</setting-button>
<setting-button class="failed retry" data-type="primary">重试</setting-button>
<setting-button class="detail" data-type="secondary">详情</setting-button>
</div>
</setting-item>
@@ -0,0 +1,136 @@
<setting-section>
<style>
[class^=list-dialog]::backdrop {
background: rgba(0, 0, 100, 0.4);
}
[class^=list-dialog] button {
cursor: pointer;
}
button {
user-select: none;
}
.mirror-select {
user-select: none;
}
.manipulate {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.list-dialog-install {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
margin: 0;
border-radius: 5px;
width: 50%;
background-color: var(--bg_white);
color: var(--text_primary)
}
.list-dialog {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
margin: 0;
border-radius: 5px;
width: 50%;
background-color: var(--background-04);
color: var(--text_primary)
}
.list-dialog-content {
display: flex;
justify-content: space-around;
flex-direction: column;
white-space: pre-wrap;
}
</style>
<setting-panel>
<setting-list data-direction="column">
<setting-item>
<input type="text" id="list-search" placeholder="过滤下方插件,支持插件名、描述、作者名、版本号"
style="height: 25px; width: 100%; color: inherit" />
</setting-item>
<setting-item data-direction="row">
<div>
<setting-text>
<span>插件总数:<span class="total-text"></span></span>
</setting-text>
<setting-text data-type="secondary">
刷新会禁用缓存并请求,所以会比较慢,可用来检查更新,建议不要频繁刷新
</setting-text>
</div>
<div>
<setting-button data-type="primary" class="refresh-btn">刷新</setting-button>
</div>
</setting-item>
<setting-item data-direction="row">
<div>
<setting-text>是否使用镜像</setting-text>
<setting-text data-type="secondary">设置系统代理时建议关闭</setting-text>
</div>
<div class="manipulate">
<!-- <div>
不使用带缓存的镜像
<setting-switch class="mirror-no-cache"></setting-switch>
</div> -->
<setting-switch class="mirror-switch" is-active></setting-switch>
<setting-button class="mirror-add-btn" data-type="secondary">修改镜像</setting-button>
<!-- <setting-select class="mirror-select" style="width: 150px;">
<setting-option data-value="1" is-selected>不使用镜像</setting-option>
<setting-option data-value="2">2</setting-option>
<setting-option data-value="3">3</setting-option>
</setting-select> -->
</div>
</setting-item>
</setting-list>
</setting-panel>
</setting-section>
<setting-section data-title="插件列表">
<setting-panel>
<setting-list data-direction="column" id="plugin-list"> </setting-list>
</setting-panel>
</setting-section>
<dialog class="list-dialog-install">
<div style="font-size: large; display: flex; justify-content: space-between">
选择包来源
<button class="list-dialog-install-btn-close q-button q-button--secondary default-btn"
style="width: 25px">×</button>
</div>
<hr />
<div style="color: red;margin-bottom: 10px;">注意:安装/更新 时会删除插件原文件(不包括配置),如果有修改过的文件,请先备份或者使用 git 进行安装/更新</div>
<div style="display: flex; justify-content: space-around">
<div style="display: flex; flex-direction: column; gap: 10px">
<setting-button class="list-dialog-btn-repo" data-type="secondary">使用仓库代码包</button>
</div>
<div style="display: flex; flex-direction: column; gap: 10px">
<setting-button class="list-dialog-btn-release" data-type="secondary">使用release包</button>
</div>
</div>
</dialog>
<dialog class="list-dialog">
<div style="font-size: large; display: flex; justify-content: space-between">
<span class="list-dialog-title">标题</span>
<button class="list-dialog-btn-close q-button q-button--secondary default-btn" style="width: 25px">×</button>
</div>
<hr />
<div class="list-dialog-content">
<div>content</div>
</div>
<hr />
<div style="display: flex; justify-content: flex-end; gap: 20px;">
<setting-button class="list-dialog-btn-confirm" data-type="primary">确认</setting-button>
<setting-button class="list-dialog-btn-cancel" data-type="secondary">取消</setting-button>
</div>
</dialog>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,31 @@
{
"$schema": "./manifest_schema.json",
"manifest_version": 4,
"type": "extension",
"name": "插件列表查看",
"slug": "list-viewer",
"description": "插件列表查看·安装·更新",
"icon": "assets/icon.svg",
"thumb": "assets/icon.svg",
"version": "1.2.1",
"authors": [
{
"name": "ltxhhz",
"link": "https://github.com/ltxhhz"
}
],
"platform": [
"win32",
"linux",
"darwin"
],
"injects": {
"main": "./main/index.js",
"preload": "./preload/index.js",
"renderer": "./renderer/index.js"
},
"repository": {
"repo": "ltxhhz/LL-plugin-list-viewer",
"branch": "master"
}
}
@@ -0,0 +1 @@
"use strict";const r=require("electron"),t=r.contextBridge.exposeInMainWorld;t("ListViewer",{getPkg:(e,i)=>r.ipcRenderer.invoke("LiteLoader.ListViewer.getPkg",e,i),removePkg:e=>r.ipcRenderer.invoke("LiteLoader.ListViewer.removePkg",e),log:(...e)=>r.ipcRenderer.send("LiteLoader.ListViewer.removePkg",...e)});
@@ -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
};