1
0
mirror of https://github.com/bestnite/sub2clash.git synced 2025-12-02 22:25:35 +00:00

2 Commits

Author SHA1 Message Date
f16779b441 fix: update short link 2025-12-02 23:16:10 +08:00
516657f849 refactor(frontend): Extract short link UI into dedicated component 2025-10-20 16:45:42 +11:00
4 changed files with 211 additions and 65 deletions

View File

@@ -16,7 +16,7 @@
}, },
"devDependencies": { "devDependencies": {
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.1.7" "vite": "^7.2.6"
} }
}, },
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
@@ -1785,7 +1785,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -1897,10 +1896,10 @@
} }
}, },
"node_modules/tar": { "node_modules/tar": {
"version": "7.5.1", "version": "7.5.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz",
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==",
"license": "ISC", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"@isaacs/fs-minipass": "^4.0.0", "@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0", "chownr": "^3.0.0",
@@ -1943,11 +1942,10 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "7.1.10", "version": "7.2.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz",
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",

View File

@@ -17,6 +17,6 @@
}, },
"devDependencies": { "devDependencies": {
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.1.7" "vite": "^7.2.6"
} }
} }

View File

@@ -7,6 +7,7 @@ import { base64EncodeUnicode, base64decodeUnicode } from "./utils.js";
import "./components/rule-provider-input.js"; import "./components/rule-provider-input.js";
import "./components/rule-input.js"; import "./components/rule-input.js";
import "./components/rename-input.js"; import "./components/rename-input.js";
import "./components/short-link-input-group.js";
@customElement("sub2clash-app") @customElement("sub2clash-app")
export class Sub2clashApp extends LitElement { export class Sub2clashApp extends LitElement {
@@ -145,6 +146,7 @@ export class Sub2clashApp extends LitElement {
// 设置返回的短链ID和密码 // 设置返回的短链ID和密码
this.shortLinkID = response.data.id; this.shortLinkID = response.data.id;
this.shortLinkPasswd = response.data.password; this.shortLinkPasswd = response.data.password;
this.showDialog("成功", "生成短链成功");
}) })
.catch((error) => { .catch((error) => {
if (error.response && error.response.data) { if (error.response && error.response.data) {
@@ -183,11 +185,11 @@ export class Sub2clashApp extends LitElement {
} }
) )
.then(() => { .then(() => {
this.showDialog("成功", "更新成功"); this.showDialog("成功", "更新短链成功");
}) })
.catch((error) => { .catch((error) => {
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
this.showDialog("", "密码错误"); this.showDialog("", "短链不存在或密码错误");
} else if (error.response && error.response.data) { } else if (error.response && error.response.data) {
this.showDialog("", "更新短链失败:" + error.response.data); this.showDialog("", "更新短链失败:" + error.response.data);
} else { } else {
@@ -214,7 +216,7 @@ export class Sub2clashApp extends LitElement {
}, },
}) })
.then(() => { .then(() => {
this.showDialog("成功", "删除成功"); this.showDialog("成功", "删除短链成功");
}) })
.catch((error) => { .catch((error) => {
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
@@ -238,6 +240,9 @@ export class Sub2clashApp extends LitElement {
.get(`./short/${s[1]}`) .get(`./short/${s[1]}`)
.then((resp) => { .then((resp) => {
this.config = resp.data; this.config = resp.data;
const parsedUrl = new URL(this.reverseUrl);
this.shortLinkID = parsedUrl.pathname.split("/").filter(Boolean).pop() ?? "";
this.shortLinkPasswd = parsedUrl.searchParams.get("password") ?? "";
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
if (err.response && err.response.status == 401) { if (err.response && err.response.status == 401) {
@@ -609,8 +614,7 @@ export class Sub2clashApp extends LitElement {
</fieldset> </fieldset>
<fieldset class="fieldset mb-8"> <fieldset class="fieldset mb-8">
<legend <legend class="fieldset-legend text-2xl font-semibold mb-4">
class="fieldset-legend text-2xl font-semibold mb-4 text-center">
输出配置 输出配置
</legend> </legend>
@@ -637,55 +641,19 @@ export class Sub2clashApp extends LitElement {
</div> </div>
</div> </div>
<div class="form-control mb-2"> <short-link-input-group
<div class="join w-full"> .id="${this.shortLinkID}"
<input .passwd="${this.shortLinkPasswd}"
class="input input-bordered w-1/2 join-item" @id-change="${(e: Event) => {
type="text"
placeholder="ID可选"
.value="${this.shortLinkID}"
@change="${(e: Event) => {
this.shortLinkID = (e.target as HTMLInputElement).value; this.shortLinkID = (e.target as HTMLInputElement).value;
}}" /> }}"
<input @passwd-change="${(e: Event) => {
class="input input-bordered w-1/2 join-item"
type="text"
placeholder="密码"
.value="${this.shortLinkPasswd}"
@change="${(e: Event) => {
this.shortLinkPasswd = (e.target as HTMLInputElement).value; this.shortLinkPasswd = (e.target as HTMLInputElement).value;
}}" /> }}"
<button @generate-btn-click="${this.generateShortLink}"
class="btn btn-primary join-item" @update-btn-click="${this.updateShortLink}"
type="button" @delete-btn-click="${this.deleteShortLink}">
@click="${this.generateShortLink}"> </short-link-input-group>
生成短链
</button>
<button
class="btn btn-primary join-item"
@click="${this.updateShortLink}"
type="button">
更新短链
</button>
<button
class="btn btn-primary join-item"
@click="${this.deleteShortLink}"
type="button">
删除短链
</button>
<button
class="btn btn-primary join-item"
type="button"
@click="${(e: Event) => {
this.copyToClipboard(
`${window.location.origin}${window.location.pathname}s/${this.shortLinkID}?password=${this.shortLinkPasswd}`,
e.target as HTMLButtonElement
);
}}">
复制短链
</button>
</div>
</div>
</fieldset> </fieldset>
</form> </form>
</div> </div>

View File

@@ -0,0 +1,180 @@
import { html, LitElement, unsafeCSS } from "lit";
import { customElement, property } from "lit/decorators.js";
import globalStyles from "../index.css?inline";
@customElement("short-link-input-group")
export class ShortLinkInputGroup extends LitElement {
static styles = unsafeCSS(globalStyles);
@property()
id: string = "";
@property({ type: Number })
_screenSizeLevel: number = 0;
@property()
passwd: string = "";
connectedCallback() {
super.connectedCallback();
window.addEventListener("resize", this._checkScreenSize);
this._checkScreenSize(); // Initial check
}
disconnectedCallback() {
window.removeEventListener("resize", this._checkScreenSize);
super.disconnectedCallback();
}
_checkScreenSize = () => {
const width = window.innerWidth;
if (width < 365) {
this._screenSizeLevel = 0; // sm
} else if (width < 640) {
this._screenSizeLevel = 1; // md
} else {
this._screenSizeLevel = 2; // other
}
};
async copyToClipboard(content: string, e: HTMLButtonElement) {
try {
await navigator.clipboard.writeText(content);
let text = e.textContent;
e.addEventListener("mouseout", function () {
e.textContent = text;
});
e.textContent = "复制成功";
} catch (err) {
console.error("复制到剪贴板失败:", err);
}
}
idInputTemplate() {
return html`<input
class="input input-bordered w-1/2 join-item"
type="text"
placeholder="ID可选"
.value="${this.id}"
@change="${(e: Event) => {
this.id = (e.target as HTMLInputElement).value;
this.dispatchEvent(
new CustomEvent("id-change", {
detail: this.id,
})
);
}}" />`;
}
passwdInputTemplate() {
return html`<input
class="input input-bordered w-1/2 join-item"
type="text"
placeholder="密码"
.value="${this.passwd}"
@change="${(e: Event) => {
this.passwd = (e.target as HTMLInputElement).value;
this.dispatchEvent(
new CustomEvent("passwd-change", {
detail: this.passwd,
})
);
}}" />`;
}
generateBtnTemplate(extraClass: string = "") {
return html`<button
class="btn btn-primary join-item ${extraClass}"
type="button"
@click="${(e: Event) => {
this.dispatchEvent(
new CustomEvent("generate-btn-click", { detail: e })
);
}}">
生成短链
</button>`;
}
updateBtnTemplate(extraClass: string = "") {
return html`<button
class="btn btn-primary join-item ${extraClass}"
@click="${(e: Event) => {
this.dispatchEvent(new CustomEvent("update-btn-click", { detail: e }));
}}"
type="button">
更新短链
</button>`;
}
deleteBtnTemplate(extraClass: string = "") {
return html`<button
class="btn btn-primary join-item ${extraClass}"
@click="${(e: Event) => {
this.dispatchEvent(new CustomEvent("delete-btn-click", { detail: e }));
}}"
type="button">
删除短链
</button>`;
}
copyBtnTemplate(extraClass: string = "") {
return html`<button
class="btn btn-primary join-item ${extraClass}"
type="button"
@click="${(e: Event) => {
this.copyToClipboard(
`${window.location.origin}${window.location.pathname}s/${this.id}?password=${this.passwd}`,
e.target as HTMLButtonElement
);
}}">
复制短链
</button>`;
}
render() {
const sm = html`<div class="form-control mb-2">
<div class="join w-full mb-1">
${this.idInputTemplate()} ${this.passwdInputTemplate()}
</div>
<div class="join w-full mb-1">
${this.generateBtnTemplate("w-1/2")} ${this.updateBtnTemplate("w-1/2")}
</div>
<div class="join w-full">
${this.deleteBtnTemplate("w-1/2")} ${this.copyBtnTemplate("w-1/2")}
</div>
</div>`;
const md = html`<div class="form-control mb-2">
<div class="join w-full mb-1">
${this.idInputTemplate()} ${this.passwdInputTemplate()}
</div>
<div class="join w-full">
${this.generateBtnTemplate("w-1/4")} ${this.updateBtnTemplate("w-1/4")}
${this.deleteBtnTemplate("w-1/4")} ${this.copyBtnTemplate("w-1/4")}
</div>
</div>`;
const other = html`<div class="form-control mb-2">
<div class="join w-full">
${this.idInputTemplate()} ${this.passwdInputTemplate()}
${this.generateBtnTemplate()} ${this.updateBtnTemplate()}
${this.deleteBtnTemplate()} ${this.copyBtnTemplate()}
</div>
</div>`;
switch (this._screenSizeLevel) {
case 0:
return sm;
case 1:
return md;
default:
return other;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"short-link-input-group": ShortLinkInputGroup;
}
}