chore(frontend): introduce prettier + eslint and prune unused code
Backend CI / Backend (push) Has been cancelled
Frontend CI / Frontend (push) Failing after 1m39s

- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports)
- Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier
- Add root scripts: format, format:check, lint, lint:fix
- Reformat 257 files across admin-web, ops-web, desktop-client, packages
- Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters
- Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex
- Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 20:39:09 +08:00
parent 21c7892ee4
commit 162abdc97c
258 changed files with 42261 additions and 37556 deletions
@@ -1,149 +1,149 @@
import { Buffer } from "node:buffer";
import { Buffer } from 'node:buffer'
import { nativeImage } from "electron";
import { nativeImage } from 'electron'
import { fetchDesktopApiURL, resolveDesktopApiURL } from "../transport/api-client";
import { fetchDesktopApiURL, resolveDesktopApiURL } from '../transport/api-client'
export interface ImageAssetBlob {
blob: Blob;
width: number;
height: number;
fileName: string;
blob: Blob
width: number
height: number
fileName: string
}
export interface CropRect {
x: number;
y: number;
width: number;
height: number;
x: number
y: number
width: number
height: number
}
const passthroughImageTypes = new Set(["image/png", "image/jpeg", "image/jpg", "image/gif"]);
const passthroughImageTypes = new Set(['image/png', 'image/jpeg', 'image/jpg', 'image/gif'])
function buildDesktopAssetFallbackURL(publicAssetUrl: URL): string | null {
if (!publicAssetUrl.pathname.startsWith("/api/public/assets/")) {
return null;
if (!publicAssetUrl.pathname.startsWith('/api/public/assets/')) {
return null
}
const token = publicAssetUrl.pathname.slice("/api/public/assets/".length);
const token = publicAssetUrl.pathname.slice('/api/public/assets/'.length)
if (!token) {
return null;
return null
}
const fallbackURL = new URL(resolveDesktopApiURL(`/api/desktop/content/assets/${token}`));
const fallbackURL = new URL(resolveDesktopApiURL(`/api/desktop/content/assets/${token}`))
publicAssetUrl.searchParams.forEach((value, key) => {
fallbackURL.searchParams.set(key, value);
});
fallbackURL.searchParams.set("format", "png");
return fallbackURL.toString();
fallbackURL.searchParams.set(key, value)
})
fallbackURL.searchParams.set('format', 'png')
return fallbackURL.toString()
}
export function buildAssetURLCandidates(sourceUrl: string): string[] {
const trimmed = sourceUrl.trim();
const trimmed = sourceUrl.trim()
if (!trimmed) {
return [];
return []
}
if (/^(data|blob):/i.test(trimmed)) {
return [trimmed];
return [trimmed]
}
const candidates: string[] = [];
const candidates: string[] = []
const pushCandidate = (value: string) => {
if (!candidates.includes(value)) {
candidates.push(value);
candidates.push(value)
}
};
try {
const parsed = new URL(trimmed, resolveDesktopApiURL("/"));
if (parsed.pathname.startsWith("/api/")) {
const apiAssetUrl = new URL(resolveDesktopApiURL(`${parsed.pathname}${parsed.search}`));
if (apiAssetUrl.pathname.startsWith("/api/public/assets/")) {
apiAssetUrl.searchParams.set("format", "png");
}
pushCandidate(apiAssetUrl.toString());
const desktopAssetFallbackURL = buildDesktopAssetFallbackURL(apiAssetUrl);
if (desktopAssetFallbackURL) {
pushCandidate(desktopAssetFallbackURL);
}
}
pushCandidate(parsed.toString());
} catch {
pushCandidate(trimmed);
}
return candidates;
try {
const parsed = new URL(trimmed, resolveDesktopApiURL('/'))
if (parsed.pathname.startsWith('/api/')) {
const apiAssetUrl = new URL(resolveDesktopApiURL(`${parsed.pathname}${parsed.search}`))
if (apiAssetUrl.pathname.startsWith('/api/public/assets/')) {
apiAssetUrl.searchParams.set('format', 'png')
}
pushCandidate(apiAssetUrl.toString())
const desktopAssetFallbackURL = buildDesktopAssetFallbackURL(apiAssetUrl)
if (desktopAssetFallbackURL) {
pushCandidate(desktopAssetFallbackURL)
}
}
pushCandidate(parsed.toString())
} catch {
pushCandidate(trimmed)
}
return candidates
}
function shouldUseDesktopAssetFetch(candidate: string): boolean {
try {
return new URL(candidate).pathname.startsWith("/api/desktop/content/assets/");
return new URL(candidate).pathname.startsWith('/api/desktop/content/assets/')
} catch {
return false;
return false
}
}
function fileNameForImageType(sourceType: string): string {
if (sourceType === "image/gif") {
return "image.gif";
if (sourceType === 'image/gif') {
return 'image.gif'
}
if (sourceType === "image/jpeg" || sourceType === "image/jpg") {
return "image.jpg";
if (sourceType === 'image/jpeg' || sourceType === 'image/jpg') {
return 'image.jpg'
}
return "image.png";
return 'image.png'
}
export async function fetchImageAssetBlob(sourceUrl: string): Promise<ImageAssetBlob | null> {
for (const candidate of buildAssetURLCandidates(sourceUrl)) {
const response = await (
shouldUseDesktopAssetFetch(candidate) ? fetchDesktopApiURL(candidate) : fetch(candidate)
).catch(() => null);
).catch(() => null)
if (!response?.ok) {
continue;
continue
}
const sourceBlob = await response.blob().catch(() => null);
if (!sourceBlob || !sourceBlob.type.startsWith("image/")) {
continue;
const sourceBlob = await response.blob().catch(() => null)
if (!sourceBlob || !sourceBlob.type.startsWith('image/')) {
continue
}
const image = nativeImage.createFromBuffer(Buffer.from(await sourceBlob.arrayBuffer()));
const image = nativeImage.createFromBuffer(Buffer.from(await sourceBlob.arrayBuffer()))
if (image.isEmpty()) {
continue;
continue
}
const size = image.getSize();
const sourceType = sourceBlob.type.toLowerCase();
const size = image.getSize()
const sourceType = sourceBlob.type.toLowerCase()
if (passthroughImageTypes.has(sourceType)) {
return {
blob: sourceBlob,
width: size.width,
height: size.height,
fileName: fileNameForImageType(sourceType),
};
}
}
return {
blob: new Blob([new Uint8Array(image.toPNG())], { type: "image/png" }),
blob: new Blob([new Uint8Array(image.toPNG())], { type: 'image/png' }),
width: size.width,
height: size.height,
fileName: "image.png",
};
fileName: 'image.png',
}
}
return null;
return null
}
export function getCenteredCropRect(width: number, height: number, ratio: number): CropRect {
let cropWidth = width;
let cropHeight = cropWidth / ratio;
let cropWidth = width
let cropHeight = cropWidth / ratio
if (cropHeight > height) {
cropHeight = height;
cropWidth = cropHeight * ratio;
cropHeight = height
cropWidth = cropHeight * ratio
}
return {
@@ -151,31 +151,34 @@ export function getCenteredCropRect(width: number, height: number, ratio: number
y: (height - cropHeight) / 2,
width: cropWidth,
height: cropHeight,
};
}
}
export async function cropImageAssetBlob(source: ImageAssetBlob, ratio: number): Promise<ImageAssetBlob | null> {
const image = nativeImage.createFromBuffer(Buffer.from(await source.blob.arrayBuffer()));
export async function cropImageAssetBlob(
source: ImageAssetBlob,
ratio: number,
): Promise<ImageAssetBlob | null> {
const image = nativeImage.createFromBuffer(Buffer.from(await source.blob.arrayBuffer()))
if (image.isEmpty()) {
return null;
return null
}
const crop = getCenteredCropRect(source.width, source.height, ratio);
const crop = getCenteredCropRect(source.width, source.height, ratio)
const cropped = image.crop({
x: Math.max(0, Math.round(crop.x)),
y: Math.max(0, Math.round(crop.y)),
width: Math.max(1, Math.round(crop.width)),
height: Math.max(1, Math.round(crop.height)),
});
})
if (cropped.isEmpty()) {
return null;
return null
}
const size = cropped.getSize();
const size = cropped.getSize()
return {
blob: new Blob([new Uint8Array(cropped.toPNG())], { type: "image/png" }),
blob: new Blob([new Uint8Array(cropped.toPNG())], { type: 'image/png' }),
width: size.width,
height: size.height,
fileName: "image.png",
};
fileName: 'image.png',
}
}