Compare commits

...

31 Commits

Author SHA1 Message Date
士子☀️ c0e343b696 Merge pull request #204 from caorushizi/fix-search
fix: 🐛  修复新版本在更换安装目录后下载失败的问题
2024-06-30 19:01:26 +08:00
caorushizi f18ac7c444 fix: 🐛 修复新版本在更换安装目录后下载失败的问题
下载中的视频无法停止 #202
2024-06-30 18:58:58 +08:00
士子☀️ f637610734 Merge pull request #196 from caorushizi/fix-search
fix: 🐛  修复搜索时跳转的链接
2024-06-24 23:43:47 +08:00
caorushizi 5e1046dd5b fix: 🐛 修复搜索时跳转的链接 2024-06-23 11:41:33 +08:00
士子☀️ f25584cb2f Merge pull request #195 from caorushizi/fix-videoname
fix: 🐛  修复获取视频名称的问题
2024-06-23 00:13:13 +08:00
caorushizi 03999585d0 fix: 🐛 修复获取视频名称的问题 2024-06-23 00:12:06 +08:00
士子☀️ 772c2b0300 Merge pull request #194 from caorushizi/dev-webview
Dev webview
2024-06-22 22:59:28 +08:00
caorushizi 3622e67a56 fix: 🐛 修复浏览器背景颜色 2024-06-22 22:58:25 +08:00
caorushizi c51630cdd6 fix: 🐛 修复切换隐私模式时 Proxy 和嗅探问题 2024-06-22 22:14:49 +08:00
士子☀️ dab1c55a93 Merge pull request #193 from caorushizi/dev-webview
feat(settings):   新增无痕浏览模式
2024-06-22 21:48:11 +08:00
caorushizi 7b966bc988 feat(settings): 新增无痕浏览模式
设置页面新增无痕浏览模式

新增无痕浏览模式 #52
2024-06-22 21:46:27 +08:00
士子☀️ e5e46bee38 Merge pull request #192 from caorushizi/dev-webview
feat(settings):   添加自动更新选项
2024-06-22 15:55:09 +08:00
caorushizi 3365d96a03 feat(settings): 添加自动更新选项
设置页面添加自动更新选项,下次启动时生效

auto upgrade #185
2024-06-22 15:53:42 +08:00
士子☀️ fb223cd493 Merge pull request #191 from caorushizi/dev-webview
feat:   添加下载代理开关 & 添加清空缓存
2024-06-22 15:36:18 +08:00
caorushizi 60d49c4773 feat: 添加下载代理开关 & 添加清空缓存 2024-06-22 15:35:08 +08:00
士子☀️ e7aa4ae147 Merge pull request #190 from caorushizi/dev-webview
build: 🛠  portable
2024-06-22 12:42:50 +08:00
caorushizi c45be41ddc build: 🛠 portable 2024-06-22 12:41:45 +08:00
士子☀️ 85b37facee Merge pull request #189 from caorushizi/dev-webview
Dev webview
2024-06-22 12:27:58 +08:00
caorushizi 90567009ef Merge branch 'dev-webview' of https://github.com/caorushizi/m3u8-downloader into dev-webview 2024-06-22 12:26:36 +08:00
caorushizi 899afc156e build: 🛠 upload artifact 2024-06-22 12:25:51 +08:00
caorushizi 9e33b7e488 build: 🛠 upload artifact 2024-06-22 12:24:24 +08:00
士子☀️ e7ba60a51f Merge pull request #188 from caorushizi/dev-webview
build: 🛠  portable
2024-06-22 12:11:55 +08:00
caorushizi 2a3ad325c2 build: 🛠 portable 2024-06-22 12:11:19 +08:00
caorushizi 7575b80d31 build: 🛠 portable 2024-06-22 12:08:33 +08:00
士子☀️ 8bb9d119b5 Merge pull request #187 from caorushizi/dev-webview
Dev webview
2024-06-22 11:54:18 +08:00
caorushizi 5da3f2b1c7 build: 🛠 优化构建 2024-06-22 11:50:40 +08:00
caorushizi da6cbcfb75 feat: 修复 webview 中加载的问题 2024-06-22 10:44:34 +08:00
caorushizi 3d7aff41ee feat: 优化内置浏览器逻辑 2024-06-22 03:42:15 +08:00
士子☀️ 2cc49e8679 Merge pull request #186 from caorushizi/dev-ap
feat:   ap
2024-06-21 23:44:42 +08:00
caorushizi 0e961c0647 feat: use td app 2024-06-21 23:39:45 +08:00
caorushizi 4d5757a6ce feat: ap 2024-06-21 00:04:21 +08:00
39 changed files with 585 additions and 326 deletions
+4
View File
@@ -2,6 +2,7 @@ acodec
adblocker
ahooks
antd
aplus
behaviour
bili
bilibili
@@ -15,6 +16,7 @@ conventionalcommits
datetime
DDTHH
execa
idtype
immer
inversify
KHTML
@@ -34,7 +36,9 @@ svgz
TDAPP
tiptap
typeorm
vitepress
vuedraggable
vuejs
waline
watting
Watting
+1 -1
View File
@@ -1,4 +1,4 @@
APP_NAME=mediago
APP_ID=mediago.ziying.site
APP_COPYRIGHT=caorushizi
APP_VERSION=2.2.2
APP_VERSION=2.2.3-beta.1
-1
View File
@@ -1,5 +1,4 @@
APP_TD_APPID=
APP_CLARITY_APPID=
GH_TOKEN=
LOAD_DEVTOOLS=
-1
View File
@@ -1,5 +1,4 @@
APP_TD_APPID=
APP_CLARITY_APPID=
GH_TOKEN=
DEBUG_PLUGINS=false
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mediago",
"version": "2.2.2",
"version": "2.2.3-beta.1",
"description": "在线视频下载器",
"main": "main/index.js",
"author": "caorushizi",
+4 -2
View File
@@ -50,8 +50,8 @@
"typescript-eslint": "^7.10.0"
},
"dependencies": {
"@cliqz/adblocker-electron": "^1.27.3",
"@cliqz/adblocker-electron-preload": "^1.27.3",
"@cliqz/adblocker-electron": "1.27.3",
"@cliqz/adblocker-electron-preload": "1.27.3",
"axios": "^1.7.2",
"better-sqlite3": "^10.0.0",
"cheerio": "1.0.0-rc.12",
@@ -66,7 +66,9 @@
"lint-staged": "^15.2.5",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"nanoid": "^5.0.7",
"node-fetch": "^3.3.2",
"node-machine-id": "^1.1.12",
"node-pty": "^1.0.0",
"reflect-metadata": "^0.2.2",
"strip-ansi": "^7.1.0",
+9 -13
View File
@@ -39,7 +39,7 @@ function getConfig(): esbuild.BuildOptions {
function buildOptions(
entry: string,
platform: esbuild.Platform,
target: string,
target: string
): esbuild.BuildOptions {
return {
...getConfig(),
@@ -111,10 +111,12 @@ export function getReleaseConfig(): Configuration {
},
mac: {
icon: "../assets/icon.icns",
target: {
target: "dmg",
arch: ["x64"],
},
target: [
{
target: "dmg",
arch: ["x64", "arm64"],
},
],
},
linux: {
category: "Utility",
@@ -126,17 +128,11 @@ export function getReleaseConfig(): Configuration {
},
},
nsis: {
oneClick: true,
oneClick: false,
allowElevation: true,
allowToChangeInstallationDirectory: false,
installerIcon: "",
uninstallerIcon: "",
installerHeaderIcon: "",
allowToChangeInstallationDirectory: true,
createDesktopShortcut: true,
createStartMenuShortcut: true,
shortcutName: "",
include: "",
script: "",
},
};
}
+1 -1
View File
@@ -84,7 +84,7 @@ async function pack() {
provider: "github",
repo: "mediago",
owner: "caorushizi",
releaseType: "release",
releaseType: "draft",
};
}
await builder.build({ config });
+2 -3
View File
@@ -34,12 +34,11 @@ export default class ElectronApp {
@inject(TYPES.ElectronDevtools)
private readonly devTools: ElectronDevtools,
@inject(TYPES.ElectronStore)
private readonly store: ElectronStore,
private readonly store: ElectronStore
) {}
private async serviceInit(): Promise<void> {
this.mainWindow.init();
this.webview.init();
}
private async vendorInit() {
@@ -94,7 +93,7 @@ export default class ElectronApp {
const videoIds = videos.map((video) => video.id);
await this.videoRepository.changeVideoStatus(
videoIds,
DownloadStatus.Failed,
DownloadStatus.Failed
);
}
}
@@ -24,6 +24,8 @@ import WebviewService from "../services/WebviewService.ts";
import FavoriteRepository from "../repository/FavoriteRepository.ts";
import VideoRepository from "../repository/VideoRepository.ts";
import ConversionRepository from "../repository/ConversionRepository.ts";
import { machineId } from "node-machine-id";
import { nanoid } from "nanoid";
@injectable()
export default class HomeController implements Controller {
@@ -146,6 +148,10 @@ export default class HomeController implements Controller {
if (key === "isMobile") {
this.webviewService.setUserAgent(val);
}
// privacy
if (key === "privacy") {
this.webviewService.setDefaultSession(val);
}
this.store.set(key, val);
}
@@ -271,4 +277,21 @@ export default class HomeController implements Controller {
}
return "";
}
@handle("get-machine-id")
async getMachineId() {
try {
const id = this.store.get("machineId");
if (id) return id;
const newId = await machineId();
this.store.set("machineId", newId);
return newId;
} catch (e) {
const id = this.store.get("machineId");
if (id) return id;
const newId = nanoid();
this.store.set("machineId", newId);
return newId;
}
}
}
@@ -20,7 +20,7 @@ export default class WebviewController implements Controller {
@inject(TYPES.ElectronStore)
private readonly store: ElectronStore,
@inject(TYPES.SniffingHelper)
private readonly sniffingHelper: SniffingHelper,
private readonly sniffingHelper: SniffingHelper
) {}
@handle("set-webview-bounds")
@@ -29,7 +29,7 @@ export default class WebviewController implements Controller {
}
@handle("webview-load-url")
async browserViewLoadUrl(e: IpcMainEvent, url?: string): Promise<void> {
async browserViewLoadUrl(e: IpcMainEvent, url: string): Promise<void> {
await this.webview.loadURL(url);
}
@@ -85,4 +85,9 @@ export default class WebviewController implements Controller {
async pluginReady() {
this.sniffingHelper.pluginReady();
}
@handle("clear-webview-cache")
async clearWebviewCache() {
return this.webview.clearCache();
}
}
+1
View File
@@ -36,6 +36,7 @@ export const workspace = resolve(appData, appName);
export const defaultScheme = "mediago";
export const PERSIST_MEDIAGO = "persist:mediago";
export const PERSIST_WEBVIEW = "persist:webview";
export const PRIVACY_WEBVIEW = "webview";
export const db = resolve(workspace, "app.db");
// bin path
+6
View File
@@ -52,6 +52,12 @@ declare interface AppStore {
showTerminal: boolean;
// 隐私模式
privacy: boolean;
// 机器id
machineId: string;
// 下载代理设置
downloadProxySwitch: boolean;
// 自动更新
autoUpgrade: boolean;
}
declare interface BrowserStore {
+8 -1
View File
@@ -60,7 +60,8 @@ const electronApi = {
): Promise<void> {
return ipcRenderer.invoke("set-app-store", key, val);
},
openDir(dir: string): Promise<void> {
async openDir(dir?: string): Promise<void> {
if (!dir) return;
return ipcRenderer.invoke("open-dir", dir);
},
addDownloadItem(video: Omit<DownloadItem, "id">): Promise<Video> {
@@ -169,6 +170,12 @@ const electronApi = {
deleteConversion(id: number): Promise<void> {
return ipcRenderer.invoke("delete-conversion", id);
},
getMachineId(): Promise<string> {
return ipcRenderer.invoke("get-machine-id");
},
clearWebviewCache(): Promise<void> {
return ipcRenderer.invoke("clear-webview-cache");
},
};
contextBridge.exposeInMainWorld(apiKey, electronApi);
+15 -8
View File
@@ -74,6 +74,9 @@ const processList: Schema[] = [
proxy: {
argsName: ["--custom-proxy"],
},
__common__: {
argsName: ["--no-log"],
},
},
consoleReg: {
percent: "([\\d.]+)%",
@@ -121,7 +124,7 @@ export default class DownloadService extends EventEmitter {
@inject(TYPES.VideoRepository)
private readonly videoRepository: VideoRepository,
@inject(TYPES.ElectronStore)
private readonly storeService: ElectronStore,
private readonly storeService: ElectronStore
) {
super();
@@ -148,7 +151,7 @@ export default class DownloadService extends EventEmitter {
try {
await this.videoRepository.changeVideoStatus(
task.id,
DownloadStatus.Downloading,
DownloadStatus.Downloading
);
this.emit("download-start", task.id);
@@ -174,8 +177,8 @@ export default class DownloadService extends EventEmitter {
callback,
};
const { proxy, useProxy } = this.storeService.store;
if (useProxy) {
const { proxy, downloadProxySwitch } = this.storeService.store;
if (downloadProxySwitch && proxy) {
params.proxy = proxy;
}
@@ -185,7 +188,7 @@ export default class DownloadService extends EventEmitter {
await this.videoRepository.changeVideoStatus(
task.id,
DownloadStatus.Success,
DownloadStatus.Success
);
this.emit("download-success", task.id);
} catch (err: any) {
@@ -194,7 +197,7 @@ export default class DownloadService extends EventEmitter {
// 下载暂停
await this.videoRepository.changeVideoStatus(
task.id,
DownloadStatus.Stopped,
DownloadStatus.Stopped
);
this.emit("download-stop", task.id);
} else {
@@ -202,7 +205,7 @@ export default class DownloadService extends EventEmitter {
// 下载失败
await this.videoRepository.changeVideoStatus(
task.id,
DownloadStatus.Failed,
DownloadStatus.Failed
);
this.emit("download-failed", task.id, err);
}
@@ -239,7 +242,7 @@ export default class DownloadService extends EventEmitter {
private _execa(
binPath: string,
args: string[],
params: DownloadOptions,
params: DownloadOptions
): Promise<void> {
const { abortSignal, onMessage, id } = params;
@@ -326,6 +329,10 @@ export default class DownloadService extends EventEmitter {
if (key === "proxy" && proxy) {
argsName && argsName.forEach((i) => spawnParams.push(i, proxy));
}
if (key === "__common__") {
argsName && spawnParams.push(...argsName);
}
}
const { consoleReg } = schema;
@@ -4,7 +4,11 @@ import { TYPES } from "../types.ts";
import ElectronLogger from "../vendor/ElectronLogger.ts";
import EventEmitter from "events";
import { OnSendHeadersListenerDetails, session } from "electron";
import { PERSIST_WEBVIEW, formatHeaders } from "../helper/index.ts";
import {
PERSIST_WEBVIEW,
PRIVACY_WEBVIEW,
formatHeaders,
} from "../helper/index.ts";
export interface SourceParams {
url: string;
@@ -61,6 +65,10 @@ export class SniffingHelper extends EventEmitter {
});
}
update(pageInfo: PageInfo) {
this.pageInfo = pageInfo;
}
reset(pageInfo: PageInfo) {
this.pageInfo = pageInfo;
this.ready = false;
@@ -85,8 +93,9 @@ export class SniffingHelper extends EventEmitter {
}
}
start() {
const viewSession = session.fromPartition(PERSIST_WEBVIEW);
start(privacy: boolean = false) {
const partition = privacy ? PRIVACY_WEBVIEW : PERSIST_WEBVIEW;
const viewSession = session.fromPartition(partition);
viewSession.webRequest.onSendHeaders(this.onSendHeaders);
}
+179 -93
View File
@@ -1,9 +1,10 @@
import { WebContentsView, session } from "electron";
import { Event, HandlerDetails, WebContentsView, session } from "electron";
import { inject, injectable } from "inversify";
import { TYPES } from "../types.ts";
import isDev from "electron-is-dev";
import {
PERSIST_WEBVIEW,
PRIVACY_WEBVIEW,
fetch,
mobileUA,
pcUA,
@@ -15,14 +16,15 @@ import ElectronStore from "../vendor/ElectronStore.ts";
import MainWindow from "../windows/MainWindow.ts";
import BrowserWindow from "../windows/BrowserWindow.ts";
import VideoRepository from "../repository/VideoRepository.ts";
import { SniffingHelper } from "./SniffingHelperService.ts";
import { SniffingHelper, SourceParams } from "./SniffingHelperService.ts";
import { resolve } from "path";
import { readFileSync } from "fs-extra";
@injectable()
export default class WebviewService {
public view: WebContentsView;
private view: WebContentsView | null = null;
private blocker?: ElectronBlocker;
private defaultSession: string;
constructor(
@inject(TYPES.MainWindow)
@@ -36,129 +38,182 @@ export default class WebviewService {
@inject(TYPES.VideoRepository)
private readonly videoRepository: VideoRepository,
@inject(TYPES.SniffingHelper)
private readonly sniffingHelper: SniffingHelper,
private readonly sniffingHelper: SniffingHelper
) {
// 初始化 blocker
this.initBlocker();
const { useProxy, proxy, privacy } = this.store.store;
this.sniffingHelper.start(privacy);
this.sniffingHelper.on("source", this.onSource);
this.setDefaultSession(privacy, true);
this.setProxy(useProxy, proxy);
}
async init(): Promise<void> {
this.view = new WebContentsView({
webPreferences: {
partition: PERSIST_WEBVIEW,
partition: this.defaultSession,
preload: resolve(__dirname, "./preload.js"),
},
});
this.view.setBackgroundColor("#fff");
this.webContents.setAudioMuted(true);
this.view.webContents.setAudioMuted(true);
const { useProxy, proxy, isMobile } = this.store.store;
this.setProxy(useProxy, proxy);
const { isMobile } = this.store.store;
this.setUserAgent(isMobile);
this.webContents.on("dom-ready", async () => {
const baseInfo = {
title: this.webContents.getTitle(),
url: this.webContents.getURL(),
};
this.sniffingHelper.reset(baseInfo);
this.window.webContents.send("webview-dom-ready", baseInfo);
this.view.webContents.on("dom-ready", this.onDomReady);
this.view.webContents.on("did-navigate", this.onDidNavigate);
this.view.webContents.on("did-fail-load", this.onDidFailLoad);
this.view.webContents.on("did-navigate-in-page", this.onDidNavigateInPage);
this.view.webContents.setWindowOpenHandler(this.onOpenNewWindow);
}
try {
if (isDev && process.env.DEBUG_PLUGINS === "true") {
const content =
'function addScript(src){const script=document.createElement("script");script.src=src;script.type="module";document.body.appendChild(script)}addScript("http://localhost:8080/src/main.ts");';
await this.webContents.executeJavaScript(content);
} else {
const content = readFileSync(pluginPath, "utf-8");
await this.webContents.executeJavaScript(content);
}
} catch (err) {
// empty
}
});
onDomReady = () => {
if (!this.view) return;
const pageInfo = this.getPageInfo();
this.sniffingHelper.update(pageInfo);
this.window.webContents.send("webview-dom-ready", pageInfo);
};
// 兼容网站在当前页面中打开
this.webContents.setWindowOpenHandler(({ url }) => {
this.loadURL(url, true);
onDidNavigate = async () => {
if (!this.view) return;
const pageInfo = this.getPageInfo();
this.sniffingHelper.reset(pageInfo);
this.window.webContents.send("webview-did-navigate", pageInfo);
return { action: "deny" };
});
this.sniffingHelper.start();
this.sniffingHelper.on("source", async (item) => {
// 这里需要判断是否使用浏览器插件
const useExtension = this.store.get("useExtension");
if (useExtension) {
this.webContents.send("webview-link-message", item);
try {
if (isDev && process.env.DEBUG_PLUGINS === "true") {
const content =
'function addScript(src){const script=document.createElement("script");script.src=src;script.type="module";document.body.appendChild(script)}addScript("http://localhost:8080/src/main.ts");';
await this.view.webContents.executeJavaScript(content);
} else {
const video = await this.videoRepository.findVideoByName(item.name);
if (video) {
item.name = `${item.name}-${Date.now()}`;
}
const res = await this.videoRepository.addVideo(item);
const mainWebContents = this.mainWindow.window?.webContents;
if (!mainWebContents) return;
// 这里向页面发送消息,通知页面更新
mainWebContents.send("download-item-notifier", res);
const content = readFileSync(pluginPath, "utf-8");
await this.view.webContents.executeJavaScript(content);
}
});
} catch (err) {
// empty
}
};
onDidFailLoad = (e: Event, code: number, desc: string) => {
this.window.webContents.send("webview-fail-load", { code, desc });
};
onDidNavigateInPage = () => {
if (!this.view) return;
const pageInfo = this.getPageInfo();
this.sniffingHelper.update(pageInfo);
this.window.webContents.send("webview-did-navigate-in-page", pageInfo);
};
onOpenNewWindow = ({ url }: HandlerDetails) => {
this.loadURL(url);
return { action: "deny" } as { action: "deny" };
};
onSource = async (item: SourceParams) => {
if (!this.view) return;
// 这里需要判断是否使用浏览器插件
const useExtension = this.store.get("useExtension");
if (useExtension) {
this.view.webContents.send("webview-link-message", item);
} else {
const video = await this.videoRepository.findVideoByName(item.name);
if (video) {
item.name = `${item.name}-${Date.now()}`;
}
const res = await this.videoRepository.addVideo(item);
const mainWebContents = this.mainWindow.window?.webContents;
if (!mainWebContents) return;
// 这里向页面发送消息,通知页面更新
mainWebContents.send("download-item-notifier", res);
}
};
getPageInfo() {
if (!this.view) {
throw new Error("未找到 view");
}
return {
title: this.view.webContents.getTitle(),
url: this.view.webContents.getURL(),
};
}
getBounds(): Electron.Rectangle {
if (!this.view) {
throw new Error("未找到 view");
}
return this.view.getBounds();
}
setBounds(bounds: Electron.Rectangle): void {
if (!this.view) return;
this.view.setBounds(bounds);
}
setBackgroundColor(color: string): void {
if (!this.view) return;
this.view.setBackgroundColor(color);
}
show() {
if (!this.view) return;
this.window.contentView.addChildView(this.view);
isDev && this.webContents.openDevTools();
}
hide() {
if (!this.view) return;
this.window.contentView.removeChildView(this.view);
isDev && this.webContents.closeDevTools();
}
setBounds(bounds: Electron.Rectangle): void {
this.view.setBounds(bounds);
}
async loadURL(url?: string, isNewWindow?: boolean) {
const canGoBack = this.webContents.canGoBack();
try {
this.webContents.stop();
await this.webContents.loadURL(url || "");
} catch (err: unknown) {
this.logger.error("加载 url 时出现错误: ", err);
throw err;
} finally {
if (!canGoBack && !isNewWindow) {
this.webContents.clearHistory();
}
loadURL(url: string) {
// 开始加载 url
if (!this.view) {
this.init();
}
if (!this.view) return;
// 1. 停止当前导航
this.view.webContents.stop();
// 2. 加载新的 url
this.view.webContents.loadURL(url);
}
async goBack() {
if (this.webContents.canGoBack()) {
this.webContents.goBack();
if (!this.view) {
throw new Error("未找到 view");
}
const { webContents } = this.view;
if (webContents.canGoBack()) {
webContents.goBack();
return true;
} else {
this.destroyView();
return false;
}
}
async reload() {
this.webContents.reload();
if (!this.view) {
throw new Error("未找到 view");
}
this.view.webContents.reload();
}
async goHome() {
this.webContents.stop();
this.webContents.clearHistory();
if (!this.view) {
throw new Error("未找到 view");
}
this.view.webContents.stop();
this.view.webContents.clearHistory();
this.destroyView();
}
get window() {
@@ -168,12 +223,12 @@ export default class WebviewService {
}
private get session() {
return session.fromPartition(PERSIST_WEBVIEW);
return session.fromPartition(this.defaultSession);
}
private enableProxy(proxy: string) {
if (!proxy) {
this.logger.error("[proxy] 代理地址不能为空");
this.logger.error("[Proxy] 代理地址不能为空");
return;
}
@@ -183,12 +238,12 @@ export default class WebviewService {
}
this.session.setProxy({ proxyRules: proxy });
this.logger.info(`[proxy] 代理开启成功,代理地址为${proxy}`);
this.logger.info(`[Proxy] 代理开启${proxy}`);
}
private disableProxy() {
this.session.setProxy({ proxyRules: "" });
this.logger.info("[proxy] 代理关闭成功");
this.logger.info("[Proxy] 代理关闭");
}
setProxy(useProxy: boolean, proxy: string): void {
@@ -216,47 +271,78 @@ export default class WebviewService {
private enableBlocking() {
if (!this.blocker) {
this.logger.error("开启 blocker 失败未初始化");
this.logger.error("[AdBlocker] 开启失败未初始化");
return;
}
this.blocker.enableBlockingInSession(this.session);
this.logger.info("开启 blocker 成功");
this.logger.info("[AdBlocker] 开启");
}
private disableBlocking() {
if (!this.blocker) {
this.logger.error("关闭 blocker 失败未初始化");
this.logger.error("[AdBlocker] 关闭失败未初始化");
return;
}
if (!this.blocker.isBlockingEnabled(this.session)) {
return;
}
this.blocker.disableBlockingInSession(this.session);
this.logger.info("关闭 blocker 成功");
this.logger.info("[AdBlocker] 关闭");
}
setUserAgent(isMobile?: boolean) {
if (!this.view) return;
if (isMobile) {
this.webContents.setUserAgent(mobileUA);
this.view.webContents.setUserAgent(mobileUA);
} else {
this.webContents.setUserAgent(pcUA);
this.view.webContents.setUserAgent(pcUA);
}
this.logger.info("设置 user-agent 成功", isMobile);
}
get debugger() {
return this.webContents.debugger;
}
get webContents() {
return this.view.webContents;
this.logger.info(`[UA] 设置为${isMobile ? "移动端" : " pc 端"}`);
}
captureView(): Promise<Electron.NativeImage> {
if (!this.view) {
throw new Error("未找到 view");
}
return this.view.webContents.capturePage();
}
sendToWindow(channel: string, ...args: unknown[]) {
this.window.webContents.send(channel, ...args);
}
destroyView() {
if (this.view && this.window) {
this.view.webContents.close();
this.window.contentView.removeChildView(this.view);
}
// FIXME: 为了避免内存泄漏,这里需要销毁 view
this.view = null;
}
async clearCache() {
await this.session.clearCache();
await this.session.clearStorageData();
}
setDefaultSession(isPrivacy = false, init = false) {
this.logger.info(`[Session] ${isPrivacy ? "隐私" : "正常"}模式`);
if (isPrivacy) {
this.defaultSession = PRIVACY_WEBVIEW;
} else {
this.defaultSession = PERSIST_WEBVIEW;
}
if (this.view) {
const { useProxy, proxy } = this.store.store;
this.destroyView();
this.init();
this.setProxy(useProxy, proxy);
this.sniffingHelper.start(isPrivacy);
}
if (!init) {
this.window.webContents.send("change-privacy");
}
}
}
+3
View File
@@ -28,6 +28,9 @@ export default class StoreService extends Store<AppStore> implements Vendor {
language: AppLanguage.System,
showTerminal: false,
privacy: false,
machineId: "",
downloadProxySwitch: false,
autoUpgrade: true,
},
});
}
+8 -1
View File
@@ -4,16 +4,23 @@ import { TYPES } from "../types.ts";
import isDev from "electron-is-dev";
import ElectronLogger from "./ElectronLogger.ts";
import { Vendor } from "../core/vendor.ts";
import ElectronStore from "electron-store";
@injectable()
export default class UpdateService implements Vendor {
constructor(
@inject(TYPES.ElectronLogger) private readonly logger: ElectronLogger,
@inject(TYPES.ElectronLogger)
private readonly logger: ElectronLogger,
@inject(TYPES.ElectronStore)
private readonly store: ElectronStore
) {}
async init() {
if (isDev) return;
const { autoUpgrade } = this.store.store;
if (!autoUpgrade) return;
try {
autoUpdater.disableWebInstaller = true;
autoUpdater.logger = this.logger.logger;
@@ -115,7 +115,7 @@ export class FloatButton extends LitElement {
};
receiveMessage = (_: unknown, data: SourceData) => {
this.data = [...this.data, data];
this.data = [...this.data, { ...data, name: document.title }];
};
handleMouseStart = (event: MouseEvent) => {
+4 -1
View File
@@ -1,3 +1,6 @@
{
"extends": ["stylelint-config-standard", "stylelint-config-standard-scss"]
"extends": ["stylelint-config-standard", "stylelint-config-standard-scss"],
"rules": {
"no-descending-specificity": null
}
}
+15 -2
View File
@@ -2,13 +2,15 @@ import { ConfigProvider, theme } from "antd";
import React, { FC, Suspense, lazy, useEffect } from "react";
import { useDispatch } from "react-redux";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { setAppStore, increase } from "./store";
import { setAppStore, increase, setBrowserStore, PageMode } from "./store";
import "dayjs/locale/zh-cn";
import zhCN from "antd/locale/zh_CN";
import "./App.scss";
import useElectron from "./hooks/electron";
import Loading from "./components/Loading";
import { DownloadFilter } from "./types";
import { tdApp } from "./utils";
import { useAsyncEffect } from "ahooks";
const AppLayout = lazy(() => import("./layout/App"));
const HomePage = lazy(() => import("./pages/HomePage"));
@@ -23,7 +25,7 @@ function getAlgorithm(appTheme: "dark" | "light") {
const App: FC = () => {
const dispatch = useDispatch();
const [appTheme, setAppTheme] = React.useState<"dark" | "light">("light");
const { addIpcListener, removeIpcListener } = useElectron();
const { addIpcListener, removeIpcListener, getMachineId } = useElectron();
const themeChange = (event: MediaQueryListEvent) => {
if (event.matches) {
@@ -42,16 +44,27 @@ const App: FC = () => {
dispatch(increase());
};
const onChangePrivacy = () => {
dispatch(setBrowserStore({ url: "", title: "", mode: PageMode.Default }));
};
useEffect(() => {
addIpcListener("store-change", onAppStoreChange);
addIpcListener("download-item-notifier", onReceiveDownloadItem);
addIpcListener("change-privacy", onChangePrivacy);
return () => {
removeIpcListener("store-change", onAppStoreChange);
removeIpcListener("download-item-notifier", onReceiveDownloadItem);
removeIpcListener("change-privacy", onChangePrivacy);
};
}, []);
useAsyncEffect(async () => {
const deviceId = await getMachineId();
tdApp.onEvent("页面加载", { deviceId });
}, []);
useEffect(() => {
const isDarkTheme = matchMedia("(prefers-color-scheme: dark)");
isDarkTheme.addEventListener("change", themeChange);
+18 -2
View File
@@ -75,7 +75,8 @@ i18n
pleaseSelectLanguage: "Please select language",
downloadPrompt: "Download Prompt",
browserSetting: "Browser Setting",
pleaseEnterProxy: "Please enter proxy",
pleaseEnterProxy:
"You need to enter the full address, for example: http://127.0.0.1:8000",
proxySetting: "Proxy Setting",
pleaseEnterProxyFirst: "Please enter proxy first",
blockAds: "Block Ads",
@@ -84,11 +85,15 @@ i18n
deleteSegments: "Delete segments after download",
moreAction: "More Action",
configDir: "Config Directory",
clearCache: "Clear Cache",
clearCacheSuccess: "Clear Cache Success",
clearCacheFailed: "Clear Cache Failed",
binPath: "Executable Path",
localDir: "Local Directory",
currentVersion: "Current Version",
selectFolder: "Select Folder",
proxySwitch: "Proxy Switch",
downloadProxySwitch: "Download Proxy Switch",
proxyDescription:
"The proxy will take effect on the built-in browser and download",
useImmersiveSniffing: "Use immersive sniffing",
@@ -121,6 +126,8 @@ Referer: http://www.example.com`,
close: "Close",
exportLog: "Export Log",
showTerminal: "Show Console",
autoUpgrade: "Auto Upgrade",
autoUpgradeTooltip: "Take effect next time you start",
consoleOutput: "Console",
downloadNow: "Download",
addToDownloadList: "Confirm",
@@ -131,6 +138,8 @@ Referer: http://www.example.com`,
canUseMouseWheelToAdjust: "Can use mouse wheel to adjust",
openBrowser: "Open Browser",
privacy: "Privacy Mode",
privacyTooltip:
"After turning on the switch, the browser will not save any data",
converter: "Converter",
addFile: "Add File",
},
@@ -193,7 +202,7 @@ Referer: http://www.example.com`,
pleaseSelectLanguage: "请选择显示语言",
downloadPrompt: "下载完成提示",
browserSetting: "浏览器设置",
pleaseEnterProxy: "请填写代理地址",
pleaseEnterProxy: "需要填写完整的地址,例如:http://127.0.0.1:8000",
proxySetting: "代理设置",
pleaseEnterProxyFirst: "请先输入代理地址",
blockAds: "开启广告过滤",
@@ -202,11 +211,15 @@ Referer: http://www.example.com`,
deleteSegments: "下载完成删除分片",
moreAction: "更多操作",
configDir: "配置文件目录",
clearCache: "清除缓存",
clearCacheSuccess: "清除缓存成功",
clearCacheFailed: "清除缓存失败",
binPath: "可执行程序目录",
localDir: "本地存储目录",
currentVersion: "当前版本",
selectFolder: "选择文件夹",
proxySwitch: "代理开关",
downloadProxySwitch: "下载代理开关",
proxyDescription: "该代理会对软件自带浏览器以及下载时生效",
useImmersiveSniffing: "使用沉浸式嗅探",
immersiveSniffingDescription:
@@ -242,6 +255,8 @@ Referer: http://www.example.com`,
close: "关闭",
exportLog: "导出日志",
showTerminal: "显示控制台",
autoUpgrade: "自动更新",
autoUpgradeTooltip: "下次启动时生效",
consoleOutput: "控制台",
downloadNow: "下载",
addToDownloadList: "确认",
@@ -252,6 +267,7 @@ Referer: http://www.example.com`,
canUseMouseWheelToAdjust: "可以使用鼠标滚轮调整",
openBrowser: "打开浏览器",
privacy: "隐私模式",
privacyTooltip: "打开开关后浏览器将不保存任何数据",
converter: "格式转换",
addFile: "添加文件",
},
+33 -28
View File
@@ -1,10 +1,11 @@
import React, { FC, useState } from "react";
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom";
import { Badge, Button, Layout, Menu, MenuProps, Flex } from "antd";
import { Badge, Button, Layout, Menu, MenuProps, Flex, Typography } from "antd";
import {
CheckCircleOutlined,
DownloadOutlined,
ExportOutlined,
EyeInvisibleOutlined,
ProfileOutlined,
QuestionCircleOutlined,
SettingOutlined,
@@ -21,8 +22,10 @@ import {
import { useAsyncEffect } from "ahooks";
import { useTranslation } from "react-i18next";
import { useStyles } from "./style";
import classNames from "classnames";
const { Footer, Sider, Content } = Layout;
const { Text } = Typography;
type MenuItem = Required<MenuProps>["items"][number];
@@ -45,7 +48,6 @@ const App: FC = () => {
const location = useLocation();
const navigate = useNavigate();
const dispatch = useDispatch();
const [showExport, setShowExport] = useState(false);
const count = useSelector(selectCount);
const appStore = useSelector(selectAppStore);
const { styles } = useStyles();
@@ -89,38 +91,33 @@ const App: FC = () => {
},
{
label: (
<Link to="/source" className={styles.linkItem}>
<Link
to="/source"
className={classNames([styles.linkItem, styles.extract])}
>
<ProfileOutlined />
<span>{t("materialExtraction")}</span>
{showExport && (
<Button
title={t("openInNewWindow")}
type="text"
style={{ marginLeft: "auto" }}
icon={<ExportOutlined />}
onClick={async (e) => {
e.stopPropagation();
e.preventDefault();
<Button
title={t("openInNewWindow")}
type="text"
className={styles.hoverButton}
icon={<ExportOutlined />}
onClick={async (e) => {
e.stopPropagation();
e.preventDefault();
dispatch(setAppStore({ openInNewWindow: true }));
if (location.pathname === "/source") {
navigate("/");
}
// FIXME: 有可能 webview 还没有完全隐藏
await ipcSetAppStore("openInNewWindow", true);
await showBrowserWindow();
}}
/>
)}
dispatch(setAppStore({ openInNewWindow: true }));
if (location.pathname === "/source") {
navigate("/");
}
// FIXME: 有可能 webview 还没有完全隐藏
await ipcSetAppStore("openInNewWindow", true);
await showBrowserWindow();
}}
/>
</Link>
),
key: "source",
onMouseEnter: () => {
setShowExport(true);
},
onMouseLeave: () => {
setShowExport(false);
},
},
{
label: (
@@ -169,6 +166,14 @@ const App: FC = () => {
<Outlet />
</Content>
<Footer className={styles.containerFooter}>
<Text>
{appStore.privacy && (
<>
<EyeInvisibleOutlined />
</>
)}
</Text>
<Button
type={"link"}
onClick={openHelpUrl}
+40 -24
View File
@@ -1,26 +1,42 @@
import { createStyles } from "antd-style";
export const useStyles = createStyles(({ css, token }) => ({
container: css`
height: 100vh;
width: 100vw;
overflow: hidden;
`,
containerSider: css`
border-right: ${token.colorBorderSecondary} solid 1px;
`,
containerInner: css`
height: 100%;
`,
linkItem: css`
display: flex;
flex-direction: row;
align-items: center;
`,
containerFooter: css`
padding: 5px;
background: ${token.colorBgContainer};
text-align: right;
border-top: ${token.colorBorderSecondary} solid 1px;
`,
}));
export const useStyles = createStyles(({ css, token, cx }) => {
const hoverButton = cx(css`
margin-left: auto;
display: none;
`);
return {
container: css`
height: 100vh;
width: 100vw;
overflow: hidden;
`,
containerSider: css`
border-right: ${token.colorBorderSecondary} solid 1px;
`,
containerInner: css`
height: 100%;
`,
linkItem: css`
display: flex;
flex-direction: row;
align-items: center;
`,
extract: css`
&:hover {
.${hoverButton} {
display: block;
}
}
`,
containerFooter: css`
padding: 5px 5px 5px 15px;
background: ${token.colorBgContainer};
text-align: right;
border-top: ${token.colorBorderSecondary} solid 1px;
justify-content: space-between;
display: flex;
`,
hoverButton,
};
});
+2 -3
View File
@@ -5,18 +5,17 @@ import { Provider } from "react-redux";
import store from "./store";
import dayjs from "dayjs";
import "dayjs/locale/zh-cn";
import { tdApp, initClarity } from "./utils";
import { tdApp } from "./utils";
import App from "./App";
import "./i18n";
dayjs.locale("zh-cn");
tdApp.init();
initClarity();
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
</StrictMode>
);
@@ -41,7 +41,7 @@ const Converter = () => {
);
const onClickConvertToAudio = async (item: Conversion) => {
const nextState = produce(converting, (draft) => {
const nextState = produce((draft) => {
draft[item.id] = true;
});
setConverting(nextState);
@@ -51,7 +51,7 @@ const Converter = () => {
} catch (e: any) {
messageApi.error(e.message);
} finally {
const nextState = produce(converting, (draft) => {
const nextState = produce((draft) => {
draft[item.id] = false;
});
setConverting(nextState);
@@ -78,7 +78,7 @@ const HomePage: FC<Props> = ({ filter = DownloadFilter.list }) => {
});
const onDownloadProgress = (e: unknown, currProgress: DownloadProgress) => {
const nextState = produce(progress, (draft) => {
const nextState = produce((draft) => {
draft[currProgress.id] = currProgress;
});
setProgress(nextState);
@@ -10,7 +10,11 @@ import {
} from "@ant-design/pro-components";
import "./index.scss";
import { Button, FormInstance, message, Space, Tooltip } from "antd";
import { FolderOpenOutlined, QuestionCircleOutlined } from "@ant-design/icons";
import {
ClearOutlined,
FolderOpenOutlined,
QuestionCircleOutlined,
} from "@ant-design/icons";
import { selectAppStore, setAppStore } from "../../store";
import { useDispatch, useSelector } from "react-redux";
import useElectron from "../../hooks/electron";
@@ -26,6 +30,7 @@ const SettingPage: React.FC = () => {
setAppStore: ipcSetAppStore,
getEnvPath,
openDir,
clearWebviewCache,
} = useElectron();
const dispatch = useDispatch();
const { t } = useTranslation();
@@ -125,7 +130,17 @@ const SettingPage: React.FC = () => {
<ProFormSwitch label={t("openInNewWindow")} name="openInNewWindow" />
<ProFormSwitch label={t("downloadPrompt")} name="promptTone" />
<ProFormSwitch label={t("showTerminal")} name="showTerminal" />
{/* <ProFormSwitch label={t("privacy")} name="privacy" /> */}
<ProFormSwitch
label={renderTooltipLabel(
t("autoUpgrade"),
t("autoUpgradeTooltip")
)}
name="autoUpgrade"
/>
<ProFormSwitch
label={renderTooltipLabel(t("privacy"), t("privacyTooltip"))}
name="privacy"
/>
</ProFormGroup>
<ProFormGroup title={t("browserSetting")} direction={"vertical"}>
<ProFormText
@@ -136,17 +151,16 @@ const SettingPage: React.FC = () => {
/>
<ProFormSwitch
name="useProxy"
label={renderTooltipLabel(t("proxySwitch"), t("proxyDescription"))}
label={t("proxySwitch")}
rules={[
({ getFieldValue, setFieldValue }) => ({
validator() {
if (getFieldValue("proxy") === "") {
setFieldValue("useProxy", false);
{
validator(rules, value) {
if (value && formRef.current.getFieldValue("proxy") === "") {
return Promise.reject(t("pleaseEnterProxyFirst"));
}
return Promise.resolve();
},
}),
},
]}
/>
<ProFormSwitch label={t("blockAds")} name="blockAds" />
@@ -154,17 +168,48 @@ const SettingPage: React.FC = () => {
<ProFormSwitch
label={renderTooltipLabel(
t("useImmersiveSniffing"),
t("immersiveSniffingDescription"),
t("immersiveSniffingDescription")
)}
name="useExtension"
/>
<ProFormText label={t("moreAction")}>
<Space>
<Button
onClick={async () => {
try {
await clearWebviewCache();
messageApi.success(t("clearCacheSuccess"));
} catch (err) {
messageApi.error(t("clearCacheFailed"));
}
}}
icon={<ClearOutlined />}
>
{t("clearCache")}
</Button>
</Space>
</ProFormText>
</ProFormGroup>
<ProFormGroup title={t("downloadSetting")} direction={"vertical"}>
<ProFormSwitch
name="downloadProxySwitch"
label={t("downloadProxySwitch")}
rules={[
{
validator(rules, value) {
if (value && formRef.current.getFieldValue("proxy") === "") {
return Promise.reject(t("pleaseEnterProxyFirst"));
}
return Promise.resolve();
},
},
]}
/>
<ProFormSwitch label={t("deleteSegments")} name="deleteSegments" />
<ProFormDigit
label={renderTooltipLabel(
t("maxRunner"),
t("maxRunnerDescription"),
t("maxRunnerDescription")
)}
name="maxRunner"
min={1}
@@ -174,13 +219,13 @@ const SettingPage: React.FC = () => {
<ProFormText label={t("moreAction")}>
<Space>
<Button
onClick={() => envPath?.workspace && openDir(envPath.workspace)}
onClick={() => openDir(envPath.workspace)}
icon={<FolderOpenOutlined />}
>
{t("configDir")}
</Button>
<Button
onClick={() => envPath?.binPath && openDir(envPath.binPath)}
onClick={() => openDir(envPath.binPath)}
icon={<FolderOpenOutlined />}
>
{t("binPath")}
@@ -105,9 +105,14 @@
align-items: center;
justify-content: center;
border-radius: 5px;
transition: background 0.3s ease;
&:hover {
background: #f2f6fc;
.favorite-remove-btn {
display: block;
}
}
@media (prefers-color-scheme: dark) {
@@ -116,6 +121,14 @@
}
}
.favorite-remove-btn {
position: absolute;
top: 2px;
right: -2px;
color: gray;
display: none;
}
.card-text {
width: 100%;
margin-top: 6px;
@@ -24,7 +24,7 @@ import {
Space,
Spin,
} from "antd";
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import PageContainer from "../../components/PageContainer";
import useElectron from "../../hooks/electron";
@@ -41,7 +41,6 @@ import {
import WebView from "../../components/WebView";
import { selectAppStore } from "../../store";
import { useTranslation } from "react-i18next";
import { nanoid } from "nanoid";
import DownloadForm, { DownloadFormRef } from "../../components/DownloadForm";
interface SourceExtractProps {
@@ -72,56 +71,36 @@ const SourceExtract: React.FC<SourceExtractProps> = ({ page = false }) => {
const { data: favoriteList = [], refresh } = useRequest(getFavorites);
const [favoriteAddForm] = Form.useForm<Favorite>();
const [messageApi, contextHolder] = message.useMessage();
const [hoverId, setHoverId] = useState<number>(-1);
const store = useSelector(selectBrowserStore);
const appStore = useSelector(selectAppStore);
const [modalShow, setModalShow] = useState(false);
const [placeHolder, setPlaceHolder] = useState<string>("");
const sessionId = useRef("");
const downloadForm = useRef<DownloadFormRef>(null);
const originTitle = useRef(document.title);
const curIsFavorite = favoriteList.find((item) => item.url === store.url);
const curIsFavorite = useMemo(() => {
return favoriteList.find((item) => item.url === store.url);
}, [favoriteList, store.url]);
useAsyncEffect(async () => {
const store = await ipcGetAppStore();
dispatch(setAppStore(store));
}, []);
const loadUrl = async (url: string) => {
const id = nanoid();
sessionId.current = id;
try {
dispatch(
setBrowserStore({
url: "",
mode: PageMode.Browser,
status: BrowserStatus.Loading,
})
);
await webviewLoadURL(url);
if (sessionId.current === id) {
dispatch(
setBrowserStore({
url: url,
status: BrowserStatus.Loaded,
})
);
}
} catch (err) {
if (sessionId.current === id) {
dispatch(
setBrowserStore({
status: BrowserStatus.Failed,
errMsg: (err as any).message,
})
);
}
}
const loadUrl = (url: string) => {
dispatch(
setBrowserStore({
url,
mode: PageMode.Browser,
status: BrowserStatus.Loading,
})
);
webviewLoadURL(url);
};
const goto = async () => {
const goto = () => {
const link = generateUrl(store.url);
await loadUrl(link);
loadUrl(link);
};
const onInputKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
@@ -155,14 +134,9 @@ const SourceExtract: React.FC<SourceExtractProps> = ({ page = false }) => {
const onClickGoBack = async () => {
const back = await webviewGoBack();
if (!back) {
dispatch(
setBrowserStore({
url: "",
mode: PageMode.Default,
})
);
document.title = originTitle.current;
dispatch(setBrowserStore({ url: "", title: "", mode: PageMode.Default }));
}
};
@@ -171,6 +145,7 @@ const SourceExtract: React.FC<SourceExtractProps> = ({ page = false }) => {
dispatch(
setBrowserStore({
url: "",
title: "",
mode: PageMode.Default,
})
);
@@ -184,20 +159,8 @@ const SourceExtract: React.FC<SourceExtractProps> = ({ page = false }) => {
await goto();
};
const onClickLoadItem = async (item: Favorite) => {
await loadUrl(item.url);
};
const onDomReady = (e: unknown, data: UrlDetail) => {
if (data.url) {
document.title = data.title;
dispatch(
setBrowserStore({
url: data.url,
title: data.title,
})
);
}
const onClickLoadItem = (item: Favorite) => {
loadUrl(item.url);
};
const onFavoriteEvent = async (
@@ -251,17 +214,56 @@ const SourceExtract: React.FC<SourceExtractProps> = ({ page = false }) => {
};
useEffect(() => {
const prevTitle = document.title;
addIpcListener("webview-dom-ready", onDomReady);
addIpcListener("webview-fail-load", onFailLoad);
addIpcListener("webview-did-navigate", onDidNavigate);
addIpcListener("webview-did-navigate-in-page", onDidNavigateInPage);
addIpcListener("favorite-item-event", onFavoriteEvent);
addIpcListener("show-download-dialog", onShowDownloadDialog);
return () => {
document.title = prevTitle;
removeIpcListener("webview-dom-ready", onDomReady);
removeIpcListener("webview-fail-load", onFailLoad);
removeIpcListener("webview-did-navigate", onDidNavigate);
removeIpcListener("webview-did-navigate-in-page", onDidNavigateInPage);
removeIpcListener("favorite-item-event", onFavoriteEvent);
removeIpcListener("show-download-dialog", onShowDownloadDialog);
};
}, [store.status]);
const setPageInfo = ({ url, title }: UrlDetail) => {
document.title = title;
dispatch(setBrowserStore({ url, title }));
};
const onDomReady = (e: unknown, info: UrlDetail) => {
setPageInfo(info);
};
const onFailLoad = (e: unknown, data: { code: number; desc: string }) => {
dispatch(
setBrowserStore({
status: BrowserStatus.Failed,
errCode: data.code,
errMsg: data.desc,
})
);
};
const onDidNavigate = (e: unknown, info: UrlDetail) => {
setPageInfo(info);
dispatch(setBrowserStore({ status: BrowserStatus.Loaded }));
};
const onDidNavigateInPage = (e: unknown, info: UrlDetail) => {
setPageInfo(info);
};
useEffect(() => {
document.title = store.title || document.title;
return () => {
document.title = originTitle.current;
};
}, []);
// 合并到主页
@@ -385,7 +387,9 @@ const SourceExtract: React.FC<SourceExtractProps> = ({ page = false }) => {
);
} else if (store.status === BrowserStatus.Failed) {
content = (
<Empty description={store.errMsg || t("loadFailed")}>
<Empty
description={`${store.errMsg || t("loadFailed")} (${store.errCode})`}
>
<Space>
<Button type="primary" onClick={onClickGoHome}>
{t("backToHome")}
@@ -479,35 +483,19 @@ const SourceExtract: React.FC<SourceExtractProps> = ({ page = false }) => {
onFavoriteItemContextMenu(item.id);
}}
>
<div
className="list-tem-card"
onClick={() => onClickLoadItem(item)}
onMouseLeave={() => {
setHoverId(-1);
}}
onMouseOver={() => {
setHoverId(item.id);
}}
>
{hoverId === item.id && (
<Button
style={{
position: "absolute",
right: -1,
top: 2,
color: "gray",
}}
type="link"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeFavorite(item.id);
refresh();
}}
>
<CloseOutlined />
</Button>
)}
<div className="list-tem-card" onClick={() => onClickLoadItem(item)}>
<Button
className="favorite-remove-btn"
type="link"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeFavorite(item.id);
refresh();
}}
>
<CloseOutlined />
</Button>
{item.icon ? (
<Avatar shape="square" src={item.icon} icon={<LinkOutlined />} />
) : (
+9
View File
@@ -72,6 +72,14 @@ declare interface AppStore {
language?: AppLanguage;
// 是否显示终端
showTerminal?: boolean;
// 隐私模式
privacy?: boolean;
// 机器id
machineId?: string;
// 下载代理设置
downloadProxySwitch?: boolean;
// 自动更新
autoUpgrade?: boolean;
}
declare interface BrowserStore {
@@ -79,6 +87,7 @@ declare interface BrowserStore {
url: string;
title: string;
status: BrowserStatus;
errCode?: number;
errMsg?: string;
}
+4
View File
@@ -16,6 +16,10 @@ const initialState: AppStore = {
maxRunner: 2,
language: AppLanguage.System,
showTerminal: false,
privacy: false,
machineId: "",
downloadProxySwitch: false,
autoUpgrade: true,
};
export const appSlice = createSlice({
-14
View File
@@ -1,14 +0,0 @@
/* eslint-disable */
// @ts-nocheck
// prettier-ignore
export function initClarity() {
try {
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", import.meta.env.APP_CLARITY_APPID);
} catch (e) {
// empty
}
}
+1 -2
View File
@@ -3,7 +3,6 @@ import { isUrl } from "./url";
export { http } from "./http";
export { tdApp } from "./tdapp";
export { initClarity } from "./clarity";
export const requestImage = (url: string, timeout = 1000): Promise<void> => {
return new Promise((resolve, reject) => {
@@ -42,7 +41,7 @@ export const generateUrl = (url: string) => {
result = `http://${url}`;
}
if (!isUrl(result)) {
result = `https://baidu.com/s?word=${url}`;
result = `https://www.baidu.com/s?word=${url}`;
}
return result;
};
+1 -1
View File
@@ -17,7 +17,7 @@ class TDEvent {
headElement.appendChild(script);
}
private onEvent(eventId: string, mapKv: Record<string, string> = {}) {
onEvent(eventId: string, mapKv: Record<string, string> = {}) {
try {
window.TDAPP?.onEvent(eventId, "", mapKv);
} catch (e) {
+1 -2
View File
@@ -8,10 +8,9 @@ declare global {
onEvent: (
eventId: string,
label: "",
mapKv: Record<string, string>,
mapKv: Record<string, string>
) => void;
};
clarity?: any;
}
}
+20 -9
View File
@@ -74,10 +74,10 @@ importers:
packages/main:
dependencies:
'@cliqz/adblocker-electron':
specifier: ^1.27.3
specifier: 1.27.3
version: 1.27.3(electron@30.0.8)
'@cliqz/adblocker-electron-preload':
specifier: ^1.27.3
specifier: 1.27.3
version: 1.27.3(electron@30.0.8)
axios:
specifier: ^1.7.2
@@ -121,9 +121,15 @@ importers:
mime-types:
specifier: ^2.1.35
version: 2.1.35
nanoid:
specifier: ^5.0.7
version: 5.0.7
node-fetch:
specifier: ^3.3.2
version: 3.3.2
node-machine-id:
specifier: ^1.1.12
version: 1.1.12
node-pty:
specifier: ^1.0.0
version: 1.0.0
@@ -4206,6 +4212,9 @@ packages:
engines: {node: ^12.13 || ^14.13 || >=16}
hasBin: true
node-machine-id@1.1.12:
resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==}
node-pty@1.0.0:
resolution: {integrity: sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==}
@@ -7813,7 +7822,7 @@ snapshots:
app-builder-bin@4.0.0: {}
app-builder-lib@24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
app-builder-lib@24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
dependencies:
'@develar/schema-utils': 2.6.5
'@electron/notarize': 2.2.1
@@ -7827,7 +7836,7 @@ snapshots:
builder-util-runtime: 9.2.4
chromium-pickle-js: 0.2.0
debug: 4.3.4
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3)
ejs: 3.1.10
electron-builder-squirrel-windows: 24.13.3(dmg-builder@24.13.3)
electron-publish: 24.13.1
@@ -8619,9 +8628,9 @@ snapshots:
dependencies:
path-type: 4.0.0
dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3):
dependencies:
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
builder-util: 24.13.1
builder-util-runtime: 9.2.4
fs-extra: 10.1.0
@@ -8694,7 +8703,7 @@ snapshots:
electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3):
dependencies:
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
archiver: 5.3.2
builder-util: 24.13.1
fs-extra: 10.1.0
@@ -8704,11 +8713,11 @@ snapshots:
electron-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
dependencies:
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
builder-util: 24.13.1
builder-util-runtime: 9.2.4
chalk: 4.1.2
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3)
fs-extra: 10.1.0
is-ci: 3.0.1
lazy-val: 1.0.5
@@ -10137,6 +10146,8 @@ snapshots:
- bluebird
- supports-color
node-machine-id@1.1.12: {}
node-pty@1.0.0:
dependencies:
nan: 2.19.0
+1 -1
View File
@@ -2,7 +2,7 @@ import { ProcessOutput } from "zx/core";
export function printObject(
object: Record<string, unknown> | ProcessOutput,
method: "log" | "warn" | "error" = "log",
method: "log" | "warn" | "error" = "log"
) {
for (const [key, value] of Object.entries(object)) {
// eslint-disable-next-line no-console